htadaptor

package module
v0.1.7 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2024 License: MIT Imports: 20 Imported by: 0

README

Hyper Text Adaptors

Package htadaptor provides convenient generic domain logic adaptors for HTTP handlers. It eliminates boiler plate code, increases security by enforcing read limits and struct validation, and reduces bugs by providing a more intuitive request data parsing API than the standard library.

Why do you need this package?

An HTTP request contains at least five various sources of input that your HTTP handlers may consider: URL path, URL query, headers, cookies, and the request body. Much of the code that you have to write manually is wrestling those inputs into a struct. Willem Schots wrote an excellent explanation here. htadaptor can do all of it for you:

myHandler := htadaptor.Must(htadaptor.NewUnaryFuncAdaptor(
  // your domain function call
  func(context.Context, *myInputStruct) (*myOutputStruct, error) {
    // ... myInputStruct is passed in already validated
    // ... the fields of myInputStruct will be populated with
    // ... the contents of `request.Body` with overrides
    //     from sources below in their given order:
  },
  htadaptor.WithPathValues("slug"),           // (1) URL routing path
  htadaptor.WithQueryValues("search"),        // (2) URL query
  htadaptor.WithHeaderValues("accessToken"),  // (3) header
  htadaptor.WithCookieValues("sessionID"),    // (4) cookie
  htadaptor.WithSessionValues("role"),        // (5) session
))

The adaptors address common function signatures of domain logic calls that operate on a request struct and return a response struct with contextual awareness all the way through the call stack including the slog.Logger:

Struct Adaptor Parameter Values Return Values
UnaryFunc context, inputStruct any, error
NullaryFunc context any, error
VoidFunc context, inputStruct error

String adaptors are best when only one request value is needed:

String Adaptor Parameter Values Return Values
UnaryStringFunc context, string any, error
VoidStringFunc context, string error

Installation

go get github.com/dkotik/htadaptor@latest

Basic Usage

mux := http.NewServeMux()
mux.Handle("/api/v1/order", htadaptor.Must(
  htadaptor.NewUnaryFuncAdaptor(myService.Order),
))

See examples folder for common project uses.

Adaptor Options

Extractors

The order of extractors matters with the latter overriding the former. Request body is always processed first.

  • Path
  • Chi Path
  • Query
  • Header
  • Cookie
  • Session
  • Request properties can also be included into deserialization:
    • extract.NewMethodExtractor
    • extract.NewHostExtractor
    • extract.NewRemoteAddressExtractor
    • extract.NewUserAgentExtractor
  • Or, make your own by implementing Extractor interface.

Credits

The core idea was sparked in conversations with members of the Ardan Labs team. Package includes reflection schema decoder from Gorilla toolkit. Similar projects:

How is htadaptor different from the other generic HTTP adaptors? It is more terse due to focusing on wrapping http.Handlers from the standard library. It is expected that the REST interface will be handled separately by either an http.Mux or a helper.

Documentation

Overview

Package htadaptor provides generic domain logic adaptors for HTTP handlers. Adaptors come in three flavors:

  1. UnaryFunc: func(context, inputStruct) (outputStruct, error)
  2. NullaryFunc: func(context) (outputStruct, error)
  3. VoidFunc: func(context, inputStruct) error

Validation errors are decorated with the correct http.StatusUnprocessableEntity status code.

Index

Constants

This section is empty.

Variables

View Source
var JSONEncoder = EncoderFunc(
	func(w http.ResponseWriter, r *http.Request, code int, v any) error {
		w.Header().Set("content-type", "application/json")
		w.WriteHeader(code)
		return json.NewEncoder(w).Encode(v)
	},
)

Functions

func ApplyMiddleware added in v0.0.11

func ApplyMiddleware(h http.Handler, mws ...Middleware) http.Handler

Apply wraps an http.Handler into Middleware in reverse order.

func ContextWithLanguage added in v0.1.0

func ContextWithLanguage(parent context.Context, t language.Tag) context.Context

func ContextWithLocalizer added in v0.1.0

func ContextWithLocalizer(parent context.Context, l *i18n.Localizer) context.Context

ContextWithLocalizer adds localizer into context as a value. Use LocalizerFromContext to recover it later.

func DefaultErrorTemplate added in v0.0.10

func DefaultErrorTemplate() *template.Template

func GetHyperTextStatusCode added in v0.0.10

func GetHyperTextStatusCode(err error) int

func LanguageFromContext added in v0.1.0

func LanguageFromContext(ctx context.Context) language.Tag

func LocalizerFromContext added in v0.1.0

