ep

package module
v0.0.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 23, 2020 License: MIT Imports: 9 Imported by: 0

README

ep

A miniature Go(lang) framework for rapid development of http.Handlers while reducing code duplication and increasing readability. Designed to build both APIs and regular web applications more efficiently while keeping the flexibility that is expected in the Go ecosystem.

Features:

  • Works with any HTTP router that accepts the http.Handler interface
  • Supports any library for input validation, both system-wide or customized per endpoint
  • Provides customizable error handling for system errors and errors specific to your application
  • Automatically encodes and decodes HTTP payloads using content negotation
  • Uses language negotiation os your code can use best supported language for translations
  • Well tested, benchmarked and depends only on the standard library
  • Supports streaming requests and responses

Making error handling less unwieldy

  • Don't need a logger to see them
  • Don't need to create custom output
  • Don't need to setup a custom template with a specific name

The general premise is ok, the response will store any errors that it encounters during the response lifecycle and renders an output of it instead of the actual output. But having the different error types is confusing.

  • Maybe just one config that turns errors into outputs. Also handle panics?
  • Suitable for errors that are always handler wide: unexpected server errors and client errors.
  • AppErrors are confusing, just for custom status code? Basically an utility to render an error output with a certain status code and message.
  • Question is, may the error returned from exec methods be used for rendering "expected" errors, like 404 or 412? If so, an handler basically has two outputs the "appError" output and the output type. That is confusing, it's probably better if the main Output type would have a "error" embedded type and an "valid" embedded type, with optional rendering of it. The status would be a function of that with hooks.
  • Question: what will be the default behaviour of the error handler func. Like net/html it might just log to stderr by default. But what json/html would it render?
  • What happens if the output doesn't have a template method? maybe configure the template encoding with a default template so it doesn't crash with weird errors?
  • Or maybe just skip encoding by default?

