core

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 23 Imported by: 0

README

parse/core

Configuration, schema generation, value normalization, JSON Pointers, and errors shared by the parser packages. Use this package to configure a typed parser or implement a custom schema adapter.

Usage and configuration

Guide Contents
Schema, tags, options, and value model Consumer guide
Error handling and codes Consumer guide
Concurrency and trust boundaries Consumer guide

Start with the runnable package example. The focused core example is compiled and output-checked in the unit suite.

API reference

  • Config: DefaultConfig, ParserConfig, Option, (*ParserConfig).GetCodec
  • Generation: NewSchemaGenerator, SchemaGenerator (TableName, ColumnExpr, ColumnName, FieldColumn, FieldNameTag, ColumnTagKey, StructFields), GeneratorOptions, GeneratorOption, SQLColumnMapper, NamingConvention, TableConvention, SnakeCase, PluralSnake
  • Types: FieldType (FieldTypeString … FieldTypeGeo), SQLType (SQLInet, SQLCIDR, SQLInterval and their arrays: database types a resource declares for its dialect to read as text), TypeOf, TypeInfo, IsDocument, NumericKind, IsUUIDType / UUIDText (any UUID type, recognized by shape), Decimal / IsDecimalType (any decimal type with the Coefficient / Exponent method set, recognized by shape)
  • Values: NormalizeInt, NormalizeUint, NormalizeFloat, NormalizeScalar, NormalizeJSON, CompareNumbers, IsNumber, DecimalText / CompareDecimals / MaxDecimalDigits (canonical exact decimal text and its order), AnySlice, FormatTimestamp / TimestampLayout (canonical timestamp text: UTC, nine fractional digits, fixed width)
  • Pointers: ParsePointer, FormatPointer, EscapePointerToken, MatchWildcard
  • Relations: RelationSchema, ParseRelationTag, IsIdentifier
  • Errors: Code (string constants; error identity, status and default message), ParseError (ErrorCode, ErrorHTTPStatus, IsErrorUserFacing), ParseErrorKind; policy: PolicySubject
  • Fields: StructFieldsOf, StructFields (External, ColumnTagged), Field (Value, Settable, Hidden), Tags, Capabilities — one walk of a struct type shared by every schema
  • Codec: Codec, StdlibJSON; naming: ToSnakeCase, Pluralize; Validator

Code map

File Responsibility
core.go ParserConfig + options, Codec / StdlibJSON, FieldType, SQLType, ParseError, SchemaGenerator + generator options
code.go Immutable error-code metadata; no application catalog dependency
fields.go StructFields — one gowalker walk per struct type: encoding/json field names with embedded structs promoted, hidden fields kept for columns, every tag parsed once (Tags)
typeof.go TypeOf — Go type → storage model; IsUUIDType / UUIDText
number.go Exact integer / float normalization and comparison
decimal.go Decimal, IsDecimalType, canonical decimal text (DecimalText), exact decimal order (CompareDecimals)
value.go NormalizeScalar, canonical timestamps, AnySlice
json.go NormalizeJSON — canonical JSON documents
pointer.go RFC 6901 pointers
relation.go Relation tag grammar, IsIdentifier
doc.go Package documentation

