router

package
v0.23.4 Latest Latest
Warning

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

Go to latest
Published: Dec 2, 2024 License: MIT Imports: 24 Imported by: 1

Documentation

Index

Constants

View Source
const DefaultMaxMemory = 32 << 20 // 32mb
View Source
const IndexPage = "index.html"
View Source
const JSONPayloadKey string = "@jsonPayload"

JSONPayloadKey is the key for the special UnmarshalRequestData case used for reading serialized json payload without normalization.

Variables

View Source
var ErrFileNotFound = NewNotFoundError("File not found", nil)
View Source
var ErrInvalidRedirectStatusCode = NewInternalServerError("Invalid redirect status code", nil)
View Source
var ErrUnsupportedContentType = NewBadRequestError("Unsupported Content-Type", nil)

Functions

func ErrorHandler

func ErrorHandler(resp http.ResponseWriter, req *http.Request, err error)

func UnmarshalRequestData

func UnmarshalRequestData(data map[string][]string, dst any, structTagKey string, structPrefix string) error

UnmarshalRequestData unmarshals url.Values type of data (query, multipart/form-data, etc.) into dst.

dst must be a pointer to a map[string]any or struct.

If dst is a map[string]any, each data value will be inferred and converted to its bool, numeric, or string equivalent value (refer to inferValue() for the exact rules).

If dst is a struct, the following field types are supported:

  • bool
  • string
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • serialized json string if submitted under the special "@jsonPayload" key
  • encoding.TextUnmarshaler
  • pointer and slice variations of the above primitives (ex. *string, []string, *[]string []*string, etc.)
  • named/anonymous struct fields Dot-notation is used to target nested fields, ex. "nestedStructField.title".
  • embedded struct fields The embedded struct fields are treated by default as if they were defined in their parent struct. If the embedded struct has a tag matching structTagKey then to set its fields the data keys must be prefixed with that tag similar to the regular nested struct fields.

structTagKey and structPrefix are used only when dst is a struct.

structTagKey represents the tag to use to match a data entry with a struct field (defaults to "form"). If the struct field doesn't have the structTagKey tag, then the exported struct field name will be used as it is.

structPrefix could be provided if all of the data keys are prefixed with a common string and you want the struct field to match only the value without the structPrefix (ex. for "user.name", "user.email" data keys and structPrefix "user", it will match "name" and "email" struct fields).

Note that while the method was inspired by binders from echo, gorrila/schema, ozzo-routing and other similar common routing packages, it is not intended to be a drop-in replacement.

@todo Consider adding support for dot-notation keys, in addition to the prefix, (ex. parent.child.title) to express nested object keys.

Types

type ApiError

type ApiError struct {
	Data    map[string]any `json:"data"`
	Message string         `json:"message"`
	Status  int            `json:"status"`
	// contains filtered or unexported fields
}

ApiError defines the struct for a basic api error response.

func NewApiError

func NewApiError(status int, message string, rawErrData any) *ApiError

NewApiError creates and returns new normalized ApiError instance.

func NewBadRequestError

func NewBadRequestError(message string, rawErrData any) *ApiError

NewBadRequestError creates and returns 400 ApiError.

func NewForbiddenError

func NewForbiddenError(message string, rawErrData any) *ApiError

NewForbiddenError creates and returns 403 ApiError.

func NewInternalServerError

func NewInternalServerError(message string, rawErrData any) *ApiError

NewInternalServerError creates and returns 500 ApiError.

func NewNotFoundError

func NewNotFoundError(message string, rawErrData any) *ApiError

NewNotFoundError creates and returns 404 ApiError.

func NewTooManyRequestsError

func NewTooManyRequestsError(message string, rawErrData any) *ApiError

func NewUnauthorizedError

func NewUnauthorizedError(message string, rawErrData any) *ApiError

NewUnauthorizedError creates and returns 401 ApiError.

func ToApiError

func ToApiError(err error) *ApiError

ToApiError wraps err into ApiError instance (if not already).

func (*ApiError) Error

func (e *ApiError) Error() string

Error makes it compatible with the `error` interface.

func (*ApiError) Is

func (e *ApiError) Is(target error) bool

