Help us shape the future of Go! Take the 2026 Go Developer Survey

modelbinding

package
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package modelbinding resolves typed route keys into concrete models before domain handlers run. Transport DTOs and response contracts remain explicit.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DescribeAuthenticatedEndpoint added in v0.2.0

func DescribeAuthenticatedEndpoint[P, Q, B, S, M, R any](e AuthenticatedEndpoint[P, Q, B, S, M, R]) (foundryhttp.EndpointInfo, error)

DescribeAuthenticatedEndpoint validates the model-binding declaration and returns transport metadata.

func DescribeAuthenticatedIdempotentEndpoint added in v0.2.0

func DescribeAuthenticatedIdempotentEndpoint[P, Q, B, S, M, R any](e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) (foundryhttp.EndpointInfo, error)

DescribeAuthenticatedIdempotentEndpoint validates the model-binding declaration and returns authentication and idempotency metadata.

func DescribeEndpoint added in v0.2.0

func DescribeEndpoint[P, Q, B, M, R any](e Endpoint[P, Q, B, M, R]) (foundryhttp.EndpointInfo, error)

DescribeEndpoint retains transport metadata without treating the stored model as a public request or response DTO. Model lookup does not change wire types.

func DescribeIdempotentEndpoint added in v0.2.0

func DescribeIdempotentEndpoint[P, Q, B, M, R any](e IdempotentEndpoint[P, Q, B, M, R]) (foundryhttp.EndpointInfo, error)

DescribeIdempotentEndpoint validates the model-binding declaration and returns idempotency metadata.

func Resolve added in v0.2.0

func Resolve[P, M any](r Resolver[P, M], ctx context.Context, path P) (M, error)

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 ValidateAuthenticatedEndpoint added in v0.2.0

func ValidateAuthenticatedEndpoint[P, Q, B, S, M, R any](e AuthenticatedEndpoint[P, Q, B, S, M, R]) error

ValidateAuthenticatedEndpoint checks the authenticated transport, resolver and any resource-authorization callback.

func ValidateAuthenticatedIdempotentEndpoint added in v0.2.0

func ValidateAuthenticatedIdempotentEndpoint[P, Q, B, S, M, R any](e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]) error

ValidateAuthenticatedIdempotentEndpoint checks resource authorization, authenticated idempotency and model resolver declarations.

func ValidateEndpoint added in v0.2.0

func ValidateEndpoint[P, Q, B, M, R any](e Endpoint[P, Q, B, M, R]) error

ValidateEndpoint checks the transport, resolver and any resource-authorization callback.

func ValidateIdempotentEndpoint added in v0.2.0

func ValidateIdempotentEndpoint[P, Q, B, M, R any](e IdempotentEndpoint[P, Q, B, M, R]) error

ValidateIdempotentEndpoint checks resource authorization, idempotency and model resolver declarations.

func ValidateResolver added in v0.2.0

func ValidateResolver[P, M any](r Resolver[P, M]) error

ValidateResolver 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.

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 AuthorizeAuthenticatedResource added in v0.2.0

func AuthorizeAuthenticatedResource[P, Q, B, S, M, R any](e AuthenticatedEndpoint[P, Q, B, S, M, R], authorize func(context.Context, S, Input[P, Q, B, M]) error) AuthenticatedEndpoint[P, Q, B, S, M, R]

AuthorizeAuthenticatedResource preserves both the concrete guard actor (including optional actors) and the complete bound resource. Relationship matching is not a grant.

func BindAuthenticated

func BindAuthenticated[P, Q, B, S, M, R any](transport AuthenticatedTransport[P, Q, B, S, R], resolver Resolver[P, M]) AuthenticatedEndpoint[P, Q, B, S, M, R]

BindAuthenticated connects a typed model resolver to an authenticated binding transport.

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

Handle resolves and authorizes the bound model before invoking the typed subject handler at the transport's binding stage.

type AuthenticatedIdempotentEndpoint

type AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R any] struct {
	// contains filtered or unexported fields
}

AuthenticatedIdempotentEndpoint combines a typed model resolver and resource authorization with actor-aware idempotency.

func AuthenticatedIdempotentHeaders added in v0.2.0

func AuthenticatedIdempotentHeaders[P, Q, B, S, M, R any](e AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R], fn func(context.Context, R) ([]foundryhttp.ResponseHeader, error)) AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]

