Documentation
¶
Overview ¶
Package schematics validates and transforms arbitrary JSON documents against a declarative, data-driven schema.
The model is simple: a document is flattened into dotted keys (for example user.profile.name or tags.0), each schema field targets one or more of those keys (literally, with a * wildcard, or with a full regular expression), and every matched value is run through a chain of validators and operators. Validators report problems as typed ValidationError values; operators transform the value in place.
The package has no third-party dependencies.
Example ¶
Example loads the bundled person schema and prints the validation errors for an invalid document. Errors are reported in schema field order.
package main
import (
"errors"
"fmt"
schematics "github.com/ashbeelghouri/json-schematics-v2"
)
func main() {
s := schematics.New()
if err := s.LoadFile("examples/person.schema.json"); err != nil {
panic(err)
}
data := map[string]any{
"user": map[string]any{
"profile": map[string]any{"name": "a", "age": 200},
"tags": []any{"x", "x"},
},
}
if err := s.Validate(data); err != nil {
var ve *schematics.ValidationErrors
if errors.As(err, &ve) {
for _, msg := range ve.Strings("en", "%target: %message") {
fmt.Println(msg)
}
}
}
}
Output: user.profile.name: name is too short user.profile.age: age must be 0-120 user.tags: items must be unique (duplicate x)
Index ¶
- Constants
- func Deflate(flat map[string]any, separator string) map[string]any
- func Flatten(data map[string]any, separator string) map[string]any
- func RuleName(c Code) (string, bool)
- func WriteProblem(w http.ResponseWriter, r *http.Request, err error, opts ...ProblemOption) error
- type API
- type APIEndpoint
- type APIGlobal
- type APISchema
- type Args
- func (a Args) Bool(key string) (bool, error)
- func (a Args) Float(key string) (float64, error)
- func (a Args) FloatOr(key string, def float64) float64
- func (a Args) Get(key string) (any, bool)
- func (a Args) Has(key string) bool
- func (a Args) Int(key string) (int, error)
- func (a Args) String(key string) (string, error)
- func (a Args) StringOr(key, def string) string
- func (a Args) Strings(key string) ([]string, error)
- type Attribute
- type Catalog
- type Code
- type Condition
- type ConditionRef
- type Context
- type Event
- type EventKind
- type Field
- type FieldBuilder
- func (fb *FieldBuilder) AddToDB() *FieldBuilder
- func (fb *FieldBuilder) After(date string) *FieldBuilder
- func (fb *FieldBuilder) AfterNow() *FieldBuilder
- func (fb *FieldBuilder) Alpha() *FieldBuilder
- func (fb *FieldBuilder) Alphanumeric() *FieldBuilder
- func (fb *FieldBuilder) Before(date string) *FieldBuilder
- func (fb *FieldBuilder) BeforeNow() *FieldBuilder
- func (fb *FieldBuilder) Between(min, max float64) *FieldBuilder
- func (fb *FieldBuilder) Capitalize() *FieldBuilder
- func (fb *FieldBuilder) Contains(sub string) *FieldBuilder
- func (fb *FieldBuilder) Default(v any) *FieldBuilder
- func (fb *FieldBuilder) DependsOn(targets ...string) *FieldBuilder
- func (fb *FieldBuilder) Description(d string) *FieldBuilder
- func (fb *FieldBuilder) DisplayName(n string) *FieldBuilder
- func (fb *FieldBuilder) Done() *SchemaBuilder
- func (fb *FieldBuilder) Email() *FieldBuilder
- func (fb *FieldBuilder) EndsWith(suffix string) *FieldBuilder
- func (fb *FieldBuilder) Equals(v string) *FieldBuilder
- func (fb *FieldBuilder) ExactLength(n int) *FieldBuilder
- func (fb *FieldBuilder) Field(target string) *FieldBuilder
- func (fb *FieldBuilder) HasDigit() *FieldBuilder
- func (fb *FieldBuilder) HasLower() *FieldBuilder
- func (fb *FieldBuilder) HasUpper() *FieldBuilder
- func (fb *FieldBuilder) InOptions(opts ...string) *FieldBuilder
- func (fb *FieldBuilder) IsArray() *FieldBuilder
- func (fb *FieldBuilder) IsBase64() *FieldBuilder
- func (fb *FieldBuilder) IsBoolean() *FieldBuilder
- func (fb *FieldBuilder) IsCIDR() *FieldBuilder
- func (fb *FieldBuilder) IsDate() *FieldBuilder
- func (fb *FieldBuilder) IsFloat() *FieldBuilder
- func (fb *FieldBuilder) IsHTTPS() *FieldBuilder
- func (fb *FieldBuilder) IsHex() *FieldBuilder
- func (fb *FieldBuilder) IsIP() *FieldBuilder
- func (fb *FieldBuilder) IsInteger() *FieldBuilder
- func (fb *FieldBuilder) IsJSON() *FieldBuilder
- func (fb *FieldBuilder) IsNumber() *FieldBuilder
- func (fb *FieldBuilder) IsObject() *FieldBuilder
- func (fb *FieldBuilder) IsString() *FieldBuilder
- func (fb *FieldBuilder) IsURL() *FieldBuilder
- func (fb *FieldBuilder) IsUUID() *FieldBuilder
- func (fb *FieldBuilder) ItemsInOptions(opts ...string) *FieldBuilder
- func (fb *FieldBuilder) LengthBetween(min, max int) *FieldBuilder
- func (fb *FieldBuilder) Like(p string) *FieldBuilder
- func (fb *FieldBuilder) Lower() *FieldBuilder
- func (fb *FieldBuilder) Max(v float64) *FieldBuilder
- func (fb *FieldBuilder) MaxItems(n int) *FieldBuilder
- func (fb *FieldBuilder) MaxLength(n int) *FieldBuilder
- func (fb *FieldBuilder) Message(msg string) *FieldBuilder
- func (fb *FieldBuilder) Meta(m map[string]any) *FieldBuilder
- func (fb *FieldBuilder) Min(v float64) *FieldBuilder
- func (fb *FieldBuilder) MinItems(n int) *FieldBuilder
- func (fb *FieldBuilder) MinLength(n int) *FieldBuilder
- func (fb *FieldBuilder) MultipleOf(of float64) *FieldBuilder
- func (fb *FieldBuilder) Name(n string) *FieldBuilder
- func (fb *FieldBuilder) Negative() *FieldBuilder
- func (fb *FieldBuilder) New(opts ...Option) (*Schematics, error)
- func (fb *FieldBuilder) NonNegative() *FieldBuilder
- func (fb *FieldBuilder) NotEmpty() *FieldBuilder
- func (fb *FieldBuilder) NotInOptions(opts ...string) *FieldBuilder
- func (fb *FieldBuilder) Op(name string, args Args) *FieldBuilder
- func (fb *FieldBuilder) Pattern(p string) *FieldBuilder
- func (fb *FieldBuilder) Positive() *FieldBuilder
- func (fb *FieldBuilder) Regex() *FieldBuilder
- func (fb *FieldBuilder) Replace(old, new string) *FieldBuilder
- func (fb *FieldBuilder) Required() *FieldBuilder
- func (fb *FieldBuilder) Rule(name string, args Args) *FieldBuilder
- func (fb *FieldBuilder) Schema() Schema
- func (fb *FieldBuilder) Slugify() *FieldBuilder
- func (fb *FieldBuilder) StartsWith(prefix string) *FieldBuilder
- func (fb *FieldBuilder) Tags(tags ...string) *FieldBuilder
- func (fb *FieldBuilder) ToString() *FieldBuilder
- func (fb *FieldBuilder) Trim() *FieldBuilder
- func (fb *FieldBuilder) Truncate(length int, suffix string) *FieldBuilder
- func (fb *FieldBuilder) Type(t string) *FieldBuilder
- func (fb *FieldBuilder) Unique() *FieldBuilder
- func (fb *FieldBuilder) Upper() *FieldBuilder
- func (fb *FieldBuilder) When(condition string, args Args) *FieldBuilder
- func (fb *FieldBuilder) WhenAbsent(field string) *FieldBuilder
- func (fb *FieldBuilder) WhenEquals(field string, value any) *FieldBuilder
- func (fb *FieldBuilder) WhenNot(condition string, args Args) *FieldBuilder
- func (fb *FieldBuilder) WhenPresent(field string) *FieldBuilder
- type FieldView
- type Metrics
- type Observer
- type ObserverFunc
- type Operation
- type Operator
- type OperatorRef
- type Option
- func WithArrayIDKey(key string) Option
- func WithCatalog(cat *Catalog) Option
- func WithCollectAll() Option
- func WithDB(db map[string]any) Option
- func WithLocale(locale string) Option
- func WithLogger(l *slog.Logger) Option
- func WithMaxBodyBytes(n int64) Option
- func WithObserver(o Observer) Option
- func WithSeparator(sep string) Option
- func WithTracer(t Tracer) Option
- func WithTypeChecks() Option
- type Problem
- type ProblemError
- type ProblemOption
- func WithProblemDetail(detail string) ProblemOption
- func WithProblemInstance(uri string) ProblemOption
- func WithProblemLocale(locale string) ProblemOption
- func WithProblemStatus(status int) ProblemOption
- func WithProblemTitle(title string) ProblemOption
- func WithProblemType(uri string) ProblemOption
- type RowResult
- type Rule
- type RuleRef
- type Schema
- type SchemaBuilder
- func (b *SchemaBuilder) ArrayIDKey(k string) *SchemaBuilder
- func (b *SchemaBuilder) DB(m map[string]any) *SchemaBuilder
- func (b *SchemaBuilder) Field(target string) *FieldBuilder
- func (b *SchemaBuilder) Locale(l string) *SchemaBuilder
- func (b *SchemaBuilder) New(opts ...Option) (*Schematics, error)
- func (b *SchemaBuilder) Schema() Schema
- func (b *SchemaBuilder) Separator(sep string) *SchemaBuilder
- type SchemaError
- type SchemaProblem
- type Schematics
- func (s *Schematics) Check() error
- func (s *Schematics) ConditionNames() []string
- func (s *Schematics) LoadBytes(b []byte) error
- func (s *Schematics) LoadFile(path string) error
- func (s *Schematics) LoadMap(m any) error
- func (s *Schematics) Operate(data any) (any, error)
- func (s *Schematics) OperateCtx(ctx context.Context, data any) (any, error)
- func (s *Schematics) OperatorNames() []string
- func (s *Schematics) RegisterCondition(name string, fn Condition) *Schematics
- func (s *Schematics) RegisterOperator(name string, fn Operator) *Schematics
- func (s *Schematics) RegisterRule(name string, fn Rule) *Schematics
- func (s *Schematics) RuleNames() []string
- func (s *Schematics) Schema() Schema
- func (s *Schematics) SetSchema(schema Schema) *Schematics
- func (s *Schematics) Validate(data any) error
- func (s *Schematics) ValidateBytes(b []byte, isArray bool) error
- func (s *Schematics) ValidateBytesCtx(ctx context.Context, b []byte, isArray bool) error
- func (s *Schematics) ValidateCtx(ctx context.Context, data any) error
- func (s *Schematics) ValidateSchema(sample any, ignoreTargets ...string) error
- func (s *Schematics) ValidateStream(r io.Reader, opts ...StreamOption) error
- func (s *Schematics) ValidateStreamCtx(ctx context.Context, r io.Reader, opts ...StreamOption) error
- func (s *Schematics) ValidateStreamEach(ctx context.Context, r io.Reader, fn func(RowResult) error, ...) error
- type SlogObserver
- type Span
- type StreamOption
- type StreamPanic
- type Tracer
- type ValidationError
- type ValidationErrors
- func (es *ValidationErrors) Add(e *ValidationError)
- func (es *ValidationErrors) Error() string
- func (es *ValidationErrors) ForTarget(target string) []*ValidationError
- func (es *ValidationErrors) HasErrors() bool
- func (es *ValidationErrors) Len() int
- func (es *ValidationErrors) Messages(locale string) []string
- func (es *ValidationErrors) Pointers() []string
- func (es *ValidationErrors) Strings(locale, format string) []string
- type WhenNode
Examples ¶
Constants ¶
const ProblemContentType = "application/problem+json"
ProblemContentType is the media type RFC 7807 requires on a problem details response.
const Version = "2.0"
Version is the schema/library generation this package implements.
Variables ¶
This section is empty.
Functions ¶
func Deflate ¶
Deflate reconstructs a nested structure from a flattened map. Segments that are consecutive integers starting at zero are rebuilt as slices.
func Flatten ¶
Flatten converts a nested map into a single-level map whose keys are the paths to each leaf value, joined by separator. Arrays become indexed keys, so {"tags":["a","b"]} becomes {"tags.0":"a","tags.1":"b"}. Empty maps and slices are preserved as leaf values so length-style validators can still see them.
Flatten/Deflate round-trip losslessly only when object keys do not contain the separator and are non-empty. A literal key like "a.b" is indistinguishable from nested {"a":{"b":...}} once flattened, so if your documents can carry keys containing "." (the default separator), choose a separator that cannot occur in your keys via WithSeparator.
func RuleName ¶ added in v1.0.3
RuleName returns the validator name embedded in a rule code and reports whether c was in fact a rule code. It is the inverse of RuleCode.
func WriteProblem ¶ added in v1.0.3
func WriteProblem(w http.ResponseWriter, r *http.Request, err error, opts ...ProblemOption) error
WriteProblem renders err as an RFC 7807 response. It is the one-line form of NewProblem followed by Write, and is a no-op when err is nil.
if err := api.ValidateRequest(r); err != nil {
_ = schematics.WriteProblem(w, r, err)
return
}
r may be nil; when it is not, its path is used as the problem's "instance".
Example ¶
ExampleWriteProblem returns validation failures as an RFC 7807 application/problem+json response — the shape most HTTP clients already know how to display.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
schematics "github.com/ashbeelghouri/json-schematics-v2"
)
const orderSchema = `{"fields":[
{"target":"customer.email","required":true,"validate":[{"rule":"email"}]},
{"target":"quantity","validate":[{"rule":"min","args":{"min":1}}]}
]}`
func main() {
s, err := schematics.ImportSchema([]byte(orderSchema))
if err != nil {
panic(err)
}
handler := func(w http.ResponseWriter, r *http.Request) {
if err := s.ValidateBytes([]byte(`{"customer":{"email":"nope"},"quantity":2}`), false); err != nil {
_ = schematics.WriteProblem(w, r, err,
schematics.WithProblemType("https://example.com/probs/validation"))
return
}
w.WriteHeader(http.StatusNoContent)
}
rec := httptest.NewRecorder()
handler(rec, httptest.NewRequest("POST", "/orders", nil))
fmt.Println(rec.Code)
fmt.Println(rec.Header().Get("Content-Type"))
fmt.Println(rec.Body.String())
}
Output: 422 application/problem+json {"type":"https://example.com/probs/validation","title":"Unprocessable Entity","status":422,"detail":"the request payload failed 1 validation rule","instance":"/orders","errors":[{"pointer":"/customer/email","target":"customer.email","code":"rule.email","rule":"email","detail":"\"nope\" is not a valid email address"}]}
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API validates *http.Request values against an APISchema. It reuses a base Schematics for its registries and options, so custom rules registered there are available to request validation too.
func NewAPI ¶
NewAPI creates an API validator with the given options (shared with the underlying engine).
func (*API) Base ¶
func (a *API) Base() *Schematics
Base exposes the underlying Schematics so callers can register custom rules, operators, and conditions used by the request schema.
func (*API) ValidateRequest ¶
ValidateRequest validates r against the endpoint matching its method and path. It returns nil, a *ValidationErrors, or a *SchemaError. If no endpoint matches, it returns a *ValidationErrors describing the mismatch.
func (*API) WriteProblem ¶ added in v1.0.3
func (a *API) WriteProblem(w http.ResponseWriter, r *http.Request, err error, opts ...ProblemOption) error
WriteProblem is WriteProblem bound to this API, so failure messages are rendered in the locale the engine was configured with rather than the library default. An explicit WithProblemLocale still wins.
type APIEndpoint ¶
type APIEndpoint struct {
Path string `json:"path"`
Method string `json:"method"`
Headers []Field `json:"headers,omitempty"`
Query []Field `json:"query,omitempty"`
Body []Field `json:"body,omitempty"`
}
APIEndpoint validates one method+path combination.
type APIGlobal ¶
type APIGlobal struct {
Headers []Field `json:"headers,omitempty"`
}
APIGlobal holds fields applied to every endpoint (currently headers).
type APISchema ¶
type APISchema struct {
Version string `json:"version,omitempty"`
Separator string `json:"separator,omitempty"`
Global APIGlobal `json:"global,omitempty"`
Endpoints []APIEndpoint `json:"endpoints"`
}
APISchema describes request validation for a set of HTTP endpoints. Each endpoint validates its headers, query parameters, and JSON body using the same Field model as the core schema.
Example (JSON):
{
"version": "2.0",
"global": { "headers": [ { "target": "authorization", "required": true } ] },
"endpoints": [
{
"path": "/users/:id",
"method": "POST",
"query": [ { "target": "verbose", "validate": [ { "rule": "inOptions", "args": { "options": ["true","false"] } } ] } ],
"body": [ { "target": "email", "required": true, "validate": [ { "rule": "email" } ] } ]
}
]
}
type Args ¶
Args holds the arguments a schema passes to a validator, operator, or condition. The typed accessors never panic: a missing key or a wrong type is reported as an error instead, which is what makes built-in and custom rules safe to run against arbitrary data.
type Attribute ¶ added in v1.0.3
Attribute is one key/value pair recorded on a span. Value is one of string, int, int64, float64, or bool — the types every tracing backend can represent natively.
type Catalog ¶ added in v1.0.3
type Catalog struct {
// DefaultLocale is tried after the requested locale's own fallback chain
// is exhausted (see localeChain). Defaults to "en" when empty.
DefaultLocale string
// contains filtered or unexported fields
}
Catalog holds external, file-loaded message templates keyed by locale then by rule name. It exists so localization can scale past hand-writing "message"/"messages" on every RuleRef in a schema: load one catalog file (JSON or a small TOML subset) instead, and attach it with WithCatalog.
ValidationError.Message consults a catalog only when the failing rule has no inline "message"/"messages" of its own for the requested locale — an explicit per-rule schema message always wins over the catalog.
func LoadCatalogBytes ¶ added in v1.0.3
LoadCatalogBytes parses a JSON catalog of the shape {"locale": {"rule": "template", ...}, ...}, e.g.:
{
"en": { "required": "{target} is required.", "minLength": "must be at least {min} characters" },
"ar": { "required": "{target} مطلوب." }
}
func LoadCatalogFile ¶ added in v1.0.3
LoadCatalogFile reads and parses a JSON catalog file.
func LoadCatalogTOMLBytes ¶ added in v1.0.3
LoadCatalogTOMLBytes parses a minimal TOML subset — one [locale] section per locale, each followed by `rule = "template"` lines:
[en]
required = "{target} is required."
[ar]
required = "{target} مطلوب."
This is NOT a general TOML parser: no arrays, no tables-of-tables, no multiline strings, no non-string values — just enough to let a catalog be hand-edited as TOML without pulling in a dependency (this library is zero-dependency by design). Blank lines and lines starting with '#' are ignored.
func LoadCatalogTOMLFile ¶ added in v1.0.3
LoadCatalogTOMLFile reads and parses a TOML catalog file. See LoadCatalogTOMLBytes for the supported subset.
func NewCatalog ¶ added in v1.0.3
func NewCatalog() *Catalog
NewCatalog returns an empty catalog. Add entries with Merge, or start from a file with LoadCatalogFile / LoadCatalogTOMLFile.
func (*Catalog) Merge ¶ added in v1.0.3
Merge adds (or overwrites) the rule -> template entries for locale.
func (*Catalog) Render ¶ added in v1.0.3
Render looks up a template for rule under locale — walking localeChain until one locale has an entry for rule — and renders it against args via renderTemplate. ok is false when no locale in the chain has a template for rule, so the caller can fall back predictably (to the schema's own message, or a generated description) rather than showing nothing.
type Code ¶ added in v1.0.3
type Code string
Code is a stable machine-readable identifier for a failure. Compare it against the exported constants, or against RuleCode(name) for a validator.
const ( // CodeRequired: a field marked required matched nothing in the document. CodeRequired Code = "field.required" // CodeDependsOn: a field's dependsOn targets were not all present. CodeDependsOn Code = "field.dependsOn" )
Data-level codes. These describe a document that did not satisfy the schema.
const ( // CodeEmptyTarget: a field declares no target. CodeEmptyTarget Code = "schema.emptyTarget" // CodeDuplicateTarget: two fields declare the same target. CodeDuplicateTarget Code = "schema.duplicateTarget" // CodeUnknownRule: a field references a validator that is not registered. CodeUnknownRule Code = "schema.unknownRule" // CodeUnknownOperator: a field references an operator that is not registered. CodeUnknownOperator Code = "schema.unknownOperator" // CodeUnknownCondition: a when node references a condition that is not registered. CodeUnknownCondition Code = "schema.unknownCondition" // CodeMalformedWhen: a when node is neither a valid leaf nor a valid group. CodeMalformedWhen Code = "schema.malformedWhen" // CodeSchemaInvalid: the API layer could not run a section's schema at all. // It wraps a *SchemaError raised while validating one request section. CodeSchemaInvalid Code = "schema.invalid" // CodeSampleMismatch: ValidateSchema found a target that matches nothing in // the sample document. CodeSampleMismatch Code = "schema.sampleMismatch" )
Schema-level codes. These describe a schema that cannot be executed, and are carried by SchemaProblem rather than by ValidationError.
const ( // CodeRouteNotFound: no endpoint in the API schema matches the request's // method and path. CodeRouteNotFound Code = "request.routeNotFound" // CodeBodyTooLarge: the request body exceeded WithMaxBodyBytes. CodeBodyTooLarge Code = "request.bodyTooLarge" )
Request-level codes. These describe an HTTP request the API layer could not validate as presented, as opposed to one whose contents were invalid.
func RuleCode ¶ added in v1.0.3
RuleCode returns the stable code for a failing validator: the rule's registered name under the "rule." namespace, so the built-in minLength reports "rule.minLength" and a custom rule registered as "isTenantID" reports "rule.isTenantID".
Because the code is derived from the registered name, renaming a custom rule renames its code. Treat a rule name as the public identifier it is.
type ConditionRef ¶
type ConditionRef = WhenNode
ConditionRef is a deprecated alias for WhenNode, kept so existing Go code that builds Field.When by hand (schematics.ConditionRef{...}) keeps compiling unchanged now that When holds boolean-composition groups too. Prefer WhenNode in new code.
type Context ¶
type Context struct {
Ctx context.Context
DB map[string]any
Locale string
Separator string
Flat map[string]any
RowID string
Field *FieldView
// contains filtered or unexported fields
}
Context is handed to every validator, operator, and condition. It carries the shared DB, the active locale and separator, the full flattened document, and metadata about the field currently being processed. Ctx is a standard context.Context so long-running custom rules can honor cancellation.
A *Context, its *FieldView, and the DB map it carries are valid only for the duration of the rule call they are passed to. Validation reuses these structures between fields and between rows of an array via a sync.Pool, so a rule that needs to keep any of them must copy what it needs rather than retaining the pointer. Flat is the caller's own flattened document and is not pooled, but treat it as read-only.
func (*Context) FieldPresent ¶
FieldPresent reports whether target selects at least one value in the document under validation. It honors the active separator and wildcard rules.
type Event ¶ added in v1.0.3
type Event struct {
// Kind is the granularity of this event.
Kind EventKind
// Op is the entry point that produced it.
Op Operation
// Target is the concrete flattened key the rule ran against (rule events).
Target string
// Rule is the validator that ran (rule events).
Rule string
// Code is the stable failure code, set only when Failed is true.
Code Code
// RowID identifies the array row, for array and stream operations.
RowID string
// Failed reports the outcome: for a rule event, whether the rule rejected
// the value; for a validation event, whether any rule did.
Failed bool
// Errors counts the failures collected (validation events).
Errors int
// Rows counts the rows validated: 1 for an object, the array length for an
// array, 1 per row for a stream (validation events).
Rows int
// Duration is how long the validation took (validation events). It is never
// set on rule events — see the note at the top of this file.
Duration time.Duration
}
Event describes something the engine just did. Which fields are meaningful depends on Kind:
EventValidation: Op, Duration, Rows, Errors, Failed EventRule: Op, Target, Rule, Code, RowID, Failed
Fields that do not apply are left at their zero value.
type EventKind ¶ added in v1.0.3
type EventKind string
EventKind distinguishes the granularities the engine reports at. Callers should ignore kinds they do not recognize rather than treating them as an error, so new kinds can be added without breaking them.
const ( // EventValidation is emitted once per completed validation: one Validate // call, or one row of a streamed array. EventValidation EventKind = "validation" // EventRule is emitted once per rule evaluated against one value, whether // it passed or failed. The field-level checks (required, dependsOn) report // this way too, so a failure rate computed from these events has an honest // denominator. A field that was never evaluated at all — optional, absent, // and with unmet dependencies — reports nothing. EventRule EventKind = "rule" )
type Field ¶
type Field struct {
// Target selects flattened keys: a literal path, a path with a "*"
// wildcard, or (when TargetRegex is true) a regular expression.
Target string `json:"target"`
TargetRegex bool `json:"targetRegex,omitempty"`
Name string `json:"name,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
// Required fails when the target selects no value.
Required bool `json:"required,omitempty"`
// DependsOn lists other targets that must be present for this field to be
// validated.
DependsOn []string `json:"dependsOn,omitempty"`
// AddToDB copies the matched value into the shared DB before validation, so
// other rules can reference it.
AddToDB bool `json:"addToDB,omitempty"`
Tags []string `json:"tags,omitempty"`
// When lists conditions that must all hold for the field to be processed.
// Each entry is a WhenNode: a flat list is ANDed together (backward
// compatible with the pre-boolean-logic behavior), and any entry may
// itself be a nested any/all/not group.
When []WhenNode `json:"when,omitempty"`
// Validate is the ordered chain of validators. The first failing rule stops
// evaluation of this field and produces one error.
Validate []RuleRef `json:"validate,omitempty"`
// Operate is the ordered chain of operators applied by Operate.
Operate []OperatorRef `json:"operate,omitempty"`
Meta map[string]any `json:"meta,omitempty"`
}
Field targets one or more flattened keys and describes how to validate and transform the matched values.
type FieldBuilder ¶ added in v1.0.2
type FieldBuilder struct {
// contains filtered or unexported fields
}
FieldBuilder accumulates one field's settings, validators, operators, and conditions. Any terminator method (Field, Done, Schema, New) commits it.
func (*FieldBuilder) AddToDB ¶ added in v1.0.2
func (fb *FieldBuilder) AddToDB() *FieldBuilder
AddToDB copies the matched value into the shared DB before validation.
func (*FieldBuilder) After ¶ added in v1.0.2
func (fb *FieldBuilder) After(date string) *FieldBuilder
func (*FieldBuilder) AfterNow ¶ added in v1.0.2
func (fb *FieldBuilder) AfterNow() *FieldBuilder
func (*FieldBuilder) Alpha ¶ added in v1.0.2
func (fb *FieldBuilder) Alpha() *FieldBuilder
func (*FieldBuilder) Alphanumeric ¶ added in v1.0.2
func (fb *FieldBuilder) Alphanumeric() *FieldBuilder
func (*FieldBuilder) Before ¶ added in v1.0.2
func (fb *FieldBuilder) Before(date string) *FieldBuilder
func (*FieldBuilder) BeforeNow ¶ added in v1.0.2
func (fb *FieldBuilder) BeforeNow() *FieldBuilder
func (*FieldBuilder) Between ¶ added in v1.0.2
func (fb *FieldBuilder) Between(min, max float64) *FieldBuilder
func (*FieldBuilder) Capitalize ¶ added in v1.0.2
func (fb *FieldBuilder) Capitalize() *FieldBuilder
func (*FieldBuilder) Contains ¶ added in v1.0.2
func (fb *FieldBuilder) Contains(sub string) *FieldBuilder
func (*FieldBuilder) Default ¶ added in v1.0.2
func (fb *FieldBuilder) Default(v any) *FieldBuilder
func (*FieldBuilder) DependsOn ¶ added in v1.0.2
func (fb *FieldBuilder) DependsOn(targets ...string) *FieldBuilder
DependsOn adds required-present dependency targets.
func (*FieldBuilder) Description ¶ added in v1.0.2
func (fb *FieldBuilder) Description(d string) *FieldBuilder
Description sets the field description.
func (*FieldBuilder) DisplayName ¶ added in v1.0.2
func (fb *FieldBuilder) DisplayName(n string) *FieldBuilder
DisplayName sets the human-facing name.
func (*FieldBuilder) Done ¶ added in v1.0.2
func (fb *FieldBuilder) Done() *SchemaBuilder
Done commits this field and returns the schema builder.
func (*FieldBuilder) Email ¶ added in v1.0.2
func (fb *FieldBuilder) Email() *FieldBuilder
func (*FieldBuilder) EndsWith ¶ added in v1.0.2
func (fb *FieldBuilder) EndsWith(suffix string) *FieldBuilder
func (*FieldBuilder) Equals ¶ added in v1.0.2
func (fb *FieldBuilder) Equals(v string) *FieldBuilder
func (*FieldBuilder) ExactLength ¶ added in v1.0.2
func (fb *FieldBuilder) ExactLength(n int) *FieldBuilder
func (*FieldBuilder) Field ¶ added in v1.0.2
func (fb *FieldBuilder) Field(target string) *FieldBuilder
Field commits this field and starts another.
func (*FieldBuilder) HasDigit ¶ added in v1.0.2
func (fb *FieldBuilder) HasDigit() *FieldBuilder
func (*FieldBuilder) HasLower ¶ added in v1.0.2
func (fb *FieldBuilder) HasLower() *FieldBuilder
func (*FieldBuilder) HasUpper ¶ added in v1.0.2
func (fb *FieldBuilder) HasUpper() *FieldBuilder
func (*FieldBuilder) InOptions ¶ added in v1.0.2
func (fb *FieldBuilder) InOptions(opts ...string) *FieldBuilder
func (*FieldBuilder) IsArray ¶ added in v1.0.2
func (fb *FieldBuilder) IsArray() *FieldBuilder
func (*FieldBuilder) IsBase64 ¶ added in v1.0.2
func (fb *FieldBuilder) IsBase64() *FieldBuilder
func (*FieldBuilder) IsBoolean ¶ added in v1.0.2
func (fb *FieldBuilder) IsBoolean() *FieldBuilder
func (*FieldBuilder) IsCIDR ¶ added in v1.0.2
func (fb *FieldBuilder) IsCIDR() *FieldBuilder
func (*FieldBuilder) IsDate ¶ added in v1.0.2
func (fb *FieldBuilder) IsDate() *FieldBuilder
func (*FieldBuilder) IsFloat ¶ added in v1.0.2
func (fb *FieldBuilder) IsFloat() *FieldBuilder
func (*FieldBuilder) IsHTTPS ¶ added in v1.0.2
func (fb *FieldBuilder) IsHTTPS() *FieldBuilder
func (*FieldBuilder) IsHex ¶ added in v1.0.2
func (fb *FieldBuilder) IsHex() *FieldBuilder
func (*FieldBuilder) IsIP ¶ added in v1.0.2
func (fb *FieldBuilder) IsIP() *FieldBuilder
func (*FieldBuilder) IsInteger ¶ added in v1.0.2
func (fb *FieldBuilder) IsInteger() *FieldBuilder
func (*FieldBuilder) IsJSON ¶ added in v1.0.2
func (fb *FieldBuilder) IsJSON() *FieldBuilder
func (*FieldBuilder) IsNumber ¶ added in v1.0.2
func (fb *FieldBuilder) IsNumber() *FieldBuilder
func (*FieldBuilder) IsObject ¶ added in v1.0.2
func (fb *FieldBuilder) IsObject() *FieldBuilder
func (*FieldBuilder) IsString ¶ added in v1.0.2
func (fb *FieldBuilder) IsString() *FieldBuilder
func (*FieldBuilder) IsURL ¶ added in v1.0.2
func (fb *FieldBuilder) IsURL() *FieldBuilder
func (*FieldBuilder) IsUUID ¶ added in v1.0.2
func (fb *FieldBuilder) IsUUID() *FieldBuilder
func (*FieldBuilder) ItemsInOptions ¶ added in v1.0.2
func (fb *FieldBuilder) ItemsInOptions(opts ...string) *FieldBuilder
func (*FieldBuilder) LengthBetween ¶ added in v1.0.2
func (fb *FieldBuilder) LengthBetween(min, max int) *FieldBuilder
func (*FieldBuilder) Like ¶ added in v1.0.2
func (fb *FieldBuilder) Like(p string) *FieldBuilder
func (*FieldBuilder) Lower ¶ added in v1.0.2
func (fb *FieldBuilder) Lower() *FieldBuilder
func (*FieldBuilder) Max ¶ added in v1.0.2
func (fb *FieldBuilder) Max(v float64) *FieldBuilder
func (*FieldBuilder) MaxItems ¶ added in v1.0.2
func (fb *FieldBuilder) MaxItems(n int) *FieldBuilder
func (*FieldBuilder) MaxLength ¶ added in v1.0.2
func (fb *FieldBuilder) MaxLength(n int) *FieldBuilder
func (*FieldBuilder) Message ¶ added in v1.0.2
func (fb *FieldBuilder) Message(msg string) *FieldBuilder
Message sets the custom message on the most recently added validator.
func (*FieldBuilder) Meta ¶ added in v1.0.2
func (fb *FieldBuilder) Meta(m map[string]any) *FieldBuilder
Meta attaches arbitrary metadata.
func (*FieldBuilder) Min ¶ added in v1.0.2
func (fb *FieldBuilder) Min(v float64) *FieldBuilder
func (*FieldBuilder) MinItems ¶ added in v1.0.2
func (fb *FieldBuilder) MinItems(n int) *FieldBuilder
func (*FieldBuilder) MinLength ¶ added in v1.0.2
func (fb *FieldBuilder) MinLength(n int) *FieldBuilder
func (*FieldBuilder) MultipleOf ¶ added in v1.0.2
func (fb *FieldBuilder) MultipleOf(of float64) *FieldBuilder
func (*FieldBuilder) Name ¶ added in v1.0.2
func (fb *FieldBuilder) Name(n string) *FieldBuilder
Name sets the machine name.
func (*FieldBuilder) Negative ¶ added in v1.0.2
func (fb *FieldBuilder) Negative() *FieldBuilder
func (*FieldBuilder) New ¶ added in v1.0.2
func (fb *FieldBuilder) New(opts ...Option) (*Schematics, error)
New commits this field and builds a checked *Schematics.
func (*FieldBuilder) NonNegative ¶ added in v1.0.2
func (fb *FieldBuilder) NonNegative() *FieldBuilder
func (*FieldBuilder) NotEmpty ¶ added in v1.0.2
func (fb *FieldBuilder) NotEmpty() *FieldBuilder
func (*FieldBuilder) NotInOptions ¶ added in v1.0.2
func (fb *FieldBuilder) NotInOptions(opts ...string) *FieldBuilder
func (*FieldBuilder) Op ¶ added in v1.0.2
func (fb *FieldBuilder) Op(name string, args Args) *FieldBuilder
Op appends an arbitrary operator by name.
func (*FieldBuilder) Pattern ¶ added in v1.0.2
func (fb *FieldBuilder) Pattern(p string) *FieldBuilder
func (*FieldBuilder) Positive ¶ added in v1.0.2
func (fb *FieldBuilder) Positive() *FieldBuilder
func (*FieldBuilder) Regex ¶ added in v1.0.2
func (fb *FieldBuilder) Regex() *FieldBuilder
Regex marks the target as a regular expression.
func (*FieldBuilder) Replace ¶ added in v1.0.2
func (fb *FieldBuilder) Replace(old, new string) *FieldBuilder
func (*FieldBuilder) Required ¶ added in v1.0.2
func (fb *FieldBuilder) Required() *FieldBuilder
Required marks the field required.
func (*FieldBuilder) Rule ¶ added in v1.0.2
func (fb *FieldBuilder) Rule(name string, args Args) *FieldBuilder
Rule appends an arbitrary validator by name.
func (*FieldBuilder) Schema ¶ added in v1.0.2
func (fb *FieldBuilder) Schema() Schema
Schema commits this field and returns the assembled Schema.
func (*FieldBuilder) Slugify ¶ added in v1.0.2
func (fb *FieldBuilder) Slugify() *FieldBuilder
func (*FieldBuilder) StartsWith ¶ added in v1.0.2
func (fb *FieldBuilder) StartsWith(prefix string) *FieldBuilder
func (*FieldBuilder) Tags ¶ added in v1.0.2
func (fb *FieldBuilder) Tags(tags ...string) *FieldBuilder
Tags sets the field tags.
func (*FieldBuilder) ToString ¶ added in v1.0.2
func (fb *FieldBuilder) ToString() *FieldBuilder
func (*FieldBuilder) Trim ¶ added in v1.0.2
func (fb *FieldBuilder) Trim() *FieldBuilder
func (*FieldBuilder) Truncate ¶ added in v1.0.2
func (fb *FieldBuilder) Truncate(length int, suffix string) *FieldBuilder
func (*FieldBuilder) Type ¶ added in v1.0.2
func (fb *FieldBuilder) Type(t string) *FieldBuilder
Type sets the declared type (enforced when the engine has WithTypeChecks).
func (*FieldBuilder) Unique ¶ added in v1.0.2
func (fb *FieldBuilder) Unique() *FieldBuilder
func (*FieldBuilder) Upper ¶ added in v1.0.2
func (fb *FieldBuilder) Upper() *FieldBuilder
func (*FieldBuilder) When ¶ added in v1.0.2
func (fb *FieldBuilder) When(condition string, args Args) *FieldBuilder
When appends a condition that must hold for the field to run.
func (*FieldBuilder) WhenAbsent ¶ added in v1.0.2
func (fb *FieldBuilder) WhenAbsent(field string) *FieldBuilder
func (*FieldBuilder) WhenEquals ¶ added in v1.0.2
func (fb *FieldBuilder) WhenEquals(field string, value any) *FieldBuilder
func (*FieldBuilder) WhenNot ¶ added in v1.0.2
func (fb *FieldBuilder) WhenNot(condition string, args Args) *FieldBuilder
WhenNot appends a negated condition.
func (*FieldBuilder) WhenPresent ¶ added in v1.0.2
func (fb *FieldBuilder) WhenPresent(field string) *FieldBuilder
type FieldView ¶
type FieldView struct {
Target string
Name string
Type string
Required bool
Provided bool
Tags []string
}
FieldView is a read-only snapshot of the field a rule is running against.
type Metrics ¶ added in v1.0.3
type Metrics struct {
// Validations is called once per completed validation with the operation
// ("validate", "validate_array", "validate_stream", "validate_request"),
// the outcome ("valid" or "invalid"), and how long it took. Increment a
// counter and observe a histogram here.
Validations func(op, outcome string, duration time.Duration)
// RuleFailures is called once per failing rule with the rule name, its
// stable code, and the concrete target that failed. Prefer rule and code as
// label values; see the note on target cardinality above before using
// target as one.
RuleFailures func(rule, code, target string)
// RuleEvaluations is called once per rule evaluated, passing or failing.
// Leave it nil unless you want a denominator for a failure rate — it fires
// far more often than RuleFailures. Field-level checks (required,
// dependsOn) count as evaluations, so the two callbacks are counting the
// same population and their ratio is meaningful.
RuleEvaluations func(rule, target string)
}
Metrics adapts the observer hook to a metrics backend. Set only the callbacks you want; a nil callback is skipped. Every callback may be invoked concurrently, which Prometheus collectors already tolerate.
Example ¶
ExampleMetrics wires validation into a metrics backend. The callbacks below print instead of incrementing, but the shape is exactly what a Prometheus wiring looks like:
m := schematics.Metrics{
Validations: func(op, outcome string, d time.Duration) {
validationsTotal.WithLabelValues(op, outcome).Inc()
validationSeconds.WithLabelValues(op).Observe(d.Seconds())
},
RuleFailures: func(rule, code, _ string) {
ruleFailuresTotal.WithLabelValues(rule, code).Inc()
},
}
s := schematics.New(schematics.WithObserver(m.Observer()))
Note that the target is deliberately dropped from the label set: a wildcard target expands to one concrete key per array element, which would create a time series per index.
package main
import (
"fmt"
"sort"
"strings"
"time"
schematics "github.com/ashbeelghouri/json-schematics-v2"
)
const orderSchema = `{"fields":[
{"target":"customer.email","required":true,"validate":[{"rule":"email"}]},
{"target":"quantity","validate":[{"rule":"min","args":{"min":1}}]}
]}`
func main() {
var lines []string
m := schematics.Metrics{
Validations: func(op, outcome string, d time.Duration) {
lines = append(lines, fmt.Sprintf("validations_total{op=%q,outcome=%q} +1", op, outcome))
},
RuleFailures: func(rule, code, _ string) {
lines = append(lines, fmt.Sprintf("rule_failures_total{rule=%q,code=%q} +1", rule, code))
},
}
s := schematics.New(schematics.WithObserver(m.Observer()))
if err := s.LoadBytes([]byte(orderSchema)); err != nil {
panic(err)
}
_ = s.Validate(map[string]any{"quantity": 5}) // missing email
_ = s.Validate(map[string]any{ // valid
"customer": map[string]any{"email": "ada@example.com"},
"quantity": 5,
})
sort.Strings(lines)
fmt.Println(strings.Join(lines, "\n"))
}
Output: rule_failures_total{rule="required",code="field.required"} +1 validations_total{op="validate",outcome="invalid"} +1 validations_total{op="validate",outcome="valid"} +1
type Observer ¶ added in v1.0.3
type Observer interface {
Observe(Event)
}
Observer receives events from the validation engine. Observe is called synchronously on the validating goroutine, so an implementation that does anything slow should hand off to a buffered channel rather than block the caller. It may be called concurrently when a shared Schematics validates on several goroutines, so it must be safe for concurrent use.
A panic in Observe is not swallowed: it surfaces to whoever called Validate, so a bug in an observer fails loudly rather than silently corrupting the numbers it reports. On the streaming path, where rows are validated on goroutines this package owns, the panic is captured and re-raised on the caller's goroutine wrapped in a *StreamPanic — see that type for why the wrapper exists.
type ObserverFunc ¶ added in v1.0.3
type ObserverFunc func(Event)
ObserverFunc adapts a plain function to the Observer interface.
func (ObserverFunc) Observe ¶ added in v1.0.3
func (f ObserverFunc) Observe(e Event)
Observe implements Observer.
type Operation ¶ added in v1.0.3
type Operation string
Operation names the entry point that produced an event.
const ( // OpValidate is a Validate/ValidateCtx/ValidateBytes call on an object. OpValidate Operation = "validate" // OpValidateArray is a Validate call on an array of objects. OpValidateArray Operation = "validate_array" // OpValidateStream is one row of a ValidateStream call. OpValidateStream Operation = "validate_stream" // OpValidateRequest is an API.ValidateRequest call. OpValidateRequest Operation = "validate_request" )
type Operator ¶
Operator transforms a value and returns the replacement. Returning an error aborts the whole Operate call.
type OperatorRef ¶
OperatorRef references a registered operator and its arguments.
type Option ¶
type Option func(*Schematics)
Option configures a Schematics at construction time.
func WithArrayIDKey ¶
WithArrayIDKey sets the flattened key whose value identifies each row when validating an array of objects.
func WithCatalog ¶ added in v1.0.3
WithCatalog attaches an external message catalog (see LoadCatalogFile / LoadCatalogTOMLFile) that ValidationError.Message consults when a failing rule has no inline "message"/"messages" of its own for the requested locale. An explicit per-rule schema message always overrides the catalog.
func WithCollectAll ¶ added in v1.0.2
func WithCollectAll() Option
WithCollectAll makes Validate report every failing rule on each field rather than stopping at the first failure. Useful for form validation where the caller wants to show all problems at once.
func WithLocale ¶
WithLocale sets the default locale for error messages.
func WithLogger ¶
WithLogger attaches a slog.Logger. By default logs are discarded.
func WithMaxBodyBytes ¶ added in v1.0.2
WithMaxBodyBytes caps how many bytes the API layer reads from a request body, guarding against unbounded-read denial of service. A value <= 0 means unlimited. It has no effect on the plain Validate path.
func WithObserver ¶ added in v1.0.3
WithObserver attaches an observer that receives an event per validation and per rule evaluated. Passing nil leaves observation off, which is the default and costs nothing: with no observer the engine never reads the clock and never builds an event.
s := schematics.New(schematics.WithObserver(
schematics.ObserverFunc(func(e schematics.Event) {
if e.Kind == schematics.EventRule && e.Failed {
log.Printf("%s rejected %s", e.Rule, e.Target)
}
})))
Example ¶
ExampleWithObserver watches validation as it happens. The observer is called once per rule and once per completed validation; with no observer attached none of this work happens at all.
package main
import (
"fmt"
schematics "github.com/ashbeelghouri/json-schematics-v2"
)
const orderSchema = `{"fields":[
{"target":"customer.email","required":true,"validate":[{"rule":"email"}]},
{"target":"quantity","validate":[{"rule":"min","args":{"min":1}}]}
]}`
func main() {
s := schematics.New(schematics.WithObserver(
schematics.ObserverFunc(func(e schematics.Event) {
switch e.Kind {
case schematics.EventRule:
if e.Failed {
fmt.Printf("rule %s rejected %s (%s)\n", e.Rule, e.Target, e.Code)
}
case schematics.EventValidation:
fmt.Printf("%s finished: %d row(s), %d error(s)\n", e.Op, e.Rows, e.Errors)
}
})))
if err := s.LoadBytes([]byte(orderSchema)); err != nil {
panic(err)
}
_ = s.Validate(map[string]any{"customer": map[string]any{"email": "nope"}, "quantity": 3})
}
Output: rule email rejected customer.email (rule.email) validate finished: 1 row(s), 1 error(s)
func WithSeparator ¶
WithSeparator sets the key separator used when flattening documents.
func WithTracer ¶ added in v1.0.3
WithTracer attaches a tracer. Validation then runs inside a span named after the operation ("schematics.validate", "schematics.validate_array", ...) with attributes for the fields in the schema, rows validated, and failures found. Passing nil leaves tracing off, which is the default and costs nothing: with no tracer no span is started and no context is derived.
s := schematics.New(schematics.WithTracer(myOTelAdapter{tracer}))
Example ¶
ExampleWithTracer traces a validation as a span inside whatever trace the caller is already running. Without a tracer no span is started.
package main
import (
"context"
"fmt"
schematics "github.com/ashbeelghouri/json-schematics-v2"
)
const orderSchema = `{"fields":[
{"target":"customer.email","required":true,"validate":[{"rule":"email"}]},
{"target":"quantity","validate":[{"rule":"min","args":{"min":1}}]}
]}`
// printTracer is the shape an OpenTelemetry adapter takes. A real one wraps
// trace.Tracer:
//
// type otelTracer struct{ t trace.Tracer }
//
// func (o otelTracer) Start(ctx context.Context, name string) (context.Context, schematics.Span) {
// ctx, span := o.t.Start(ctx, name)
// return ctx, otelSpan{span}
// }
//
// type otelSpan struct{ s trace.Span }
//
// func (o otelSpan) End() { o.s.End() }
// func (o otelSpan) SetAttributes(attrs ...schematics.Attribute) {
// kvs := make([]attribute.KeyValue, 0, len(attrs))
// for _, a := range attrs {
// switch v := a.Value.(type) {
// case string:
// kvs = append(kvs, attribute.String(a.Key, v))
// case int:
// kvs = append(kvs, attribute.Int(a.Key, v))
// case bool:
// kvs = append(kvs, attribute.Bool(a.Key, v))
// }
// }
// o.s.SetAttributes(kvs...)
// }
type printTracer struct{}
func (printTracer) Start(ctx context.Context, name string) (context.Context, schematics.Span) {
fmt.Println("start", name)
return ctx, printSpan{}
}
type printSpan struct{}
func (printSpan) SetAttributes(attrs ...schematics.Attribute) {
for _, a := range attrs {
if a.Key == "schematics.errors" {
fmt.Println(" errors =", a.Value)
}
}
}
func (printSpan) End() { fmt.Println("end") }
func main() {
s := schematics.New(schematics.WithTracer(printTracer{}))
if err := s.LoadBytes([]byte(orderSchema)); err != nil {
panic(err)
}
_ = s.ValidateCtx(context.Background(), map[string]any{"quantity": 1})
}
Output: start schematics.validate errors = 1 end
func WithTypeChecks ¶ added in v1.0.2
func WithTypeChecks() Option
WithTypeChecks makes a field's "type" enforce a matching built-in check (string, number, integer, boolean, array, date, object) before its own validators run. Without this option, "type" is treated as documentation only, preserving the default behavior.
type Problem ¶ added in v1.0.3
type Problem struct {
// Type is a URI identifying the problem kind. Defaults to "about:blank",
// which the RFC defines as "no more specific type than the status code".
Type string `json:"type"`
// Title is a short, human-readable summary of the problem kind. It should
// not change from occurrence to occurrence.
Title string `json:"title"`
// Status is the HTTP status code, repeated here per the RFC so the document
// is self-contained when it is logged away from its response.
Status int `json:"status"`
// Detail is a human-readable explanation specific to this occurrence.
Detail string `json:"detail,omitempty"`
// Instance is a URI identifying this specific occurrence, typically the
// request path.
Instance string `json:"instance,omitempty"`
// Errors is the per-failure breakdown (an RFC 7807 extension member).
Errors []ProblemError `json:"errors,omitempty"`
}
Problem is an RFC 7807 problem details document. The first five fields are the members the RFC defines; Errors is an extension member carrying the per-failure breakdown.
func NewProblem ¶ added in v1.0.3
func NewProblem(err error, opts ...ProblemOption) *Problem
NewProblem builds an RFC 7807 document from an error returned by Validate, ValidateRequest, or ValidateStream. It understands *ValidationErrors and *SchemaError; any other non-nil error becomes a 500 with its message as the detail. It returns nil when err is nil, so a handler can write a problem response only when there is one.
type ProblemError ¶ added in v1.0.3
type ProblemError struct {
// Pointer is an RFC 6901 JSON Pointer to the offending value, relative to
// the request body. Empty for failures that are not about a location in the
// document, such as an unmatched route.
Pointer string `json:"pointer"`
// Target is the flattened key that failed, kept alongside the pointer
// because it is what the schema itself is written in terms of.
Target string `json:"target,omitempty"`
// Code is the stable machine-readable failure code, e.g. "rule.email".
Code string `json:"code"`
// Rule is the validator that rejected the value.
Rule string `json:"rule,omitempty"`
// Detail is the localized human-readable message.
Detail string `json:"detail"`
// RowID identifies the array row, for array payloads.
RowID string `json:"id,omitempty"`
}
ProblemError is one failure inside a Problem's errors array.
type ProblemOption ¶ added in v1.0.3
type ProblemOption func(*problemConfig)
ProblemOption customizes a generated Problem.
func WithProblemDetail ¶ added in v1.0.3
func WithProblemDetail(detail string) ProblemOption
WithProblemDetail overrides the derived detail line.
func WithProblemInstance ¶ added in v1.0.3
func WithProblemInstance(uri string) ProblemOption
WithProblemInstance sets the "instance" URI, usually the request path.
func WithProblemLocale ¶ added in v1.0.3
func WithProblemLocale(locale string) ProblemOption
WithProblemLocale selects the locale used to render each failure's message. Defaults to the engine's locale when called through API.WriteProblem, and to the library default otherwise.
func WithProblemStatus ¶ added in v1.0.3
func WithProblemStatus(status int) ProblemOption
WithProblemStatus overrides the derived HTTP status code.
func WithProblemTitle ¶ added in v1.0.3
func WithProblemTitle(title string) ProblemOption
WithProblemTitle overrides the derived title.
func WithProblemType ¶ added in v1.0.3
func WithProblemType(uri string) ProblemOption
WithProblemType sets the "type" URI identifying the problem kind.
type RowResult ¶ added in v1.0.3
type RowResult struct {
// Index is the row's zero-based position in the array.
Index int
// RowID identifies the row in error messages. It is the value of the
// schema's arrayIdKey when the row carries one, and the row's index
// otherwise — the same rule the non-streaming array path uses.
RowID string
// Errors holds every validation failure for this row, or nil when the row
// is valid.
Errors *ValidationErrors
// contains filtered or unexported fields
}
RowResult is the outcome of validating a single row of a streamed array.
type Rule ¶
Rule validates a value. It returns nil when the value is acceptable, or an error describing why it is not. Rules must never panic: use the typed Args accessors and the value-inspection helpers, both of which report bad input as errors.
type RuleRef ¶
type RuleRef struct {
Rule string `json:"rule"`
Args Args `json:"args,omitempty"`
Message string `json:"message,omitempty"`
Messages map[string]string `json:"messages,omitempty"`
}
RuleRef references a registered validator and its per-use configuration.
type Schema ¶
type Schema struct {
Version string `json:"version,omitempty"`
Separator string `json:"separator,omitempty"`
ArrayIDKey string `json:"arrayIdKey,omitempty"`
Locale string `json:"locale,omitempty"`
DB map[string]any `json:"db,omitempty"`
Fields []Field `json:"fields"`
}
Schema is the declarative document that drives validation and operation.
Example (JSON):
{
"version": "2.0",
"separator": ".",
"arrayIdKey": "id",
"locale": "en",
"db": { "minAge": 18 },
"fields": [
{
"target": "user.profile.name",
"type": "string",
"required": true,
"validate": [ { "rule": "minLength", "args": { "min": 2 } } ],
"operate": [ { "op": "trim" }, { "op": "capitalize" } ]
}
]
}
type SchemaBuilder ¶ added in v1.0.2
type SchemaBuilder struct {
// contains filtered or unexported fields
}
SchemaBuilder accumulates schema-level settings and fields.
func NewSchema ¶ added in v1.0.2
func NewSchema() *SchemaBuilder
NewSchema starts a fluent schema definition.
func (*SchemaBuilder) ArrayIDKey ¶ added in v1.0.2
func (b *SchemaBuilder) ArrayIDKey(k string) *SchemaBuilder
ArrayIDKey sets the per-row identifier key for array validation.
func (*SchemaBuilder) DB ¶ added in v1.0.2
func (b *SchemaBuilder) DB(m map[string]any) *SchemaBuilder
DB seeds the shared DB baked into the schema.
func (*SchemaBuilder) Field ¶ added in v1.0.2
func (b *SchemaBuilder) Field(target string) *FieldBuilder
Field begins a new field targeting target.
func (*SchemaBuilder) Locale ¶ added in v1.0.2
func (b *SchemaBuilder) Locale(l string) *SchemaBuilder
Locale sets the default error locale.
func (*SchemaBuilder) New ¶ added in v1.0.2
func (b *SchemaBuilder) New(opts ...Option) (*Schematics, error)
New builds a *Schematics from the schema, applies opts, and runs Check so a malformed schema fails fast.
func (*SchemaBuilder) Schema ¶ added in v1.0.2
func (b *SchemaBuilder) Schema() Schema
Schema returns the assembled Schema value.
func (*SchemaBuilder) Separator ¶ added in v1.0.2
func (b *SchemaBuilder) Separator(sep string) *SchemaBuilder
Separator sets the flattening separator.
type SchemaError ¶
type SchemaError struct {
// Problems holds one human-readable description per problem found.
Problems []string
// Details holds the same problems in machine-readable form, in the same
// order as Problems. Errors raised before this field existed may leave it
// nil, so treat an empty Details with a non-empty Problems as "uncoded"
// rather than as "no problems".
Details []SchemaProblem
}
SchemaError is returned when a schema references a rule, operator, or condition that is not registered, or is otherwise malformed. It is distinct from ValidationErrors, which reports problems with the data.
func (*SchemaError) Codes ¶ added in v1.0.3
func (e *SchemaError) Codes() []Code
Codes returns the code of every problem, in order. Useful for asserting in a test that a schema failed for the reason you expected rather than by matching on message text.
func (*SchemaError) Error ¶
func (e *SchemaError) Error() string
Error implements the error interface. It tolerates a nil receiver, because a typed-nil *SchemaError stored in an error interface is non-nil to the caller and would otherwise panic the first time anything printed it.
type SchemaProblem ¶ added in v1.0.3
type SchemaProblem struct {
Code Code `json:"code"`
Target string `json:"target,omitempty"`
Message string `json:"message"`
}
SchemaProblem is one machine-readable entry in a SchemaError: what kind of problem it is, which target it was found on (empty when the problem is not tied to a single field), and the human-readable description.
type Schematics ¶
type Schematics struct {
// contains filtered or unexported fields
}
Schematics is the entry point: it holds a schema, the registries of validators/operators/conditions (pre-loaded with the built-ins), and configuration. Create one with New, load a schema, then call Validate or Operate.
A Schematics is safe for concurrent use by multiple goroutines once its schema is loaded and any custom rules are registered: Validate, ValidateCtx, Operate, ValidateBytes and the API layer may all be called in parallel on a shared instance. Registering rules or loading a new schema is not safe to do concurrently with validation — do that during setup, before sharing.
func ImportSchema ¶ added in v1.0.2
func ImportSchema(b []byte, opts ...Option) (*Schematics, error)
ImportSchema is a convenience constructor for callers who start from raw schema bytes (a config file already read into memory, a network response, an embedded fixture): it combines New and LoadBytes into one call.
s, err := schematics.ImportSchema(schemaBytes)
if err != nil {
log.Fatal(err)
}
Any options are applied the same way they are for New, before the schema is loaded.
func New ¶
func New(opts ...Option) *Schematics
New creates a Schematics with every built-in validator, operator, and condition registered, then applies the given options.
func (*Schematics) Check ¶
func (s *Schematics) Check() error
Check verifies that every rule, operator, and condition referenced by the schema is registered and that fields are well formed. It returns a *SchemaError describing all problems, or nil.
func (*Schematics) ConditionNames ¶
func (s *Schematics) ConditionNames() []string
ConditionNames returns the names of every registered condition.
func (*Schematics) LoadBytes ¶
func (s *Schematics) LoadBytes(b []byte) error
LoadBytes parses a JSON schema from b.
func (*Schematics) LoadFile ¶
func (s *Schematics) LoadFile(path string) error
LoadFile reads and parses a JSON schema file.
func (*Schematics) LoadMap ¶
func (s *Schematics) LoadMap(m any) error
LoadMap parses a schema from an in-memory value (a map or struct) by round-tripping it through JSON.
func (*Schematics) Operate ¶
func (s *Schematics) Operate(data any) (any, error)
Operate applies each field's operator chain to the matching values in data and returns the transformed document. data may be a single object or an array of objects. Validators are not run.
func (*Schematics) OperateCtx ¶
OperateCtx is Operate with a caller-supplied context.Context.
func (*Schematics) OperatorNames ¶
func (s *Schematics) OperatorNames() []string
OperatorNames returns the names of every registered operator.
func (*Schematics) RegisterCondition ¶
func (s *Schematics) RegisterCondition(name string, fn Condition) *Schematics
RegisterCondition adds or replaces a named condition.
func (*Schematics) RegisterOperator ¶
func (s *Schematics) RegisterOperator(name string, fn Operator) *Schematics
RegisterOperator adds or replaces a named operator.
func (*Schematics) RegisterRule ¶
func (s *Schematics) RegisterRule(name string, fn Rule) *Schematics
RegisterRule adds or replaces a named validator. It is safe to call after loading a schema and before Validate.
The name is a public identifier, not just a lookup key: a failure from this rule reports the stable code "rule."+name (see CODES.md), which is what dashboards group by and clients switch on. Renaming a registered rule therefore renames its error code and silently breaks anything downstream that was matching on it. Pick the name as deliberately as you would an exported symbol.
func (*Schematics) RuleNames ¶
func (s *Schematics) RuleNames() []string
RuleNames returns the names of every registered validator.
func (*Schematics) Schema ¶
func (s *Schematics) Schema() Schema
Schema returns the currently loaded schema.
func (*Schematics) SetSchema ¶
func (s *Schematics) SetSchema(schema Schema) *Schematics
SetSchema installs a schema value directly.
func (*Schematics) Validate ¶
func (s *Schematics) Validate(data any) error
Validate checks data against the loaded schema. data may be a single object (map or struct) or an array of objects. It returns nil when the data is valid, a *ValidationErrors when it is not, or a *SchemaError when the schema itself references something unregistered.
Validate never modifies data. It may, however, read it without copying: when data is already made of JSON-native values (as it is when it came from json.Unmarshal), validation walks the caller's own maps and slices instead of round-tripping them through JSON first. Do not mutate data concurrently with a Validate call on it.
func (*Schematics) ValidateBytes ¶ added in v1.0.2
func (s *Schematics) ValidateBytes(b []byte, isArray bool) error
ValidateBytes parses raw JSON data bytes and validates them against the loaded schema, saving the caller a manual json.Unmarshal before calling Validate. isArray tells it whether to parse b as a single JSON object (false) or as an array of objects (true) — pass it explicitly rather than relying on shape-sniffing, so a payload that doesn't match the expected shape fails with a clear parse error instead of silently going through Validate's try-object-then-array fallback.
func (*Schematics) ValidateBytesCtx ¶ added in v1.0.2
ValidateBytesCtx is ValidateBytes with a caller-supplied context.Context, made available to every rule via Context.Ctx.
func (*Schematics) ValidateCtx ¶
func (s *Schematics) ValidateCtx(ctx context.Context, data any) error
ValidateCtx is Validate with a caller-supplied context.Context, made available to every rule via Context.Ctx.
func (*Schematics) ValidateSchema ¶ added in v1.0.1
func (s *Schematics) ValidateSchema(sample any, ignoreTargets ...string) error
ValidateSchema runs every check that Check does — unknown rule/operator/ condition names, empty targets, duplicate targets — and additionally verifies that every field's target actually resolves against sample once flattened. sample should be a representative example of the data you intend to validate: a test fixture, a golden payload, or real (secret- stripped) production data.
Check alone cannot catch a typo inside a target's value. Given `"target": "mane"` instead of `"name"`, "mane" is a perfectly valid string, so Check has nothing to object to — the field just silently never matches anything at runtime. ValidateSchema closes that gap by matching every target against real data and flagging the ones that match nothing.
A typo in the JSON *key* itself — `"tagret"` instead of `"target"` — never needs sample data to catch: encoding/json silently drops an unrecognized key, so Target is left as "" and Check already reports "field #N has an empty target". ValidateSchema surfaces that too, since it runs Check first.
Pass ignoreTargets for fields that are legitimately allowed to be missing from every sample you have — an optional field with no example value in your fixtures, say — so they are not flagged as suspected typos.
Wildcard (target containing "*") and targetRegex fields are matched the same way they are at validation time: if nothing in sample matches the pattern, the field is flagged. If that's simply because your sample doesn't happen to contain that shape, add the target to ignoreTargets rather than treating the result as a bug in the schema.
ValidateSchema does not replace tests. It is meant to run in CI or a unit test alongside a fixture, so drift between your schema and your real data shape fails the build instead of failing silently in production.
func (*Schematics) ValidateStream ¶ added in v1.0.3
func (s *Schematics) ValidateStream(r io.Reader, opts ...StreamOption) error
ValidateStream reads a JSON array of objects from r and validates every row against the loaded schema, decoding one row at a time rather than holding the whole array in memory.
It returns nil when every row is valid, a *ValidationErrors aggregating every failure in row order when some row is not, or a *SchemaError when the schema itself is unsound. A malformed stream returns a plain error describing where decoding failed.
Errors are accumulated in memory, so a stream in which nearly every row fails will still grow with the size of the input. Use ValidateStreamEach when you need to handle failures as they arrive rather than collecting them.
A panic in a custom rule, condition, or observer is re-raised on the calling goroutine as a *StreamPanic, so a caller's recover sees it exactly as it would on the non-streaming path.
err := s.ValidateStream(resp.Body, schematics.WithConcurrency(8))
func (*Schematics) ValidateStreamCtx ¶ added in v1.0.3
func (s *Schematics) ValidateStreamCtx(ctx context.Context, r io.Reader, opts ...StreamOption) error
ValidateStreamCtx is ValidateStream with a caller-supplied context.Context. Cancelling the context stops decoding and validation and returns the context's error.
Cancellation takes effect between reads of r, not during one. encoding/json offers no way to interrupt a decoder mid-read, so if r is a network body that stalls, this call stays blocked in that Read until it returns or the connection times out. Give the reader its own deadline when that matters — http.Client.Timeout, or a net.Conn SetReadDeadline — rather than relying on the context alone.
func (*Schematics) ValidateStreamEach ¶ added in v1.0.3
func (s *Schematics) ValidateStreamEach(ctx context.Context, r io.Reader, fn func(RowResult) error, opts ...StreamOption) error
ValidateStreamEach reads a JSON array of objects from r and calls fn once per row, in row order, as each row's result becomes available. It is the streaming API in its general form: ValidateStream is this function with an fn that collects everything.
Because fn sees each result as it arrives and nothing is retained afterwards, this is the form to use when the array is too large for its *errors* to fit in memory, or when failures should be acted on immediately — written to a log, streamed to a client, counted.
Returning a non-nil error from fn stops the walk early and that error is returned to the caller. Rows already in flight are abandoned; fn is not called again. Use it to implement a failure budget:
var bad int
err := s.ValidateStreamEach(ctx, r, func(res schematics.RowResult) error {
if res.Valid() {
return nil
}
if bad++; bad > 100 {
return fmt.Errorf("too many invalid rows, stopping at %d", res.Index)
}
log.Printf("row %s: %v", res.RowID, res.Errors)
return nil
}, schematics.WithConcurrency(4))
fn is always called from a single goroutine, so it does not need to be safe for concurrent use even when rows are validated in parallel.
type SlogObserver ¶ added in v1.0.3
type SlogObserver struct {
// contains filtered or unexported fields
}
SlogObserver logs events to a *slog.Logger: validation events at level, and failing rules at level too, with the target, rule and code as attributes. Passing rules are not logged — a log line per passing rule is noise at any realistic volume, and the count belongs in a metric rather than a log.
func NewSlogObserver ¶ added in v1.0.3
func NewSlogObserver(logger *slog.Logger, level slog.Level) *SlogObserver
NewSlogObserver builds an Observer that writes to logger at level. A nil logger falls back to slog.Default().
s := schematics.New(schematics.WithObserver(
schematics.NewSlogObserver(slog.Default(), slog.LevelDebug)))
func (*SlogObserver) Observe ¶ added in v1.0.3
func (o *SlogObserver) Observe(e Event)
Observe implements Observer.
type Span ¶ added in v1.0.3
type Span interface {
// SetAttributes records attributes on the span.
SetAttributes(attrs ...Attribute)
// End marks the span complete. It is called exactly once per span.
End()
}
Span is one unit of work in a trace. It mirrors the subset of the OpenTelemetry span API this library needs; an adapter satisfying it is a handful of forwarding methods. Its methods may be called concurrently; a panic in one surfaces to the caller of Validate, the same way a panic in Observer.Observe does.
type StreamOption ¶ added in v1.0.3
type StreamOption func(*streamConfig)
StreamOption configures a single streaming validation call.
func WithConcurrency ¶ added in v1.0.3
func WithConcurrency(n int) StreamOption
WithConcurrency sets how many rows may be validated in parallel, which is also the bound on how many rows are held in memory at once. The default is 1: rows are validated one at a time, in order, and memory stays flat regardless of the size of the input.
Raising it trades memory for throughput and is worth doing when rules are expensive — a rule that calls out to a database, say. It does not change results: errors are always reported in row order, and for any given input the output is identical at every concurrency level. Values below 1 are treated as 1.
type StreamPanic ¶ added in v1.0.3
type StreamPanic struct {
// Value is the value the original panic was raised with.
Value any
// Stack is the stack trace captured at the point of recovery, on the
// goroutine where the panic actually happened. It is deliberately not part
// of Error(): anything that logs an error string would then dump a full
// stack inline, and a caller who wants it can read this field.
Stack []byte
// Suppressed is the error the call would have returned had it not panicked
// — a decode failure, a context cancellation, or an error the caller's own
// ValidateStreamEach callback returned before the panic surfaced.
//
// A panic outranks an error because they are different kinds of news: an
// error says the data was bad, a panic says the code is broken, and only
// one of those can be handled by retrying with better input. But the error
// is real and discarding it would hide, say, a truncated stream behind a
// rule's nil dereference — so it travels here rather than being dropped.
Suppressed error
}
StreamPanic carries a panic that happened on a goroutine this package owns, so it can be re-raised on the caller's goroutine where their recover can actually see it.
Streaming validates rows on worker goroutines. A panic there — from a custom rule, a condition, or an Observer — cannot be recovered by the caller, and an unrecovered panic in any goroutine terminates the process. That would make switching from Validate to ValidateStream turn a recoverable per-request failure into a guaranteed crash, which is not a trade a library gets to make for its callers. So the worker recovers, and the consumer re-panics.
The re-panicked value is this wrapper rather than the original, because the original panic's stack is the useful part and there is nowhere else to put it: by the time the value reaches the caller's goroutine the stack it came from is gone. Value is the original panic value and Unwrap returns it when it is an error, so errors.As and errors.Is still work through it.
func (*StreamPanic) Error ¶ added in v1.0.3
func (p *StreamPanic) Error() string
Error implements the error interface. It is one line on purpose; read Stack for the trace and Suppressed for anything that was outranked.
func (*StreamPanic) Unwrap ¶ added in v1.0.3
func (p *StreamPanic) Unwrap() error
Unwrap exposes the original panic value when it was an error, so a caller that recovers can errors.As it as whatever it originally was.
type Tracer ¶ added in v1.0.3
Tracer starts spans. Start returns a context carrying the new span, which the engine threads into every rule via Context.Ctx, so a rule that calls out to a database can start its own child span and have it nest correctly.
An implementation may decline to start a span — a sampler dropping it, say — by returning a nil Span; the engine treats that as tracing being off for that call. Returning a nil context is treated as "no derived context" and the original is kept, rather than handing nil to every rule.
type ValidationError ¶
type ValidationError struct {
// Target is the concrete flattened key that failed, e.g. "user.profile.name".
Target string
// Rule is the name of the validator (or "required"/"dependsOn") that failed.
Rule string
// Code is the stable machine-readable identifier for this failure, e.g.
// "rule.minLength" or "field.required". Unlike the message, it is safe to
// switch on and to use as a metric label; see codes.go and CODES.md.
Code Code
// Value is the offending value.
Value any
// RowID identifies the array row for array inputs; empty for plain objects.
RowID string
// contains filtered or unexported fields
}
ValidationError describes a single rule that failed on a single target value.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error implements the error interface using the default locale.
func (*ValidationError) Format ¶
func (e *ValidationError) Format(locale, format string) string
Format renders the error using a template. Recognized tokens are %message, %target, %pointer, %rule (alias %validator), %code, %value and %id.
func (*ValidationError) MarshalJSON ¶
func (e *ValidationError) MarshalJSON() ([]byte, error)
MarshalJSON renders the error as a stable JSON object.
func (*ValidationError) Message ¶
func (e *ValidationError) Message(locale string) string
Message returns the message for locale. Resolution order: an explicit per-error message for locale (set via the schema's RuleRef.Message / Messages) always wins; then, if the engine has a Catalog attached (see WithCatalog), a rendered catalog template for this rule; then the default-locale/fallback message; then a generated description. A schema message always overrides the catalog — the catalog fills gaps, it doesn't replace explicit per-rule wording.
func (*ValidationError) Pointer ¶ added in v1.0.3
func (e *ValidationError) Pointer() string
Pointer returns an RFC 6901 JSON Pointer to the value that failed, relative to the document passed to Validate.
target "user.email", plain object -> "/user/email" target "user.email", array row 2 -> "/2/user/email" target "tags.0", plain object -> "/tags/0"
It returns "" — the pointer to the whole document — for failures that are not about a location in the data, such as an unmatched route or an oversized request body. A wildcard or regex target that never matched anything has no concrete location either; its pattern is returned as written, escaped, since that is the most specific thing that can honestly be said about where the value should have been.
Example ¶
ExampleValidationError_Pointer shows the JSON Pointer each failure carries. It addresses the offending value in the document the caller submitted, so a browser or API client can highlight the right field without knowing anything about how the schema flattens keys.
package main
import (
"errors"
"fmt"
schematics "github.com/ashbeelghouri/json-schematics-v2"
)
const orderSchema = `{"fields":[
{"target":"customer.email","required":true,"validate":[{"rule":"email"}]},
{"target":"quantity","validate":[{"rule":"min","args":{"min":1}}]}
]}`
func main() {
s, err := schematics.ImportSchema([]byte(orderSchema))
if err != nil {
panic(err)
}
verr := s.Validate(map[string]any{
"customer": map[string]any{"email": "not-an-email"},
"quantity": 0,
})
var ve *schematics.ValidationErrors
if errors.As(verr, &ve) {
for _, e := range ve.Errors {
fmt.Printf("%s\t%s\n", e.Pointer(), e.Code)
}
}
}
Output: /customer/email rule.email /quantity rule.min
type ValidationErrors ¶
type ValidationErrors struct {
Errors []*ValidationError
}
ValidationErrors is the aggregate error returned by Validate. It implements the error interface, so callers can use errors.As to recover it.
func (*ValidationErrors) Add ¶
func (es *ValidationErrors) Add(e *ValidationError)
Add appends a non-nil error.
func (*ValidationErrors) Error ¶
func (es *ValidationErrors) Error() string
Error implements the error interface.
func (*ValidationErrors) ForTarget ¶
func (es *ValidationErrors) ForTarget(target string) []*ValidationError
ForTarget returns the subset of errors whose Target equals target.
func (*ValidationErrors) HasErrors ¶
func (es *ValidationErrors) HasErrors() bool
HasErrors reports whether any errors were collected.
func (*ValidationErrors) Len ¶
func (es *ValidationErrors) Len() int
Len returns the number of collected errors.
func (*ValidationErrors) Messages ¶
func (es *ValidationErrors) Messages(locale string) []string
Messages returns just the localized messages.
func (*ValidationErrors) Pointers ¶ added in v1.0.3
func (es *ValidationErrors) Pointers() []string
Pointers returns the JSON Pointer of every collected error, in order.
func (*ValidationErrors) Strings ¶
func (es *ValidationErrors) Strings(locale, format string) []string
Strings renders every error with the given locale and format template.
type WhenNode ¶ added in v1.0.3
type WhenNode struct {
// Leaf fields — set Condition (and optionally Args/Negate) to reference a
// registered condition. Leave Any/All/Not nil.
Condition string `json:"condition,omitempty"`
Args Args `json:"args,omitempty"`
Negate bool `json:"negate,omitempty"`
// Group fields — set exactly one to compose child nodes instead of
// referencing a condition directly. Leave Condition empty.
Any []WhenNode `json:"any,omitempty"`
All []WhenNode `json:"all,omitempty"`
Not *WhenNode `json:"not,omitempty"`
}
WhenNode is one entry in a Field's "when" list: either a leaf reference to a registered condition, or a boolean-composition group (any/all/not) of other WhenNodes. Go's encoding/json handles the recursive Any/All/Not fields natively, so a nested group is just more JSON, not a special case.
Leaf example:
{ "condition": "fieldPresent", "args": { "field": "user.email" } }
Group examples:
{ "any": [ {"condition":"fieldEquals","args":{"field":"plan","value":"pro"}},
{"condition":"fieldEquals","args":{"field":"plan","value":"team"}} ] }
{ "all": [ {"condition":"fieldPresent","args":{"field":"a"}},
{"condition":"fieldPresent","args":{"field":"b"}} ] }
{ "not": { "condition": "fieldPresent", "args": { "field": "legacyMode" } } }
A leaf and a group are mutually exclusive on one node; Check reports it as a malformed schema if both (or none) are set. Negate applies uniformly to either kind, inverting whatever the node otherwise evaluates to.
Source Files
¶
- api.go
- args.go
- builder.go
- builtins.go
- builtins_array.go
- builtins_conditions.go
- builtins_date.go
- builtins_extra.go
- builtins_number.go
- builtins_operators.go
- builtins_string.go
- catalog.go
- codes.go
- context.go
- conv.go
- doc.go
- errors.go
- flatten.go
- io.go
- metrics.go
- msgfmt.go
- normalize.go
- observer.go
- operate.go
- pointer.go
- problem.go
- registry.go
- schema.go
- schematics.go
- stream.go
- trace.go
- validate.go
- validate_schema.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
schematics
command
Command schematics is a CLI wrapper around the json-schematics-v2 library so schemas and data can be checked from CI pipelines, Makefiles, and GitHub Actions without writing any Go code.
|
Command schematics is a CLI wrapper around the json-schematics-v2 library so schemas and data can be checked from CI pipelines, Makefiles, and GitHub Actions without writing any Go code. |