restapi

package
v0.197.0 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2024 License: Apache-2.0 Imports: 32 Imported by: 0

README

package restapi

TODO:

  • think through the mount path thing. It is not intuitive, and can be easily overlooked when someone tries to put a router under a mux.
  • the Resource CRUD coupling with the handler feels out of place.
    • provide a factory function instead that can turn a CRUD resource into
  • use Accept Content Type

REST API stands for Representational State Transfer and is an architectural pattern for creating web services.

Roy Fielding developed it in 2000 and has led to a growing collection of RESTful web services that follow the REST principles. Now, REST APIs see widespread use by application developers due to how simply it communicates with other machines over complex operations like COBRA, RPC, or Simple Object Access Protocol (SOAP).

REST is a ruleset that defines best practices for sharing data between clients and the server. It’s essentially a design style used when creating HTTP or other APIs that only asks you to use CRUD functions, regardless of the complexity.

REST applications use HTTP methods like GET, POST, DELETE, and PUT. REST emphasizes the scalability of components and the simplicity of interfaces.

While neglecting a portion of your tools may seem counterintuitive, it ultimately forces you to describe complex behaviours in simple terms.

Not all HTTP APIs are REST APIs.

The API needs to meet the following architectural requirements listed below to be considered a REST API.

These constraints combine to create an application with strong boundaries and a clear separation of concerns. The client receives server data when requested. The client manipulates or displays the data. The client notifies the server of any state changes. REST APIs don’t conceal data from the client, only implementations.

Client-server

REST applications have a server that manages application data and state. The server communicates with a client that handles the user interactions. A clear separation of concerns divides the two components.

Stateless

Servers don’t maintain client state; clients manage their application state. The client’s requests to the server contain all the information required to process them.

Cacheable

Being cacheable is one of the architectural constraints of REST. The servers must mark their responses as cacheable or not. Systems and clients can cache responses when convenient to improve performance. They also dispose of non-cacheable information, so no client uses stale data.

per HTTP methods

GET requests should be cachable by default – until an exceptional condition arises. Usually, browsers treat all GET requests as cacheable.

POST requests are not cacheable by default but can be made cacheable if either an Expires header or a Cache-Control header with a directive, to explicitly allows caching to be added to the response.

Responses to PUT and DELETE requests are not cacheable. Please note that HTTP dates are always expressed in GMT, never local time.

Cache-Control Headers

Below given are the main HTTP response headers that we can use to control caching behaviour:

Expires Header

The Expires HTTP header specifies an absolute expiry time for a cached representation. Beyond that time, a cached representation is considered stale and must be re-validated with the origin server. The server can include time up to one year in the future to indicate a never expiring cached representation.

Expires: Fri, 20 May 2016 19:20:49 GMT
Cache-Control Header

The header value comprises one or more comma-separated directives. These directives determine whether a response is cacheable and, if so, by whom and for how long, e.g. max-age directives.

Cache-Control: max-age=3600

Cacheable responses (whether to a GET or a POST request) should also include a validator — either an ETag or a Last-Modified header.

Last-Modified Header

Whereas a response’s Date header indicates when the server generated the response, the Last-Modified header indicates when the server last changed the associated resource.

This header is a validator to determine if the resource is the same as the previously stored one by the client’s cache. Less accurate than an ETag header, it is a fallback mechanism.

The Last-Modified value cannot be greater than the Date value. Note that the Date header is listed in the forbidden header names.

Uniform interface

Uniform interface is REST’s most well-known feature or rule. Fielding says:

The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components.

REST services provide data as resources with a consistent namespace.

Layered system

Components in the system cannot see beyond their layer. This confined scope allows you to add load-balancers easily and proxies to improve authentication security or performance.

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
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.

View Source
var DefaultResourceClientHTTPClient http.Client = http.Client{
	Transport: httpkit.RetryRoundTripper{
		RetryStrategy: retry.ExponentialBackoff{
			WaitTime: time.Second,
			Timeout:  time.Minute,
		},
	},
	Timeout: 25 * time.Second,
}
View Source
var DefaultSerializer = SerializerDefault{
	Serializer: JSONSerializer{},
	MIMEType:   JSON,
}
View Source
var DefaultSerializers = map[MIMEType]Serializer{
	JSON:                       JSONSerializer{},
	"application/problem+json": JSONSerializer{},
	"application/x-ndjson":     JSONStreamSerializer{},
	"application/stream+json":  JSONStreamSerializer{},
	"application/json-stream":  JSONStreamSerializer{},
}
View Source
var ErrEntityAlreadyExist = errorkit.UserError{
	ID:      "entity-already-exists",
	Message: "The entity could not be created as it already exists.",
}
View Source
var ErrEntityNotFound = errorkit.UserError{
	ID:      "entity-not-found",
	Message: "The requested entity is not found in this resource.",
}
View Source
var ErrInternalServerError = errorkit.UserError{
	ID:      "internal-server-error",
	Message: "An unexpected internal server error occurred.",
}
View Source
var ErrInvalidRequestBody = errorkit.UserError{
	ID:      "invalid-request-body",
	Message: "The request body is invalid.",
}
View Source
var ErrMalformedID = errorkit.UserError{
	ID:      "malformed-id-in-path",
	Message: "The received entity id in the path is malformed.",
}
View Source
var ErrMethodNotAllowed = errorkit.UserError{
	ID:      "restapi-method-not-allowed",
	Message: "The requested RESTful method is not supported.",
}
View Source
var ErrPathNotFound = errorkit.UserError{
	ID:      "path-not-found",
	Message: "The requested path is not found.",
}
View Source
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 ErrorMapping