func LocalizerFromContext(ctx context.Context) (l *i18n.Localizer, ok bool)

LocalizerFromContext raises request-scoped localizer. Warning: localizer will be <nil> if it was not set using ContextWithLocalizer.

func Must

func Must(h http.Handler, err error) http.Handler

Must panics if an http.Handler was created with an error.

func NewHostMux added in v0.1.3

func NewHostMux(hostHandlers ...HostMuxAssociation) (http.Handler, error)

NewHostMux creates an http.Handler that multiplexes by http.Request host name.

func NewMethodMux added in v0.1.3

func NewMethodMux(ms *MethodSwitch) http.Handler

NewMethodMux returns a handler that is able to satisfy REST interface expectations. It does not modify response status codes, but they can be updated using WithStatusCode option for individual handlers.

func NewPermanentRedirect added in v0.1.3

func NewPermanentRedirect(to string) http.Handler

func NewTemporaryRedirect added in v0.1.3

func NewTemporaryRedirect(to string) http.Handler

Types

type Decoder

type Decoder interface {
	Decode(any, *http.Request) error
}

type DecoderFunc added in v0.0.10

type DecoderFunc func(any, *http.Request) error

func (DecoderFunc) Decoder added in v0.0.10

func (f DecoderFunc) Decoder(v any, r *http.Request) error

type DecodingError added in v0.0.10

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

func (*DecodingError) Error added in v0.0.10

func (e *DecodingError) Error() string

func (*DecodingError) HyperTextStatusCode added in v0.0.10

func (e *DecodingError) HyperTextStatusCode() int

func (*DecodingError) Unwrap added in v0.0.10

func (e *DecodingError) Unwrap() error

type Deduplicator added in v0.1.1

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

Deduplicator tracks recent request structures. Use Deduplicator.IsDuplicate to determine if a request was seen in a certain time window.

Uses struct hashing by Mitchell Hashimoto. Using request deduplicator is superior to deduplicating HTTP requests, because they are large and can vary in myriads of ways. Struct deduplication can also be applied at network edge easing the pressure on the database or the event bus.

Handles most data structs. Cannot process functions inside structs.

func NewDeduplicator added in v0.1.1

func NewDeduplicator(ctx context.Context, window time.Duration) *Deduplicator

NewDeduplicator returns a new Deduplicator.

Context is used for the clean up go routine termination.

Window specifies the minimum duration of how long the duplicate tags are remembered for. Real duration can extend up to 50% longer because it depends on the clean up cycle.

func (*Deduplicator) IsDuplicate added in v0.1.1

func (d *Deduplicator) IsDuplicate(request any) (bool, error)

IsDuplicate returns true if the message hash tag calculated using a [MessageHasher] was seen in deduplication time window.

func (*Deduplicator) Len added in v0.1.1

func (d *Deduplicator) Len() (count int)

Len returns the number of known tags that have not been cleaned out yet.

type Encoder

type Encoder interface {
	Encode(http.ResponseWriter, *http.Request, int, any) error
}

func NewPermanentRedirectEncoder added in v0.1.5

func NewPermanentRedirectEncoder() Encoder

NewPermanentRedirectEncoder redirects the HTTP client to the location returned by a domain call using http.StatusPermanentRedirect status.

If domain call does not return a string, returns an error.

func NewTemplateEncoder added in v0.0.11

func NewTemplateEncoder(t *template.Template) Encoder

func NewTemporaryRedirectEncoder added in v0.1.5

func NewTemporaryRedirectEncoder() Encoder

NewTemporaryRedirectEncoder redirects the HTTP client to the location returned by a domain call using http.StatusTemporaryRedirect status.

If domain call does not return a string, returns an error.

type EncoderFunc

type EncoderFunc func(http.ResponseWriter, *http.Request, int, any) error

func (EncoderFunc) Encode

func (f EncoderFunc) Encode(w http.ResponseWriter, r *http.Request, code int, v any) error

type EncodingError added in v0.0.10

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

func (*EncodingError) Error added in v0.0.10

func (e *EncodingError) Error() string

func (*EncodingError) HyperTextStatusCode added in v0.0.10

func (e *EncodingError) HyperTextStatusCode() int

func (*EncodingError) Unwrap added in v0.0.10

func (e *EncodingError) Unwrap() error

type Error

type Error interface {
	error
	HyperTextStatusCode() int
}

Error extends the [error] interface to include HTTP status code.

func NewDecodingError added in v0.0.10

func NewDecodingError(fromError error) Error

TODO: deprecate - there is no benefit to wrapping the error!