Is reports whether the current ApiError wraps the target.

func (*ApiError) RawData

func (e *ApiError) RawData() any

RawData returns the unformatted error data (could be an internal error, text, etc.)

type Event

type Event struct {
	Response http.ResponseWriter
	Request  *http.Request

	hook.Event
	// contains filtered or unexported fields
}

Event specifies based Route handler event that is usually intended to be embedded as part of a custom event struct.

NB! It is expected that the Response and Request fields are always set.

func (*Event) BadRequestError

func (e *Event) BadRequestError(message string, errData any) *ApiError

func (*Event) BindBody

func (e *Event) BindBody(dst any) error

BindBody unmarshal the request body into the provided dst.

dst must be either a struct pointer or map[string]any.

The rules how the body will be scanned depends on the request Content-Type.

Currently the following Content-Types are supported:

  • application/json
  • text/xml, application/xml
  • multipart/form-data, application/x-www-form-urlencoded

Respectively the following struct tags are supported (again, which one will be used depends on the Content-Type):

  • "json" (json body)- uses the builtin Go json package for unmarshaling.
  • "xml" (xml body) - uses the builtin Go xml package for unmarshaling.
  • "form" (form data) - utilizes the custom router.UnmarshalRequestData method.

NB! When dst is a struct make sure that it doesn't have public fields that shouldn't be bindable and it is advisible such fields to be unexported or have a separate struct just for the binding. For example:

data := struct{
   somethingPrivate string

   Title string `json:"title" form:"title"`
   Total int    `json:"total" form:"total"`
}
err := e.BindBody(&data)

func (*Event) Blob added in v0.23.1

func (e *Event) Blob(status int, contentType string, b []byte) error

Blob writes a blob (bytes slice) response.

func (*Event) Error

func (e *Event) Error(status int, message string, errData any) *ApiError

func (*Event) FileFS

func (e *Event) FileFS(fsys fs.FS, filename string) error

FileFS serves the specified filename from fsys.

It is similar to [echo.FileFS] for consistency with earlier versions.

func (*Event) FindUploadedFiles

func (e *Event) FindUploadedFiles(key string) ([]*filesystem.File, error)

FindUploadedFiles extracts all form files of "key" from a http request and returns a slice with filesystem.File instances (if any).

func (*Event) Flush

func (e *Event) Flush() error

Flush flushes buffered data to the current response.

Returns http.ErrNotSupported if e.Response doesn't implement the http.Flusher interface (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one).

func (*Event) ForbiddenError

func (e *Event) ForbiddenError(message string, errData any) *ApiError

func (*Event) Get

func (e *Event) Get(key string) any

Get retrieves single value from the current event data store.

func (*Event) GetAll

func (e *Event) GetAll() map[string]any

GetAll returns a copy of the current event data store.

func (*Event) HTML

func (e *Event) HTML(status int, data string) error

HTML writes an HTML response.

func (*Event) InternalServerError

func (e *Event) InternalServerError(message string, errData any) *ApiError

func (*Event) IsTLS

func (e *Event) IsTLS() bool

IsTLS reports whether the connection on which the request was received is TLS.

func (*Event) JSON

func (e *Event) JSON(status int, data any) error

JSON writes a JSON response.

It also provides a generic response data fields picker if the "fields" query parameter is set.

func (*Event) NoContent

func (e *Event) NoContent(status int) error

NoContent writes a response with no body (ex. 204).

func (*Event) NotFoundError

func (e *Event) NotFoundError(message string, errData any) *ApiError

func (*Event) Redirect

func (e *Event) Redirect(status int, url string) error

Redirect writes a redirect response to the specified url. The status code must be in between 300 – 399 range.

func (*Event) RemoteIP

func (e *Event) RemoteIP() string

RemoteIP returns the IP address of the client that sent the request.

IPv6 addresses are returned expanded. For example, "2001:db8::1" becomes "2001:0db8:0000:0000:0000:0000:0000:0001".

Note that if you are behind reverse proxy(ies), this method returns the IP of the last connecting proxy.

func (*Event) Set

func (e *Event) Set(key string, value any)

Set saves single value into the current event data store.

func (*Event) SetAll

func (e *Event) SetAll(m map[string]any)

