restapi

package
v0.211.0 Latest Latest
Warning

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

Go to latest
Published: Apr 11, 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: serializers.JSON{},
	MIMEType:   JSON,
}
View Source
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{},
}
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 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

func MountPoint

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

func WithPathParam added in v0.196.0

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

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]) 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 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

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

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.

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 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"
	FormUrlencoded MIMEType = "application/x-www-form-urlencoded"
)

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
}

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

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 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 NewRouter

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

func RouterFrom added in v0.188.0

func RouterFrom[V Routes](v V) *Router

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

func (router *Router) Connect(path string, handler http.Handler)
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

func (router *Router) Delete(path string, handler http.Handler)
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

func (router *Router) Get(path string, handler http.Handler)
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

func (router *Router) Handle(pattern string, handler http.Handler)

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

func (router *Router) Head(path string, handler http.Handler)
Example
var router restapi.Router
router.Head("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusTeapot)
}))

func (*Router) Mount

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

Mount

func (*Router) MountRoutes

func (router *Router) MountRoutes(routes Routes)

MountRoutes

DEPRECATED

func (*Router) Namespace added in v0.211.0

func (router *Router) Namespace(path string, blk func(r *Router))

func (*Router) Options added in v0.211.0

func (router *Router) Options(path string, handler http.Handler)

func (*Router) Patch added in v0.211.0

func (router *Router) Patch(path string, handler http.Handler)
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

func (router *Router) Post(path string, handler http.Handler)
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

func (router *Router) Put(path string, handler http.Handler)
Example
var router restapi.Router
router.Put("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusTeapot)
}))

func (*Router) Resource added in v0.211.0

func (router *Router) Resource(identifier string, r Resource[testent.Foo, testent.FooID])

func (*Router) ServeHTTP

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Router) Trace added in v0.211.0

func (router *Router) Trace(path string, handler http.Handler)

func (*Router) Use added in v0.211.0

func (router *Router) Use(mws ...httpkit.MiddlewareFactoryFunc)

type Routes

type Routes map[string]http.Handler

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
}

type StringID added in v0.185.0

type StringID[ID ~string] struct{}

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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