AuthenticatedIdempotentHeaders returns an endpoint with application headers captured for exact replay.

func AuthorizeAuthenticatedIdempotentResource added in v0.2.0

func AuthorizeAuthenticatedIdempotentResource[P, Q, B, S, M, R any](endpoint AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R], authorize func(context.Context, S, Input[P, Q, B, M]) error) AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]

AuthorizeAuthenticatedIdempotentResource returns an endpoint with actor-aware resource authorization before idempotent execution or replay.

func BindAuthenticatedIdempotent added in v0.2.0

func BindAuthenticatedIdempotent[P, Q, B, S, M, R any](endpoint foundryhttp.AuthenticatedIdempotentEndpoint[P, Q, B, S, R], resolver Resolver[P, M]) AuthenticatedIdempotentEndpoint[P, Q, B, S, M, R]

BindAuthenticatedIdempotent attaches a typed model resolver to an authenticated idempotent endpoint.

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

Handle resolves and authorizes the model before choosing the actor-aware scope and transaction handler for execution or replay.

type AuthenticatedTransport added in v0.2.0

type AuthenticatedTransport[P, Q, B, S, R any] interface {
	// Validate checks the authenticated transport declaration.
	Validate() error
	// Description returns the authenticated endpoint's contract metadata.
	Description() (foundryhttp.EndpointInfo, error)
	// HandleBound registers a binding stage receiving the concrete subject and typed
	// request.
	HandleBound(foundryhttp.AuthenticatedBinding[P, Q, B, S, R]) foundryhttp.RouteRegistration
}

AuthenticatedTransport requires explicit binding-stage ownership.

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 AuthorizeResource added in v0.2.0

func AuthorizeResource[P, Q, B, M, R any](e Endpoint[P, Q, B, M, R], authorize func(context.Context, Input[P, Q, B, M]) error) Endpoint[P, Q, B, M, R]

AuthorizeResource 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.

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 AuthorizeResource) 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. Custom transports implement this stage explicitly, or opt into http.AfterValidation to resolve before their handler. No database I/O occurs during construction/registration.

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.

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 AuthorizeIdempotentResource added in v0.2.0

func AuthorizeIdempotentResource[P, Q, B, M, R any](endpoint IdempotentEndpoint[P, Q, B, M, R], authorize func(context.Context, Input[P, Q, B, M]) error) IdempotentEndpoint[P, Q, B, M, R]

AuthorizeIdempotentResource returns an endpoint with resource authorization before idempotent execution or replay.

func BindIdempotent added in v0.2.0

func BindIdempotent[P, Q, B, M, R any](endpoint foundryhttp.IdempotentEndpoint[P, Q, B, R], resolver Resolver[P, M]) IdempotentEndpoint[P, Q, B, M, R]

BindIdempotent attaches a typed model resolver to an idempotent endpoint.

func IdempotentHeaders added in v0.2.0

func IdempotentHeaders[P, Q, B, M, R any](e IdempotentEndpoint[P, Q, B, M, R], fn func(context.Context, R) ([]foundryhttp.ResponseHeader, error)) IdempotentEndpoint[P, Q, B, M, R]

IdempotentHeaders returns an endpoint with application headers captured for exact replay.

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

Handle resolves and authorizes the model before choosing the scope and transaction handler for execution or replay.

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 checks the typed key-query declaration.
	Validate() error
	// Find looks up a model by its concrete key using the supplied executor, returning
	// omission when absent.
	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(),orm.TextExpr(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

func Define[P, M any](lookup func(context.Context, P) (value.Optional[M], error)) Resolver[P, M]

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,orm.TextExpr(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,orm.TextExpr(Tasks.Slug),slugFromPath).

func WithMissing added in v0.2.0

func WithMissing[P, M any](r Resolver[P, M], missing func(context.Context, P) error) Resolver[P, M]

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 checks the binding transport declaration.
	Validate() error
	// Description returns the endpoint's contract metadata.
	Description() (foundryhttp.EndpointInfo, error)
	// HandleBound registers the typed request's model-binding stage.
	HandleBound(foundryhttp.Binding[P, Q, B, R]) foundryhttp.RouteRegistration
}

Transport is the endpoint capability used by the binding adapter. Ordinary ForBinding and ForSignedBinding construct it, preserving signature checks and all existing middleware, decoding, validation, errors and response contracts.

Jump to

Keyboard shortcuts

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