SetAll saves all items from m into the current event data store.

func (*Event) SetCookie

func (e *Event) SetCookie(cookie *http.Cookie)

SetCookie is an alias for http.SetCookie.

SetCookie adds a Set-Cookie header to the current response's headers. The provided cookie must have a valid Name. Invalid cookies may be silently dropped.

func (*Event) Status

func (e *Event) Status() int

Status reports the status code of the current response.

This method always returns 0 if e.Response doesn't implement the StatusTracker interface (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one).

func (*Event) Stream

func (e *Event) Stream(status int, contentType string, reader io.Reader) error

Stream streams the specified reader into the response.

func (*Event) String

func (e *Event) String(status int, data string) error

String writes a plain string response.

func (*Event) TooManyRequestsError

func (e *Event) TooManyRequestsError(message string, errData any) *ApiError

func (*Event) UnauthorizedError

func (e *Event) UnauthorizedError(message string, errData any) *ApiError

func (*Event) Written

func (e *Event) Written() bool

Written reports whether the current response has already been written.

This method always returns false if e.ResponseWritter doesn't implement the WriteTracker interface (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one).

func (*Event) XML

func (e *Event) XML(status int, data any) error

XML writes an XML response. It automatically prepends the generic xml.Header string to the response.

type EventCleanupFunc

type EventCleanupFunc func()

type EventFactoryFunc

type EventFactoryFunc[T hook.Resolver] func(w http.ResponseWriter, r *http.Request) (T, EventCleanupFunc)

EventFactoryFunc defines the function responsible for creating a Route specific event based on the provided request handler ServeHTTP data.

Optionally return a clean up function that will be invoked right after the route execution.

type RWUnwrapper

type RWUnwrapper interface {
	Unwrap() http.ResponseWriter
}

RWUnwrapper specifies that an http.ResponseWriter could be "unwrapped" (usually used with http.ResponseController).

type RereadableReadCloser

type RereadableReadCloser struct {
	io.ReadCloser
	// contains filtered or unexported fields
}

RereadableReadCloser defines a wrapper around a io.ReadCloser reader allowing to read the original reader multiple times.

func (*RereadableReadCloser) Read

func (r *RereadableReadCloser) Read(b []byte) (int, error)

Read implements the standard io.Reader interface.

It reads up to len(b) bytes into b and at at the same time writes the read data into an internal bytes buffer.

On EOF the r is "rewinded" to allow reading from r multiple times.

func (*RereadableReadCloser) Reread

func (r *RereadableReadCloser) Reread()

Reread satisfies the Rereader interface and resets the r internal state to allow rereads.

note: not named Reset to avoid conflicts with other reader interfaces.

type Rereader

type Rereader interface {
	Reread()
}

Rereader defines an interface for rewindable readers.

type ResponseWriter

type ResponseWriter struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

ResponseWriter wraps a http.ResponseWriter to track its write state.

func (*ResponseWriter) Flush

func (rw *ResponseWriter) Flush()

Flush implements http.Flusher and allows an HTTP handler to flush buffered data to the client. This method is no-op if the wrapped writer doesn't support it.

func (*ResponseWriter) FlushError

func (rw *ResponseWriter) FlushError() error

FlushError is similar to [Flush] but returns http.ErrNotSupported if the wrapped writer doesn't support it.

func (*ResponseWriter) Hijack

func (rw *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error)

Hijack implements http.Hijacker and allows an HTTP handler to take over the current connection.

func (*ResponseWriter) Push

func (rw *ResponseWriter) Push(target string, opts *http.PushOptions) error

Pusher implements http.Pusher to indicate HTTP/2 server push support.

func (*ResponseWriter) ReadFrom

func (rw *ResponseWriter) ReadFrom(r io.Reader) (n int64, err error)

ReaderFrom implements io.ReaderFrom by checking if the underlying writer supports it. Otherwise calls io.Copy.

func (*ResponseWriter) Status

func (rw *ResponseWriter) Status() int

Written implements StatusTracker and returns the written status code of the current response.

func (*ResponseWriter) Unwrap

func (rw *ResponseWriter) Unwrap() http.ResponseWriter

