modelbinding

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 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

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

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

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

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

func (r Resolver[P, M]) Resolve(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 (Resolver[P, M]) Validate

func (r Resolver[P, M]) Validate() error

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

func (r Resolver[P, M]) WithMissing(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() 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.

Jump to

Keyboard shortcuts

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