parse

package module
v0.10.1 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: MIT Imports: 19 Imported by: 0

README

github.com/ashtonian/pkg/parse

Parse resource API filters, sorts, pagination, search, facets, relations, and patches from Go struct tags. Compile validated requests to parameterized PostgreSQL or SQLite SQL, or apply supported patches to Go values. The core performs no database I/O; your application owns execution, authorization, and transactions, and the optional parsepgx runs the plans on PostgreSQL through pgx.

Architecture

flowchart LR
    repo["Application repository"] -->|"ParseList / BuildList / BuildUpdate"| resource["parse.Resource"]
    pgx["parse/parsepgx"] -->|"Run plans / build pages"| resource
    resource -->|"Walk fields / normalize values"| core["parse/core"]
    resource -->|"Compile lists / cursors"| filter["parse/filter"]
    resource -->|"Validate patches / compile writes"| patch["parse/patch"]

Entry points: NewResource declares a table and key; ParseList, BuildList, BuildUpdate, Page and the filter/patch methods serve requests.

Exit points: Parameterized statements, parsed values and pages. Repositories execute statements; parsepgx provides optional pgx execution.

Failure modes: Invalid declarations fail construction; request validation returns core.ParseError with its code and path (patch failures can be joined); missing tenant scope returns ErrTenantRequired; unreadable row keys return an error while building a cursor.

Dependencies: Resource composes core, filter and patch. The compiler packages import no application code; callers own connections, authorization and transactions.

Quick start

Declare the resource once; then each list request is parsed, compiled under the server's scope, and turned into a page. This complete program compiles a request without opening a database. Requires Go 1.27 or later. Install with go get github.com/ashtonian/pkg/parse@latest.

package main

import (
	"fmt"
	"net/url"

	"github.com/ashtonian/pkg/parse"
	"github.com/ashtonian/pkg/parse/filter"
)

type Device struct {
	ID       string `json:"id"        db:"id"        parse:"filter,sort"`
	TenantID string `json:"tenant_id" db:"tenant_id"`
	Name     string `json:"name"      db:"name"      parse:"filter,sort,patch"`
	Status   string `json:"status"    db:"status"    parse:"filter,patch" enum:"active,offline"`
}

func main() {
	devices := parse.MustResource[Device]("devices", parse.TenantColumn("tenant_id"), parse.PageSize(25, 100))

	q, _ := url.ParseQuery(`status=active&filter={"name":{"$ne":"spare"}}&sort=name&page_size=10`)
	req, err := devices.ParseList(q)
	if err != nil {
		fmt.Println(err)
		return
	}
	plan, err := devices.BuildList(filter.PostgresDialect{}, req, parse.Scope{Tenant: "t1"})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(plan.Page.SQL)
	fmt.Println(plan.Page.Args)
}
SELECT devices.id, devices.tenant_id, devices.name, devices.status FROM devices WHERE ((devices.name <> $1 AND devices.status = $2) AND devices.tenant_id = $3) ORDER BY devices.name COLLATE pg_c_utf8 ASC, devices.id COLLATE pg_c_utf8 ASC LIMIT 11
[spare active t1]

On PostgreSQL, parsepgx.QueryPage(ctx, pool, devices, plan) runs the plan and returns the page with its total, facets and includes. Elsewhere, run plan.Page and pass the rows to devices.Page(rows, plan) for the cursors and has-next flags; run plan.Count when the request asked for a total, plan.Facets for facets (filter.ScanFacetUnion, then filter.FacetMap into page.Facets), and devices.IncludeQueries(plan, keys) once the page's keys are known. parse.Scope carries what the server decides — tenant, authorization policy, governance, deleted-row visibility — and never comes from the request. Write a request error with parsehttp.WriteError. The output above is ExampleResource_BuildList, checked by the unit lane. Updates take the same Scope: devices.BuildUpdate(dialect, patch, parse.ByID(id), scope) returns one UPDATE … RETURNING scoped to the tenant, live rows and policy, which parsepgx.Update applies with its relation changes in one transaction (Resource updates, ExampleResource_BuildUpdate). See the complete SQLite HTTP server and the pgx recipe.