Unwrap returns the underlying ResponseWritter instance (usually used by http.ResponseController).

func (*ResponseWriter) Write

func (rw *ResponseWriter) Write(b []byte) (int, error)

func (*ResponseWriter) WriteHeader

func (rw *ResponseWriter) WriteHeader(status int)

func (*ResponseWriter) Written

func (rw *ResponseWriter) Written() bool

Written implements WriteTracker and returns whether the current response body has been already written.

type Route

type Route[T hook.Resolver] struct {
	Action      func(e T) error
	Method      string
	Path        string
	Middlewares []*hook.Handler[T]
	// contains filtered or unexported fields
}

func (*Route[T]) Bind

func (route *Route[T]) Bind(middlewares ...*hook.Handler[T]) *Route[T]

Bind registers one or multiple middleware handlers to the current route.

func (*Route[T]) BindFunc

func (route *Route[T]) BindFunc(middlewareFuncs ...func(e T) error) *Route[T]

BindFunc registers one or multiple middleware functions to the current route.

The registered middleware functions are "anonymous" and with default priority, aka. executes in the order they were registered.

If you need to specify a named middleware (ex. so that it can be removed) or middleware with custom exec prirority, use the Route.Bind method.

func (*Route[T]) Unbind

func (route *Route[T]) Unbind(middlewareIds ...string) *Route[T]

Unbind removes one or more middlewares with the specified id(s) from the current route.

It also adds the removed middleware ids to an exclude list so that they could be skipped from the execution chain in case the middleware is registered in a parent group.

Anonymous middlewares are considered non-removable, aka. this method does nothing if the middleware id is an empty string.

type Router

type Router[T hook.Resolver] struct {
	// @todo consider renaming the type to just Group and replace the embed type
	// with an alias after Go 1.24 adds support for generic type aliases
	*RouterGroup[T]
	// contains filtered or unexported fields
}

Router defines a thin wrapper around the standard Go http.ServeMux by adding support for routing sub-groups, middlewares and other common utils.

Example:

r := NewRouter[*MyEvent](eventFactory)

// middlewares
r.BindFunc(m1, m2)

// routes
r.GET("/test", handler1)

// sub-routers/groups
api := r.Group("/api")
api.GET("/admins", handler2)

// generate a http.ServeMux instance based on the router configurations
mux, _ := r.BuildMux()

http.ListenAndServe("localhost:8090", mux)

func NewRouter

func NewRouter[T hook.Resolver](eventFactory EventFactoryFunc[T]) *Router[T]

NewRouter creates a new Router instance with the provided event factory function.

func (*Router[T]) BuildMux

func (r *Router[T]) BuildMux() (http.Handler, error)

BuildMux constructs a new mux http.Handler instance from the current router configurations.

type RouterGroup

type RouterGroup[T hook.Resolver] struct {
	Prefix      string
	Middlewares []*hook.Handler[T]
	// contains filtered or unexported fields
}

RouterGroup represents a collection of routes and other sub groups that share common pattern prefix and middlewares.

func (*RouterGroup[T]) Any

func (group *RouterGroup[T]) Any(path string, action func(e T) error) *Route[T]

Any is a shorthand for [RouterGroup.AddRoute] with "" as route method (aka. matches any method).

func (*RouterGroup[T]) Bind

func (group *RouterGroup[T]) Bind(middlewares ...*hook.Handler[T]) *RouterGroup[T]

Bind registers one or multiple middleware handlers to the current group.

func (*RouterGroup[T]) BindFunc

func (group *RouterGroup[T]) BindFunc(middlewareFuncs ...func(e T) error) *RouterGroup[T]

BindFunc registers one or multiple middleware functions to the current group.

The registered middleware functions are "anonymous" and with default priority, aka. executes in the order they were registered.

If you need to specify a named middleware (ex. so that it can be removed) or middleware with custom exec prirority, use RouterGroup.Bind method.

func (*RouterGroup[T]) DELETE

func (group *RouterGroup[T]) DELETE(path string, action func(e T) error) *Route[T]

DELETE is a shorthand for [RouterGroup.AddRoute] with DELETE as route method.

func (*RouterGroup[T]) GET

