resourcekit

package
v1.1.10 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package resourcekit is the canonical registry for API resources. Each resource type registers exactly one Definition that names its loader (BatchGet by IDs) and its expandable sub-resources. The runtime walks the registered graph to resolve includes, batch loader calls per level, and stitch results back onto parents without any per-endpoint code.

See api/wip/presenters-refactor.md for the full architecture.

Index

Constants

View Source
const DefaultMaxIncludeDepth = 8

DefaultMaxIncludeDepth caps the recursion depth of include resolution, which bounds an include key at that many dot-separated segments. Cycles in the resource graph (e.g. child_accounts.parent_account) are also bounded by per-request memoization, but the depth cap protects against pathological client requests that pile up include paths. Clients cannot reach it on their own — every endpoint whitelists its include keys, and IncludesFor rejects a whitelisted key deeper than this at startup.

Variables

This section is empty.

Functions

func AllowedIncludeKeys

func AllowedIncludeKeys(root constants.ObjectType, maxDepth int) []string

AllowedIncludeKeys returns the transitive set of include paths reachable from `root`, capped at maxDepth segments. Used by OpenAPI generation to emit the `include[]` enum per endpoint and by the request parser to validate client-supplied keys.

Cycles in the resource graph are broken by tracking the set of ObjectTypes currently on the traversal path — a Definition can be revisited on a different branch but not on the same one.

func FilterIncludes

func FilterIncludes(ctx context.Context, supported ...string) []string

FilterIncludes returns the subset of supported includes that the client requested, preserving the order of supported. supported is the set a given backend call understands (historically passed verbatim, which over-fetched); the result is what should now be forwarded so the backend only enriches data the client asked for. Returns nil when nothing was requested.

func PreheatCache

func PreheatCache(ctx context.Context, ot constants.ObjectType, id string, v any)

PreheatCache inserts a resource into the request-scoped resolver cache so that a subsequent ResolveIncludes call finds it without invoking the resource's Load function. Use this when the service method already has the resource data (e.g. denormalized from a parent proto) and a loader call would be redundant.

func Register

func Register(d *Definition)

Register adds a Definition to the registry. Intended to be called from init() in per-resource registration files. Panics on duplicate ObjectType, because a duplicate would silently shadow real loaders.

func RequestedIncludeSet

func RequestedIncludeSet(ctx context.Context) map[string]bool

RequestedIncludeSet returns the client's requested include paths as a set for cheap membership checks (e.g. gating per-field name hydration). Nil when nothing was requested.

func RequestedIncludes

func RequestedIncludes(ctx context.Context) []string

RequestedIncludes returns the include paths the client requested, or nil when none were requested (or the endpoint does not support includes).

func ResetForTest

func ResetForTest()

ResetForTest clears registry state between test cases. Production code must never call this.

func ResolveIncludes

func ResolveIncludes(ctx context.Context, roots []any, objectType constants.ObjectType, tree *IncludeNode) *apierror.APIError

ResolveIncludes walks `tree` against the SubFields registered for `objectType`, batches loader calls per (level, target), and assigns the loaded children back onto `roots`.

`roots` is a slice of `*Resource` pointer values typed as `any`. The resolver never inspects the concrete type — the SubField closures do that.

The function is safe to call with an empty or nil tree (no-op) and with an empty roots slice (no-op).

func WithLoadCache

func WithLoadCache(ctx context.Context) context.Context

WithLoadCache attaches a fresh load-cache to ctx if one isn't already present. Subsequent ResolveIncludes calls on this context will share the cache. Idempotent — repeated calls keep the first cache.

func WithLoadMeta

func WithLoadMeta(ctx context.Context) context.Context

WithLoadMeta attaches a fresh LoadMeta to ctx if one isn't already present. Idempotent — repeated calls keep the first attachment. Mirrors WithLoadCache.

func WithRequestedIncludes