Choose the feature

Need Entry point Guide
Serve a list endpoint: parse, scope, compile, page NewResource[T], ParseList (or ParseListInput off HTTP), BuildList, Page, IncludeQueries List queries
Run a list or update plan on PostgreSQL parsepgx.QueryPage, parsepgx.Update pgx
Answer a request error parsehttp.WriteError HTTP and errors
Parse a stored/server-policy filter with a per-call cost budget Resource.ParseFilterWithCost Parser configuration
Parse an HTTP filter alone, including flat parameters Resource.ParseRequestFilter Queries
Compose list fragments over a declared resource Resource.ListQuery(dialect).Build (the primary key joins every keyset) List queries
Compile only a predicate for existing SQL filter.NewSQLBuilder Query composition
Filter, sort and facet typed members of JSON documents paths / storage tags, struct member tags, Resource.WithPaths (per request: custom fields) JSON paths
Filter JSON arrays by their elements, every predicate on one element elements tag or a slice-of-struct document; $some, $none Array elements
Filter by related rows' attributes (has-many, belongs-to, many-to-many) Resource.Relate with HasMany, BelongsTo, ManyToMany; $some, $none, $is, $is_not Relations
Match related IDs or return relation memberships Relation tags, filter.BuildIncludeQuery Relations
Serve an update endpoint: patch rows scoped to tenant, live rows and policy ParseMergePatch / ParsePatch, BuildUpdate, RelationStatements Resource updates
Write, stamp and count only the rows a patch changes, and tell unchanged from not found SkipUnchanged, UpdatePlan.Current, Unchanged Skipping unchanged rows
Merge fields or replace a JSON document ParseMergePatch, ParsePatch, patch.BuildPatchSQL Patches
Apply a patch to an existing Go value ApplyValidatedPatch, ApplyPatch Typed documents
Advertise capabilities to a UI/client NewSchemaRegistry, RegisterResource (or Register[T]), SchemaHandler HTTP and errors
Build a patch in code instead of a JSON body MergePatchMap, ServerMergePatchMap, ValidateOperations, ValidateServerOperations Patches
Call the API from TypeScript createClient in sdk/ts Client integration
Build requests from Go: typed filters, sorts, list parameters, merge and JSON patches parse/client Go client

Use this package when a resource API needs validated user-controlled query/update expressions. Keep hand-written SQL for workflows that require a different query model; the lower-level AST, schemas, and SQL fragments can be composed independently.

Backends and boundaries

Select a dialect when building SQL; connection setup and drivers belong to the caller.

Backend Selection Important differences
PostgreSQL filter.PostgresDialect{} Native arrays/JSONB, full-text search; polygon/intersection predicates require PostGIS
SQLite filter.SQLiteDialect{} JSON text and JSON functions; phrase search and ASCII case folding; no polygon/intersection predicates
Go values ApplyValidatedPatch Supports allowed nested paths, test, copy, and move; persist the result yourself

String ordering uses PostgreSQL's pg_c_utf8 collation (PostgreSQL 17+; filter.CollationC for 14–16) and SQLite binary ordering. A flat parameter naming no filterable field is refused (E2071) on every entry point unless the endpoint accepts it. Documents filter whole or by declared typed members ({"attrs.color": "red"}, JSON paths); related rows filter by their own attributes through declared relations ({"commands": {"$some": {"status": "pending"}}}, Relations). SQL patch compilation rejects unsupported operations rather than skipping them. Backend details · Patch limits

Configuration

Construct parsers once, customize schemas before serving, and treat them as read-only afterward.

