Documentation
¶
Overview ¶
Package modelbinding resolves typed route keys into concrete models before domain handlers run. Transport DTOs and response contracts remain explicit.
Index ¶
- type AuthenticatedEndpoint
- func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) Description() (foundryhttp.EndpointInfo, error)
- func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) Handle(handler func(context.Context, S, Input[P, Q, B, M]) (R, error)) foundryhttp.RouteRegistration
- func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) Validate() error
- func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) WithAuthorization(authorize func(context.Context, S, Input[P, Q, B, M]) error) AuthenticatedEndpoint[P, Q, B, S, M, R]
- type AuthenticatedIdempotentEndpoint
- func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Description() (foundryhttp.EndpointInfo, error)
- func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Handle(scope func(context.Context, S, Input[P, Q, B, M]) (idempotency.Scope, error), ...) foundryhttp.RouteRegistration
- func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Validate() error
- func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) WithHeaders(fn func(context.Context, R) ([]foundryhttp.ResponseHeader, error)) AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]
- type Endpoint
- func (e Endpoint[P, Q, B, M, R]) Description() (foundryhttp.EndpointInfo, error)
- func (e Endpoint[P, Q, B, M, R]) Handle(handler func(context.Context, Input[P, Q, B, M]) (R, error)) foundryhttp.RouteRegistration
- func (e Endpoint[P, Q, B, M, R]) Validate() error
- func (e Endpoint[P, Q, B, M, R]) WithAuthorization(authorize func(context.Context, Input[P, Q, B, M]) error) Endpoint[P, Q, B, M, R]
- type IdempotentEndpoint
- func (e IdempotentEndpoint[P, Q, B, M, R]) Description() (foundryhttp.EndpointInfo, error)
- func (e IdempotentEndpoint[P, Q, B, M, R]) Handle(scope func(context.Context, Input[P, Q, B, M]) (idempotency.Scope, error), ...) foundryhttp.RouteRegistration
- func (e IdempotentEndpoint[P, Q, B, M, R]) Validate() error
- func (e IdempotentEndpoint[P, Q, B, M, R]) WithHeaders(fn func(context.Context, R) ([]foundryhttp.ResponseHeader, error)) IdempotentEndpoint[P, Q, B, M, R]
- type Input
- type KeyQuery
- type Models
- type Resolver
- func ByColumn[P, M any, K comparable](executor database.Executor, source orm.Query[M], field orm.KeyColumn[M, K], ...) Resolver[P, M]
- func ByKey[P, M, K any](executor database.Executor, source KeyQuery[M, K], key func(P) K) Resolver[P, M]
- func Define[P, M any](lookup func(context.Context, P) (value.Optional[M], error)) Resolver[P, M]
- func Then[P, A, B any](parent Resolver[P, A], ...) Resolver[P, Models[A, B]]
- func ThroughRelation[P, A, B any, K comparable, R orm.DirectRelation[A, B]](executor database.Executor, parent Resolver[P, A], relation R, ...) Resolver[P, Models[A, B]]
- func ThroughSelectedRelation[P, A, M, B any, K comparable, R orm.DirectRelation[M, B]](executor database.Executor, parent Resolver[P, A], selectParent func(A) M, ...) Resolver[P, Models[A, B]]
- type Transport
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuthenticatedEndpoint ¶
type AuthenticatedEndpoint[P, Q, B, S, M, R any] struct { // contains filtered or unexported fields }
AuthenticatedEndpoint retains two independent model types: the transport's subject S and the bound resource M. For an optional transport S is Optional[A]. A resource lookup is not authorization; apply a concrete resource policy too.
func BindAuthenticated ¶
func BindAuthenticated[P, Q, B, S, M, R any](transport foundryhttp.AuthenticatedTransport[P, Q, B, S, R], resolver Resolver[P, M]) AuthenticatedEndpoint[P, Q, B, S, M, R]
func (AuthenticatedEndpoint[P, Q, B, S, M, R]) Description ¶
func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) Description() (foundryhttp.EndpointInfo, error)
func (AuthenticatedEndpoint[P, Q, B, S, M, R]) Handle ¶
func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) Handle(handler func(context.Context, S, Input[P, Q, B, M]) (R, error)) foundryhttp.RouteRegistration
func (AuthenticatedEndpoint[P, Q, B, S, M, R]) Validate ¶
func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) Validate() error
func (AuthenticatedEndpoint[P, Q, B, S, M, R]) WithAuthorization ¶
func (e AuthenticatedEndpoint[P, Q, B, S, M, R]) WithAuthorization(authorize func(context.Context, S, Input[P, Q, B, M]) error) AuthenticatedEndpoint[P, Q, B, S, M, R]
WithAuthorization preserves both the concrete guard actor (including optional actors) and the complete bound resource. Relationship matching is not a grant.
type AuthenticatedIdempotentEndpoint ¶
type AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R any] struct { // contains filtered or unexported fields }
func IdempotentAuthenticated ¶
func IdempotentAuthenticated[P, Q, B, S, M, R any](endpoint AuthenticatedEndpoint[P, Q, B, S, M, R], store *idempotency.Store, definition idempotency.Definition) AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]
func (AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Description ¶
func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Description() (foundryhttp.EndpointInfo, error)
func (AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Handle ¶
func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Handle(scope func(context.Context, S, Input[P, Q, B, M]) (idempotency.Scope, error), handler func(context.Context, *database.Tx, S, Input[P, Q, B, M]) (R, error)) foundryhttp.RouteRegistration
func (AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Validate ¶
func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) Validate() error
func (AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) WithHeaders ¶
func (e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) WithHeaders(fn func(context.Context, R) ([]foundryhttp.ResponseHeader, error)) AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]
type Endpoint ¶
type Endpoint[P, Q, B, M, R any] struct { // contains filtered or unexported fields }
Endpoint binds one concrete model to a transport declaration. Configure the original endpoint before Bind; use it for typed URL generation as usual.
func Bind ¶
func Bind[P, Q, B, M, R any](transport Transport[P, Q, B, R], resolver Resolver[P, M]) Endpoint[P, Q, B, M, R]
Bind composes model resolution with a transport. Framework endpoints resolve the model (and apply WithAuthorization) at their binding stage: after request authorization, before validation. A missing model is therefore 404 and a resource denial 403 before body validation reports 422, and the resolved model reaches the handler without a second lookup. A custom transport without HandleBound resolves after validation, before the handler. No database I/O occurs during construction/registration.
func (Endpoint[P, Q, B, M, R]) Description ¶
func (e Endpoint[P, Q, B, M, R]) Description() (foundryhttp.EndpointInfo, error)
Description retains transport metadata without treating the stored model as a public request or response DTO. Model lookup does not change wire types.
func (Endpoint[P, Q, B, M, R]) Handle ¶
func (e Endpoint[P, Q, B, M, R]) Handle(handler func(context.Context, Input[P, Q, B, M]) (R, error)) foundryhttp.RouteRegistration
Handle requires the declared model, path/query/body and response types. The resolver runs once per accepted request. It does not start a transaction or cache a model across requests; the handler owns business transactions.
func (Endpoint[P, Q, B, M, R]) WithAuthorization ¶
func (e Endpoint[P, Q, B, M, R]) WithAuthorization(authorize func(context.Context, Input[P, Q, B, M]) error) Endpoint[P, Q, B, M, R]
WithAuthorization configures a resource policy after the complete model bundle resolves. On framework endpoints it runs at the binding stage, before validation and domain work. Request-only authorization belongs on the original HTTP endpoint and runs before any model lookups.
type IdempotentEndpoint ¶
type IdempotentEndpoint[P, Q, B, M, R any] struct { // contains filtered or unexported fields }
IdempotentEndpoint resolves and authorizes the resource on every request, then supplies the same concrete model and request to a transaction-bound callback.
func Idempotent ¶
func Idempotent[P, Q, B, M, R any](endpoint Endpoint[P, Q, B, M, R], store *idempotency.Store, definition idempotency.Definition) IdempotentEndpoint[P, Q, B, M, R]
func (IdempotentEndpoint[P, Q, B, M, R]) Description ¶
func (e IdempotentEndpoint[P, Q, B, M, R]) Description() (foundryhttp.EndpointInfo, error)
func (IdempotentEndpoint[P, Q, B, M, R]) Handle ¶
func (e IdempotentEndpoint[P, Q, B, M, R]) Handle(scope func(context.Context, Input[P, Q, B, M]) (idempotency.Scope, error), handler func(context.Context, *database.Tx, Input[P, Q, B, M]) (R, error)) foundryhttp.RouteRegistration
func (IdempotentEndpoint[P, Q, B, M, R]) Validate ¶
func (e IdempotentEndpoint[P, Q, B, M, R]) Validate() error
func (IdempotentEndpoint[P, Q, B, M, R]) WithHeaders ¶
func (e IdempotentEndpoint[P, Q, B, M, R]) WithHeaders(fn func(context.Context, R) ([]foundryhttp.ResponseHeader, error)) IdempotentEndpoint[P, Q, B, M, R]
type Input ¶
type Input[P, Q, B, M any] struct { Request foundryhttp.Input[P, Q, B] Model M }
Input preserves the original typed transport request alongside its loaded model. The model is available to domain code, never added to the wire schema.
type KeyQuery ¶
type KeyQuery[M, K any] interface { Validate() error Find(context.Context, database.Executor, K) (value.Optional[M], error) }
KeyQuery is satisfied by orm.Keyed and legacy generated model queries. Both model M and key K remain concrete; a query for one model cannot resolve another model's ID. Find retains the query's filters, eager loads, soft-delete scope and retrieval hooks. Transaction-only locked queries have a different executor contract.
type Models ¶
type Models[P, C any] struct { Parent P Child C }
Models carries every concrete result in a nested lookup. Parent may itself be Models for another level. Nothing is placed in a heterogeneous context map.
type Resolver ¶
type Resolver[P, M any] struct { // contains filtered or unexported fields }
Resolver owns one lookup from decoded path P to model M. It retains no request result between calls. Define supports custom route keys and scopes; ByKey reuses a generated model query's typed primary-key lookup.
func ByColumn ¶
func ByColumn[P, M any, K comparable](executor database.Executor, source orm.Query[M], field orm.KeyColumn[M, K], key func(P) K) Resolver[P, M]
ByColumn resolves an ORM alternate key within the supplied query's scope. Duplicate matches fail; no transaction or lock is acquired. Example: modelbinding.ByColumn(db,Users.Query(),Users.Email,emailFromPath).
func ByKey ¶
func ByKey[P, M, K any](executor database.Executor, source KeyQuery[M, K], key func(P) K) Resolver[P, M]
ByKey binds a decoded path key to a scoped primary-key lookup. Example: modelbinding.ByKey(db,orm.Keyed(Users.Query(),Users.Key()),pathKey). The executor is supplied by application assembly, not retrieved from a global container. For a stored alternate key use ByField; Through composes declared parent/child relationships. Define remains available for custom application scopes.
func Define ¶
Define declares a custom, context-aware lookup. Return an omitted Optional for an absent model. Returned errors remain ordinary domain/infrastructure errors; they are never treated as an absent record. The callback must be safe for concurrent requests and return only fully hydrated models.
func Then ¶
func Then[P, A, B any](parent Resolver[P, A], child func(context.Context, P, A) (value.Optional[B], error)) Resolver[P, Models[A, B]]
Then resolves the parent once, then supplies that model and the original path to a custom child lookup. No partial bundle escapes. Callbacks borrow input, must be concurrency-safe and retain ownership until they return after cancel.
func ThroughRelation ¶
func ThroughRelation[P, A, B any, K comparable, R orm.DirectRelation[A, B]](executor database.Executor, parent Resolver[P, A], relation R, field orm.KeyColumn[B, K], key func(P) K) Resolver[P, Models[A, B]]
ThroughRelation resolves a child through a direct ORM relationship. Example: modelbinding.ThroughRelation(db,teams,TeamRelations.Projects,Projects.Slug,slugFromPath).
func ThroughSelectedRelation ¶
func ThroughSelectedRelation[P, A, M, B any, K comparable, R orm.DirectRelation[M, B]](executor database.Executor, parent Resolver[P, A], selectParent func(A) M, relation R, field orm.KeyColumn[B, K], key func(P) K) Resolver[P, Models[A, B]]
ThroughSelectedRelation extends a nested ORM binding while retaining earlier models. Example: modelbinding.ThroughSelectedRelation(db,projects,func(p Models[Team,Project])Project{return p.Child},ProjectRelations.Tasks,Tasks.Slug,slugFromPath).
func (Resolver[P, M]) Resolve ¶
Resolve validates the declaration, then performs exactly one lookup, returning 404 (or the WithMissing error) for an omitted result. A failed or canceled lookup publishes no partial model. Panic and Goexit remain internal failures; cancellation never abandons a running callback. Resolving a route model does not authorize access or lock the row.
func (Resolver[P, M]) Validate ¶
Validate checks the declaration without resolving a model or opening a transaction. Custom declaration callbacks remain owned until they return. Endpoints validate once at registration; nested resolvers validate each ancestor once through their composed check.
func (Resolver[P, M]) WithMissing ¶
WithMissing replaces the default 404 for an omitted lookup result. The callback receives the decoded path and returns the error to report, such as a declared application error; returning nil still reports NotFound. It runs in an owned callback and is never invoked for lookup failures.
type Transport ¶
type Transport[P, Q, B, R any] interface { Validate() error Description() (foundryhttp.EndpointInfo, error) Handle(foundryhttp.Handler[P, Q, B, R]) foundryhttp.RouteRegistration }
Transport is the endpoint capability used by the binding adapter. Ordinary Endpoint and SignedEndpoint both satisfy it, preserving signature checks and all existing middleware, decoding, validation, errors and response contracts.