func NewEncodingError added in v0.0.10

func NewEncodingError(fromError error) Error

type ErrorHandler

type ErrorHandler interface {
	HandleError(http.ResponseWriter, *http.Request, error) error
}

type ErrorHandlerFunc

type ErrorHandlerFunc func(http.ResponseWriter, *http.Request, error) error

func NewErrorHandler added in v0.0.10

func NewErrorHandler(encoder Encoder) ErrorHandlerFunc

func NewErrorHandlerFromTemplate added in v0.0.10

func NewErrorHandlerFromTemplate(t *template.Template) ErrorHandlerFunc

func (ErrorHandlerFunc) HandleError

func (e ErrorHandlerFunc) HandleError(w http.ResponseWriter, r *http.Request, err error) error

type ErrorMessage added in v0.0.10

type ErrorMessage struct {
	StatusCode int
	Title      string
	Message    string
}

func (*ErrorMessage) Render added in v0.0.10

func (e *ErrorMessage) Render(w io.Writer) error

type FuncType

type FuncType uint8
const (
	FuncTypeUnary FuncType = iota + 1
	FuncTypeNullary
	FuncTypeVoid
)

func Detect

func Detect(f any) (FuncType, error)

type HostMuxAssociation added in v0.1.5

type HostMuxAssociation struct {
	Name    string
	Handler http.Handler
}

HostMuxAssociation assigns a handler to a host for NewHostMux initialization.

It is used instead of a `map[string]http.Handler` because the intended order of hosts is preserved in case the resulting handler uses a list implementation internally. Golang maps do not preserve the order of their keys or values.

type InvalidRequestError

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

type Logger added in v0.0.10

type Logger interface {
	LogRequest(*http.Request, error)
}

type LoggerFunc added in v0.0.10

type LoggerFunc func(*http.Request, error)

func (LoggerFunc) LogRequest added in v0.0.10

func (f LoggerFunc) LogRequest(r *http.Request, err error)

type MethodSwitch added in v0.1.3

type MethodSwitch struct {
	Get    http.Handler
	Post   http.Handler
	Put    http.Handler
	Patch  http.Handler
	Delete http.Handler
	Head   http.Handler
}

MethodSwitch provides method selections for NewMethodMux.

func (*MethodSwitch) AllowedMethods added in v0.1.3

func (ms *MethodSwitch) AllowedMethods() (methods []string)

type Middleware added in v0.0.11

type Middleware func(http.Handler) http.Handler

Middleware modifies an http.Handler.

type NotFoundError added in v0.0.3

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

func NewNotFoundError added in v0.0.10

func NewNotFoundError(p string) *NotFoundError

func (*NotFoundError) Error added in v0.0.3

func (e *NotFoundError) Error() string

func (*NotFoundError) HyperTextStatusCode added in v0.0.3

func (e *NotFoundError) HyperTextStatusCode() int

func (*NotFoundError) LogValue added in v0.0.3

func (e *NotFoundError) LogValue() slog.Value

type NullaryFuncAdaptor

type NullaryFuncAdaptor[O any] struct {
	// contains filtered or unexported fields
}

NullaryFuncAdaptor calls a domain function with no input and returns a response struct.

func NewNullaryFuncAdaptor

func NewNullaryFuncAdaptor[O any](
	domainCall func(context.Context) (O, error),
	withOptions ...Option,
) (*NullaryFuncAdaptor[O], error)

NewNullaryFuncAdaptor creates a new adaptor for a function that takes no input and returns a struct.

func (*NullaryFuncAdaptor[O]) ServeHTTP

func (a *NullaryFuncAdaptor[O]) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request,
)

ServeHTTP satisfies http.Handler interface.

type Option

type Option func(*options) error

func WithCookieValues added in v0.0.6

func WithCookieValues(names ...string) Option

func WithDecoder

func WithDecoder(d Decoder) Option

func WithDecoderOptions

func WithDecoderOptions(withOptions ...reflectd.Option) Option

func WithDefaultDecoder

func WithDefaultDecoder() Option

func WithDefaultEncoder

func WithDefaultEncoder() Option

func WithDefaultErrorHandler

func WithDefaultErrorHandler() Option

func WithDefaultLogger

func WithDefaultLogger() Option

func WithDefaultStatusCode added in v0.1.5

func WithDefaultStatusCode() Option

func WithEncoder

func WithEncoder(e Encoder) Option

func WithErrorHandler

func WithErrorHandler(h ErrorHandler) Option

func WithExtractors added in v0.0.4

func WithExtractors(exs ...extract.RequestValueExtractor) Option