func (group *RouterGroup[T]) GET(path string, action func(e T) error) *Route[T]

GET is a shorthand for [RouterGroup.AddRoute] with GET as route method.

func (*RouterGroup[T]) Group

func (group *RouterGroup[T]) Group(prefix string) *RouterGroup[T]

Group creates and register a new child Group into the current one with the specified prefix.

The prefix follows the standard Go net/http ServeMux pattern format ("[HOST]/[PATH]") and will be concatenated recursively into the final route path, meaning that only the root level group could have HOST as part of the prefix.

Returns the newly created group to allow chaining and registering sub-routes and group specific middlewares.

func (*RouterGroup[T]) HEAD

func (group *RouterGroup[T]) HEAD(path string, action func(e T) error) *Route[T]

HEAD is a shorthand for [RouterGroup.AddRoute] with HEAD as route method.

func (*RouterGroup[T]) HasRoute

func (group *RouterGroup[T]) HasRoute(method string, path string) bool

HasRoute checks whether the specified route pattern (method + path) is registered in the current group or its children.

This could be useful to conditionally register and checks for routes in order prevent panic on duplicated routes.

Note that routes with anonymous and named wildcard placeholder are treated as equal, aka. "GET /abc/" is considered the same as "GET /abc/{something...}".

func (*RouterGroup[T]) OPTIONS

func (group *RouterGroup[T]) OPTIONS(path string, action func(e T) error) *Route[T]

OPTIONS is a shorthand for [RouterGroup.AddRoute] with OPTIONS as route method.

func (*RouterGroup[T]) PATCH

func (group *RouterGroup[T]) PATCH(path string, action func(e T) error) *Route[T]

PATCH is a shorthand for [RouterGroup.AddRoute] with PATCH as route method.

func (*RouterGroup[T]) POST

func (group *RouterGroup[T]) POST(path string, action func(e T) error) *Route[T]

POST is a shorthand for [RouterGroup.AddRoute] with POST as route method.

func (*RouterGroup[T]) PUT

func (group *RouterGroup[T]) PUT(path string, action func(e T) error) *Route[T]

PUT is a shorthand for [RouterGroup.AddRoute] with PUT as route method.

func (*RouterGroup[T]) Route

func (group *RouterGroup[T]) Route(method string, path string, action func(e T) error) *Route[T]

Route registers a single route into the current group.

Note that the final route path will be the concatenation of all parent groups prefixes + the route path. The path follows the standard Go net/http ServeMux format ("[HOST]/[PATH]"), meaning that only a top level group route could have HOST as part of the prefix.

Returns the newly created route to allow attaching route-only middlewares.

func (*RouterGroup[T]) SEARCH

func (group *RouterGroup[T]) SEARCH(path string, action func(e T) error) *Route[T]

SEARCH is a shorthand for [RouterGroup.AddRoute] with SEARCH as route method.

func (*RouterGroup[T]) Unbind

func (group *RouterGroup[T]) Unbind(middlewareIds ...string) *RouterGroup[T]

Unbind removes one or more middlewares with the specified id(s) from the current group and its children (if any).

Anonymous middlewares are not removable, aka. this method does nothing if the middleware id is an empty string.

type SafeErrorItem

type SafeErrorItem interface {
	// Code represents a fixed unique identifier of the error (usually used as translation key).
	Code() string

	// Error is the default English human readable error message that will be returned.
	Error() string
}

SafeErrorItem defines a common error interface for a printable public safe error.

type SafeErrorParamsResolver

type SafeErrorParamsResolver interface {
	// Params defines a map with dynamic parameters to return as part of the public safe error view.
	Params() map[string]any
}

SafeErrorParamsResolver defines an optional interface for specifying dynamic error parameters.

type SafeErrorResolver

type SafeErrorResolver interface {
	// Resolve allows modifying and returning a new public safe error data map.
	Resolve(errData map[string]any) any
}

SafeErrorResolver defines an error interface for resolving the public safe error fields.

type StatusTracker

type StatusTracker interface {
	// Status reports the written response status code.
	Status() int
}

type WriteTracker

type WriteTracker interface {
	// Written reports whether a write operation has occurred.
	Written() bool
}

Jump to

Keyboard shortcuts

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