Documentation
¶
Index ¶
- Variables
- func ErrorMapping(ctx context.Context, err error, dto *rfc7807.DTO)
- func Mount(multiplexer httpkit.Multiplexer, pattern string, handler http.Handler)
- func MountPoint(mountPoint string, next http.Handler) http.Handler
- func WithPathParam(ctx context.Context, key, val string) context.Context
- type Client
- func (r Client[Entity, ID]) Create(ctx context.Context, ptr *Entity) error
- func (r Client[Entity, ID]) DeleteAll(ctx context.Context) error
- func (r Client[Entity, ID]) DeleteByID(ctx context.Context, id ID) error
- func (r Client[Entity, ID]) FindAll(ctx context.Context) iterators.Iterator[Entity]
- func (r Client[Entity, ID]) FindByID(ctx context.Context, id ID) (ent Entity, found bool, err error)
- func (r Client[Entity, ID]) Update(ctx context.Context, ptr *Entity) error
- type ClientErrUnexpectedResponse
- type ClientSerializer
- type DTOMapping
- type ErrorHandler
- type IDConverter
- type IDInContext
- type IntID
- type MIMEType
- type Mapping
- type Resource
- type ResourceSerialization
- type Router
- func (router *Router) Connect(path string, handler http.Handler)
- func (router *Router) Delete(path string, handler http.Handler)
- func (router *Router) Get(path string, handler http.Handler)
- func (router *Router) Handle(pattern string, handler http.Handler)
- func (router *Router) Head(path string, handler http.Handler)
- func (router *Router) Mount(path string, handler http.Handler)
- func (router *Router) MountRoutes(routes Routes)
- func (router *Router) Namespace(path string, blk func(r *Router))
- func (router *Router) Options(path string, handler http.Handler)
- func (router *Router) Patch(path string, handler http.Handler)
- func (router *Router) Post(path string, handler http.Handler)
- func (router *Router) Put(path string, handler http.Handler)
- func (router *Router) Resource(identifier string, r Resource[testent.Foo, testent.FooID])
- func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (router *Router) Trace(path string, handler http.Handler)
- func (router *Router) Use(mws ...httpkit.MiddlewareFactoryFunc)
- type Routes
- type Serializer
- type SerializerDefault
- type StringID
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultBodyReadLimit int = 16 * units.Megabyte
DefaultBodyReadLimit is the maximum number of bytes that a restapi.Handler will read from the requester, if the Handler.BodyReadLimit is not provided.
var DefaultResourceClientHTTPClient http.Client = http.Client{ Transport: httpkit.RetryRoundTripper{ RetryStrategy: retry.ExponentialBackoff{ WaitTime: time.Second, Timeout: time.Minute, }, }, Timeout: 25 * time.Second, }
var DefaultSerializer = SerializerDefault{ Serializer: serializers.JSON{}, MIMEType: JSON, }
var DefaultSerializers = map[MIMEType]Serializer{ JSON.Base(): serializers.JSON{}, "application/problem+json": serializers.JSON{}, "application/x-ndjson": serializers.JSONStream{}, "application/stream+json": serializers.JSONStream{}, "application/json-stream": serializers.JSONStream{}, FormUrlencoded.Base(): serializers.FormURLEncoder{}, }
var ErrEntityAlreadyExist = errorkit.UserError{
ID: "entity-already-exists",
Message: "The entity could not be created as it already exists.",
}
var ErrEntityNotFound = errorkit.UserError{
ID: "entity-not-found",
Message: "The requested entity is not found in this resource.",
}
var ErrInternalServerError = errorkit.UserError{
ID: "internal-server-error",
Message: "An unexpected internal server error occurred.",
}
var ErrInvalidRequestBody = errorkit.UserError{
ID: "invalid-request-body",
Message: "The request body is invalid.",
}
var ErrMalformedID = errorkit.UserError{
ID: "malformed-id-in-path",
Message: "The received entity id in the path is malformed.",
}
var ErrMethodNotAllowed = errorkit.UserError{
ID: "restapi-method-not-allowed",
Message: "The requested RESTful method is not supported.",
}
var ErrPathNotFound = errorkit.UserError{
ID: "path-not-found",
Message: "The requested path is not found.",
}
var ErrRequestEntityTooLarge = errorkit.UserError{
ID: "request-entity-too-large",
Message: "The request body was larger than the size limit allowed for the server.",
}
Functions ¶
func Mount ¶
func Mount(multiplexer httpkit.Multiplexer, pattern string, handler http.Handler)
Mount will help to register a handler on a request multiplexer in both as the concrete path to the handler and as a prefix match. example:
if pattern -> "/something" registered as "/something" for exact match registered as "/something/" for prefix match
Types ¶
type Client ¶ added in v0.196.0
type Client[Entity, ID any] struct { BaseURL string HTTPClient *http.Client MIMEType MIMEType Mapping Mapping[Entity] Serializer ClientSerializer IDConverter idConverter[ID] LookupID crud.LookupIDFunc[Entity, ID] }
Example ¶
package main import ( "context" "go.llib.dev/frameless/pkg/restapi" "go.llib.dev/frameless/pkg/serializers" "go.llib.dev/frameless/spechelper/testent" ) func main() { var ( ctx = context.Background() fooRepo = restapi.Client[testent.Foo, testent.FooID]{ BaseURL: "https://mydomain.dev/api/v1/foos", MIMEType: restapi.JSON, Mapping: restapi.DTOMapping[testent.Foo, testent.FooDTO]{}, Serializer: serializers.JSON{}, IDConverter: restapi.IDConverter[testent.FooID]{}, LookupID: testent.Foo.LookupID, } ) var ent = testent.Foo{ Foo: "foo", Bar: "bar", Baz: "baz", } err := fooRepo.Create(ctx, &ent) if err != nil { panic(err) } gotEnt, found, err := fooRepo.FindByID(ctx, ent.ID) if err != nil { panic(err) } _, _ = gotEnt, found err = fooRepo.Update(ctx, &ent) if err != nil { panic(err) } err = fooRepo.DeleteByID(ctx, ent.ID) if err != nil { panic(err) } }
func (Client[Entity, ID]) DeleteByID ¶ added in v0.196.0
type ClientErrUnexpectedResponse ¶ added in v0.196.0
func (ClientErrUnexpectedResponse) Error ¶ added in v0.196.0
func (err ClientErrUnexpectedResponse) Error() string
type ClientSerializer ¶ added in v0.207.0
type ClientSerializer interface { serializers.Serializer serializers.ListDecoderMaker }
type DTOMapping ¶ added in v0.188.0
type DTOMapping[Entity, DTO any] struct { // ToEnt is an optional function to describe how to map a DTO into an Entity. // // default: dtos.Map[Entity, DTO](...) ToEnt func(ctx context.Context, dto DTO) (Entity, error) // ToDTO is an optional function to describe how to map an Entity into a DTO. // // default: dtos.Map[DTO, Entity](...) ToDTO func(ctx context.Context, ent Entity) (DTO, error) }
DTOMapping is a type safe implementation for the generic Mapping interface. When using the frameless/pkg/dtos package, all you need to provide is the type arguments; nothing else is required.
type ErrorHandler ¶
type ErrorHandler interface {
HandleError(w http.ResponseWriter, r *http.Request, err error)
}
type IDConverter ¶ added in v0.185.0
IDConverter is an OldMapping tool that you can embed in your OldMapping implementation, and it will implement the ID encoding that will be used in the URL.
func (IDConverter[ID]) FormatID ¶ added in v0.185.0
func (m IDConverter[ID]) FormatID(id ID) (string, error)
func (IDConverter[ID]) ParseID ¶ added in v0.185.0
func (m IDConverter[ID]) ParseID(data string) (ID, error)
type IDInContext ¶ added in v0.185.0
type IDInContext[CtxKey, EntityIDType any] struct{}
IDInContext is an OldMapping tool that you can embed in your OldMapping implementation, and it will implement the context handling related methods.
func (IDInContext[CtxKey, EntityIDType]) ContextLookupID ¶ added in v0.185.0
func (cm IDInContext[CtxKey, EntityIDType]) ContextLookupID(ctx context.Context) (EntityIDType, bool)
func (IDInContext[CtxKey, EntityIDType]) ContextWithID ¶ added in v0.185.0
func (cm IDInContext[CtxKey, EntityIDType]) ContextWithID(ctx context.Context, id EntityIDType) context.Context
type IntID ¶ added in v0.185.0
type IntID[ID ~int] struct{}
IntID is an OldMapping tool that you can embed in your OldMapping implementation, and it will implement the ID encoding that will be used in the URL.
type MIMEType ¶ added in v0.188.0
type MIMEType string
MIMEType or Multipurpose Internet Mail Extensions is an internet standard that extends the original email protocol to support non-textual content, such as images, audio files, and binary data.
It was first defined in RFC 1341 and later updated in RFC 2045. MIMEType allows for the encoding different types of data using a standardised format that can be transmitted over email or other internet protocols. This makes it possible to send and receive messages with a variety of content, such as text, images, audio, and video, in a consistent way across different mail clients and servers.
The MIMEType type is an essential component of this system, as it specifies the format of the data being transmitted. A MIMEType type consists of two parts: the type and the subtype, separated by a forward slash (`/`). The type indicates the general category of the data, such as `text`, `image`, or `audio`. The subtype provides more information about the specific format of the data, such as `plain` for plain text or `jpeg` for JPEG images. Today MIMEType is not only used for email but also for other internet protocols, such as HTTP, where it is used to specify the format of data in web requests and responses.
MIMEType type is commonly used in RESTful APIs as well. In an HTTP request or response header, the Content-Type field specifies the MIMEType type of the entity body.
func (MIMEType) WithCharset ¶ added in v0.188.0
type Mapping ¶
type Mapping[Entity any] interface { // contains filtered or unexported methods }
Mapping is a generic interface used for representing a DTO-Entity mapping relationship. Its primary function is to allow Resource to list various mappings, each with its own DTO type, for different MIMEType values. This means we can use different DTO types within the same restful Resource handler based on different content types, making it more flexible and adaptable to support different Serialization formats.
It is implemented by DTOMapping.
type Resource ¶ added in v0.188.0
type Resource[Entity, ID any] struct { // Create will create a new entity in the restful resource. // POST / Create func(ctx context.Context, ptr *Entity) error // Index will return the entities, optionally filtered with the query argument. // GET / Index func(ctx context.Context, query url.Values) (iterators.Iterator[Entity], error) // Show will return a single entity, looked up by its ID. // GET /:id Show func(ctx context.Context, id ID) (ent Entity, found bool, err error) // Update will update/replace an entity with the new state. // PUT /:id - update/replace // PATCH /:id - partial update (WIP) Update func(ctx context.Context, id ID, ptr *Entity) error // Destroy will delete an entity, identified by its id. // Delete /:id Destroy func(ctx context.Context, id ID) error // DestroyAll will delete all entity. // Delete / DestroyAll func(ctx context.Context, query url.Values) error // Serialization is responsible to serialize and unserialize DTOs. // JSON, line separated JSON stream and FormUrlencoded formats are supported out of the box. // // Serialization is an optional field. // Unless you have specific needs in serialization, don't configure it. Serialization ResourceSerialization[Entity, ID] // Mapping is the primary Entity to DTO mapping configuration. Mapping Mapping[Entity] // MappingForMIME defines a per MIMEType restapi.Mapping, that takes priority over Mapping MappingForMIME map[MIMEType]Mapping[Entity] // ErrorHandler is used to handle errors from the request, by mapping the error value into an error DTOMapping. ErrorHandler ErrorHandler // IDContextKey is an optional field used to store the parsed ID from the URL in the context. // // Default: IDContextKey[Entity, ID]{} IDContextKey any // SubRoutes is an http.Handler that will receive resource-specific requests. // SubRoutes is optional. // // The http.Request.Context will contain the parsed ID from the request path, // and can be accessed with the IDContextKey. // // Example paths // /plural-resource-identifier-name/:id/sub-routes // /users/42/status // /users/42/jobs/13 // // Request paths will be stripped from their prefix. // For example, "/users/42/jobs" will end up as "/jobs". SubRoutes http.Handler // BodyReadLimit is the max bytes that the handler is willing to read from the request body. // // The default value is DefaultBodyReadLimit, which is preset to 16MB. BodyReadLimit units.ByteSize }
Resource is an HTTP Handler that allows you to expose a resource such as a repository as a Restful API resource. Depending on what CRUD operation is supported by the Handler.Resource, the Handler supports the following actions:
Example ¶
fooRepository := memory.NewRepository[X, XID](memory.NewMemory()) fooRestfulResource := restapi.Resource[X, XID]{ Create: fooRepository.Create, Index: func(ctx context.Context, query url.Values) (iterators.Iterator[X], error) { foos := fooRepository.FindAll(ctx) if bt := query.Get("bigger"); bt != "" { bigger, err := strconv.Atoi(bt) if err != nil { return nil, err } foos = iterators.Filter(foos, func(foo X) bool { return bigger < foo.N }) } return foos, nil }, Show: fooRepository.FindByID, Update: func(ctx context.Context, id XID, ptr *X) error { ptr.ID = id return fooRepository.Update(ctx, ptr) }, Destroy: fooRepository.DeleteByID, Mapping: restapi.DTOMapping[X, XDTO]{}, MappingForMIME: map[restapi.MIMEType]restapi.Mapping[X]{ restapi.JSON: restapi.DTOMapping[X, XDTO]{}, }, } mux := http.NewServeMux() restapi.Mount(mux, "/foos", fooRestfulResource)
func (Resource[Entity, ID]) HTTPRequest ¶ added in v0.196.0
type ResourceSerialization ¶ added in v0.196.0
type ResourceSerialization[Entity, ID any] struct { Serializers map[MIMEType]Serializer IDConverter idConverter[ID] }
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Example ¶
var router restapi.Router router.Namespace("/path", func(r *restapi.Router) { r.Use(SampleMiddleware) r.Get("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })) r.Post("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })) r.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // sub route catch-all handle })) r.Resource("foo", restapi.Resource[Foo, FooID]{ Mapping: restapi.DTOMapping[Foo, FooDTO]{}, Index: func(ctx context.Context, query url.Values) (iterators.Iterator[Foo], error) { foo := Foo{ ID: "42", Foo: "foo", Bar: "bar", Baz: "baz", } return iterators.Slice([]Foo{foo}), nil }, }) }) router.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // handler that catches all requests that doesn't match anything directly })) router.Handle("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // /foo endpoint for all methods }))
func RouterFrom ¶ added in v0.188.0
RouterFrom
DEPRECATED
Example ¶
r := restapi.RouterFrom(restapi.Routes{ "/": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(100) }), "/path": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(101) }), }) _ = http.ListenAndServe("0.0.0.0:8080", r)
func (*Router) Connect ¶ added in v0.211.0
Example ¶
var router restapi.Router router.Connect("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
func (*Router) Delete ¶ added in v0.211.0
Example ¶
var router restapi.Router router.Delete("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
func (*Router) Get ¶ added in v0.211.0
Example ¶
var router restapi.Router router.Get("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
func (*Router) Handle ¶ added in v0.211.0
Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
Example ¶
var router restapi.Router var handler http.Handler // single endpoint router.Handle("/foo", handler) // catch all endpoint router.Handle("/foo/", handler)
func (*Router) Head ¶ added in v0.211.0
Example ¶
var router restapi.Router router.Head("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
func (*Router) Patch ¶ added in v0.211.0
Example ¶
var router restapi.Router router.Patch("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
func (*Router) Post ¶ added in v0.211.0
Example ¶
var router restapi.Router router.Post("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
func (*Router) Put ¶ added in v0.211.0
Example ¶
var router restapi.Router router.Put("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
func (*Router) Use ¶ added in v0.211.0
func (router *Router) Use(mws ...httpkit.MiddlewareFactoryFunc)
type Routes ¶
Routes
DEPRECATED: this early implementation will be removed in the near future. Use Router directly instead.
Example ¶
m := memory.NewMemory() fooRepository := memory.NewRepository[X, XID](m) barRepository := memory.NewRepository[Y, string](m) r := restapi.NewRouter(func(router *restapi.Router) { router.MountRoutes(restapi.Routes{ "/v1/api/foos": restapi.Resource[X, XID]{ Mapping: restapi.DTOMapping[X, XDTO]{}, SubRoutes: restapi.NewRouter(func(router *restapi.Router) { router.MountRoutes(restapi.Routes{ "/bars": restapi.Resource[Y, string]{}.WithCRUD(barRepository)}) }), }.WithCRUD(fooRepository), }) }) // Generated endpoints: // // Foo Index - GET /v1/api/foos // Foo Create - POST /v1/api/foos // Foo Show - GET /v1/api/foos/:foo_id // Foo Update - PATCH/PUT /v1/api/foos/:foo_id // Foo Delete - DELETE /v1/api/foos/:foo_id // // Bar Index - GET /v1/api/foos/:foo_id/bars // Bar Create - POST /v1/api/foos/:foo_id/bars // Bar Show - GET /v1/api/foos/:foo_id/bars/:bar_id // Bar Update - PATCH/PUT /v1/api/foos/:foo_id/bars/:bar_id // Bar Delete - DELETE /v1/api/foos/:foo_id/bars/:bar_id // if err := http.ListenAndServe(":8080", r); err != nil { log.Fatalln(err.Error()) }
type Serializer ¶ added in v0.188.0
type Serializer interface { serializers.Serializer }
type SerializerDefault ¶ added in v0.188.0
type SerializerDefault struct { Serializer interface { serializers.Serializer serializers.ListDecoderMaker } MIMEType MIMEType }