func WithRequestedIncludes(ctx context.Context, includes []string) context.Context

WithRequestedIncludes stores the flat list of include paths the client asked for on this request. The list is already validated against the endpoint's allowed include set and is flattened so that a nested request like "definition.config" yields both "definition" and "definition.config".

Gateway service handlers read it via FilterIncludes to forward only the includes the client actually requested to the backend, instead of always over-fetching a fixed set.

Types

type Cardinality

type Cardinality int

Cardinality describes the shape of a sub-resource field on a parent.

const (
	// CardinalityOnePtr — a `*T` field with a single FK id (or zero).
	CardinalityOnePtr Cardinality = iota
	// CardinalityList — a `*List[T]` field backed by `[]string` FK ids.
	CardinalityList
)

type Definition

type Definition struct {
	// ObjectType is the resource's identity in the registry.
	ObjectType constants.ObjectType

	// Load fetches base records by ID. Sub-resources on the returned objects
	// must be nil/empty; the resolver fills them when includes request them.
	Load Loader

	// Subs is the ordered list of expandable relations on this resource.
	// Order is preserved by the resolver for deterministic loader fan-out.
	Subs []SubField
}

Definition is the single registry entry for one resource type.

func Lookup

func Lookup(ot constants.ObjectType) *Definition

Lookup returns the registered Definition for an ObjectType, or nil if none.

type IncludeNode

type IncludeNode struct {
	Children map[string]*IncludeNode
}

IncludeNode represents one segment in a parsed include tree. The tree supports dot-paths: `?include[]=freight_preferences.carrier&include[]=child_accounts` parses to:

root
├── freight_preferences
│   └── carrier
└── child_accounts

A node without Children is a leaf — the client asked for the parent but not any of its sub-resources.

func NewIncludeTree

func NewIncludeTree() *IncludeNode

NewIncludeTree returns an empty tree.

func ParseIncludeTree

func ParseIncludeTree(keys []string) *IncludeNode

ParseIncludeTree builds a tree from a flat list of dot-paths (e.g. ["freight_preferences.carrier", "child_accounts"]). Order of paths does not matter; longer paths and their prefixes collapse correctly.

Empty path segments are tolerated (a stray "..") and ignored.

func (*IncludeNode) Add

func (n *IncludeNode) Add(key string)

Add inserts a single dot-path into the tree.

func (*IncludeNode) Child

func (n *IncludeNode) Child(key string) *IncludeNode

Child returns the sub-tree under `key` (which may be dot-separated for multi-segment lookups like "freight_preferences.carrier"), or nil if no such path exists.

func (*IncludeNode) Flatten

func (n *IncludeNode) Flatten() []string

Flatten returns every dot-path reachable from this node, sorted for deterministic output. Empty tree yields nil.

func (*IncludeNode) Has

func (n *IncludeNode) Has(key string) bool

Has returns true if `key` was requested at any level under this node.

func (*IncludeNode) HasChildren

func (n *IncludeNode) HasChildren() bool

HasChildren reports whether this node has any descendants. Safe on nil receivers — returns false.

type LoadMeta

type LoadMeta struct {
	// contains filtered or unexported fields
}

LoadMeta is a request-scoped store of loader-side metadata that does NOT belong on the public apiresource struct. Carriers attach their account_id here, the list of service_level IDs they own, "has more" flags for truncated sub-lists, etc. SubField closures read this metadata via the request ctx so that apiresource Go structs stay untouched.

Keyed by (ObjectType, resource ID, metadata key). Concurrent-safe.

func GetLoadMeta

func GetLoadMeta(ctx context.Context) *LoadMeta

GetLoadMeta returns the LoadMeta attached to ctx, or a fresh detached one when none was attached. The detached fallback means callers don't have to guard against nil — they just lose cross-call state.

func (*LoadMeta) Get

func (m *LoadMeta) Get(ot constants.ObjectType, id, key string) (any, bool)

