Documentation
¶
Overview ¶
Package parse provides the Resource and SchemaRegistry that unify filter and patch parsing for a single resource type.
For filter-only or patch-only usage, import the sub-packages directly:
import "github.com/ashtonian/pkg/parse/filter" import "github.com/ashtonian/pkg/parse/patch"
Shared types (FieldType, ParseError, ParserConfig, SchemaGenerator) live in:
import "github.com/ashtonian/pkg/parse/core"
Resource ¶
Resource combines filter, patch, sort, and search parsing for a resource:
tp, err := parse.NewResource[Product]("products")
result, err := tp.ParseFilter(`{"status":"active"}`)
patched, err := tp.ApplyPatch(body, existing)
lq := tp.ListQuery(filter.PostgresDialect{})
SchemaRegistry ¶
SchemaRegistry stores all schemas and serves them via HTTP for frontend auto-generation of filter sidebars, search UIs, and edit forms:
reg := parse.NewSchemaRegistry()
parse.MustRegister[Product](reg)
mux.HandleFunc("GET /api/{resource}/schema", reg.SchemaHandler())
Index ¶
- Constants
- Variables
- func MustRegister[T any](r *SchemaRegistry)
- func Register[T any](r *SchemaRegistry) error
- func RegisterResource[T any](r *SchemaRegistry, res *Resource[T])
- type Code
- type Dialect
- type FilterFieldResponse
- type FilterSchemaResponse
- type GeoFieldConfigResponse
- type Junction
- type ListInput
- type ListPlan
- type ListRequest
- type ParseError
- type PatchOption
- type PatchPathResponse
- type PatchSchemaResponse
- type RelatedResource
- type Relation
- type RelationOption
- type RelationResponse
- type Resource
- func (p Resource) ApplyPatch(body []byte, target T, opts ...PatchOption) (T, error)
- func (p Resource) ApplyValidatedPatch(parsed *patch.ParsedPatch, target T) (T, error)
- func (r *Resource[T]) BuildList(d filter.Dialect, req *ListRequest, s Scope) (*ListPlan, error)
- func (r *Resource[T]) BuildUpdate(d Dialect, p *patch.ParsedPatch, target Target, s Scope, opts ...UpdateOption) (*UpdatePlan, error)
- func (r *Resource[T]) ColumnSQLType(column string) (core.SQLType, bool)
- func (r *Resource[T]) Columns(d filter.Dialect) []string
- func (r *Resource[T]) DeletedColumnName() string
- func (p Resource) FilterSchema() *filter.ResourceSchema
- func (r *Resource[T]) IncludeQueries(plan *ListPlan, ownerKeys []any) ([]filter.IncludeQuery, error)
- func (r *Resource[T]) Key(item any) (any, error)
- func (p Resource) ListQuery(dialect filter.Dialect) *filter.ListQuery
- func (p Resource) MergePatchMap(m map[string]any, opts ...PatchOption) (*patch.ParsedPatch, error)
- func (r *Resource[T]) MustRelate(name string, rel Relation)
- func (r *Resource[T]) Page(items []T, plan *ListPlan) (*filter.Page[T], error)
- func (r *Resource[T]) PagePointers(items []*T, plan *ListPlan) (*filter.Page[*T], error)
- func (p Resource) ParseFilter(query string) (*filter.ParsedFilter, error)
- func (p Resource) ParseFilterMap(m map[string]any) (*filter.ParsedFilter, error)
- func (r *Resource[T]) ParseFilterWithCost(query string, maxCost int) (*filter.ParsedFilter, error)
- func (r *Resource[T]) ParseList(q url.Values) (*ListRequest, error)
- func (r *Resource[T]) ParseListInput(in ListInput) (*ListRequest, error)
- func (p Resource) ParseMergePatch(body []byte, opts ...PatchOption) (*patch.ParsedPatch, error)
- func (p Resource) ParsePatch(body []byte, opts ...PatchOption) (*patch.ParsedPatch, error)
- func (p Resource) ParseRequestFilter(q url.Values, opts ...filter.QueryParamOption) (*filter.ParsedFilter, error)
- func (p Resource) ParseServerMergePatch(body []byte) (*patch.ParsedPatch, error)
- func (p Resource) ParseSort(sortParam string) (*filter.SortResult, error)
- func (p Resource) PatchSchema() *patch.PatchSchema
- func (r *Resource[T]) PrimaryKeyColumn() string
- func (r *Resource[T]) Relate(name string, rel Relation) error
- func (r *Resource[T]) RelationStatements(plan *UpdatePlan, ownerKeys []any) ([]patch.RenderedRelationStatement, error)
- func (p Resource) SearchSchema() *filter.SearchSchema
- func (p Resource) ServerMergePatchMap(m map[string]any) (*patch.ParsedPatch, error)
- func (p Resource) ServerPatchSchema() *patch.PatchSchema
- func (p Resource) SortSchema() *filter.SortSchema
- func (r *Resource[T]) Table() string
- func (r *Resource[T]) TenantColumnName() string
- func (p Resource) ValidateOperations(ops []patch.RawPatchOp, opts ...PatchOption) (*patch.ParsedPatch, error)
- func (p Resource) ValidateServerOperations(ops []patch.RawPatchOp) (*patch.ParsedPatch, error)
- func (r *Resource[T]) WithPaths(decls ...filter.PathDecl) (*Resource[T], error)
- type ResourceOption
- func AcceptParams(names ...string) ResourceOption
- func ColumnType(column string, t core.SQLType) ResourceOption
- func DeletedColumn(column string) ResourceOption
- func PageSize(def, max int) ResourceOption
- func ParserOptions(opts ...core.Option) ResourceOption
- func PrimaryKey(column string) ResourceOption
- func ReadColumns(columns ...string) ResourceOption
- func TenantColumn(column string) ResourceOption
- type SchemaRegistry
- func (r *SchemaRegistry) FilterSchema(resource string) (*filter.ResourceSchema, bool)
- func (r *SchemaRegistry) PatchSchema(resource string) (*patch.PatchSchema, bool)
- func (r *SchemaRegistry) ResourceNames() []string
- func (r *SchemaRegistry) SchemaFor(resource string) *SchemaResponse
- func (r *SchemaRegistry) SchemaHandler() http.HandlerFunc
- func (r *SchemaRegistry) SchemaListHandler() http.HandlerFunc
- func (r *SchemaRegistry) SortSchema(resource string) (*filter.SortSchema, bool)
- type SchemaResponse
- type Scope
- type SearchSchemaResponse
- type SortFieldResponse
- type SortSchemaResponse
- type Statement
- type Target
- type UpdateOption
- type UpdatePlan
Constants ¶
const SchemaVersion = 1
SchemaVersion is the schema document's format version. It changes when a member is removed or renamed or its meaning changes; adding a member does not change it. A client refuses a version it does not know.
Variables ¶
var ErrNoTarget = errors.New("parse: an update needs a key or a filter")
ErrNoTarget is an update built without keys or a non-empty filter.
var ErrTenantRequired = errors.New("parse: a tenant-scoped resource is listed without Scope.Tenant")
ErrTenantRequired is a list of a tenant-scoped resource built without a tenant. It is the server's mistake, never the request's.
var ErrUpdateScope = errors.New("parse: an update reaches only its tenant's live rows")
ErrUpdateScope is an update built with a widening Scope: an update reaches only its own tenant's live rows, so Governance and IncludeDeleted do not apply to it.
Functions ¶
func MustRegister ¶
func MustRegister[T any](r *SchemaRegistry)
MustRegister is like Register but panics on error.
func Register ¶
func Register[T any](r *SchemaRegistry) error
Register generates and registers all schemas for type T.
func RegisterResource ¶
func RegisterResource[T any](r *SchemaRegistry, res *Resource[T])
RegisterResource registers a resource's schemas under its table: its filter schema (declared relations included), patch, sort and search schemas, as live as the resource — relations declared after registering are served too.
Types ¶
type Dialect ¶
type Dialect interface {
filter.Dialect
patch.SQLDialect
}
Dialect is a dialect both the filter and the patch compiler speak, as filter.PostgresDialect and filter.SQLiteDialect do.
type FilterFieldResponse ¶
type FilterFieldResponse struct {
Name string `json:"name"`
Label string `json:"label,omitempty"`
Description string `json:"description,omitempty"`
DataType core.FieldType `json:"data_type"`
Operators []filter.FilterOperator `json:"operators"`
Indexed bool `json:"indexed,omitempty"`
Immutable bool `json:"immutable,omitempty"`
EnumValues []string `json:"enum_values,omitempty"`
Facetable bool `json:"facetable,omitempty"`
Searchable bool `json:"searchable,omitempty"`
Includable bool `json:"includable,omitempty"`
Group string `json:"group,omitempty"`
Order int `json:"order,omitempty"`
GeoConfig *GeoFieldConfigResponse `json:"geo_config,omitempty"`
// Relation describes a relation field: its kind and, for a declared
// relation, the target resource whose schema its `$some` / `$none` /
// `$is` / `$is_not` filters use.
Relation *RelationResponse `json:"relation,omitempty"`
// Elements are a JSON array's element paths, which `$some` / `$none`
// filter on one element.
Elements map[string]*FilterFieldResponse `json:"elements,omitempty"`
}
FilterFieldResponse is the JSON representation of a filter field.
type FilterSchemaResponse ¶
type FilterSchemaResponse struct {
Fields map[string]*FilterFieldResponse `json:"fields"`
}
FilterSchemaResponse is the JSON representation of a filter schema.
type GeoFieldConfigResponse ¶
type GeoFieldConfigResponse struct {
StorageType string `json:"storage_type"`
SRID int `json:"srid"`
}
GeoFieldConfigResponse is the JSON representation of geo field metadata.
type Junction ¶
type Junction struct {
Table, Source, Target, Tenant string
}
Junction is a many-to-many's pair table: Source holds the owner's key, Target the target's, Tenant (optional) the tenant of each pair.
type ListInput ¶
type ListInput struct {
// Filter is a filter document in the JSON grammar; empty filters nothing.
Filter string
// FieldFilters are flat filters, ANDed with Filter: a filterable field's
// name, or the name with an operator suffix (rank_gte, tags_in), to its
// values as text. Several values on an equality key match any of them.
// Every key names a field; none is read as a list parameter.
FieldFilters map[string][]string
// Sort is the sort grammar: comma-separated fields, '-' for descending.
Sort string
Cursor string
// PageSize zero takes the resource's default; a size above its maximum is
// clamped, and a negative one is E2067.
PageSize int
IncludeTotal bool
IncludeDeleted bool
Search string
// Facets, Include and Fields are field names. Each name is trimmed, and
// empty and repeated names are dropped.
Facets []string
Include []string
Fields []string
}
ListInput is a list request in transport-neutral form: the parameters ParseList reads from a query string, already typed and split. ParseListInput validates it as ParseList validates a query, with the same limits and error codes, so an RPC or message handler serves the same lists as an HTTP endpoint.
type ListPlan ¶
type ListPlan struct {
// Page selects Columns for the page: up to Limit+1 rows, in keyset order.
Page Statement
// Fragment is Page's `WHERE … ORDER BY … LIMIT n` alone, with Page.Args,
// for a caller that selects its own projection from the resource's table.
Fragment string
// Count is the matched population's size — filter, policy and search,
// no cursor — when the request asked for it.
Count *Statement
// Facets counts the same population by the requested fields.
Facets *filter.FacetQuery
// Include names the relation fields to read once the page's keys are
// known (IncludeQueries).
Include []string
// Keyset turns the fetched rows into a page (Page).
Keyset filter.CursorPlan
Limit int
// contains filtered or unexported fields
}
ListPlan is a list request compiled for one dialect.
type ListRequest ¶
type ListRequest struct {
// Filter is the JSON filter parameter ANDed with the flat field
// parameters, bounded as one expression; nil filters nothing.
Filter *filter.ParsedFilter
// Sort is the requested order; nil orders by the key.
Sort *filter.SortResult
Cursor string
PageSize int
// IncludeTotal asks for the matched population's size.
IncludeTotal bool
// IncludeDeleted asks for soft-deleted rows too; Scope.IncludeDeleted
// decides whether the caller gets them.
IncludeDeleted bool
Search string
// Facets names facetable fields to count by.
Facets []string
// Include names relation fields whose IDs to project beside the page.
Include []string
// Fields is the response field mask, for the caller to apply.
Fields []string
}
ListRequest is a list request validated against its resource: what the caller asked for. Nothing in it is trusted — the server's decisions travel in a Scope.
type ParseError ¶
type ParseError = core.ParseError
ParseError carries a code and the location and context of a refused request.
type PatchOption ¶
type PatchOption func(*patchOptConfig)
PatchOption configures optional behavior for patch parsing.
func WithPatchPolicy ¶
func WithPatchPolicy(pc *patch.PatchPolicyConstraints, subject *core.PolicySubject) PatchOption
WithPatchPolicy sets policy constraints for patch validation.
type PatchPathResponse ¶
type PatchPathResponse struct {
AllowedOps []patch.PatchOp `json:"allowed_ops"`
DataType core.FieldType `json:"data_type"`
// Nullable reports that the path accepts null (remove, or clear).
Nullable bool `json:"nullable,omitempty"`
// EnumValues is the path's allowlist.
EnumValues []string `json:"enum_values,omitempty"`
// Elem is an array path's element type.
Elem core.FieldType `json:"elem,omitempty"`
// NumberKind is the Go numeric kind of a number path, or of an array
// path's elements (int8 … int64, uint8 … uint64, float32, float64).
NumberKind string `json:"number_kind,omitempty"`
// Relation reports a relation's membership path: its value is a list
// of target keys.
Relation bool `json:"relation,omitempty"`
}
PatchPathResponse is the JSON representation of a patchable path: what a client needs to judge a value as the server will.
type PatchSchemaResponse ¶
type PatchSchemaResponse struct {
Paths map[string]*PatchPathResponse `json:"paths"`
MaxOperations int `json:"max_operations"`
}
PatchSchemaResponse is the JSON representation of a patch schema.
type RelatedResource ¶
type RelatedResource interface {
Table() string
PrimaryKeyColumn() string
TenantColumnName() string
DeletedColumnName() string
FilterSchema() *filter.ResourceSchema
// contains filtered or unexported methods
}
RelatedResource is a resource a relation leads to. Every *Resource[U] is one; the interface is sealed so a relation's target always owns its key, tenancy, soft deletion and filter schema.
type Relation ¶
type Relation struct {
// contains filtered or unexported fields
}
Relation is a declared relation: HasMany, BelongsTo or ManyToMany, then Resource.Relate.
func BelongsTo ¶
func BelongsTo(target RelatedResource, foreignKey string, opts ...RelationOption) Relation
BelongsTo relates each owner to the target row its foreignKey column names (a command belongs to a device: commands.device_id).
func HasMany ¶
func HasMany(target RelatedResource, foreignKey string, opts ...RelationOption) Relation
HasMany relates each owner to the target rows whose foreignKey holds the owner's key (devices have commands: commands.device_id).
func ManyToMany ¶
func ManyToMany(target RelatedResource, through Junction, opts ...RelationOption) Relation
ManyToMany relates owners and targets through a pair table.
type RelationOption ¶
type RelationOption func(*Relation)
RelationOption configures a Relation.
func Describe ¶
func Describe(label, description string) RelationOption
Describe gives the relation field the label and description schema discovery serves for it, as the `label` and `description` tags do for a column's field.
func Facetable ¶
func Facetable() RelationOption
Facetable lets a list count its rows per related key (`facets=name`): distinct owners per visible child or target. A belongs-to facets through its foreign-key column.
func Includable ¶
func Includable() RelationOption
Includable lets a list include the related rows' keys (`include=name`): a has-many's children, a many-to-many's targets, as visible to the request. A belongs-to's key is the owner's own foreign-key column.
func Indexed ¶
func Indexed() RelationOption
Indexed declares that an index leads with the correlation column — the has-many's foreign key, or the pair table's owner column — so the relation costs as an indexed field. A belongs-to correlates on the target's key and is always indexed.
func PolicyScoped ¶
func PolicyScoped() RelationOption
PolicyScoped holds the related rows to the server's policy for the target table (Scope.Related) instead of the request's tenant: a relation to rows another tenant shares, such as platform-wide device types.
func References ¶
func References(column string) RelationOption
References names the column a foreign key holds when it is not the referenced resource's key: the owner's for a has-many, the target's for a belongs-to.
func Writable ¶
func Writable() RelationOption
Writable lets a patch change a many-to-many's membership (replace, add, remove of target keys): pairs are made only with targets the request may relate, and removed only from them. The pair table must hold nothing but the pairs (and their tenant).
type RelationResponse ¶
type RelationResponse struct {
// Kind is many_to_many, has_many or belongs_to.
Kind string `json:"kind"`
// Target is the target resource's name; empty for a pair table
// compared by key alone.
Target string `json:"target,omitempty"`
}
RelationResponse is the JSON representation of a relation field.
type Resource ¶
type Resource[T any] struct { // contains filtered or unexported fields }
Resource is one table's request grammar and identity: its key, tenant and soft-delete columns, its page bounds, and the filter, sort, search and patch schemas T's struct tags declare. It parses list requests (ParseList), compiles them for a dialect under a server-decided Scope (BuildList) and turns fetched rows into a page (Page). It also parses filters and patches. Build one per type at startup; it is safe for concurrent use.
func MustResource ¶
func MustResource[T any](table string, opts ...ResourceOption) *Resource[T]
MustResource is NewResource for package-level declarations; it panics on a declaration error.
func NewResource ¶
func NewResource[T any](table string, opts ...ResourceOption) (*Resource[T], error)
NewResource builds the resource for T stored in table. Identifiers are declared here, never taken from a request: table and every column option must be SQL identifiers, and the columns must be T's (a `db` tag, or the snake-case field name).
func (Resource) ApplyPatch ¶
func (p Resource) ApplyPatch(body []byte, target T, opts ...PatchOption) (T, error)
ApplyPatch validates a JSON Patch document and applies it to a struct.
func (Resource) ApplyValidatedPatch ¶
func (p Resource) ApplyValidatedPatch(parsed *patch.ParsedPatch, target T) (T, error)
ApplyValidatedPatch applies an already-parsed patch to a target struct and returns the patched value. It never mutates target: when T is a pointer the patch applies to a copy of the pointee, so an operation that fails part-way leaves the caller's value untouched. Uses a reflection-based fast path for the common case (top-level replace/add/remove), falling back to a document round-trip for everything else.
func (*Resource[T]) BuildList ¶
BuildList compiles req for dialect d under scope s: the tenant floor (or governance), soft-delete masking and policy ANDed with the request's filter, the request's sort with the key as the final keyset column, the cursor's seek, search, and the count and facets when asked. A nil req is a first page with no filter.
func (*Resource[T]) BuildUpdate ¶
func (r *Resource[T]) BuildUpdate(d Dialect, p *patch.ParsedPatch, target Target, s Scope, opts ...UpdateOption) (*UpdatePlan, error)
BuildUpdate compiles a patch into the statement that applies it to target under scope s: the patch's SET clause, then a WHERE of the target, the tenant floor, live rows only and s.Policy, then RETURNING the resource's Columns. A tenant-scoped resource needs s.Tenant (ErrTenantRequired); a zero Target is ErrNoTarget; Governance or IncludeDeleted is ErrUpdateScope.
func (*Resource[T]) ColumnSQLType ¶
ColumnSQLType returns a column's explicit storage declaration. The returned value cannot mutate the resource's metadata; false means no declaration.
func (*Resource[T]) Columns ¶
Columns is the projection every statement over the resource reads for dialect d: a list's page, an update's RETURNING, and a repository's own reads of the table. It is T's columns in field order, or the ReadColumns among them, table-qualified; a column with a declared ColumnType is read as d reads that type, under the column's own name, so rows scan by name.
func (*Resource[T]) DeletedColumnName ¶
DeletedColumnName is the soft-delete column; "" without soft delete.
func (Resource) FilterSchema ¶
func (p Resource) FilterSchema() *filter.ResourceSchema
FilterSchema returns the filter schema for this type.
func (*Resource[T]) IncludeQueries ¶
func (r *Resource[T]) IncludeQueries(plan *ListPlan, ownerKeys []any) ([]filter.IncludeQuery, error)
IncludeQueries returns, for each relation field plan includes, the statement reading its IDs for the page's owner keys (Key), scoped as the list was. Read each one's rows with query.Scan, then attach the result with page.SetInclude(query.Field, rows). No keys need no statements.
func (Resource) ListQuery ¶
ListQuery returns a ListQuery builder pre-loaded with this type's filter, sort, and search schemas and the parser's config, so ListFilter input is held to the same limits, codec and clock as ParseFilter. When the type declares `parse:"...,search"` fields, a ListSearch query composes a dialect-aware search predicate.
func (Resource) MergePatchMap ¶
func (p Resource) MergePatchMap(m map[string]any, opts ...PatchOption) (*patch.ParsedPatch, error)
MergePatchMap validates a JSON Merge Patch given as a map — decoded elsewhere, or built in code — exactly as ParseMergePatch validates its body: a nested map merges, any other value (a Go value of the path's type included) replaces, nil removes. No JSON round trip.
func (*Resource[T]) MustRelate ¶
MustRelate is Relate for startup declarations; it panics on a declaration error.
func (*Resource[T]) Page ¶
Page builds the page from rows fetched with plan.Page, in query order: truncated to the limit, restored to presentation order after a backward seek, with has-next / has-previous and both cursors read from the rows' columns.
func (*Resource[T]) PagePointers ¶
PagePointers is Page for rows scanned as pointers.
func (Resource) ParseFilter ¶
func (p Resource) ParseFilter(query string) (*filter.ParsedFilter, error)
ParseFilter parses a filter query string.
func (Resource) ParseFilterMap ¶
func (p Resource) ParseFilterMap(m map[string]any) (*filter.ParsedFilter, error)
ParseFilterMap parses a pre-decoded filter map into a ParsedFilter, skipping the JSON marshal/unmarshal roundtrip used by ParseFilter. Prefer this in adapter and service code that constructs filters from typed values:
parsed, err := SessionParser.ParseFilterMap(map[string]any{"user_id": userID})
instead of
body, _ := json.Marshal(map[string]any{"user_id": userID})
parsed, err := SessionParser.ParseFilter(string(body))
func (*Resource[T]) ParseFilterWithCost ¶
ParseFilterWithCost applies a caller-selected cost budget for this call while preserving the resource's schema, codec, clock and other parser limits. The positive budget comes from server policy; request parsing uses ParseFilter.
func (*Resource[T]) ParseList ¶
func (r *Resource[T]) ParseList(q url.Values) (*ListRequest, error)
ParseList reads a list request's query parameters (filter.Param*) into a ListInput and validates it with ParseListInput. A page size that is not a positive integer, or a flag that is not a boolean, is E2067. Every other parameter that AcceptParams does not name is a field filter, so a misspelt filter is E2071 and cannot widen a list.
func (*Resource[T]) ParseListInput ¶
func (r *Resource[T]) ParseListInput(in ListInput) (*ListRequest, error)
ParseListInput validates a list request against its resource: the JSON and flat filters, bounded as one expression, sort, cursor, page size, the total, deleted rows, search, facets, includes and the field mask. A negative page size, deleted rows of a resource without them, or search on a resource without search fields is E2067; a field filter, facet or include naming no field it can serve is E2071 (an include that is no relation, E2083).
func (Resource) ParseMergePatch ¶
func (p Resource) ParseMergePatch(body []byte, opts ...PatchOption) (*patch.ParsedPatch, error)
ParseMergePatch validates and parses a JSON Merge Patch (RFC 7396) body.
func (Resource) ParsePatch ¶
func (p Resource) ParsePatch(body []byte, opts ...PatchOption) (*patch.ParsedPatch, error)
ParsePatch validates and parses a JSON Patch document.
func (Resource) ParseRequestFilter ¶
func (p Resource) ParseRequestFilter(q url.Values, opts ...filter.QueryParamOption) (*filter.ParsedFilter, error)
ParseRequestFilter parses a list request's filter: the JSON filter parameter (filter.ParamFilter) and the flat field parameters (status=active&price_gte=10, see filter.QueryParamParser), ANDed and bounded by the parser's limits as one filter. A parameter that is neither a list parameter (filter.Param*), a name filter.QueryParamAccept lists, nor a filterable field is E2071; filter.QueryParamLenient ignores it instead. The parser's own configuration bounds the flat parameters, whatever opts say.
func (Resource) ParseServerMergePatch ¶
func (p Resource) ParseServerMergePatch(body []byte) (*patch.ParsedPatch, error)
ParseServerMergePatch parses a JSON Merge Patch the server itself writes: it may also set the `parse:"server"` fields — columns only the server sets, such as a rotated credential's fingerprint. Never pass it a request body; ParseMergePatch is the request path. The result applies through the same repositories, ApplyValidatedPatch included.
func (Resource) ParseSort ¶
func (p Resource) ParseSort(sortParam string) (*filter.SortResult, error)
ParseSort parses a sort parameter string.
func (Resource) PatchSchema ¶
func (p Resource) PatchSchema() *patch.PatchSchema
PatchSchema returns the patch schema for this type.
func (*Resource[T]) PrimaryKeyColumn ¶
PrimaryKeyColumn is the key column.
func (*Resource[T]) Relate ¶
Relate declares a relation named name on r: requests filter by it with the target's own filter (`$some`, `$none`, `$is`, `$is_not`) or by the target's key. Columns are checked against both resources' types. Declare every relation before the resources serve requests; Relate is not safe for concurrent use with them.
func (*Resource[T]) RelationStatements ¶
func (r *Resource[T]) RelationStatements(plan *UpdatePlan, ownerKeys []any) ([]patch.RenderedRelationStatement, error)
RelationStatements renders the patch's relation changes for the rows Owner returned — ownerKeys read from them with Key, never the requested keys — tenant-scoped as the update was. Run them after Owner, in its transaction. No owners need no statements.
func (Resource) SearchSchema ¶
func (p Resource) SearchSchema() *filter.SearchSchema
SearchSchema returns the search schema for this type.
func (Resource) ServerMergePatchMap ¶
func (p Resource) ServerMergePatchMap(m map[string]any) (*patch.ParsedPatch, error)
ServerMergePatchMap is MergePatchMap for a write the server makes, which may also set the `parse:"server"` fields (ParseServerMergePatch).
func (Resource) ServerPatchSchema ¶
func (p Resource) ServerPatchSchema() *patch.PatchSchema
ServerPatchSchema returns the schema of the server's own writes: the patch schema's paths and the `parse:"server"` fields. Build a patch against it only for a write the server makes, never from a request.
func (Resource) SortSchema ¶
func (p Resource) SortSchema() *filter.SortSchema
SortSchema returns the sort schema for this type.
func (*Resource[T]) TenantColumnName ¶
TenantColumnName is the tenant column; "" for a resource without tenants.
func (Resource) ValidateOperations ¶
func (p Resource) ValidateOperations(ops []patch.RawPatchOp, opts ...PatchOption) (*patch.ParsedPatch, error)
ValidateOperations validates JSON Patch operations built in code against the patch schema, as ParsePatch validates a body: paths, operations, value types, limits. Use it instead of assembling a ParsedPatch by hand.
func (Resource) ValidateServerOperations ¶
func (p Resource) ValidateServerOperations(ops []patch.RawPatchOp) (*patch.ParsedPatch, error)
ValidateServerOperations is ValidateOperations for a write the server makes, against the server patch schema.
func (*Resource[T]) WithPaths ¶
WithPaths returns a view of r for one request with decls declared as JSON paths — filterable, and sortable or facetable as each declares — leaving r and its other requests unchanged. A tenant's custom fields are declared this way; the view parses, lists and updates like r.
type ResourceOption ¶
type ResourceOption func(*resourceConfig)
ResourceOption configures NewResource.
func AcceptParams ¶
func AcceptParams(names ...string) ResourceOption
AcceptParams names query parameters the endpoint reads itself. ParseList leaves them alone; any other parameter that is neither a list parameter (filter.Param*) nor a filterable field is an error (E2071).
func ColumnType ¶
func ColumnType(column string, t core.SQLType) ResourceOption
ColumnType declares a column's storage role, including JSON documents and native SQL arrays, or a dialect read cast for network/interval values. NewResource validates the field representation and refuses duplicate or unknown declarations. Values still require their declared storage codec.
func DeletedColumn ¶
func DeletedColumn(column string) ResourceOption
DeletedColumn names the soft-delete timestamp. Lists exclude deleted rows unless Scope.IncludeDeleted.
func PageSize ¶
func PageSize(def, max int) ResourceOption
PageSize sets the page size a list request gets when it names none, and the largest it may name; default 50 and 1000.
func ParserOptions ¶
func ParserOptions(opts ...core.Option) ResourceOption
ParserOptions sets the parser's limits, clock, codec and schema generation (core.With*).
func PrimaryKey ¶
func PrimaryKey(column string) ResourceOption
PrimaryKey names the key column; default "id". It is the final keyset column of every list, so pages are stable over a non-unique sort.
func ReadColumns ¶
func ReadColumns(columns ...string) ResourceOption
ReadColumns limits the columns the resource reads — Columns, a list's page, an update's RETURNING — to these columns of T; the default is every column. Sensitive columns (hashes, secrets, tokens) then stay in the database and their fields read as zero. The key and every sort key's column must be read: pages mint cursors from the rows.
func TenantColumn ¶
func TenantColumn(column string) ResourceOption
TenantColumn names the column that holds a row's tenant. Every list is then held to Scope.Tenant, and so are tenant-scoped relation junctions.
type SchemaRegistry ¶
type SchemaRegistry struct {
// contains filtered or unexported fields
}
SchemaRegistry holds all generated schemas with caching.
func NewSchemaRegistry ¶
func NewSchemaRegistry(opts ...core.GeneratorOption) *SchemaRegistry
NewSchemaRegistry creates a new SchemaRegistry.
func (*SchemaRegistry) FilterSchema ¶
func (r *SchemaRegistry) FilterSchema(resource string) (*filter.ResourceSchema, bool)
FilterSchema returns the filter schema for the named resource.
func (*SchemaRegistry) PatchSchema ¶
func (r *SchemaRegistry) PatchSchema(resource string) (*patch.PatchSchema, bool)
PatchSchema returns the patch schema for the named resource.
func (*SchemaRegistry) ResourceNames ¶
func (r *SchemaRegistry) ResourceNames() []string
ResourceNames returns all registered resource names.
func (*SchemaRegistry) SchemaFor ¶
func (r *SchemaRegistry) SchemaFor(resource string) *SchemaResponse
SchemaFor builds an independent metadata snapshot for the given resource. Changing the response does not change the registered schemas.
func (*SchemaRegistry) SchemaHandler ¶
func (r *SchemaRegistry) SchemaHandler() http.HandlerFunc
SchemaHandler returns an http.HandlerFunc that serves schema metadata.
func (*SchemaRegistry) SchemaListHandler ¶
func (r *SchemaRegistry) SchemaListHandler() http.HandlerFunc
SchemaListHandler returns an http.HandlerFunc that lists all schemas.
func (*SchemaRegistry) SortSchema ¶
func (r *SchemaRegistry) SortSchema(resource string) (*filter.SortSchema, bool)
SortSchema returns the sort schema for the named resource.
type SchemaResponse ¶
type SchemaResponse struct {
// Version is SchemaVersion.
Version int `json:"version"`
Resource string `json:"resource"`
Filter *FilterSchemaResponse `json:"filter,omitempty"`
Patch *PatchSchemaResponse `json:"patch,omitempty"`
Sort *SortSchemaResponse `json:"sort,omitempty"`
Search *SearchSchemaResponse `json:"search,omitempty"`
}
SchemaResponse is the schema document of one resource.
type Scope ¶
type Scope struct {
// Tenant holds rows, and tenant-scoped relation junctions, to one tenant.
// Required for a resource with a tenant column.
Tenant string
// Governance, when set, replaces the tenant floor: it decides tenancy
// itself, grants included (filter.ComposeConstraints).
Governance *filter.PolicyConstraints
// Policy is the authorization constraint, ANDed with everything else.
Policy *filter.PolicyConstraints
// Related is the caller's policy for the target table of each
// policy-scoped relation (PolicyScoped), keyed by table: the related
// rows a relation predicate may reach. A predicate on such a relation
// without its table's policy is filter.ErrRelatedPolicyRequired.
Related map[string]*filter.PolicyConstraints
// IncludeDeleted shows soft-deleted rows.
IncludeDeleted bool
}
Scope is what the server decides about a list; nothing in it comes from the request.
type SearchSchemaResponse ¶
type SearchSchemaResponse struct {
Enabled bool `json:"enabled"`
Fields []string `json:"fields"`
Param string `json:"param"`
}
SearchSchemaResponse is the JSON representation of a search schema.
type SortFieldResponse ¶
type SortFieldResponse struct {
Indexed bool `json:"indexed,omitempty"`
}
SortFieldResponse is the JSON representation of a sortable field.
type SortSchemaResponse ¶
type SortSchemaResponse struct {
Fields map[string]*SortFieldResponse `json:"fields"`
DefaultField string `json:"default_field,omitempty"`
DefaultDir filter.SortDirection `json:"default_dir,omitempty"`
MaxFields int `json:"max_fields"`
}
SortSchemaResponse is the JSON representation of a sort schema.
type Target ¶
type Target struct {
// contains filtered or unexported fields
}
Target names the rows an update changes: keys or a filter. The zero Target names none, and an update refuses it rather than change every row.
func ByFilter ¶
func ByFilter(f *filter.ParsedFilter) Target
ByFilter targets the rows a filter matches.
type UpdateOption ¶
type UpdateOption func(*updateConfig)
UpdateOption configures BuildUpdate.
func Stamp ¶
func Stamp(column string, at time.Time) UpdateOption
Stamp sets column to at on every update that writes a column.
func StampSQL ¶
func StampSQL(column, expr string) UpdateOption
StampSQL sets column to expr — the database's clock, such as NOW() — on every update that writes a column. expr is trusted server SQL.
type UpdatePlan ¶
type UpdatePlan struct {
// Owner returns the target rows' Columns as the update leaves them:
// an UPDATE … RETURNING when the patch writes a column; a SELECT that
// locks the rows (Dialect.RowLock) when it changes relations alone; a
// plain SELECT — a scoped read — when it changes nothing. It matches only
// rows the scope admits.
Owner Statement
// Writes reports whether Owner changes rows.
Writes bool
// contains filtered or unexported fields
}
UpdatePlan is a patch compiled for one dialect.
func (*UpdatePlan) Dialect ¶
func (p *UpdatePlan) Dialect() Dialect
Dialect is the dialect the plan's statements are written for.
func (*UpdatePlan) HasRelations ¶
func (p *UpdatePlan) HasRelations() bool
HasRelations reports whether the patch changes relation memberships: run Owner and RelationStatements in one transaction.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package client builds requests in parse's grammar from Go: filters, sort orders, list parameters, merge-patch members and JSON Patch operations.
|
Package client builds requests in parse's grammar from Go: filters, sort orders, list parameters, merge-patch members and JSON Patch operations. |
|
Package core provides shared types for the parse/filter and parse/patch sub-packages: field types, error types, parser configuration, and the schema generator with configurable naming conventions.
|
Package core provides shared types for the parse/filter and parse/patch sub-packages: field types, error types, parser configuration, and the schema generator with configurable naming conventions. |
|
examples
|
|
|
server
command
Command server is a runnable API built on github.com/ashtonian/pkg/parse.
|
Command server is a runnable API built on github.com/ashtonian/pkg/parse. |
|
Package filter provides secure parsing of query filters with multi-dialect SQL generation, faceted aggregation, cursor pagination, full-text search, and query param parsing.
|
Package filter provides secure parsing of query filters with multi-dialect SQL generation, faceted aggregation, cursor pagination, full-text search, and query param parsing. |
|
filtertest
Package filtertest is the filter conformance fixture: one entity with a field of every filterable type, its rows, and the rows every backend must return for each filter.
|
Package filtertest is the filter conformance fixture: one entity with a field of every filterable type, its rows, and the rows every backend must return for each filter. |
|
internal
|
|
|
dialect
Package dialect is the SQL dialect kind parse compiles for: the placeholder and identifier rules of PostgreSQL and SQLite.
|
Package dialect is the SQL dialect kind parse compiles for: the placeholder and identifier rules of PostgreSQL and SQLite. |
|
jsonmerge
Package jsonmerge implements RFC 7386 (JSON Merge Patch) on top of stdlib encoding/json's `any` representation.
|
Package jsonmerge implements RFC 7386 (JSON Merge Patch) on top of stdlib encoding/json's `any` representation. |
|
Package parsehttp writes parse's request errors as HTTP responses , for servers that do not have an error envelope of their own.
|
Package parsehttp writes parse's request errors as HTTP responses , for servers that do not have an error envelope of their own. |
|
Package parsepgx runs parse plans on PostgreSQL through pgx.
|
Package parsepgx runs parse plans on PostgreSQL through pgx. |
|
Package patch provides JSON Patch (RFC 6902) and Merge Patch (RFC 7396) parsing with struct-tag-driven schema validation, SQL SET clause generation, and policy enforcement.
|
Package patch provides JSON Patch (RFC 6902) and Merge Patch (RFC 7396) parsing with struct-tag-driven schema validation, SQL SET clause generation, and policy enforcement. |