func WithHeaderValues added in v0.0.4

func WithHeaderValues(names ...string) Option

func WithLogger

func WithLogger(l Logger) Option

func WithMemoryLimit added in v0.0.4

func WithMemoryLimit(upto int64) Option

func WithOptions

func WithOptions(withOptions ...Option) Option

func WithPathValues added in v0.0.4

func WithPathValues(names ...string) Option

func WithQueryValues added in v0.0.4

func WithQueryValues(names ...string) Option

func WithReadLimit added in v0.0.4

func WithReadLimit(upto int64) Option

func WithSessionValues added in v0.0.14

func WithSessionValues(names ...string) Option

func WithStatusCode added in v0.1.5

func WithStatusCode(statusCode int) Option

WithStatusCode applies [NewStatusCodeEncoder] to the handler encoder to override the default http.StatusOK success code.

func WithTemplate added in v0.0.11

func WithTemplate(t *template.Template) Option

type SlogLogger added in v0.0.10

type SlogLogger struct {
	Logger  *slog.Logger
	Success slog.Level
	Error   slog.Level
}

func (*SlogLogger) LogRequest added in v0.0.10

func (s *SlogLogger) LogRequest(r *http.Request, err error)

type UnaryFuncAdaptor

type UnaryFuncAdaptor[T any, V *T, O any] struct {
	// contains filtered or unexported fields
}

UnaryFuncAdaptor extracts a struct from request and calls a domain function with it expecting a struct response.

func NewUnaryFuncAdaptor

func NewUnaryFuncAdaptor[T any, V *T, O any](
	domainCall func(context.Context, V) (O, error),
	withOptions ...Option,
) (*UnaryFuncAdaptor[T, V, O], error)

NewUnaryFuncAdaptor creates a new adaptor for a function that takes a validatable struct and returns a struct.

func (*UnaryFuncAdaptor[T, V, O]) ServeHTTP

func (a *UnaryFuncAdaptor[T, V, O]) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request,
)

ServeHTTP satisfies http.Handler interface.

type UnaryStringFuncAdaptor added in v0.0.2

type UnaryStringFuncAdaptor[O any] struct {
	// contains filtered or unexported fields
}

UnaryStringFuncAdaptor extracts a string value from request and calls a domain function with it expecting a struct response.

func NewUnaryStringFuncAdaptor added in v0.0.2

func NewUnaryStringFuncAdaptor[O any](
	domainCall func(context.Context, string) (O, error),
	stringExtractor extract.StringValueExtractor,
	withOptions ...Option,
) (*UnaryStringFuncAdaptor[O], error)

NewUnaryStringFuncAdaptor creates a new adaptor for a function that takes a string and returns a struct.

func (*UnaryStringFuncAdaptor[O]) ServeHTTP added in v0.0.2

func (a *UnaryStringFuncAdaptor[O]) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request,
)

ServeHTTP satisfies http.Handler interface.

type Validatable

type Validatable interface {
	Validate(context.Context) error
}

Validatable constrains a domain request. Validation errors are wrapped as InvalidRequestError by the adapter. context.Context is essential for passing locale information that can be retrieved using LanguageFromContext inside the validation method and other similar uses.

DEPRECATED: will be removed from 1.0 release.

type VoidFuncAdaptor

type VoidFuncAdaptor[T any, V *T] struct {
	// contains filtered or unexported fields
}

VoidStringFuncAdaptor calls a domain function with decoded request without returning no response other than an error.

func NewVoidFuncAdaptor

func NewVoidFuncAdaptor[T any, V *T](
	domainCall func(context.Context, V) error,
	withOptions ...Option,
) (*VoidFuncAdaptor[T, V], error)

NewVoidFuncAdaptor creates a new adaptor for a function that takes a decoded request and returns nothing.

func (*VoidFuncAdaptor[T, V]) ServeHTTP

func (a *VoidFuncAdaptor[T, V]) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request,
)

ServeHTTP satisfies http.Handler interface.

type VoidStringFuncAdaptor added in v0.0.2

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

VoidStringFuncAdaptor extracts a string value from request and calls a domain function with it without expecting no response other than an error value.

func NewVoidStringFuncAdaptor added in v0.0.2

func NewVoidStringFuncAdaptor(
	domainCall func(context.Context, string) error,
	stringExtractor extract.StringValueExtractor,
	withOptions ...Option,
) (*VoidStringFuncAdaptor, error)

NewVoidStringFuncAdaptor creates a new adaptor for a function that takes a string and returns nothing.