Get returns the raw stored value and whether it was present.

func (*LoadMeta) GetBool

func (m *LoadMeta) GetBool(ot constants.ObjectType, id, key string) (bool, bool)

GetBool returns the stored value as a bool. Missing key or wrong type both yield (false, false).

func (*LoadMeta) GetString

func (m *LoadMeta) GetString(ot constants.ObjectType, id, key string) (string, bool)

GetString returns the stored value as a string. Missing key or wrong type both yield ("", false).

func (*LoadMeta) GetStrings

func (m *LoadMeta) GetStrings(ot constants.ObjectType, id, key string) ([]string, bool)

GetStrings returns the stored value as a []string. Missing key or wrong type both yield (nil, false).

func (*LoadMeta) Set

func (m *LoadMeta) Set(ot constants.ObjectType, id, key string, value any)

Set stores a metadata value for a (ObjectType, id, key) triple. Overwrites any previous value for the same triple.

type Loader

type Loader func(ctx context.Context, ids []string) (map[string]any, *apierror.APIError)

Loader fetches resources by ID. The returned map is keyed by the resource ID (the same string that appeared in the input slice). Missing IDs are simply absent from the result — the resolver leaves the parent's sub-field unset.

Values in the returned map MUST be `*T` (pointer to the resource type), so the resolver can mutate them in place when recursing into deeper includes.

type SubField

type SubField struct {
	// Key is the dot-separated include path, relative to the parent. Examples:
	// "owner", "owner.account", "service_levels". The runtime matches client
	// `?include[]=<key>` values against this exact string.
	Key string

	// Target is the ObjectType of the loaded child resource. When set, must
	// itself be a registered Definition — the resolver looks it up to find
	// the child loader. Leave empty for include keys that don't require a
	// fetch.
	Target constants.ObjectType

	// Cardinality declares whether the resulting field is a *T (OnePtr) or
	// *List[T] (List). Used by the OpenAPI generator and by codegen.
	Cardinality Cardinality

	// ExtractIDs returns the FK IDs to load for this sub-field on `parent`.
	// Required when Target is set; ignored otherwise. IDs are deduplicated
	// and batched with peers across all roots before the loader is invoked
	// exactly once per (level, target). The closure may read parent FK info
	// from GetLoadMeta(ctx).
	ExtractIDs func(ctx context.Context, parent any) []string

	// Populate writes data onto `parent`. Always called when the include
	// key is requested. `loaded` is keyed by ID and holds *Resource pointers
	// the resolver fetched via Target's loader; empty when Target is unset.
	// The closure may read parent FK info from GetLoadMeta(ctx).
	Populate func(ctx context.Context, parent any, loaded map[string]any)

	// ExtractRefs returns pointers to child objects already present on `parent`
	// for traversal-only resolution. When set, the resolver skips ExtractIDs
	// and Load — it uses the returned references as child roots for recursive
	// include resolution. Target must still be set so the resolver knows the
	// child Definition to recurse into. Populate is not called.
	ExtractRefs func(ctx context.Context, parent any) []any
}

SubField declares one include-gated field on a parent resource.

Every SubField has a Populate closure that runs only when the client requests this field's Key. The closure may use data the loader already put on the parent (Owner.Type, derived from the parent's account_id) or data the resolver fetched via Target's loader (a full Account record).

When Target is set, the resolver first batches ExtractIDs across all roots, calls Lookup(Target).Load once, and passes the result to Populate as `loaded`. When Target is empty, no fetch happens and Populate runs with an empty `loaded` map — used for fields whose data is fully determined by the parent's own row (e.g. Carrier.Owner.Type comes from carrier.account_id being null or not; nothing is hallucinated).

Both closures take ctx so they can read foreign-key info from the request-scoped LoadMeta side-table — the apiresource Go structs stay clean (no FK stowaway fields).

Jump to

Keyboard shortcuts

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