func ErrorMapping(ctx context.Context, err error, dto *rfc7807.DTO)

func Mount

func Mount(multiplexer 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

func MountPoint

func MountPoint(mountPoint Path, next http.Handler) http.Handler

func WithPathParam added in v0.196.0

func WithPathParam(ctx context.Context, key, val string) context.Context

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]) Create added in v0.196.0

func (r Client[Entity, ID]) Create(ctx context.Context, ptr *Entity) error

func (Client[Entity, ID]) DeleteAll added in v0.196.0

func (r Client[Entity, ID]) DeleteAll(ctx context.Context) error

func (Client[Entity, ID]) DeleteByID added in v0.196.0

func (r Client[Entity, ID]) DeleteByID(ctx context.Context, id ID) error

func (Client[Entity, ID]) FindAll added in v0.196.0

func (r Client[Entity, ID]) FindAll(ctx context.Context) iterators.Iterator[Entity]

func (Client[Entity, ID]) FindByID added in v0.196.0

func (r Client[Entity, ID]) FindByID(ctx context.Context, id ID) (ent Entity, found bool, err error)

func (Client[Entity, ID]) Update added in v0.196.0

func (r Client[Entity, ID]) Update(ctx context.Context, ptr *Entity) error

type ClientErrUnexpectedResponse added in v0.196.0

type ClientErrUnexpectedResponse struct {
	StatusCode int
	Body       string
	URL        *url.URL
}

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())
}

func (Handler[Entity, ID, DTO]) ServeHTTP

func (h Handler[Entity, ID, DTO]) ServeHTTP(w http.ResponseWriter, r *http.Request)

type IDConverter added in v0.185.0

type IDConverter[ID any] struct {
	Format func(ID) (string, error)
	Parse  func(string) (ID, error)
}

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.

func (IntID[ID]) FormatID added in v0.185.0

func (m IntID[ID]) FormatID(id ID) (string, error)

func (IntID[ID]) ParseID added in v0.185.0

func (m IntID[ID]) ParseID(id string) (ID, error)

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

func (JSONSerializer) Unmarshal added in v0.188.0

func (s JSONSerializer) Unmarshal(data []byte, dtoPtr any) error

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

func (JSONStreamSerializer) Unmarshal added in v0.188.0

func (s JSONStreamSerializer) Unmarshal(data []byte, ptr any) error

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 ListEncoder interface {
	// Encode will encode an Entity in the underlying io writer.
	Encode(v any) error
	// Closer represent the finishing of the List encoding process.
	io.Closer
}

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.

const (
	PlainText   MIMEType = "text/plain"
	JSON        MIMEType = "application/json"
	XML         MIMEType = "application/xml"
	HTML        MIMEType = "text/html"
	OctetStream MIMEType = "application/octet-stream"
)

func (MIMEType) Base added in v0.196.0

func (ct MIMEType) Base() MIMEType

func (MIMEType) String added in v0.188.0

func (ct MIMEType) String() string

func (MIMEType) WithCharset added in v0.188.0

func (ct MIMEType) WithCharset(charset string) MIMEType

type Mapping

type Mapping[Entity any] interface {
	// contains filtered or unexported methods
}

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 Path

type Path = string

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

func (res Resource[Entity, ID]) HTTPRequest(ctx context.Context) (*http.Request, bool)

func (Resource[Entity, ID]) ServeHTTP added in v0.188.0

func (res Resource[Entity, ID]) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (Resource[Entity, ID]) WithCRUD added in v0.196.0

func (res Resource[Entity, ID]) WithCRUD(repo crud.ByIDFinder[Entity, ID]) Resource[Entity, ID]

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 NewRouter

func NewRouter(configure ...func(*Router)) *Router

func RouterFrom added in v0.188.0

func RouterFrom[V Routes](v V) *Router
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) Mount

func (router *Router) Mount(path Path, handler http.Handler)

func (*Router) MountRoutes

func (router *Router) MountRoutes(routes Routes)

func (*Router) ServeHTTP

func (router *Router) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request)

type Routes

type Routes map[Path]http.Handler
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.

func (StringID[ID]) FormatID added in v0.185.0

func (m StringID[ID]) FormatID(id ID) (string, error)

func (StringID[ID]) ParseID added in v0.185.0

func (m StringID[ID]) ParseID(id string) (ID, error)

type UpdateOperation

type UpdateOperation[Entity, ID, DTO any] struct {
	BeforeHook BeforeHook
}

UpdateOperation

DEPRECATED

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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