Backlog

  • MUST get kitchen example back to work
  • MUST also add HTTP language negotiation
  • MUST output.Head and input.Check are now optional
  • MUST clean up the config and make config ergonomic
  • MUST allow exec to return a InvalidInput error
  • MUST allow default configuration to be configured
  • MUST test file upload usecase
  • MUST allow (url) query decoding when request body is JSON (first decoder to implement an interface is used?)
  • MUST have an form decoding that just takes an interface to do the actual decoding
  • MUST benchmark worst case sniffing, negotiation and base overhead
  • MUST run with race checker to check for race conditions
  • MUST allow outputs to overwrite the template to use
  • MUST be able to cache output templates
  • MUST be ergonomic to have translated templates as a response, or other (error) customizations
  • MUST fully test coding package
  • MUST find an alternative for comparing error interface values in Render: not needed, users can just retur nil
  • MUST have a better way to debug unexpected error responses for development: add client and server error logging
  • MUST handle panics in the handle, with the server error message rendering, should also be easy to debug
  • MUST re-think usecase of rest endpoint returning error
  • MUST don't write body if response is 204 or other status without a body
  • MUST allow html template to accept any kind of template (interface), rename to template encoding
  • MUST not server 500 status code if skipEncode is provided as an error to render
  • MUST set default error template to "error.html" it is corresponds to an actual file in the most common case
  • SHOULD implement error hooks for handling error outputs
  • SHOULD implement hooks for common status responses
  • SHOULD implement contextual output as a hook
  • SHOULD make handling errors less unwieldy, need to add a logger to see them, need to create custom outputs, needs to setup html template with correct name
  • SHOULD when both query decoder and body decoder is configured, should be easier to protect against CSRF posts with all query params
  • SHOULD have type for OnErrorRender function signature
  • SHOULD not overwrite content-type header if it is already set by the implementation explicitely, for example if it writes pdf.
  • SHOULD make it clear in docs or with an error that the order of hooks is important, if one calls "writeHeader" the others won't be able to change the header
  • SHOULD have a clearer error when here is no html template defined for "error"
  • SHOULD in general, make it easier to render some response with just a status code and a simple body (no encoding)
  • SHOULD also call head hooks when not using render (but just resp.Write())
  • SHOULD have a default OnErrorRender that logs it to stderr
  • SHOULD use a single error value in the response instead of client and server, but instead: use an error type that describes it as client or server and should change the OnErrorRender signature to allow customization of that
  • SHOULD allow outputs to embed a type that will be populated with the request context
  • SHOULD allow configuring defaults for endpoint config
  • SHOULD make the Config method more ergonomic to use
  • SHOULD come with build-in logging support to debug client and server errors
  • SHOULD remove progress keeping from reader
  • SHOULD be able to return all kinds of app errors with status code from exec
  • SHOULD also make it more ergonomic to just render a 204, 404, Conflict and other common exec status responses for REST endpoints
  • SHOULD make it ergonomic to render output with common 2xx status codes: 201, 204
  • SHOULD make it ergonomic to redirect the client
  • SHOULD make AppError fields public
  • SHOULD rename "Check" on input to "Validate", way more obvious and less suprising
  • SHOULD SkipEncode should also work when returned directly to the render
  • COULD have an hook that sets the status code based on whether the response is in a client or server error mode
  • COULD include a more composable ways for behaviour to be added to an output: what if it redirects and sets a cookie
  • COULD allow middleware to install a hook that is called just before the first byte is written to the response body for middleware that needs to write a header
  • COULD check for the user that the output in a pointer value if setContext would be called
  • COULD make withEncoding and withHooks consistent in naming (one with s, other without)
  • COULD have package level doc summary for coding package
  • COULD not get nil pointer if status created is embedded on a nil output struct. Instead, embedding should trigger behaviour differently
  • COULD use the configuration pattern as described here: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
  • COULD turn most of the coding tests into table tests
  • COULD provide tooling to make endpoints extremely easy to test
  • COULD provide tooling to fuzz endpoint
  • COULD add Conf constructors for different types of endpoints: Rest, Form
  • COULD make config method on endpoint optional
  • COULD move per endpoint config to where Handler is called instead
  • COULD come with a nice error page for development
  • COULD rename 'epcoding' to just 'coding'
  • COULD rename coding to something else entirely, cofusing with HTTP encoding header name
  • COULD create http request interface for easier testing
  • COULD remove reqProgress counter
  • COULD allow input.Read to return special error that prevents decoding
  • COULD allow output.Head to return special error that prevents encoding
  • COULD better test language negotiation
  • COULD support response buffering for errors that occur halway writing the response
  • COULD allow JSON encoder configuration, i.e: indentation
  • COULD be more flexible with what content get's accepted for decoding: (i.e application/vnd.api+json should match json)
  • COULD allow configuration what content-type will be written for a encoder: i.e: application/vnd.api+json
  • COULD also handle panics in the negotiation code
  • COULD assert status codes send to Error, Errorf to be in range of 400-600
  • COULD support something like this: https://github.com/mozillazg/go-httpheader on output structs
  • COULD encode response status also from output struct tags: maybe use AWS SDK approach of tagging with 'location:"header/uri/body"'
  • WONT do content-encoding negotiation, complex: https://github.com/nytimes/gziphandler, deserves dedicated package
  • WONT add a H/HF method for endpoints that are just the handle/exec func
  • WONT return an error from handle as well, since that might be a common usecase. We want to motivate to move into exec function
  • WONT add more logging methods to the logger to track, logging was not really used at all

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// SkipEncode can be retured by the output head to prevent any further
	// decoding
	SkipEncode = errors.New("skip encode")
)

Functions

func ContextHook added in v0.0.15

func ContextHook(out Output, w http.ResponseWriter, r *http.Request) error

ContextHook will contextual outputs to be provided with a context

func Decoding

func Decoding(ctx context.Context) (dec epcoding.Decoding)

Decoding will return the request decoding that was determined based on request headers and MIME sniffing. This will only be empty if the server didn't configure any decodings.

func Encoding

func Encoding(ctx context.Context) (enc epcoding.Encoding)

Encoding will return the response encoding as negotiated with the client. This will only be empty if the server didn't specify any supported encodings

func Language

func Language(ctx context.Context) (s string)

Language will return the language as negotiated with the client. This will only be empty if the server didn't provide any languages

func Negotiate

func Negotiate(cfg ConfReader, req *http.Request) *http.Request

Negotiate will look at the requests' headers and set context values for encoding, decoding and language.

func StatusCreatedHook added in v0.0.15

func StatusCreatedHook(out Output, w http.ResponseWriter, r *http.Request) error

StatusCreatedHook allow outputs to be rendered as created responses