Dependencies

  • Imports: stdlib (uuid, encoding/json/v2), github.com/ashtonian/pkg/gowalker
  • Imported by: parse, parse/filter, parse/patch, forge/dal/*
  • Package boundary: shared Parse types; no sibling Parse package imports

References

Package overview · Integration recipes

Civil calendar values use Date / ParseDate and FieldTypeDate, distinct from timestamps. JSON, text, SQL parameters and cursors use exactly YYYY-MM-DD; invalid dates, instant strings and a non-nullable zero/null value fail. Use *Date for nullable storage. date.go implements validation, atomic decoding and SQL scanning/values without applying a viewing timezone; the schema guide covers resource declarations.

Documentation

Overview

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.

Most users import parse or its sub-packages (filter, patch) directly. Import core when you need shared types for custom schema construction or when configuring the schema generator.

Naming Conventions

The schema generator converts Go struct field names to SQL column names and type names to table names. Override the defaults with convention functions:

gen := core.NewSchemaGenerator(
    core.WithColumnConvention(core.CamelCase),     // productType instead of product_type
    core.WithTableConvention(core.Singular),        // product instead of products
    core.WithFieldNameTag("bson"),                   // read field names from bson tag
    core.WithColumnTag("sql"),                       // read column names from sql tag
)

Tag Resolution Priority

Column name: SQLColumnMapper → column tag value (default "db") → NamingConvention → snake_case.

Table name: WithSQLTableName → TableConvention → plural snake_case.

Field name: configured tag (default "json") → skip if empty.

Error Types

ParseError is the single error type returned by all parse operations. It carries an E-code, a message, and the offending path or operation index; errors.Is matches it against any error with the same code.

Values

NormalizeScalar, NormalizeInt, NormalizeFloat and NormalizeJSON give request values their canonical form — exact integers range-checked against the destination kind, canonical UUIDs and UTC timestamps, generic JSON documents — and TypeOf classifies Go field types into the storage model schemas are built from.

Index

Constants

View Source
const MaxDecimalDigits = 1000

MaxDecimalDigits bounds the digits of a decimal's plain notation, the "0" before the point of a fraction included: PostgreSQL's limit on a declared numeric precision. A short exponent literal ("1e999999") would otherwise render, bind and compare a million digits.

View Source
const TimestampLayout = "2006-01-02T15:04:05.000000000Z07:00"

TimestampLayout is the canonical timestamp text: RFC 3339 in UTC with exactly nine fractional digits. The width is fixed, so where timestamps are stored and compared as text (SQLite) text order is time order ; time.RFC3339Nano trims trailing zeros, which sorts `…05.5Z` before `…05Z`.

Variables

This section is empty.

Functions

func AnySlice

func AnySlice(v any) ([]any, bool)

AnySlice returns v as []any when it is the decoder's []any or any typed slice or array ([]string, []int64, []uuid.UUID). Byte slices are not treated as lists.

func CanonicalJSONMapKey

func CanonicalJSONMapKey(t reflect.Type, name string) (reflect.Value, string, error)

CanonicalJSONMapKey decodes a member name into a map key and returns its JSON spelling. Dummy values keep the map element's custom codec out of this path.

func CompareDecimals added in v0.4.0

func CompareDecimals(a, b any) (cmp int, ok bool)

CompareDecimals compares two decimals exactly, returning -1, 0 or +1. Each operand is anything DecimalText accepts, pointers dereferenced; ok is false when either is not a decimal.

func CompareNumbers

func CompareNumbers(a, b any) (cmp int, ok bool)

CompareNumbers compares two numeric values exactly — no float64 widening of integers — returning -1, 0 or +1. ok is false when either operand is not a number (see IsNumber) or is NaN.

func DecimalText added in v0.4.0

func DecimalText(v any) (string, error)

DecimalText is v's canonical decimal text: plain notation, no exponent, no leading zeros, no trailing fractional zeros, and "0" for a zero of either sign. One value has one text, so equal decimals have equal text ("1.50", "1.5e0" and 1.5 are all "1.5").

It accepts a JSON number or a string in the JSON number grammar (the wire form of shopspring's decimal is a string), any Go integer, a finite float (its shortest decimal form, as strconv formats it), a Decimal, and a type whose text (encoding.TextMarshaler) is a decimal. Hexadecimal, fractions, NaN, infinities, a leading "+" or "." and anything with more than MaxDecimalDigits digits in plain notation are errors.

func EscapePointerToken

func EscapePointerToken(tok string) string

EscapePointerToken escapes one reference token for inclusion in a JSON Pointer ("~" → "~0", then "/" → "~1").

func FormatPointer

func FormatPointer(tokens ...string) string

FormatPointer joins reference tokens into an RFC 6901 JSON Pointer, escaping "~" and "/" inside each token.

func FormatTimestamp

func FormatTimestamp(t time.Time) string

FormatTimestamp renders t in the canonical timestamp form (TimestampLayout).

func IsDecimalType added in v0.4.0

func IsDecimalType(t reflect.Type) bool

IsDecimalType reports a decimal type: a named type whose value or pointer has the Decimal method set.

func IsDocument

func IsDocument(ft FieldType) bool

IsDocument reports whether values of ft are JSON documents (json, object).

func IsIdentifier

func IsIdentifier(s string) bool

IsIdentifier reports whether s is a plain SQL identifier ([A-Za-z_][A-Za-z0-9_]*), safe to emit into SQL unquoted.

func IsNumber

func IsNumber(v any) bool

IsNumber reports whether v is a numeric value NormalizeInt / NormalizeFloat / CompareNumbers accept: json.Number or any Go integer or float kind.

func IsUUIDType

func IsUUIDType(t reflect.Type) bool

IsUUIDType reports a UUID type: a named 16-byte array that marshals to text, as the standard library's uuid.UUID, github.com/google/uuid and github.com/gofrs/uuid all are. A plain [16]byte is binary.

func MatchWildcard

func MatchWildcard(pattern, path string) bool

MatchWildcard checks if a wildcard pattern matches a JSON Pointer path, token by token: * matches exactly one token and ** any number. Tokens are the pointer's own (RFC 6901), compared in their escaped form, so an empty key is a token like any other — "/a//b" is three tokens and "/a/*" does not match it. "/*" means exactly one token under root — the old match-everything special case over-granted patch policies intending top-level fields only.

func NormalizeFloat

func NormalizeFloat(v any, kind reflect.Kind) (float64, error)

NormalizeFloat converts a decoded request value into a finite float64 with the precision of kind (reflect.Float32, otherwise float64). Integers are accepted; literals are parsed directly at the destination precision so SQL parameters and typed Go values round identically.

func NormalizeInt

func NormalizeInt(v any, kind reflect.Kind) (int64, error)

NormalizeInt converts a decoded request value into an exact int64 in the domain of the signed kind (reflect.Invalid is the int64 domain). It accepts json.Number, every Go integer kind, and floats that are integral and within ±2^53 — a float beyond that has already lost precision upstream, so it is rejected rather than guessed. Fractional values, NaN/Inf, strings and out-of-range values are errors, as is an unsigned kind (NormalizeUint).

func NormalizeJSON

func NormalizeJSON(v any) (any, error)

NormalizeJSON returns v as a generic JSON document: map[string]any, []any, string, bool, nil, and numbers as int64 (integers in range), uint64 (integers above math.MaxInt64) or finite float64. It is the form JSON filter values are compared and bound in. Decoder output converts in place; other Go values (typed maps, slices, structs) are read back through their JSON encoding first. An integer literal beyond 64 bits is an error: binding it would silently round it. Decimal/exponent integers are also kept exact; fractional literals must round-trip through float64 without changing their decimal value.

func NormalizeScalar

func NormalizeScalar(dt FieldType, kind reflect.Kind, v any) (any, error)

NormalizeScalar validates a decoded request value against a field's data type and returns the canonical Go value filter ASTs and patch operations carry:

  • FieldTypeInt → int64 (signed kinds) or uint64 (unsigned), range-checked against kind
  • FieldTypeFloat → finite float64, range-checked against kind
  • FieldTypeDecimal → canonical decimal text (DecimalText)
  • FieldTypeString → string (encoding.TextMarshaler accepted)
  • FieldTypeUUID → canonical lowercase UUID string
  • FieldTypeBool → bool
  • FieldTypeTimestamp → RFC 3339 UTC string with nanosecond precision (time.Time accepted)

Other data types pass through unchanged. Non-nil pointers are dereferenced first, so typed Go input (*int64, *time.Time) normalizes like its value. A nil value is an error; callers decide separately whether a field admits null.

func NormalizeUint

func NormalizeUint(v any, kind reflect.Kind) (uint64, error)

NormalizeUint is NormalizeInt for an unsigned kind (reflect.Invalid is the uint64 domain): a negative value is an error.

func NumericKind

func NumericKind(t reflect.Type) reflect.Kind

NumericKind reports the numeric reflect.Kind a Go type decodes into (after pointer indirection), or reflect.Invalid for non-numeric types. Schemas record it so request values are range-checked against the real destination instead of a float64 approximation.

func ParsePointer

func ParsePointer(p string) ([]string, error)

ParsePointer splits an RFC 6901 JSON Pointer into its reference tokens, unescaping "~1" to "/" and "~0" to "~". The empty pointer (the whole document) yields no tokens. A non-empty pointer must start with "/"; a "~" not followed by "0" or "1" is malformed.

func Pluralize

func Pluralize(s string) string

Pluralize returns the English plural form of a word using common rules. Handles -y → -ies, sibilant suffixes → -es, and default -s.

func PortableDecimal

func PortableDecimal(s string) bool

PortableDecimal reports whether a JSON number literal denotes exactly the decimal of its nearest float64's shortest form, so every backend compares the same value: PostgreSQL's numeric sees the literal, SQLite and Go see the float64. "0.1", "1.50" and "2e3" are portable; "0.10000000000000001" (which a float64 rounds to 0.1) and "1e400" (which overflows) are not.

func ToSnakeCase

func ToSnakeCase(s string) string

ToSnakeCase converts PascalCase or camelCase to snake_case.

func UUIDText

func UUIDText(v any) (text string, ok bool)

UUIDText is v's canonical text when v is a UUID (IsUUIDType); ok is false for any other value.

func ValidateJSONKeyRepresentation

func ValidateJSONKeyRepresentation(raw []byte, decoded any) error

ValidateJSONKeyRepresentation checks physical member names against a freshly decoded value, passed by non-nil pointer, without decoding or encoding its element values again. Ordinary map keys and recognized struct fields must retain their JSON spelling. Unknown struct members and interface, RawMessage and custom JSON subtrees are opaque.

Types

type Capabilities

type Capabilities struct {
	Filter, Sort, Patch, Search, Facet, Include, Server bool
	Excluded                                            bool
}

Capabilities are the values of a field's `parse` tag. Excluded is `parse:"-"`, which also keeps a document's declared paths out of sorts.

type Code

type Code string

Code identifies a parse error independently of an application error catalog. A Code is also an errors.Is target for ParseError.

func (Code) Error

func (c Code) Error() string

Error describes the code's category, without occurrence-specific context.

func (Code) ErrorCode

func (c Code) ErrorCode() string

ErrorCode returns the wire code for error introspection.

func (Code) ErrorHTTPStatus

func (c Code) ErrorHTTPStatus() int

ErrorHTTPStatus returns the code's HTTP status; unknown codes are invalid input.

func (Code) ErrorMessage

func (c Code) ErrorMessage() string

ErrorMessage returns the code's default message, or empty for an unknown code.

func (Code) IsErrorUserFacing

func (c Code) IsErrorUserFacing() bool

IsErrorUserFacing reports whether the category's message is safe to disclose.

func (Code) String

func (c Code) String() string

String returns the wire code.

type Codec

type Codec interface {
	Encode(v any) ([]byte, error)
	Decode(data []byte, v any) error
}

Codec serializes request documents and typed patch round trips. StdlibJSON is the default; WithCodec selects another Encode/Decode implementation. Cursors use an independent, fixed wire format.

type Date added in v0.2.0

type Date string

Date is a Gregorian calendar date, independent of timezone or time of day. Its JSON, text and SQL representations are exactly YYYY-MM-DD. The zero value is invalid; use *Date for nullable values.

func ParseDate added in v0.2.0

func ParseDate(value string) (Date, error)

ParseDate validates a civil date without interpreting it as an instant.

func (Date) MarshalJSON added in v0.2.0

func (d Date) MarshalJSON() ([]byte, error)

func (Date) MarshalText added in v0.2.0

func (d Date) MarshalText() ([]byte, error)

func (*Date) Scan added in v0.2.0

func (d *Date) Scan(value any) error

Scan accepts native SQL DATE values and canonical text. It never converts a timestamp's timezone or discards a time component supplied as text.

func (Date) String added in v0.2.0

func (d Date) String() string

func (Date) Time added in v0.2.0

func (d Date) Time() (time.Time, error)

Time returns the calendar components at UTC midnight for APIs that require time.Time. The conversion does not apply a viewing or session timezone.

func (*Date) UnmarshalJSON added in v0.2.0

func (d *Date) UnmarshalJSON(value []byte) error

func (*Date) UnmarshalText added in v0.2.0

func (d *Date) UnmarshalText(value []byte) error

func (Date) Value added in v0.2.0

func (d Date) Value() (driver.Value, error)

Value preserves the date-only spelling for database/sql and pgx's Valuer path.

type Decimal added in v0.4.0

type Decimal interface {
	Coefficient() *big.Int
	Exponent() int32
}

Decimal is an exact decimal number, Coefficient × 10^Exponent: the method set of github.com/shopspring/decimal.Decimal. A Go type with it is a decimal field (FieldTypeDecimal), recognized by shape so this package depends on no decimal library; a decimal type with another method set declares its column with `storage:"scalar:decimal"`.

type Field

type Field struct {
	// GoName is the Go field name.
	GoName string
	// Type is the field's declared type.
	Type reflect.Type
	// Route is the index path from the walked type to the field, through
	// the embedded structs it is promoted from.
	Route []int
	// External is the field's name under the field-name tag, by
	// encoding/json's rule (the tag's name, else the Go name). It is empty
	// when the tag hides the field, or the embedded struct it is promoted
	// from (`json:"-"`).
	External string
	// Tags are the field's annotations, parsed once.
	Tags Tags
}

Field is one field of a StructFields walk.

func (*Field) HasFieldNameOption

func (f *Field) HasFieldNameOption(option string) bool

HasFieldNameOption reports whether the field-name tag carries option (omitempty, omitzero, ...).

func (*Field) Hidden

func (f *Field) Hidden() bool

Hidden reports a field the field-name tag hides: it has columns, but no name a request can use (relations and server fields name themselves).

func (*Field) Settable

func (f *Field) Settable(v reflect.Value) (reflect.Value, error)

Settable returns the field of v, an addressable value of the walked struct type, for writing: nil embedded pointers on the route are allocated, as encoding/json allocates them when decoding.

func (*Field) Value

func (f *Field) Value(v reflect.Value) (reflect.Value, error)

Value reads the field from v, a value of the walked struct type. A nil embedded pointer on the route is an error: the field is not there.

type FieldType

type FieldType string

FieldType represents the data type of a field in filter and patch schemas.

const (
	FieldTypeString    FieldType = "string"
	FieldTypeInt       FieldType = "int"
	FieldTypeFloat     FieldType = "float"
	FieldTypeDecimal   FieldType = "decimal"
	FieldTypeBool      FieldType = "bool"
	FieldTypeTimestamp FieldType = "timestamp"
	FieldTypeDate      FieldType = "date"
	FieldTypeUUID      FieldType = "uuid"
	FieldTypeJSON      FieldType = "json"
	FieldTypeArray     FieldType = "array"
	FieldTypeObject    FieldType = "object"
	FieldTypeAny       FieldType = "any"
	FieldTypeGeo       FieldType = "geo"
)

type GeneratorOption

type GeneratorOption func(*GeneratorOptions)

GeneratorOption configures a SchemaGenerator.

func WithColumnConvention

func WithColumnConvention(conv NamingConvention) GeneratorOption

WithColumnConvention sets the naming convention for SQL columns. Default: SnakeCase. Common alternatives: CamelCase, LowerCase.

core.WithColumnConvention(core.CamelCase)

func WithColumnMapper

func WithColumnMapper(mapper SQLColumnMapper) GeneratorOption

WithColumnMapper sets a custom column mapper (full control).

func WithColumnTag

func WithColumnTag(tag string) GeneratorOption

WithColumnTag sets which struct tag provides the SQL column name. Default: "db". The column tag value takes precedence over the naming convention.

core.WithColumnTag("sql")      // read from `sql:"column_name"`
core.WithColumnTag("column")   // read from `column:"col_name"`

func WithColumnTypes

func WithColumnTypes(types map[string]SQLType) GeneratorOption

WithColumnTypes supplies physical storage declarations keyed by column name. The generator owns a copy of the map.

func WithFieldNameTag

func WithFieldNameTag(tag string) GeneratorOption

WithFieldNameTag sets which struct tag provides the external field name. Default: "json". Use for APIs that serialize with a different format.

core.WithFieldNameTag("bson")   // read from `bson:"field_name"`
core.WithFieldNameTag("yaml")   // read from `yaml:"field_name"`

func WithSQLTableName

func WithSQLTableName(name string) GeneratorOption

WithSQLTableName overrides the table name for a specific type.

func WithSearchParam

func WithSearchParam(p string) GeneratorOption

WithSearchParam sets the query-string parameter name a generated SearchSchema advertises for its free-text search box. Default "search" (filter.ParamSearch).

func WithSortMaxFields

func WithSortMaxFields(n int) GeneratorOption

WithSortMaxFields caps how many sort fields a generated SortSchema accepts. Default 3.

func WithTableConvention

func WithTableConvention(conv TableConvention) GeneratorOption

WithTableConvention sets the naming convention for SQL table names. Default: PluralSnake ("Product" → "products").

core.WithTableConvention(core.Singular)      // "product"
core.WithTableConvention(core.PluralCamel)   // "products"

type GeneratorOptions

type GeneratorOptions struct {
	// SQLTableName overrides the derived table name for this specific type.
	// Takes precedence over TableConvention.
	SQLTableName string

	// ColumnTypes declares physical storage independently of Go field shape.
	ColumnTypes map[string]SQLType

	// ColumnMapper provides full control over column expression generation.
	// Takes precedence over ColumnConvention and ColumnTag.
	ColumnMapper SQLColumnMapper

	// ColumnConvention transforms field names to SQL column names.
	// Default: SnakeCase. Ignored when ColumnMapper is set.
	ColumnConvention NamingConvention

	// TableNameConvention transforms type names to SQL table names.
	// Default: PluralSnake ("Product" → "products").
	// Ignored when SQLTableName is set.
	TableNameConvention TableConvention

	// FieldNameTag is the struct tag to read for external field names.
	// Default: "json". Set to "bson", "yaml", etc. for non-JSON APIs.
	FieldNameTag string

	// ColumnTag is the struct tag to read for SQL column names.
	// Default: "db". Set to "sql", "column", etc. for your ORM.
	ColumnTag string

	// DefaultFilterOps maps FieldType to default filter operators when the
	// filter_ops tag is omitted.
	DefaultFilterOps map[FieldType][]string

	// DefaultPatchOps maps FieldType to default patch operations when the
	// patch_ops tag is omitted.
	DefaultPatchOps map[FieldType][]string

	// SortMaxFields caps how many sort fields a generated SortSchema accepts.
	// Zero means the default of 3.
	SortMaxFields int

	// SearchParam is the query-string parameter name a generated SearchSchema
	// advertises for its free-text search box. Empty means "search", the
	// parameter list handlers read (filter.ParamSearch).
	SearchParam string
}

GeneratorOptions holds options for schema generation. All fields have sensible defaults — override only what you need.

type NamingConvention

type NamingConvention func(string) string

NamingConvention transforms an identifier string according to a convention.

var (
	// SnakeCase converts PascalCase/camelCase to snake_case (default).
	SnakeCase NamingConvention = ToSnakeCase

	// CamelCase converts PascalCase to camelCase (first letter lowercase).
	CamelCase NamingConvention = toCamelCase

	// PascalCase returns the string unchanged (Go's default naming).
	PascalCase NamingConvention = func(s string) string { return s }

	// LowerCase converts to all lowercase with no separators.
	LowerCase NamingConvention = strings.ToLower
)

Built-in naming conventions.

type Option

type Option func(*ParserConfig)

Option configures a Parser.

func WithClock

func WithClock(now func() time.Time) Option

WithClock pins the anchor for relative-time tokens (`now`, `now-7d`). Default: time.Now().UTC. Tests pass a frozen clock for deterministic resolution.

func WithCodec

func WithCodec(codec Codec) Option

WithCodec sets the codec that decodes filter and patch bodies and round-trips documents during typed patch application. Default: StdlibJSON (numbers decode exactly). Cursors do not use it: their encoding is owned by the keyset cursor codec.

core.WithCodec(myMsgpackCodec{})

func WithGeneratorOptions

func WithGeneratorOptions(opts ...GeneratorOption) Option

WithGeneratorOptions sets GeneratorOptions used by typed parsers.

func WithMaxArraySize

func WithMaxArraySize(n int) Option

WithMaxArraySize bounds $in / $nin members, array $contains elements and the values in a JSON filter document.

func WithMaxConditions

func WithMaxConditions(n int) Option

WithMaxConditions sets the maximum number of filter conditions.

func WithMaxCost

func WithMaxCost(cost int) Option

WithMaxCost sets the maximum estimated query cost.

func WithMaxDepth

func WithMaxDepth(depth int) Option

WithMaxDepth sets the maximum request depth: filter boolean nesting, JSON filter document nesting and patch JSON Pointer segments.

func WithMaxInputSize

func WithMaxInputSize(size int) Option

WithMaxInputSize sets the maximum input size in bytes.

func WithMaxPatchCopyBytes

func WithMaxPatchCopyBytes(n int64) Option

WithMaxPatchCopyBytes bounds the bytes a patch's copy operations may add to a document during typed apply. Default 1 MiB.

type ParseError

type ParseError struct {
	Code    string         `json:"code"`
	Message string         `json:"message"`
	Kind    ParseErrorKind `json:"kind"`
	Path    string         `json:"path,omitempty"`
	Index   *int           `json:"index,omitempty"` // pointer so op index 0 survives the wire
	Details map[string]any `json:"details,omitempty"`
	Cause   error          `json:"-"`
}

ParseError is the single error type for all parse operations.

func (*ParseError) Error

func (e *ParseError) Error() string

Error implements the error interface.

func (*ParseError) ErrorCode

func (e *ParseError) ErrorCode() string

ErrorCode returns the E-code string for middleware introspection.

func (*ParseError) ErrorHTTPStatus

func (e *ParseError) ErrorHTTPStatus() int

ErrorHTTPStatus returns the code's status, or 422 for unknown codes.

func (*ParseError) ErrorMessage

func (e *ParseError) ErrorMessage() string

ErrorMessage returns the human-readable message.

func (*ParseError) Is

func (e *ParseError) Is(target error) bool

Is reports whether target carries the same E-code — any error with an ErrorCode() string method, including Code — so a ParseError matches its code's sentinel across wrap chains. An empty code matches nothing; Cause stays reachable through Unwrap.

func (*ParseError) IsErrorUserFacing

func (e *ParseError) IsErrorUserFacing() bool

IsErrorUserFacing reports the message safe to show the caller: it describes the request, never the database.

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

Unwrap returns the underlying cause for errors.Is/As compatibility.

type ParseErrorKind

type ParseErrorKind string

ParseErrorKind identifies the subsystem that produced the error.

const (
	ParseErrorKindFilter ParseErrorKind = "filter"
	ParseErrorKindPatch  ParseErrorKind = "patch"
	ParseErrorKindSort   ParseErrorKind = "sort"
	ParseErrorKindSchema ParseErrorKind = "schema"
)

type ParserConfig

type ParserConfig struct {
	// MaxDepth bounds request structure depth: filter boolean nesting,
	// JSON filter document nesting and patch JSON Pointer segments.
	MaxDepth int
	// MaxCost bounds a filter's estimated cost: about 1 per indexed
	// condition, 10 per unindexed one, more for documents, relations,
	// unanchored patterns and $or.
	MaxCost int
	// MaxInputSize bounds a filter or patch body in bytes.
	MaxInputSize int
	// MaxConditions bounds a filter's leaf conditions.
	MaxConditions int
	// MaxArraySize bounds $in / $nin members, array $contains elements and
	// the values in a JSON filter document.
	MaxArraySize int
	// MaxPatchCopyBytes bounds the total bytes a patch's copy operations may
	// add to a document during typed apply (copy amplification guard).
	MaxPatchCopyBytes int64
	GeneratorOpts     []GeneratorOption
	Codec             Codec            // Wire format codec. Nil = encoding/json fallback.
	Clock             func() time.Time // Relative-time anchor (`now-7d`). Nil = time.Now().UTC.
}

ParserConfig holds shared configuration for filter and patch parsers.

func DefaultConfig

func DefaultConfig() *ParserConfig

DefaultConfig returns a ParserConfig with sensible defaults.

func (*ParserConfig) GetCodec

func (c *ParserConfig) GetCodec() Codec

GetCodec returns the configured codec, falling back to the package default (StdlibJSON) when none is configured.

type PolicySubject

type PolicySubject struct {
	ID          string
	TenantID    string
	TenantPath  string
	Roles       []string
	Groups      []string
	Permissions []string
	Attributes  map[string]any
}

PolicySubject describes the actor making the request. Used by both filter and patch policy enforcement.

type RelationKind

type RelationKind string

RelationKind is a relation's cardinality.

const (
	// ManyToMany relates rows through a pair table; the zero kind.
	ManyToMany RelationKind = ""
	// HasMany relates an owner to the target rows whose foreign key holds
	// the owner's key.
	HasMany RelationKind = "has_many"
	// BelongsTo relates an owner to the one target row its foreign key
	// names.
	BelongsTo RelationKind = "belongs_to"
)

type RelationSchema

type RelationSchema struct {
	// Kind is the relation's cardinality.
	Kind RelationKind

	// JoinTable is the name of the junction table (e.g. "widget_tags").
	JoinTable string

	// SourceColumn is the column on JoinTable that holds the owner's PK
	// (e.g. "widget_id").
	SourceColumn string

	// TargetColumn is the column on JoinTable that holds the related
	// entity's PK (e.g. "tag_id").
	TargetColumn string

	// PrimaryKey is the owner entity's PK column (defaults to "id" when
	// empty).
	PrimaryKey string

	// TenantColumn names a tenant-scope column on the junction (typically
	// "tenant_id"). When non-empty, every junction statement parse emits —
	// filter, facet, include and relation patch — binds it to the caller's
	// tenant, and a builder without a bound tenant fails closed unless the
	// caller opts into unscoped relations explicitly. Empty
	// means the junction is not tenant-scoped (a globally shared table).
	TenantColumn string

	// DeletedColumn is JoinTable's soft-delete column: a row with it set
	// is not related.
	DeletedColumn string

	// TargetTable, when set, is the target table a many-to-many joins
	// through its pair table (on TargetTable.TargetKey = JoinTable.TargetColumn)
	// so predicates reach the target's columns; TargetTenant and
	// TargetDeleted scope its rows as TenantColumn and DeletedColumn scope
	// JoinTable's. Empty for a pair table compared by key alone.
	TargetTable   string
	TargetKey     string
	TargetTenant  string
	TargetDeleted string

	// Policy scopes the related target rows by the server's policy for the
	// target table instead of the tenant column.
	Policy bool
}

RelationSchema describes how a field's owner rows relate to other rows . Every predicate correlates JoinTable's rows to the owner by `JoinTable.SourceColumn = <owner>.PrimaryKey` and compiles to an EXISTS / NOT EXISTS. For a many-to-many JoinTable is the pair table and TargetColumn its target key; for a has-many or belongs-to JoinTable is the target table itself: a has-many correlates the target's foreign key to the owner's key, a belongs-to the target's key to the owner's foreign key (PrimaryKey).

The struct lives in parse/core (rather than parse/filter) because both parse/filter and parse/patch need to consult the same descriptor when emitting SQL.

func ParseRelationTag

func ParseRelationTag(tag string) (*RelationSchema, error)

ParseRelationTag parses a `relation:"…"` struct-tag value into a RelationSchema. The tag format is positional with optional trailing fields:

relation:"<junction>,<src>,<tgt>[,<pk>[,<tenant_col>]]"

Examples:

relation:"widget_tags,widget_id,tag_id"              // 3-arg
relation:"widget_tags,widget_id,tag_id,id"           // 4-arg, explicit pk
relation:"widget_tags,widget_id,tag_id,id,tenant_id" // 5-arg, tenant-scoped
relation:"system_tags,id,tag_id,id,-"                // 5-arg, explicit no-tenant

Every part must be a plain SQL identifier: the values are emitted into SQL verbatim, so a malformed tag fails at schema generation, not in a query.

func (*RelationSchema) RelatedTable

func (r *RelationSchema) RelatedTable() string

RelatedTable is the table whose rows a relation's nested predicates test: TargetTable through a pair table, else JoinTable itself.

type SQLColumnMapper

type SQLColumnMapper interface {
	MapColumn(table, field string, fieldType FieldType) string
}

SQLColumnMapper maps struct fields to SQL column expressions. Implement this for full control over column naming — it takes precedence over ColumnConvention and the db/column tag.

type SQLType

type SQLType string

SQLType declares a resource column's storage role and any dialect read cast. JSON and native arrays remain distinct even when their Go shapes coincide.

const (
	SQLDate          SQLType = "date"
	SQLInet          SQLType = "inet"
	SQLCIDR          SQLType = "cidr"
	SQLInterval      SQLType = "interval"
	SQLInetArray     SQLType = "inet[]"
	SQLCIDRArray     SQLType = "cidr[]"
	SQLIntervalArray SQLType = "interval[]"
	SQLJSON          SQLType = "json"
	SQLJSONB         SQLType = "jsonb"
	SQLTextArray     SQLType = "text[]"
	SQLInt32Array    SQLType = "integer[]"
	SQLUUIDArray     SQLType = "uuid[]"
)

func (SQLType) IsArray

func (t SQLType) IsArray() bool

IsArray reports a declared native SQL array storage role.

func (SQLType) IsJSON

func (t SQLType) IsJSON() bool

IsJSON reports a declared JSON document storage role, including scalar JSON.

func (SQLType) Valid

func (t SQLType) Valid() bool

Valid reports whether t is one of the SQLType constants.

type SchemaGenerator

type SchemaGenerator struct {
	Opts *GeneratorOptions
}

SchemaGenerator generates schemas from Go struct types using configurable naming conventions and struct tag keys.

func NewSchemaGenerator

func NewSchemaGenerator(opts ...GeneratorOption) *SchemaGenerator

NewSchemaGenerator creates a SchemaGenerator with the given options.

func (*SchemaGenerator) ColumnExpr

func (g *SchemaGenerator) ColumnExpr(table, field string, fieldType FieldType, dbTag string) (string, error)

ColumnExpr builds the SQL column expression for a field. Priority: ColumnMapper → columnTag value → ColumnConvention → SnakeCase default. Without a ColumnMapper the result is "table.column" and the column must be a plain identifier; a mapper's expressions are the developer's own SQL and are emitted as returned.

func (*SchemaGenerator) ColumnName

func (g *SchemaGenerator) ColumnName(field, dbTag string) (string, error)

ColumnName returns just the column name (without table prefix), for patch SQL SET clauses and cursor row reads. Priority: columnTag value → ColumnConvention → SnakeCase default.

func (*SchemaGenerator) ColumnTagKey

func (g *SchemaGenerator) ColumnTagKey() string

ColumnTagKey returns the struct tag key to read for SQL column names. Default: "db".

func (*SchemaGenerator) FieldColumn

func (g *SchemaGenerator) FieldColumn(f *Field) (column string, ok bool, err error)

FieldColumn is the field's column: the column tag's name, else the generator's convention applied to its external name (its Go name when the field-name tag hides it). ok is false for a field that is not a column (column tag "-").

func (*SchemaGenerator) FieldNameTag

func (g *SchemaGenerator) FieldNameTag() string

FieldNameTag returns the struct tag key to read for external field names. Default: "json".

func (*SchemaGenerator) FieldType

func (g *SchemaGenerator) FieldType(field *Field) (TypeInfo, error)

FieldType combines a field's Go representation with its declared storage. JSON scalar and scalar-slice fields use document operators, while typed objects retain their member schema and native arrays retain array semantics.

func (*SchemaGenerator) StructFields

func (g *SchemaGenerator) StructFields(t reflect.Type) (*StructFields, error)

StructFields walks t (a struct, or a pointer to one) under the generator's field-name and column tags. The walk is cached per type and tag pair. A name two fields claim — a shallower field shadowing a promoted one, or two promoted fields at one depth — is an error naming both fields: encoding/json keeps the shallower field or drops both, and parse must see exactly the fields encoding/json writes.

func (*SchemaGenerator) TableName

func (g *SchemaGenerator) TableName(t interface{ Name() string }) (string, error)

TableName derives the SQL table name for a type. Priority: SQLTableName (explicit) → TableNameConvention → PluralSnake default. The name is emitted into SQL verbatim, so anything but a plain identifier is an error at schema generation.

type StdlibJSON

type StdlibJSON struct{}

StdlibJSON is the default Codec: the standard library's JSON, with encoding/json's data model (field names, omitempty, durations as nanoseconds), so a document reads back the way responses are written. Decode is stricter than encoding/json's Unmarshal: a duplicate object member name, invalid UTF-8 or data after the value is an error, so a request cannot say a field twice and have one value win. It preserves numbers exactly: a number landing in an `any` becomes a json.Number rather than a float64, so integers beyond ±2^53 reach validation intact.

func (StdlibJSON) Decode

func (StdlibJSON) Decode(data []byte, v any) error

func (StdlibJSON) Encode

func (StdlibJSON) Encode(v any) ([]byte, error)

type StructFields

type StructFields struct {
	// Type is the walked struct type.
	Type reflect.Type
	// Fields holds the fields in declaration order, an embedded struct's
	// promoted fields in place of it.
	Fields []*Field
	// contains filtered or unexported fields
}

StructFields is a struct type's fields, walked once and shared by every view parse builds: columns, the filter, sort, search and patch schemas, keyset cursor values, server-only patch paths and document members. The fields are the ones encoding/json writes under the generator's field-name tag — embedded structs promoted as encoding/json promotes them — plus the fields that tag hides, which columns, server-only paths and relations still read.

func StructFieldsOf

func StructFieldsOf(t reflect.Type, nameTag, columnTag string) (*StructFields, error)

StructFieldsOf is SchemaGenerator.StructFields for explicit tag keys.

func (*StructFields) ColumnTagged

func (s *StructFields) ColumnTagged(column string) (*Field, bool)

ColumnTagged returns the field whose column tag names column exactly.

func (*StructFields) External

func (s *StructFields) External(name string) (*Field, bool)

External returns the field named name under the field-name tag.

type TableConvention

type TableConvention func(string) string

TableConvention transforms a type name into a SQL table name.

var (
	// PluralSnake converts PascalCase to plural snake_case: "Product" → "products" (default).
	PluralSnake TableConvention = func(s string) string { return Pluralize(ToSnakeCase(s)) }

	// Singular converts PascalCase to singular snake_case: "Product" → "product".
	Singular TableConvention = func(s string) string { return ToSnakeCase(s) }

	// PluralCamel converts PascalCase to plural camelCase: "Product" → "products".
	PluralCamel TableConvention = func(s string) string { return Pluralize(toCamelCase(s)) }
)

Built-in table conventions.

type Tags

type Tags struct {
	// Parse is the field's parse capabilities.
	Parse Capabilities
	// Column is the column tag's text: empty when absent, "-" for a field
	// that is not a column.
	Column string
	// FieldNameOptions are the field-name tag's options (omitempty, ...).
	FieldNameOptions string

	Relation, RelationName string
	FilterOps, PatchOps    string
	Enum                   []string
	Facet                  string
	Paths, Elements        string
	Storage                string
	Geo, GeoSRID           string
	GeoLat, GeoLon         string
	Indexed, Immutable     bool
	Label, Description     string
	Group, Order           string
	SortDefault            string
	SearchWeight           string
}

Tags are the struct tags parse reads on a field. Option-bearing tags hold their raw text for the view that interprets them.

type TypeInfo

type TypeInfo struct {
	Type FieldType
	// SQLType is the explicit storage declaration, empty for Go-only inference.
	SQLType SQLType
	// Elem is the element type of a FieldTypeArray; empty otherwise.
	Elem FieldType
	// NumberKind is the Go numeric kind of Type, or of Elem for an array;
	// reflect.Invalid when not numeric.
	NumberKind reflect.Kind
}

TypeInfo is how a Go field type is advertised and stored.

func TypeOf

func TypeOf(t reflect.Type) TypeInfo

TypeOf classifies a Go field type (pointers stripped). The classification is the storage model every backend shares:

string / int / float / bool kinds   string, int, float, bool
time.Time, a UUID type (IsUUIDType) timestamp, uuid
Date                                date
a decimal type (IsDecimalType)      decimal — PostgreSQL numeric; NUMERIC or REAL on SQLite
slice or array of a scalar above,
other than decimal                  array — a PostgreSQL native array (TEXT[], BIGINT[], …); JSON text on SQLite
map, interface, json.RawMessage,
slice of anything else              json — a document: PostgreSQL jsonb; JSON text on SQLite
struct                              object — a JSON document with a fixed shape
[]byte, chan, func, …               any — no filter or patch semantics

type Validator

type Validator func(value any) error

Validator is a function that validates a value.

Jump to

Keyboard shortcuts

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