Option Type Default Description
parse.PrimaryKey(column) ResourceOption "id" Key column; the final keyset column of every list
parse.TenantColumn(column) ResourceOption none Holds every list, and tenant-scoped junctions, to Scope.Tenant
parse.DeletedColumn(column) ResourceOption none Soft-delete timestamp; deleted rows only with Scope.IncludeDeleted
parse.PageSize(def, max) ResourceOption 50, 1000 Page size without page_size, and its ceiling (larger requests are clamped)
parse.AcceptParams(names...) ResourceOption none Query parameters the endpoint reads itself; any other unknown parameter is E2071
parse.ReadColumns(columns...) ResourceOption every column of T The columns lists select and updates return; sensitive columns stay in the database. Must include the key and every sort key's column
parse.ColumnType(column, t) ResourceOption none Physical storage profile, including core.SQLJSON / SQLJSONB, native arrays and PostgreSQL inet/cidr/interval text projection; storage declarations
parse.ParserOptions(opts...) ResourceOption bounded parser defaults Limits, codec, clock and schema generation; complete table
parse.WithPatchPolicy(pc, subject) parse.PatchOption none Request-specific path/value restrictions
filter.QueryParamAccept(names...) filter.QueryParamOption none Parameters ParseRequestFilter / NewQueryParamParser leave to the endpoint; any other unknown parameter is E2071
filter.QueryParamLenient() filter.QueryParamOption off Ignore unknown flat parameters instead of refusing them
filter.PostgresDialect{Collation: c} filter.PostgresCollation filter.CollationPgCUTF8 Code-point collation for string sorts and ranges; filter.CollationC on PostgreSQL 14–16
filter.List* / builder options functional options bounded list defaults List, filter, facet, include options
patch.With* patch.PatchSQLOption no timestamp column SQL patch options
patch.DefaultMaxOperations int 50 Operation limit on generated client/server patch schemas; a hand-built PatchSchema chooses its own MaxOperations. Patch limits

Tags expose capabilities explicitly: parse:"filter,sort,patch,facet,search,include,server", filter_ops, patch_ops, enum, minlen / maxlen / min / max / minitems / maxitems, relation, facet, and search/geo metadata. Unknown capability names fail schema construction. All tags and defaults

Documentation

Guide Covers
Schema and configuration Tags, options, storage types, schema discovery
Queries Operators, SQL examples, sort/cursors, search, facets, geography
Relations Membership filters, includes, junction writes, tenant scope
Patches and JSON JSON Patch/Merge Patch, typed documents, policy, SQL limits
HTTP and errors Request/response contract, schema routes, E-code catalog
Integrations pgx, database/sql, TypeScript, custom schemas
Operations Security, concurrency, indexing, query costs, verification

API and code map