func StatusNoContentHook added in v0.0.15

func StatusNoContentHook(out Output, w http.ResponseWriter, r *http.Request) error

StatusNoContentHook allow outputs to be rendered as created responses

func StatusRedirectHook added in v0.0.15

func StatusRedirectHook(out Output, w http.ResponseWriter, r *http.Request) error

StatusRedirectHook allow redirect outputs to be rendered correctly easily

Types

type CheckerInput

type CheckerInput interface {
	Input
	Validate() (err error)
}

CheckerInput can be implemented by Inputs to allow them to validate themselves

type Conf

type Conf struct {
	// contains filtered or unexported fields
}

Conf builds endpoint configuration

func New

func New() *Conf

New inits an empty configuration

func (Conf) Copy

func (c Conf) Copy() (cc *Conf)

Copy will duplicate the configuration

func (Conf) Decodings

func (c Conf) Decodings() []epcoding.Decoding

Decodings returns the configured input decodings

func (Conf) Encodings

func (c Conf) Encodings() []epcoding.Encoding

Encodings returns the configured output encodings

func (Conf) Handler

func (c Conf) Handler(ep Endpoint) *Handler

Handler will copy the configuration and make the endpoint as a handler

func (Conf) HandlerFunc

func (c Conf) HandlerFunc(epf EndpointFunc) *Handler

HandlerFunc will copy the configuration the func as a handler

func (Conf) Hooks added in v0.0.15

func (c Conf) Hooks() []Hook

func (Conf) Languages

func (c Conf) Languages() []string

Languages return the supported languages for this endpoint

func (Conf) OnErrorRender added in v0.0.16

func (c Conf) OnErrorRender() OnErrorFunc

func (Conf) QueryDecoder

func (c Conf) QueryDecoder() epcoding.URLValuesDecoder

QueryDecoder configures the query to be decoded into the input struct

func (*Conf) SetDecodings

func (c *Conf) SetDecodings(decs ...epcoding.Decoding) *Conf

func (*Conf) SetEncodings

func (c *Conf) SetEncodings(encs ...epcoding.Encoding) *Conf

func (*Conf) SetLanguages

func (c *Conf) SetLanguages(langs ...string) *Conf

func (*Conf) SetOnErrorRender added in v0.0.16

func (c *Conf) SetOnErrorRender(h OnErrorFunc) *Conf

OnErrorRender determines how the response rendering handles error

func (*Conf) SetQueryDecoder

func (c *Conf) SetQueryDecoder(d epcoding.URLValuesDecoder) *Conf

func (*Conf) SetValidator

func (c *Conf) SetValidator(v Validator) *Conf

func (Conf) Validator

func (c Conf) Validator() Validator

Validator returns the configured input validator

func (*Conf) WithDecoding

func (c *Conf) WithDecoding(decs ...epcoding.Decoding) *Conf

func (*Conf) WithEncoding

func (c *Conf) WithEncoding(encs ...epcoding.Encoding) *Conf

func (*Conf) WithHook added in v0.0.16

func (c *Conf) WithHook(hooks ...Hook) *Conf

Hooks returns any configured hooks

func (*Conf) WithLanguage

func (c *Conf) WithLanguage(langs ...string) *Conf

type ConfReader

type ConfReader interface {
	Hooks() []Hook
	Encodings() []epcoding.Encoding
	Decodings() []epcoding.Decoding
	Languages() []string
	Validator() Validator
	QueryDecoder() epcoding.URLValuesDecoder
	OnErrorRender() OnErrorFunc
}

type Contextual added in v0.0.15

type Contextual struct {
	// contains filtered or unexported fields
}

Contextual can be embedded in an output type to cause the request context to be injected

func (Contextual) Ctx added in v0.0.15

func (out Contextual) Ctx() context.Context

Ctx returns the injected context

func (*Contextual) SetContext added in v0.0.15

func (out *Contextual) SetContext(ctx context.Context)

SetContext is called to inject the request context

type Endpoint

type Endpoint interface {
	Handle(*Response, *http.Request)
}

Endpoint can be implemented to descibe an HTTP endpoint

type EndpointFunc

type EndpointFunc func(*Response, *http.Request)

EndpointFunc implements endpoint by providng just the Handle func

func (EndpointFunc) Handle

