turtleware

package module
v0.0.0-...-8a74032 Latest Latest
Warning

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

Go to latest
Published: Feb 11, 2024 License: MIT Imports: 31 Imported by: 3

README

test Go Reference Go Report Card codecov

turtleware

turtleware is an opinionated framework for creating REST services. It provides pluggable middlewares and some utility methods to simplify life. It uses JWT bearer authentication, and relies heavily on caching.

The framework is built on some core libraries:

Download:

go get github.com/kernle32dll/turtleware

Detailed documentation can be found on pkg.go.dev.

State of the project

turtleware is actively used in productive projects by the author.

Still, this project is still pretty much work-in-progress. Bugs happen, and breaking-changes might occur at any time. Also, only the most recent Go version is supported at any time for now. Code coverage is low, and documentation slim, so be warned.

Getting started

turtleware provides three distinct functionalities:

  1. A set of middlewares, which can be chained individually (e.g., auth)
  2. Composition methods for chaining these middlewares together in a meaningful way (e.g. a GET endpoint)
  3. Optional multi tenancy

For a complete example, look at the main.go in the examples folder.

Documentation

Index

Constants

View Source
const TracerName = "github.com/kernle32dll/turtleware"

Variables

View Source
var (
	// ErrTokenValidationFailed indicates that the token provided
	// could not be validated.
	ErrTokenValidationFailed = errors.New("failed to validate token signature")

	// ErrMissingAuthHeader indicates that a requested was
	// missing an authentication header.
	ErrMissingAuthHeader = errors.New("authentication header missing")

	// ErrAuthHeaderWrongFormat indicates that a requested contained an a authorization
	// header, but it was in the wrong format.
	ErrAuthHeaderWrongFormat = errors.New("authorization header format must be Bearer {token}")

	// ErrFailedToParsePrivateKey indicates a problem parsing a given private key as a JWK.
	ErrFailedToParsePrivateKey = errors.New("failed to parse private key as JWK")

	// ErrFailedToSetKID indicates a problem setting the KID field of a JWK.
	ErrFailedToSetKID = errors.New("failed to set 'kid' field")

	// ErrFailedToSetAlgorithm indicates a problem setting the alg field of a JWK.
	ErrFailedToSetAlgorithm = errors.New("failed to set 'alg' field")
)
View Source
var (
	// ErrContextMissingAuthToken is an internal error indicating a missing
	// auth token in the request context, whereas one was expected.
	ErrContextMissingAuthToken = errors.New("missing auth token in context")

	// ErrContextMissingEntityUUID is an internal error indicating a missing
	// entity UUID in the request context, whereas one was expected.
	ErrContextMissingEntityUUID = errors.New("missing entity UUID in context")

	// ErrContextMissingPaging is an internal error indicating missing paging
	// in the request context, whereas one was expected.
	ErrContextMissingPaging = errors.New("missing paging in context")

	// ErrContextMissingAuthClaims is an internal error indicating missing auth
	// claims in the request context, whereas they were expected.
	ErrContextMissingAuthClaims = errors.New("missing auth claims in context")

	// ErrMarshalling signals that an error occurred while marshalling.
	ErrMarshalling = errors.New("failed to parse message body")

	// ErrReceivingResults signals that an error occurred while receiving the results
	// from the database or similar.
	ErrReceivingResults = errors.New("error while receiving results")

	// ErrResourceNotFound indicates that a requested resource was not found.
	ErrResourceNotFound = errors.New("resource not found")

	// ErrReceivingMeta signals that an error occurred while receiving the metadata
	// from the database or remotes.
	ErrReceivingMeta = errors.New("error while receiving metadata")

	// ErrMissingUserUUID signals that a received JWT did not contain an user UUID.
	ErrMissingUserUUID = errors.New("token does not include user uuid")
)
View Source
var (
	ErrUnmodifiedSinceHeaderMissing = errors.New("If-Unmodified-Since header missing")
	ErrUnmodifiedSinceHeaderInvalid = errors.New("received If-Unmodified-Since header in invalid format")
	ErrNoChanges                    = errors.New("patch request did not contain any changes")
	ErrNoDateTimeLayoutMatched      = errors.New("no date time layout matched")
)
View Source
var (
	// ErrInvalidOffset indicates that the query contained an invalid
	// offset parameter (e.g. non numeric).
	ErrInvalidOffset = errors.New("invalid offset parameter")

	// ErrInvalidLimit indicates that the query contained an invalid
	// limit parameter (e.g. non numeric).
	ErrInvalidLimit = errors.New("invalid limit parameter")
)
View Source
var (

	// EmissioneWriter is the globally used writer for writing out response bodies.
	EmissioneWriter = emissione.New(jsonWriter, emissione.WriterMapping{
		"application/json":                jsonWriter,
		"application/json;charset=utf-8":  jsonWriter,
		"application/json; charset=utf-8": jsonWriter,
		"application/xml":                 xmlWriter,
		"application/xml;charset=utf-8":   xmlWriter,
		"application/xml; charset=utf-8":  xmlWriter,
	})
)

Functions

func AuthBearerHeaderMiddleware

func AuthBearerHeaderMiddleware(h http.Handler) http.Handler

AuthBearerHeaderMiddleware is a http middleware for extracting the bearer token from the authorization header, and passing it down. If the header is not existing, the WWW-Authenticate header is set and the handler bails out.

func AuthClaimsFromRequestContext

func AuthClaimsFromRequestContext(ctx context.Context) (map[string]interface{}, error)

func AuthClaimsMiddleware

func AuthClaimsMiddleware(keySet jwk.Set) func(http.Handler) http.Handler

AuthClaimsMiddleware is a http middleware for extracting authentication claims, and passing them down.

func AuthTokenFromRequestContext

func AuthTokenFromRequestContext(ctx context.Context) (string, error)

func CountHeaderMiddleware

func CountHeaderMiddleware(
	countFetcher ListCountFunc,
	errorHandler ErrorHandlerFunc,
) func(http.Handler) http.Handler

CountHeaderMiddleware is a middleware for injecting an X-Total-Count header into the response, by the provided ListCountFunc. If an error is encountered, the provided ErrorHandlerFunc is called.

func DefaultCreateErrorHandler

func DefaultCreateErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)

func DefaultErrorHandler

func DefaultErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)

func DefaultFileUploadErrorHandler

func DefaultFileUploadErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)

func DefaultPatchErrorHandler

func DefaultPatchErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)

func EntityUUIDFromRequestContext

func EntityUUIDFromRequestContext(ctx context.Context) (string, error)

func EntityUUIDMiddleware

func EntityUUIDMiddleware(entityFunc ResourceEntityFunc) func(h http.Handler) http.Handler

EntityUUIDMiddleware is a http middleware for extracting the uuid of the resource requested, and passing it down.

func ExtractCacheHeader

func ExtractCacheHeader(r *http.Request) (string, time.Time)

ExtractCacheHeader extracts the Etag (If-None-Match) and last modification (If-Modified-Since) headers from a given request.

func FileUploadMiddleware

func FileUploadMiddleware(fileHandleFunc FileHandleFunc, errorHandler ErrorHandlerFunc) func(http.Handler) http.Handler

func FromAuthHeader

func FromAuthHeader(r *http.Request) (string, error)

FromAuthHeader is a "TokenExtractor" that takes a give request and extracts the JWT token from the Authorization header.

Copied from https://github.com/auth0/go-jwt-middleware/blob/master/jwtmiddleware.go

func GetIfUnmodifiedSince

func GetIfUnmodifiedSince(r *http.Request) (time.Time, error)

GetIfUnmodifiedSince tries to parse the last modification (If-Modified-Since) header from a given request. It tries the following formats (in that order):

- time.RFC1123 - time.RFC3339Nano - time.RFC3339

func HandleFileUpload

func HandleFileUpload(ctx context.Context, r *http.Request, fileHandleFunc FileHandleFunc) error

func IsHandledByDefaultCreateErrorHandler

func IsHandledByDefaultCreateErrorHandler(err error) bool

IsHandledByDefaultCreateErrorHandler indicates if the DefaultCreateErrorHandler has any special handling for the given error, or if it defaults to handing it out as-is.

func IsHandledByDefaultErrorHandler

func IsHandledByDefaultErrorHandler(err error) bool

IsHandledByDefaultErrorHandler indicates if the DefaultErrorHandler has any special handling for the given error, or if it defaults to handing it out as-is.

func IsHandledByDefaultFileUploadErrorHandler

func IsHandledByDefaultFileUploadErrorHandler(err error) bool

IsHandledByDefaultFileUploadErrorHandler indicates if the DefaultFileUploadErrorHandler has any special handling for the given error, or if it defaults to handing it out as-is.

func IsHandledByDefaultPatchErrorHandler

func IsHandledByDefaultPatchErrorHandler(err error) bool

IsHandledByDefaultPatchErrorHandler indicates if the DefaultPatchErrorHandler has any special handling for the given error, or if it defaults to handing it out as-is.

func JWKFromPrivateKey

func JWKFromPrivateKey(privateKey crypto.PrivateKey, kid string) (jwk.Key, error)

JWKFromPrivateKey parses a given crypto.PrivateKey as a JWK, and tries to set the KID field of it. It also tries to guess the algorithm for signing with the JWK.

func JWKFromPublicKey

func JWKFromPublicKey(publicKey crypto.PublicKey, kid string) (jwk.Key, error)

JWKFromPublicKey parses a given crypto.PublicKey as a JWK, and tries to set the KID field of it.

func ListCacheMiddleware

func ListCacheMiddleware(
	hashFetcher ListHashFunc,
	errorHandler ErrorHandlerFunc,
) func(h http.Handler) http.Handler

ListCacheMiddleware is a middleware for transparently handling caching via the provided ListHashFunc. The next handler of the middleware is only called on a cache miss. That is, if the If-None-Match header and the fetched hash differ. If the ListHashFunc returns either sql.ErrNoRows or os.ErrNotExist, the sha256 hash of an empty string is assumed as the hash. If an error is encountered, the provided ErrorHandlerFunc is called.

func ListSQLHandler

func ListSQLHandler[T any](
	keySet jwk.Set,
	listEndpoint GetSQLListEndpoint[T],
) http.Handler

func ListSQLxHandler

func ListSQLxHandler[T any](
	keySet jwk.Set,
	listEndpoint GetSQLxListEndpoint[T],
) http.Handler

func PagingMiddleware

func PagingMiddleware(h http.Handler) http.Handler

PagingMiddleware is a http middleware for extracting paging information, and passing it down.

func ReadKeySetFromFolder

func ReadKeySetFromFolder(ctx context.Context, path string) (jwk.Set, error)

ReadKeySetFromFolder recursively reads a folder for public keys to assemble a JWK set from.

func RequestLoggerMiddleware

func RequestLoggerMiddleware(opts ...LoggingOption) func(next http.Handler) http.Handler

RequestLoggerMiddleware is a http middleware for logging non-sensitive properties about the request.

func RequestNotAllowedHandler

func RequestNotAllowedHandler(opts ...LoggingOption) http.Handler

RequestNotAllowedHandler is a http handler for logging requests which were url matched, but using an invalid method. This is mostly useful for gorilla/mux with its MethodNotAllowedHandler.

func RequestNotFoundHandler

func RequestNotFoundHandler(opts ...LoggingOption) http.Handler

RequestNotFoundHandler is a http handler for logging requests which were not matched. This is mostly useful for gorilla/mux with its NotFoundHandler.

func RequestTimingMiddleware

func RequestTimingMiddleware() func(next http.Handler) http.Handler

RequestTimingMiddleware is a http middleware for timing the response time of a request.

func ResourceCacheMiddleware

func ResourceCacheMiddleware(
	lastModFetcher ResourceLastModFunc,
	errorHandler ErrorHandlerFunc,
) func(h http.Handler) http.Handler

ResourceCacheMiddleware is a middleware for transparently handling caching of a single entity (or resource) via the provided ResourceLastModFunc. The next handler of the middleware is only called when the If-Modified-Since header and the fetched last modification date differ. If an error is encountered, the provided ErrorHandlerFunc is called.

func ResourceCreateHandler

func ResourceCreateHandler[T CreateDTO](
	keySet jwk.Set,
	createEndpoint CreateEndpoint[T],
	nextHandler http.Handler,
) http.Handler

func ResourceCreateMiddleware

func ResourceCreateMiddleware[T CreateDTO](createFunc CreateFunc[T], errorHandler ErrorHandlerFunc) func(http.Handler) http.Handler

func ResourceDataHandler

func ResourceDataHandler[T any](dataFetcher ResourceDataFunc[T], errorHandler ErrorHandlerFunc) http.Handler

func ResourceHandler

func ResourceHandler[T any](
	keySet jwk.Set,
	getEndpoint GetEndpoint[T],
) http.Handler

func ResourcePatchHandler

func ResourcePatchHandler[T PatchDTO](
	keySet jwk.Set,
	patchEndpoint PatchEndpoint[T],
	nextHandler http.Handler,
) http.Handler

func ResourcePatchMiddleware

func ResourcePatchMiddleware[T PatchDTO](patchFunc PatchFunc[T], errorHandler ErrorHandlerFunc) func(http.Handler) http.Handler

func SQLListDataHandler

func SQLListDataHandler[T any](dataFetcher ListSQLDataFunc, dataTransformer SQLResourceFunc[T], errorHandler ErrorHandlerFunc) http.Handler

func SQLxListDataHandler

func SQLxListDataHandler[T any](dataFetcher ListSQLxDataFunc, dataTransformer SQLxResourceFunc[T], errorHandler ErrorHandlerFunc) http.Handler

func StaticListDataHandler

func StaticListDataHandler[T any](dataFetcher ListStaticDataFunc[T], errorHandler ErrorHandlerFunc) http.Handler

func StaticListHandler

func StaticListHandler[T any](
	keySet jwk.Set,
	listEndpoint GetStaticListEndpoint[T],
) http.Handler

func StreamResponse

func StreamResponse(reader io.Reader, w http.ResponseWriter, r *http.Request, errorHandler ErrorHandlerFunc)

func TagContextSpanWithError

func TagContextSpanWithError(ctx context.Context, err error) error

TagContextSpanWithError tries to retrieve an open telemetry span from the given context, and sets some error attributes, signaling that the current span has failed. If no span exists, this function does nothing. This function returns the error as provided, to facilitate easy error returning in using functions.

func TracingMiddleware

func TracingMiddleware(name string, traceProvider trace.TracerProvider) func(http.Handler) http.Handler

TracingMiddleware is a http middleware for injecting a new named open telemetry span into the request context. If tracer is nil, otel.GetTracerProvider() is used.

func UserUUIDFromRequestContext

func UserUUIDFromRequestContext(ctx context.Context) (string, error)

func ValidateTokenBySet

func ValidateTokenBySet(
	tokenString string, keySet jwk.Set,
) (map[string]interface{}, error)

ValidateTokenBySet validates the given token with the given key set. If a key matches, the containing claims are returned.

func WrapZerologTracing

func WrapZerologTracing(ctx context.Context) zerolog.Logger

WrapZerologTracing fetches the zerolog.Logger attached with the context (if existing), and creates a new logger with the context's spanID and traceID fields set.

func WriteError

func WriteError(
	ctx context.Context,
	w http.ResponseWriter,
	r *http.Request,
	code int,
	errors ...error,
)

WriteError sets the given status code, and writes a nicely formatted json errors to the response body - if the request type is not HEAD.

Types

type CreateDTO

type CreateDTO interface {
	Validate() []error
}

type CreateEndpoint

type CreateEndpoint[T CreateDTO] interface {
	EntityUUID(r *http.Request) (string, error)
	CreateEntity(ctx context.Context, entityUUID, userUUID string, create T) error
	HandleError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)
}

type CreateFunc

type CreateFunc[T CreateDTO] func(ctx context.Context, entityUUID, userUUID string, create T) error

type ErrorHandlerFunc

type ErrorHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)

ErrorHandlerFunc is a function for handling arbitrary errors, that can happen during and turtleware middleware. If in doubt, use turtleware.DefaultErrorHandler, which handles many errors with meaningful error output.

type FileHandleFunc

type FileHandleFunc func(ctx context.Context, entityUUID, userUUID string, fileName string, file multipart.File) error

type GetEndpoint

type GetEndpoint[T any] interface {
	EntityUUID(r *http.Request) (string, error)
	LastModification(ctx context.Context, entityUUID string) (time.Time, error)
	FetchEntity(ctx context.Context, entityUUID string) (T, error)
	HandleError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)
}

type GetSQLListEndpoint

type GetSQLListEndpoint[T any] interface {
	ListHash(ctx context.Context, paging Paging) (string, error)
	TotalCount(ctx context.Context) (uint, error)
	FetchRows(ctx context.Context, paging Paging) (*sql.Rows, error)
	TransformEntity(ctx context.Context, r *sql.Rows) (T, error)
	HandleError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)
}

type GetSQLxListEndpoint

type GetSQLxListEndpoint[T any] interface {
	ListHash(ctx context.Context, paging Paging) (string, error)
	TotalCount(ctx context.Context) (uint, error)
	FetchRows(ctx context.Context, paging Paging) (*sqlx.Rows, error)
	TransformEntity(ctx context.Context, r *sqlx.Rows) (T, error)
	HandleError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)
}

type GetStaticListEndpoint

type GetStaticListEndpoint[T any] interface {
	ListHash(ctx context.Context, paging Paging) (string, error)
	TotalCount(ctx context.Context) (uint, error)
	FetchEntities(ctx context.Context, paging Paging) ([]T, error)
	HandleError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)
}

type ListCountFunc

type ListCountFunc func(ctx context.Context) (uint, error)

ListCountFunc is a function for returning the total amount of entities for a list endpoint. The function may return sql.ErrNoRows or os.ErrNotExist to indicate that there are not elements, for easier handling.

type ListHashFunc

type ListHashFunc func(ctx context.Context, paging Paging) (string, error)

ListHashFunc is a function for returning a calculated hash for a given subset of entities via the given paging, for a list endpoint. The function may return sql.ErrNoRows or os.ErrNotExist to indicate that there are not elements, for easier handling.

type ListSQLDataFunc

type ListSQLDataFunc func(ctx context.Context, paging Paging) (*sql.Rows, error)

type ListSQLxDataFunc

type ListSQLxDataFunc func(ctx context.Context, paging Paging) (*sqlx.Rows, error)

type ListStaticDataFunc

type ListStaticDataFunc[T any] func(ctx context.Context, paging Paging) ([]T, error)

type LoggingOption

type LoggingOption func(*loggingOptions)

LoggingOption represents an option for the logging parameters.

func LogHeaderBlacklist

func LogHeaderBlacklist(headerBlacklist ...string) LoggingOption

LogHeaderBlacklist sets a blacklist of headers to disallow. Automatically replaces the whitelist if used. The default is not set, which means "allow all".

func LogHeaderWhitelist

func LogHeaderWhitelist(headerWhitelist ...string) LoggingOption

LogHeaderWhitelist sets a whitelist of headers to allow. Automatically replaces the blacklist if used. The default is not set, which means "allow all".

func LogHeaders

func LogHeaders(logHeaders bool) LoggingOption

LogHeaders sets whether headers should be logged. The default is false.

type Paging

type Paging struct {
	Offset uint32
	Limit  uint16
}

Paging is a simple holder for applying offsets and limits.

func PagingFromRequestContext

func PagingFromRequestContext(ctx context.Context) (Paging, error)

func ParsePagingFromRequest

func ParsePagingFromRequest(r *http.Request) (Paging, error)

ParsePagingFromRequest parses paging information from a given request.

func (Paging) String

func (paging Paging) String() string

String provides a simple way of stringifying paging information for requests.

type PatchDTO

type PatchDTO interface {
	HasChanges() bool
	Validate() []error
}

type PatchEndpoint

type PatchEndpoint[T PatchDTO] interface {
	EntityUUID(r *http.Request) (string, error)
	UpdateEntity(ctx context.Context, entityUUID, userUUID string, patch T, ifUnmodifiedSince time.Time) error
	HandleError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)
}

type PatchFunc

type PatchFunc[T PatchDTO] func(ctx context.Context, entityUUID, userUUID string, patch T, ifUnmodifiedSince time.Time) error

type ResourceDataFunc

type ResourceDataFunc[T any] func(ctx context.Context, entityUUID string) (T, error)

type ResourceEntityFunc

type ResourceEntityFunc func(r *http.Request) (string, error)

type ResourceLastModFunc

type ResourceLastModFunc func(ctx context.Context, entityUUID string) (time.Time, error)

ResourceLastModFunc is a function for returning the last modification data for a specific entity. The function may return sql.ErrNoRows or os.ErrNotExist to indicate that there are not elements, for easier handling.

type SQLResourceFunc

type SQLResourceFunc[T any] func(ctx context.Context, r *sql.Rows) (T, error)

type SQLxResourceFunc

type SQLxResourceFunc[T any] func(ctx context.Context, r *sqlx.Rows) (T, error)

type TracingOption

type TracingOption func(*tracingOptions)

TracingOption represents an option for the tracing parameters.

func TraceHeaderBlacklist

func TraceHeaderBlacklist(headerBlacklist ...string) TracingOption

TraceHeaderBlacklist sets a blacklist of headers to disallow. Automatically replaces the whitelist if used. The default is not set, which means "deny all".

func TraceHeaderWhitelist

func TraceHeaderWhitelist(headerWhitelist ...string) TracingOption

TraceHeaderWhitelist sets a whitelist of headers to allow. Automatically replaces the blacklist if used. The default is not set, which means "deny all".

func TracingRoundTripper

func TracingRoundTripper(roundTripper http.RoundTripper) TracingOption

TracingRoundTripper sets the RoundTripper interface actually used to make requests. The default is nil, which means http.DefaultTransport.

func TracingTracer

func TracingTracer(tracer trace.TracerProvider) TracingOption

TracingTracer sets the Tracer interface used for tracing. The default is nil, which means otel.GetTracerProvider()

type TracingTransport

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

TracingTransport is an implementation of http.RoundTripper that will inject tracing information, and then call the actual Transport.

func NewTracingTransport

func NewTracingTransport(opts ...TracingOption) *TracingTransport

func (TracingTransport) RoundTrip

func (c TracingTransport) RoundTrip(req *http.Request) (*http.Response, error)

type ValidationWrapperError

type ValidationWrapperError struct {
	Errors []error
}

ValidationWrapperError is a wrapper for indicating that the validation for a create or patch endpoint failed, via the containing errors.

func (ValidationWrapperError) As

func (validationWrapperError ValidationWrapperError) As(target interface{}) bool

func (ValidationWrapperError) Error

func (validationWrapperError ValidationWrapperError) Error() string

func (ValidationWrapperError) Unwrap

func (validationWrapperError ValidationWrapperError) Unwrap() []error

Directories

Path Synopsis
examples module
tenant module

Jump to

Keyboard shortcuts

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