Package / file Responsibility
resource.go NewResource[T], MustResource, resource options, identity, system fields, projection (Columns, ColumnNames), ParseFilterWithCost, Key, WithPaths (a request's view with declared JSON paths)
list.go ListInput, ParseListInput, ListRequest, ParseList, Scope, ListPlan (Dialect), BuildList, Page, PagePointers, IncludeQueries
relate.go Relate, MustRelate, HasMany, BelongsTo, ManyToMany, Junction / Through, Indexed, Includable, Facetable, Writable, PolicyScoped, References, RelatedResource
update.go Target (ByID, ByIDs, ByFilter), BuildUpdate, UpdatePlan (Dialect, Current), RelationStatements, Stamp, StampSQL, Increment, SkipUnchanged, Unchanged, Dialect
typed.go Resource parse entry points (MergePatchMap, ValidateOperations and server variants), schema access, typed patch application
parsehttp WriteError: a parse error as an HTTP response
parsepgx QueryPage, Update: plans run on PostgreSQL through pgx batches
registry.go NewSchemaRegistry, RegisterResource, Register[T], MustRegister[T], discovery handlers; the versioned schema document (SchemaResponse, SchemaVersion) with relations, element paths and patch value metadata
core Configuration, schema generation, the field walk every schema shares (embedded structs promoted as encoding/json promotes them), values, pointers, ParseError
filter Filter AST, dialects, list plans, pages, facets, search, SQL and non-SQL include resolution; filter.Page.SetInclude owns the page projection
patch Patch validation, SET clauses, relation statements, patch policy
client Go request builders: typed catalog fields, Filter, Sort, ListParams, Nullable, JSON Patch Operation; standard library only, held to the filter and patch corpora
sdk/ts TypeScript builders, HTTP client, schema validation
filter/filtertest Shared operator conformance fixture
internal/dialect, internal/jsonmerge, internal/cursor Dialect primitives, RFC 7386 merge and the bounded MessagePack cursor envelope; the compiler has no private sibling imports

Patch parsers collect independent invalid values in order. Each declared enum/bounds failure carries *core.ConstraintError with Constraint and Limit; errors.As still finds the first *core.ParseError. See the complete patch error contract.

parse.Codes() []parse.Code exposes the application-independent error catalog (E2061–E2089 and E2112–E2119, with gaps; E2067 is a malformed list parameter). *parse.ParseError carries the string code, path/index context, cause and HTTP status. A Code is an errors.Is target; Consumers map catalog metadata into their own error conventions. Error handling and complete catalog

Verification

go test -tags=unit -race ./...
go test -tags=integration -race ./...

Database setup, targeted examples, SDK checks, and benchmarks are in Operations. The module and its released dependencies resolve independently of the repository workspace; no local replacements are required.

Dependencies and security

The module uses MIT licensing and requires Go 1.27. Runtime dependencies include the released pkg/gowalker and pkg/jsonvalue modules, JSON Patch, UUID, decimal and MessagePack support; the optional PostgreSQL adapter uses pgx. The runnable server example uses the released config/Kong provider and SQLite. The Go request builder and published TypeScript SDK have no third-party runtime dependencies.

Before v1, minor releases may change the Go API or grammar; patch releases retain the published contract. Schema documents carry their own version, independent of module versions. The default PostgreSQL collation requires 17+; the test matrix targets PostgreSQL 17/18 and SQLite. CollationC can compile for PostgreSQL 14–16, with matching index collation, but those versions are outside the CI matrix.

Applications own authenticated scope and trusted SQL expressions. Cursors are opaque positions, not authorization credentials. Bound request size and complexity, propagate context deadlines through execution, and follow the operations guide and security policy.

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

View Source
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

View Source
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.

View Source
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.

View Source
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 Code

type Code = core.Code

Code identifies a parse error without an application error catalog.

func Codes

func Codes() []Code

Codes returns the distinct filter and patch codes. A shared limit has one code even when both grammars can reject it.

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.

func Through

func Through(table, source, target string) Junction

Through names a many-to-many's pair table and its owner and target key columns.

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.

func (*ListPlan) Dialect

func (p *ListPlan) Dialect() filter.Dialect

Dialect is the dialect the plan's statements are written for.

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"`
	// MinLength and MaxLength are the fewest and most characters (Unicode
	// code points) of a string path, or of each element of an array path.
	MinLength *int `json:"min_length,omitempty"`
	MaxLength *int `json:"max_length,omitempty"`
	// Minimum and Maximum are the inclusive bounds of a number path, or of
	// each element of an array path, as exact decimal text.
	Minimum string `json:"minimum,omitempty"`
	Maximum string `json:"maximum,omitempty"`
	// MinItems and MaxItems are the fewest and most elements of an array
	// path.
	MinItems *int `json:"min_items,omitempty"`
	MaxItems *int `json:"max_items,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

func (r *Resource[T]) BuildList(d filter.Dialect, req *ListRequest, s Scope) (*ListPlan, error)

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; SkipUnchanged also ANDs the patch's change predicate into the WHERE. A tenant-scoped resource needs s.Tenant (ErrTenantRequired); a zero Target is ErrNoTarget; Governance or IncludeDeleted is ErrUpdateScope.

func (*Resource[T]) ColumnNames added in v0.3.0

func (r *Resource[T]) ColumnNames() []string

ColumnNames is the unqualified name of each Columns entry, in the same order: the name every row's value scans under, whichever dialect read it.

func (*Resource[T]) ColumnSQLType

func (r *Resource[T]) ColumnSQLType(column string) (core.SQLType, bool)

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

func (r *Resource[T]) Columns(d filter.Dialect) []string

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

func (r *Resource[T]) DeletedColumnName() string

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[T]) Key

func (r *Resource[T]) Key(item any) (any, error)

Key reads the primary key from a model value or a pointer to one.

func (Resource) ListQuery

func (p Resource) ListQuery(dialect filter.Dialect) *filter.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

func (r *Resource[T]) MustRelate(name string, rel Relation)

MustRelate is Relate for startup declarations; it panics on a declaration error.

func (*Resource[T]) Page

func (r *Resource[T]) Page(items []T, plan *ListPlan) (*filter.Page[T], error)

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

func (r *Resource[T]) PagePointers(items []*T, plan *ListPlan) (*filter.Page[*T], error)

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

func (r *Resource[T]) ParseFilterWithCost(query string, maxCost int) (*filter.ParsedFilter, error)

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

func (r *Resource[T]) PrimaryKeyColumn() string

PrimaryKeyColumn is the key column.

func (*Resource[T]) Relate

func (r *Resource[T]) Relate(name string, rel Relation) error

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, and Current when the plan has it — ownerKeys read from them with Key, never the requested keys — tenant-scoped as the update was. Run them after those statements, in their 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]) Table

func (r *Resource[T]) Table() string

Table is the table the resource reads.

func (*Resource[T]) TenantColumnName

func (r *Resource[T]) TenantColumnName() string

TenantColumnName is the tenant column; "" for a resource without tenants.

func (*Resource[T]) Unchanged added in v0.8.0

func (r *Resource[T]) Unchanged(changed, current []T) ([]T, error)

Unchanged returns the rows of current (read with UpdatePlan.Current) whose keys are not among those of changed (the rows Owner returned), in current's order: the target rows a SkipUnchanged update matched and left as they were.

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

func (r *Resource[T]) WithPaths(decls ...filter.PathDecl) (*Resource[T], error)

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 Statement

type Statement struct {
	SQL  string
	Args []any
}

Statement is one parameterized SQL statement.

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.

func ByID

func ByID(id any) Target

ByID targets the row with key id.

func ByIDs

func ByIDs(ids ...any) Target

ByIDs targets the rows with these keys.

type UpdateOption

type UpdateOption func(*updateConfig)

UpdateOption configures BuildUpdate.

func Increment added in v0.6.0

func Increment(column string) UpdateOption

Increment sets column, an integer column, to one more than it holds on every update that writes a column: a version the server moves, whatever the patch says. The statement's RETURNING reports the moved value. Each Increment adds a column; it composes with the stamp.

func SkipUnchanged added in v0.8.0

func SkipUnchanged() UpdateOption

SkipUnchanged updates only the target rows the patch's columns change: the UPDATE's WHERE also requires one of them to read back other than it does, so a row the patch leaves as it is keeps its stamp and counters, and RETURNING reports only the rows that changed. UpdatePlan.Current, run first, reads every target row, changed or not. A patch that assigns no column is unaffected: its Owner returns every target row already.

func Stamp

func Stamp(column string, at time.Time) UpdateOption

Stamp sets column to at on every update that writes a column. An update has one stamp: a later Stamp or StampSQL replaces it.

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. Under SkipUnchanged the UPDATE returns only the
	// rows it changed.
	Owner Statement
	// Current, set when SkipUnchanged guards Owner's UPDATE, selects every
	// target row the scope admits, as it stands, and locks it
	// (Dialect.RowLock): Owner's WHERE without the change predicate. Run it
	// first, then Owner, in one transaction: its lock holds every target
	// while Owner compares them, so Owner and Current split one set of rows.
	// The rows it returns whose keys Owner's do not include are the ones the
	// patch left unchanged (Unchanged); neither returning a row means the
	// target is not found. Nil when Owner returns every target row.
	Current *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 Current when set, Owner, then RelationStatements in one transaction.

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.

Jump to

Keyboard shortcuts

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