func (f EndpointFunc) Handle(res *Response, req *http.Request)

type Handler

type Handler struct {
	*Conf
	// contains filtered or unexported fields
}

Handler handles http for certain endpoint

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP allows the endpoint to serve HTTP

type Hook added in v0.0.15

type Hook func(out Output, w http.ResponseWriter, r *http.Request) error

Hook gets triggered before the first byte is written to the response

type Input

type Input interface{}

Input describes what is read from a request as input to the endpoint.

type OnErrorFunc added in v0.0.17

type OnErrorFunc func(isClient bool, err error) Output

OnErrorFunc can be provided to handle errors

type Output

type Output interface{}

An Output represents one item that results from the endpoint is will be send as the response back to the client.

type Reader

type Reader struct {
	io.Closer
	*bufio.Reader
}

Reader is a buffered reader that keeps track of the number of bytes that have been read.

func NewReader

func NewReader(r io.ReadCloser) *Reader

NewReader turns an unbuffered reader into a buffered read that keeps track of reading progress. The buffer size is 512 bytes to allow MIME sniffing of the readers content

func (*Reader) Sniff

func (r *Reader) Sniff() (ct string)

Sniff will use peek into the reader and tries to determine the content type

type ReaderInput

type ReaderInput interface {
	Input
	Read(r *http.Request) error
}

ReaderInput is an input that may optionally be implemented by inputs to indicate that it has custom logic for reading the request.

type Response

type Response struct {
	// contains filtered or unexported fields
}

Response is an http.ResponseWriter implementation that comes with a host of untility method for common tasks in http handling.

func NewResponse

func NewResponse(
	wr http.ResponseWriter,
	req *http.Request,
	cfg ConfReader,
) (res *Response)

NewResponse initializes a new Response

func (*Response) Bind

func (r *Response) Bind(in Input) (ok bool)

Bind will use the negotiated decoder to populate the input.

func (*Response) Error

func (r *Response) Error() error

Error will return any server, client or app error that was encountered while formulating the response.

func (*Response) Header

func (r *Response) Header() http.Header

Header implements the http.ResponseWriter's "Header" method

func (*Response) RecoverRender added in v0.0.6

func (r *Response) RecoverRender()

RecoverRender allows the response to recover from a panic in the stack and render an internal server error.

func (*Response) Render

func (r *Response) Render(out Output, err error)

Render will assert the provided error and earlier errors and provide appropriate feedback in the response. If 'err' is not the same error as returned by Validate() it will be handled as a server error.

func (*Response) Validate

func (r *Response) Validate(in Input) (verr error)

Validate will validate the input and return any error. It will first use any struct validator first before using the input's check method.

func (*Response) Write

func (r *Response) Write(b []byte) (int, error)

Write implements the http.ResponseWriter's "Write" method

func (*Response) WriteHeader

func (r *Response) WriteHeader(statusCode int)

WriteHeader implements the http.ResponseWriter's "WriteHeader" method

type StatusCreated added in v0.0.7

type StatusCreated struct {
	// contains filtered or unexported fields
}

StatusCreated can be embedded to automatically set the status code to 201 when it is rendered as output. If SetLocation is called the Location header will also be set.

func (*StatusCreated) SetLocation added in v0.0.7

func (s *StatusCreated) SetLocation(l string)

type StatusNoContent added in v0.0.7

type StatusNoContent struct{}

StatusNoContent can be embedded to automatically set the response status code to 204 and prevent any body from being returned.

type StatusRedirect added in v0.0.7

type StatusRedirect struct {
	// contains filtered or unexported fields
}

StatusRedirect can be embedded to render a redirect using http.Redirect. It will only do so if SetRedirect is called with a non-empty string.

func (*StatusRedirect) SetRedirect added in v0.0.7

func (s *StatusRedirect) SetRedirect(l string, code ...int)

SetRedirect will cause the response to be a redirect. By default it will be a '303 See Other' redirect unless the code is used set it to another 3xx status code.

type Validator

type Validator interface {
	Validate(v interface{}) error
}

Validator may be implemented and configured to allow automatic validation of all endpoint inputs.

Directories

Path Synopsis
Package header provides functions for parsing HTTP headers.
Package header provides functions for parsing HTTP headers.
Package epcoding provides encoding and decoding types
Package epcoding provides encoding and decoding types

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL