Documentation
¶
Index ¶
- Variables
- func ErrorMapping(ctx context.Context, err error, dto *rfc7807.DTO)
- func Mount(multiplexer multiplexer, pattern string, handler http.Handler)
- func MountPoint(mountPoint Path, next http.Handler) http.Handler
- func WithPathParam(ctx context.Context, key, val string) context.Context
- type BeforeHook
- 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 CreateOperation
- type DTOMapping
- type DeleteOperation
- type ErrorHandler
- type GenericListEncoder
- type Handler
- type IDConverter
- type IDInContext
- type IndexOperation
- type IntID
- type JSONSerializer
- type JSONStreamSerializer
- type ListDecoder
- type ListEncoder
- type MIMEType
- type Mapping
- type OldMapping
- type Operations
- type Path
- type Resource
- type ResourceMapping
- type ResourceSerialization
- type Router
- type Routes
- type Serializer
- type SerializerDefault
- type SetIDByExtIDTag
- type ShowOperation
- type StringID
- type UpdateOperation
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: JSONSerializer{}, MIMEType: JSON, }
var DefaultSerializers = map[MIMEType]Serializer{ JSON: JSONSerializer{}, "application/problem+json": JSONSerializer{}, "application/x-ndjson": JSONStreamSerializer{}, "application/stream+json": JSONStreamSerializer{}, "application/json-stream": JSONStreamSerializer{}, }
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 ¶
Types ¶
type BeforeHook ¶
type BeforeHook http.HandlerFunc
type Client ¶ added in v0.196.0
type Client[Entity, ID any] struct { BaseURL string HTTPClient *http.Client MIMEType MIMEType Mapping Mapping[Entity] Serializer Serializer IDConverter idConverter[ID] LookupID crud.LookupIDFunc[Entity, ID] }
Example ¶
package main import ( "context" "go.llib.dev/frameless/pkg/restapi" "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: restapi.JSONSerializer{}, 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 CreateOperation ¶
type CreateOperation[Entity, ID, DTO any] struct { BeforeHook BeforeHook }
CreateOperation
DEPRECATED
type DTOMapping ¶ added in v0.188.0
type DTOMapping[Entity, DTO any] struct{}
type DeleteOperation ¶
type DeleteOperation[Entity, ID, DTO any] struct { BeforeHook BeforeHook }
DeleteOperation
DEPRECATED
type ErrorHandler ¶
type ErrorHandler interface {
HandleError(w http.ResponseWriter, r *http.Request, err error)
}
type GenericListEncoder ¶ added in v0.188.0
type GenericListEncoder[T any] struct { W io.Writer Marshal func(v []T) ([]byte, error) // contains filtered or unexported fields }
func (*GenericListEncoder[T]) Close ¶ added in v0.188.0
func (enc *GenericListEncoder[T]) Close() error
func (*GenericListEncoder[T]) Encode ¶ added in v0.188.0
func (enc *GenericListEncoder[T]) Encode(v T) error
type Handler ¶
type Handler[Entity, ID, DTO any] struct { // Resource is the CRUD Resource object that we wish to expose as a restful API resource. Resource crud.ByIDFinder[Entity, ID] // Mapping takes care mapping back and forth Entity into a DTOMapping, and ID into a string. // ID needs mapping into a string because it is used as part of the restful paths. Mapping OldMapping[Entity, ID, DTO] // ErrorHandler is used to handle errors from the request, by mapping the error value into an error DTOMapping. ErrorHandler ErrorHandler // Router is the sub-router, where you can define routes related to entity related paths // > .../:id/sub-routes Router *Router // 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 int Operations[Entity, ID, DTO] // NoCreate will instruct the handler to not expose the `POST /` endpoint NoCreate bool // NoIndex will instruct the handler to not expose the `Get /` endpoint NoIndex bool // NoShow will instruct the handler to not expose the `Get /:id` endpoint NoShow bool // NoUpdate will instruct the handler to not expose the `PUT /:id` endpoint NoUpdate bool // NoDelete will instruct the handler to not expose the `DELETE /:id` endpoint NoDelete bool }
Handler is a 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 support the following actions:
- Index: GET /
- Show: GET /:id
- Create: POST /
- Update: PUT /:id
- Delete: Delete /:id
Example ¶
m := memory.NewMemory() fooRepository := memory.NewRepository[X, XID](m) h := restapi.Handler[X, XID, XDTO]{ Resource: fooRepository, Mapping: XMapping{}, } if err := http.ListenAndServe(":8080", h); err != nil { log.Fatalln(err.Error()) }
type IDConverter ¶ added in v0.185.0
IDConverter is a 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 a 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 IndexOperation ¶
type IndexOperation[Entity, ID, DTO any] struct { BeforeHook BeforeHook Override func(r *http.Request) iterators.Iterator[Entity] }
IndexOperation
DEPRECATED
type IntID ¶ added in v0.185.0
type IntID[ID ~int] struct{}
IntID is a 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 JSONSerializer ¶ added in v0.188.0
type JSONSerializer struct{}
func (JSONSerializer) MIMEType ¶ added in v0.188.0
func (s JSONSerializer) MIMEType() MIMEType
func (JSONSerializer) Marshal ¶ added in v0.188.0
func (s JSONSerializer) Marshal(v any) ([]byte, error)
func (JSONSerializer) NewListDecoder ¶ added in v0.196.0
func (s JSONSerializer) NewListDecoder(r io.ReadCloser) ListDecoder
func (JSONSerializer) NewListEncoder ¶ added in v0.188.0
func (s JSONSerializer) NewListEncoder(w io.Writer) ListEncoder
type JSONStreamSerializer ¶ added in v0.188.0
type JSONStreamSerializer struct{}
func (JSONStreamSerializer) Marshal ¶ added in v0.188.0
func (s JSONStreamSerializer) Marshal(v any) ([]byte, error)
func (JSONStreamSerializer) NewListDecoder ¶ added in v0.196.0
func (s JSONStreamSerializer) NewListDecoder(w io.ReadCloser) ListDecoder
func (JSONStreamSerializer) NewListEncoder ¶ added in v0.188.0
func (s JSONStreamSerializer) NewListEncoder(w io.Writer) ListEncoder
type ListDecoder ¶ added in v0.196.0
type ListDecoder interface { Decode(ptr any) error // Next will ensure that Value returns the next item when executed. // If the next value is not retrievable, Next should return false and ensure Err() will return the error cause. Next() bool // Err return the error cause. Err() error // Closer is required to make it able to cancel iterators where resources are being used behind the scene // for all other cases where the underling io is handled on a higher level, it should simply return nil io.Closer }
type ListEncoder ¶ added in v0.188.0
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 OldMapping ¶ added in v0.188.0
type OldMapping[Entity, ID, DTO any] interface { LookupID(Entity) (ID, bool) SetID(*Entity, ID) FormatID(ID) (string, error) ParseID(string) (ID, error) ContextWithID(context.Context, ID) context.Context ContextLookupID(ctx context.Context) (ID, bool) MapEntity(context.Context, DTO) (Entity, error) MapDTO(context.Context, Entity) (DTO, error) }
OldMapping is the previous iteration's mapping solution. It was replaced because it coupled json serialization and the JSON DTOs with the Handler, excluding support for other serialisation formats.
DEPRECATED
type Operations ¶
type Operations[Entity, ID, DTO any] struct { // Index is an OPTIONAL field if you wish to customise the index operation's behaviour // GET / // Index IndexOperation[Entity, ID, DTO] // Create is an OPTIONAL field if you wish to customise the create operation's behaviour // POST / // Create CreateOperation[Entity, ID, DTO] // Show is an OPTIONAL field if you wish to customise the show operation's behaviour // GET /:id // Show ShowOperation[Entity, ID, DTO] // Update is an OPTIONAL field if you wish to customise the update operation's behaviour // PUT /:id // PATCH /:id // Update UpdateOperation[Entity, ID, DTO] // Delete is an OPTIONAL field if you wish to customise the delete operation's behaviour // DELETE /:id // Delete DeleteOperation[Entity, ID, DTO] }
Operations is an optional config where you can customise individual restful operations.
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 serialise a DTO into or out from the right serialisation format. // Most format is supported out of the box, but in case you want to configure your own, // you can do so using this config. Serialization ResourceSerialization[Entity, ID] // Mapping is responsible to map a given entity to a given DTO Mapping ResourceMapping[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 // EntityRoutes is an http.Handler that will receive entity related requests. // 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/entity-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". EntityRoutes http.Handler // BodyReadLimitByteSize 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. BodyReadLimitByteSize int }
Resource is a 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.ResourceMapping[X]{ ForMIME: map[restapi.MIMEType]restapi.Mapping[X]{ restapi.JSON: restapi.DTOMapping[X, XDTO]{}, }, Mapping: restapi.DTOMapping[X, XDTO]{}, }, } mux := http.NewServeMux() restapi.Mount(mux, "/foos", fooRestfulResource)
func (Resource[Entity, ID]) HTTPRequest ¶ added in v0.196.0
type ResourceMapping ¶ added in v0.196.0
type ResourceMapping[Entity any] struct { // Mapping is the primary entity to DTO mapping configuration. Mapping Mapping[Entity] // ForMIME defines a per MIMEType restapi.Mapping, that takes priority over Mapping ForMIME map[MIMEType]Mapping[Entity] }
ResourceMapping is responsible for map
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
}
func RouterFrom ¶ added in v0.188.0
Example ¶
package main import ( "go.llib.dev/frameless/pkg/restapi" "net/http" ) func main() { 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) MountRoutes ¶
type Routes ¶
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.Handler[X, XID, XDTO]{ Resource: fooRepository, Mapping: XMapping{}, Router: restapi.NewRouter(func(router *restapi.Router) { router.MountRoutes(restapi.Routes{ "/bars": restapi.Handler[Y, string, YDTO]{ Resource: barRepository, Mapping: YMapping{}, }}) }), }, }) }) // 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 {
// contains filtered or unexported methods
}
type SerializerDefault ¶ added in v0.188.0
type SerializerDefault struct { Serializer Serializer MIMEType MIMEType }
type SetIDByExtIDTag ¶ added in v0.185.0
type SetIDByExtIDTag[Entity, ID any] struct{}
SetIDByExtIDTag is a OldMapping tool that allows you to extract Entity ID using the `ext:"id"` tag.
DEPRECATED
func (SetIDByExtIDTag[Entity, ID]) LookupID ¶ added in v0.185.0
func (m SetIDByExtIDTag[Entity, ID]) LookupID(ent Entity) (ID, bool)
func (SetIDByExtIDTag[Entity, ID]) SetID ¶ added in v0.185.0
func (m SetIDByExtIDTag[Entity, ID]) SetID(ptr *Entity, id ID)
type ShowOperation ¶
type ShowOperation[Entity, ID, DTO any] struct { BeforeHook BeforeHook }
ShowOperation
DEPRECATED
type StringID ¶ added in v0.185.0
type StringID[ID ~string] struct{}
StringID is a 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 UpdateOperation ¶
type UpdateOperation[Entity, ID, DTO any] struct { BeforeHook BeforeHook }
UpdateOperation
DEPRECATED