func (*VoidStringFuncAdaptor) ServeHTTP added in v0.0.2

func (a *VoidStringFuncAdaptor) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request,
)

ServeHTTP satisfies http.Handler interface.

Directories

Path Synopsis
chivalues module
examples
cookie
Package main demonstrates the use of cookie extractor for request decoding.
Package main demonstrates the use of cookie extractor for request decoding.
header
Package main demonstrates the use of header extractor for request decoding.
Package main demonstrates the use of header extractor for request decoding.
htmx
Package main demonstrates the use of template to create an HTMX endpoint.
Package main demonstrates the use of template to create an HTMX endpoint.
htmxform
Package main demonstrates an implementation of a standard feedback form with validation and localization.
Package main demonstrates an implementation of a standard feedback form with validation and localization.
htmxform/feedback
Package feedback is a standard contact form.
Package feedback is a standard contact form.
logging
Package main demonstrates the use of custom logger with an adaptor.
Package main demonstrates the use of custom logger with an adaptor.
query
Package main demonstrates the use of URL query value extractor for request decoding.
Package main demonstrates the use of URL query value extractor for request decoding.
session
Package main demonstrates the simplest implementation of a rotating session context.
Package main demonstrates the simplest implementation of a rotating session context.
urlpath
Package main demonstrates the use of URL path value extractor for request decoding.
Package main demonstrates the use of URL path value extractor for request decoding.
webservice
Package main demonstrates application of generic domain adaptors to satisfy [http.Handler] interface.
Package main demonstrates application of generic domain adaptors to satisfy [http.Handler] interface.
Package extract provides a standard set of most common [http.Request] value extractors which populate fields of a decoded generic request struct.
Package extract provides a standard set of most common [http.Request] value extractors which populate fields of a decoded generic request struct.
chivalues Module
middleware
acceptlanguage
Package acceptlanguage provides [htadaptor.Middleware] that injects [language.Tag] into request context.Context which can be recovered using [htadaptor.LanguageFromContext].
Package acceptlanguage provides [htadaptor.Middleware] that injects [language.Tag] into request context.Context which can be recovered using [htadaptor.LanguageFromContext].
ctxlogger
Package ctxlogger provides an [htadaptor.Middleware] that injects a given logger into context so that it can be recovered later in the stack for use.
Package ctxlogger provides an [htadaptor.Middleware] that injects a given logger into context so that it can be recovered later in the stack for use.
idledown
Package idledown provides an [htadaptor.Middleware] that shuts the server down due to inactivity.
Package idledown provides an [htadaptor.Middleware] that shuts the server down due to inactivity.
session
Package session presents a lazy context that manages session state with native key rotation support.
Package session presents a lazy context that manages session state with native key rotation support.
session/secrets
Package secrets provides a synchronizable set of keys backed by a key-value store.
Package secrets provides a synchronizable set of keys backed by a key-value store.
session/token
Package token provides [Authority]s that issue and validate access tokens.
Package token provides [Authority]s that issue and validate access tokens.
session/token/gorilla
Package gorilla couples [securecookie.SecureCookie] token encoder with [secrets.Rotation].
Package gorilla couples [securecookie.SecureCookie] token encoder with [secrets.Rotation].
session/token/jwt
Package jwt provides [token.Authority] with secure JSON Web Tokens issuer defaults.
Package jwt provides [token.Authority] with secure JSON Web Tokens issuer defaults.
tmodulator
Package tmodulator delays HTTP responses to protects wrapped endpoints from timing attacks.
Package tmodulator delays HTTP responses to protects wrapped endpoints from timing attacks.
traceid
Package traceid provides [htadaptor.Middleware] that injects trace identifiers into request context.Context and a matching [slog.Handler] for populating the log records with the identifier.
Package traceid provides [htadaptor.Middleware] that injects trace identifiers into request context.Context and a matching [slog.Handler] for populating the log records with the identifier.
unpanic
Package unpanic provides [htadaptor.Middleware] that gracefully recovers from request panics.
Package unpanic provides [htadaptor.Middleware] that gracefully recovers from request panics.
Package reflectd provides configurable HTTP form body to struct decoder that depends on Gorilla's schema reflection package.
Package reflectd provides configurable HTTP form body to struct decoder that depends on Gorilla's schema reflection package.
schema
Package schema fills a struct with form values.
Package schema fills a struct with form values.
Package service provides a standard library [http.Server] with conventional production defaults and a smooth configuration interface.
Package service provides a standard library [http.Server] with conventional production defaults and a smooth configuration interface.

Jump to

Keyboard shortcuts

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