Documentation
¶
Overview ¶
Package jsonschema validates JSON documents against families of JSON Schema documents (the Open Charge Alliance's OCPP and OSCP schemas) with github.com/santhosh-tekuri/jsonschema/v6, and reports each failure as a JSON pointer, the schema keyword and a short message of our own.
A Set holds a family of schema documents that refer to each other by relative file name ("phase-indicator.json") or JSON pointer ("#/definitions/X", "other.json#/definitions/Y"). References are resolved against the referring document's $id; when no document carries the resolved $id, the document added under that whole URL is used, then the one added under the same file name (some published sets name a file differently from its $id). Each document is validated by the draft its $schema names (draft-04 to 2020-12; draft-07 when it names none).
What this package adds to the library: the documents are kept in memory as added (corrections can be applied to a decoded copy before Add), every format of JSON Schema 2020-12 is checked by our own checkers with our messages (formats.go, uri.go, idna.go), patterns are ECMA-262 regular expressions (ecma.go), failures are listed in a stable order (see Translate), and CheckSchemas walks the schemas before use to refuse keywords outside a dialect (dialect.go).
format is an assertion in drafts 4, 6 and 7 and an annotation in drafts 2019-09 and 2020-12, as those drafts say; Options.AssertFormat asserts it in every draft.
Instances are decoded with json.Number (Decode), so integers keep their precision and "integer" accepts any number whose fraction is zero.
Example ¶
A family of two schema documents that refer to each other by file name; every failure comes back as pointer, keyword and message, in order.
package main
import (
"errors"
"fmt"
"github.com/cB-Sigma/jsonschema"
)
func main() {
set := jsonschema.NewSet()
_ = set.Add("meter.json", []byte(`{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string", "minLength": 1},
"reading": {"$ref": "reading.json"}
},
"required": ["id", "reading"]
}`))
_ = set.Add("reading.json", []byte(`{
"type": "object",
"properties": {"kWh": {"type": "number", "minimum": 0}, "at": {"type": "string", "format": "date-time"}},
"required": ["kWh"]
}`))
fmt.Println(set.ValidateJSON("meter.json", []byte(`{"id": "m1", "reading": {"kWh": 12.5}}`)))
err := set.ValidateJSON("meter.json", []byte(`{"id": "", "reading": {"kWh": -1, "at": "yesterday"}}`))
var v jsonschema.ValidationError
if errors.As(err, &v) {
for _, e := range v {
fmt.Println(e.Path, e.Keyword)
}
}
}
Output: <nil> /id minLength /reading/at format /reading/kWh minimum
Index ¶
- Constants
- Variables
- func Decode(data []byte) (any, error)
- type CheckContext
- type Dialect
- type Error
- type Keyword
- type Options
- type OutputFormat
- type OutputUnit
- type Regexp
- type Set
- func (s *Set) Add(name string, raw []byte) error
- func (s *Set) AddValue(name string, root any) error
- func (s *Set) CheckSchemas(name string, pointers ...string) ([]string, error)
- func (s *Set) Compile(name string) (*v6.Schema, error)
- func (s *Set) Names() []string
- func (s *Set) Output(name string, instance any, f OutputFormat) (OutputUnit, error)
- func (s *Set) Validate(name string, instance any) error
- func (s *Set) ValidateGo(name string, v any) error
- func (s *Set) ValidateJSON(name string, data []byte) error
- type Style
- type ValidationError
Examples ¶
Constants ¶
const MaxSteps = 1 << 22
MaxSteps bounds the work of the backtracking matcher for one MatchString. A pattern that needs more steps does not match.
Variables ¶
var Keywords = map[string]bool{ "type": true, "enum": true, "const": true, "properties": true, "required": true, "additionalProperties": true, "patternProperties": true, "propertyNames": true, "minProperties": true, "maxProperties": true, "dependencies": true, "items": true, "additionalItems": true, "contains": true, "minItems": true, "maxItems": true, "uniqueItems": true, "minimum": true, "maximum": true, "exclusiveMinimum": true, "exclusiveMaximum": true, "multipleOf": true, "minLength": true, "maxLength": true, "pattern": true, "format": true, "allOf": true, "anyOf": true, "oneOf": true, "not": true, "if": true, "then": true, "else": true, "$ref": true, "$schema": true, "$id": true, "$comment": true, "title": true, "description": true, "default": true, "examples": true, "definitions": true, "readOnly": true, "writeOnly": true, "contentMediaType": true, "contentEncoding": true, }
Keywords are the draft-07 keywords (and $schema, $id, $comment, title, description, default, examples, definitions, readOnly, writeOnly and contentMediaType/contentEncoding, which carry no check) that a dialect accepts by default in CheckSchemas.
var Keywords2020 = map[string]bool{ "$defs": true, "$anchor": true, "$dynamicRef": true, "$dynamicAnchor": true, "$recursiveRef": true, "$recursiveAnchor": true, "$vocabulary": true, "dependentRequired": true, "dependentSchemas": true, "prefixItems": true, "unevaluatedProperties": true, "unevaluatedItems": true, "minContains": true, "maxContains": true, "contentSchema": true, "deprecated": true, }
Keywords2020 are the keywords of drafts 2019-09 and 2020-12 that are not in Keywords. Without a dialect's Allowed, CheckSchemas accepts them in documents of those drafts.
Functions ¶
Types ¶
type CheckContext ¶
type CheckContext struct {
// contains filtered or unexported fields
}
CheckContext is what a keyword's Check sees while CheckSchemas walks.
func (*CheckContext) Fail ¶
func (c *CheckContext) Fail(format string, a ...any)
Fail reports a problem with the keyword's value.
func (*CheckContext) FailAt ¶
func (c *CheckContext) FailAt(pointer, format string, a ...any)
FailAt reports a problem at another pointer of the document.
func (*CheckContext) Pointer ¶
func (c *CheckContext) Pointer() string
Pointer is the JSON pointer of the keyword in its document.
type Dialect ¶
type Dialect struct {
// Keywords adds keywords to those CheckSchemas accepts, with a check
// of their value.
Keywords map[string]Keyword
// Allowed says which keywords CheckSchemas accepts beside those in
// Keywords; nil accepts the package's Keywords list.
Allowed func(keyword string) bool
// ObjectSchemasOnly makes CheckSchemas refuse boolean schemas (other
// than additionalProperties: false/true, which is a keyword form).
ObjectSchemasOnly bool
}
A Dialect is what CheckSchemas accepts in a family of schemas: the keywords allowed, and checks of a keyword's value. Validation itself is the library's (see jsonschema.go); a dialect only refuses, before use, schemas the validator would not check as their authors meant. Every field is optional.
func OCA ¶
func OCA() *Dialect
OCA is the dialect of the Open Charge Alliance's OCPP schemas: OCPP 1.6 (draft-04), OCPP 2.0.1 and 2.1 (draft-06). What they use is a subset of draft-07 with the same meaning, plus annotations: "id" (the draft-04 document identifier), "javaType" and "comment". Any other keyword is refused by CheckSchemas, so a later schema that relies on a keyword this package does not implement cannot be checked only halfway. The draft-04 boolean form of exclusiveMinimum/exclusiveMaximum means something else in draft-07 and is refused too.
type Error ¶
type Error struct {
// Path is the JSON pointer of the failing value in the instance.
Path string
// Keyword is the schema keyword that failed ("false" for a false
// schema, "json" for a document that is not JSON).
Keyword string
Message string
}
Error is one failed check.
type Keyword ¶
type Keyword struct {
// Check runs when CheckSchemas meets the keyword in a schema, to
// report a value of the wrong shape.
Check func(c *CheckContext, schema map[string]any, value any)
}
Keyword is the dialect's treatment of one keyword.
type Options ¶ added in v0.2.0
type Options struct {
// Dialect, when set, is applied by CheckSchemas (see NewDialectSet).
Dialect *Dialect
// AssertFormat makes "format" an assertion in every draft. Without
// it, format is an assertion in drafts 4, 6 and 7, and an annotation
// in drafts 2019-09 and 2020-12 unless the document's meta-schema
// requires the format-assertion vocabulary (2020-12) or the format
// vocabulary (2019-09). Unknown formats are never asserted.
AssertFormat bool
// AssertContent makes contentEncoding (base64) and contentMediaType
// (application/json) assertions, and contentSchema with them (2019-09
// and later). Without it they are annotations.
AssertContent bool
// GoRegexp reads "pattern", "patternProperties" and format "regex"
// with Go's regexp syntax (RE2), as v0.1 did, instead of ECMA-262
// (CompileRegexp).
GoRegexp bool
// DefaultDraft is the meta-schema URL of the draft that documents
// without $schema are read by, one of the URLs of drafts 4, 6, 7,
// 2019-09 and 2020-12 ("https://json-schema.org/draft/2020-12/schema").
// Empty means draft-07, as in v0.1 and v0.2. Another value makes
// Compile fail.
DefaultDraft string
// Draft4Integer refuses, in draft-04 documents, a number written with
// a fraction or an exponent (1.0, 1e2) where "type" allows integer but
// not number, as draft-04 defines integer. Without it a number whose
// value is whole is an integer in every draft.
Draft4Integer bool
}
Options choose how a Set made by NewSetWith validates. The zero value is what NewSet gives.
type OutputFormat ¶ added in v0.3.0
type OutputFormat int
OutputFormat is one of the four output structures of JSON Schema 2019-09 and 2020-12 (core specification, section 12.4 of 2020-12).
const ( // OutputFlag is the result alone: {"valid": false}. OutputFlag OutputFormat = iota + 1 // OutputBasic is a flat list: the failures of an invalid instance, or // the annotations of a valid one. OutputBasic // OutputDetailed is a tree that follows the schema, with only the // failing (or, when valid, annotating) branches, and a unit with a // single child replaced by that child. OutputDetailed // OutputVerbose is the whole tree: every subschema applied to every // instance location, valid or not. OutputVerbose )
type OutputUnit ¶ added in v0.3.0
type OutputUnit struct {
Valid bool
// KeywordLocation is the JSON pointer of the keyword or subschema in
// the schema, through any $ref, $dynamicRef or $recursiveRef followed.
KeywordLocation string
// AbsoluteKeywordLocation is the same place as an absolute URI: the
// enclosing schema resource's $id (a document without one is
// "mem:///" and its name in the set) and a pointer in it.
AbsoluteKeywordLocation string
// InstanceLocation is the JSON pointer of the value in the instance.
InstanceLocation string
// Error is our message for a failed keyword (empty on a unit whose
// failure is its Errors).
Error string
// Annotation is the value a keyword annotates the instance with, as
// JSON; nil when the unit carries none.
Annotation json.RawMessage
// Errors are the nested units that failed; Annotations the nested
// units that passed.
Errors []OutputUnit
Annotations []OutputUnit
// contains filtered or unexported fields
}
OutputUnit is one unit of the specification's output.
func (OutputUnit) MarshalJSON ¶ added in v0.3.0
func (u OutputUnit) MarshalJSON() ([]byte, error)
MarshalJSON writes the unit with the specification's property names; a flag result is {"valid": ...} alone.
func (OutputUnit) String ¶ added in v0.3.0
func (u OutputUnit) String() string
String is the unit as indented JSON, for logs and tests.
type Regexp ¶ added in v0.2.0
type Regexp struct {
// contains filtered or unexported fields
}
Regexp is an ECMA-262 regular expression, read with the u flag (code points, strict escapes), as JSON Schema's "pattern", "patternProperties" and format "regex" use them.
A pattern without lookaround, back references, multiline anchors or counted repeats above 1000 is translated to Go's regexp (RE2) syntax and matched in linear time; the ECMA-262 meaning of \s, ".", "$", \cX, empty classes and the rest is kept by the translation. Any other pattern is matched by a backtracking matcher that follows ECMA-262's matching rules, lookbehind included; a match that takes more than a fixed number of steps (see MaxSteps) is reported as no match.
func CompileRegexp ¶ added in v0.2.0
CompileRegexp parses an ECMA-262 regular expression (with the u flag) and refuses one the specification refuses: unknown escapes such as \a, inline flag groups such as (?i), Python's (?P<name>), ranges out of order, a quantifier with nothing to repeat, references to groups that do not exist, and unknown Unicode properties.
func (*Regexp) MatchString ¶ added in v0.2.0
MatchString reports whether the pattern matches anywhere in s.
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
Set is a family of schema documents. It is safe for concurrent use once every document is added.
func NewDialectSet ¶
NewDialectSet returns an empty set whose CheckSchemas applies the dialect; it is NewSetWith(Options{Dialect: d}).
func NewSet ¶
func NewSet() *Set
NewSet returns an empty set. format is asserted in drafts 4, 6 and 7 only (see Options.AssertFormat).
func NewSetWith ¶ added in v0.2.0
NewSetWith returns an empty set with the options.
Example ¶
In a 2020-12 document format is an annotation unless the set asserts it; patterns are ECMA-262.
package main
import (
"fmt"
"github.com/cB-Sigma/jsonschema"
)
func main() {
schema := []byte(`{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
"format": "hostname",
"pattern": "^(?!-)[a-z0-9.-]+$"
}`)
for _, assert := range []bool{false, true} {
set := jsonschema.NewSetWith(jsonschema.Options{AssertFormat: assert})
_ = set.Add("host.json", schema)
fmt.Println(assert, set.ValidateJSON("host.json", []byte(`"gw..example"`)))
}
}
Output: false <nil> true /: gw..example is not a host name
func (*Set) AddValue ¶
AddValue adds a schema document that is already decoded (map[string]any, []any, string, bool, nil and json.Number values), for instance one read from YAML.
func (*Set) CheckSchemas ¶
CheckSchemas walks the schemas at the given JSON pointers of document name, and every schema they contain or reference, before any validation: keywords the dialect does not allow are refused, each keyword's Check runs, references must resolve, and a schema must not reach itself through $ref, allOf, anyOf, oneOf, not, if, then or else alone (validating it would never reach a value). Errors carry pointers into the documents. It returns the pointers of the schemas it visited in document name.
func (*Set) Compile ¶
Compile compiles the named document (and what it references) once, so that a schema the library refuses is reported before any validation. The first call freezes the set.
func (*Set) Output ¶ added in v0.3.0
func (s *Set) Output(name string, instance any, f OutputFormat) (OutputUnit, error)
Output validates an instance (as Validate does) and returns the result in one of the specification's output formats. The result's Valid is always the outcome of Validate. A schema that cannot be compiled is an error.
How the tree is built: each subschema is evaluated against the part of the instance it applies to, failures come from the library's evaluation, and annotations are read from the schema documents. A subschema reached through $dynamicRef or $recursiveRef is shown at its static target; whether it passed comes from the evaluation in context.
Example ¶
The specification's basic output format: each failure with its keyword location, absolute keyword location and instance location.
package main
import (
"fmt"
"github.com/cB-Sigma/jsonschema"
)
func main() {
set := jsonschema.NewSet()
_ = set.Add("evse.json", []byte(`{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/evse",
"properties": {"evseId": {"type": "integer", "minimum": 0}}
}`))
inst, _ := jsonschema.Decode([]byte(`{"evseId": -1}`))
out, _ := set.Output("evse.json", inst, jsonschema.OutputBasic)
fmt.Println(out.Valid)
for _, e := range out.Errors {
fmt.Println(e.KeywordLocation, e.AbsoluteKeywordLocation, e.InstanceLocation, e.Error)
}
}
Output: false /properties/evseId/minimum https://example.com/evse#/properties/evseId/minimum /evseId must be >= 0.00000000
func (*Set) Validate ¶
Validate checks an instance decoded with Decode (or any value built of map[string]any, []any, string, bool, nil, json.Number and float64). A failed check is a ValidationError; a schema that cannot be compiled is another error.
func (*Set) ValidateGo ¶ added in v0.3.0
ValidateGo validates any Go value (a struct, a map, a slice) as the JSON that encoding/json makes of it, so struct tags and MarshalJSON methods apply. A value that cannot be encoded is a ValidationError with keyword "json".
type Style ¶
type Style struct {
// Bound prints the limit of minimum, maximum and the exclusive bounds
// (default: eight decimals); Multiple prints multipleOf's divisor
// (default: a fraction such as 1/100).
Bound, Multiple func(*big.Rat) string
// Pattern starts the message of a failed pattern (default "does not
// match "); the pattern follows.
Pattern string
// ClosestBranch adds, after an anyOf or oneOf that no branch passes,
// the failures of the branch with the fewest, each message starting
// "(closest branch) ".
ClosestBranch bool
// Max caps the failures listed (0: no cap).
Max int
// Last lists keywords whose failures at a location come after every
// other failure there and below it, in this order.
Last []string
}
Style is how Translate words its messages. The zero value is the wording of a Set.
type ValidationError ¶
type ValidationError []Error
ValidationError lists every failed check of one validation.
func Translate ¶
func Translate(err error, instance any, st *Style, schemaAt func(loc string) map[string]any) ValidationError
Translate turns a validation failure of github.com/santhosh-tekuri/jsonschema/v6 into a ValidationError: one Error per failed check, with our messages, listed depth first by instance location (members by name, items by index), and at one location in keyword order: type, enum, const, the object, array, string and number keywords, then dependencies, contains, anyOf, oneOf and not. Failures under allOf, $ref, if/then/else and propertyNames are listed where they occur; anyOf and oneOf report one failure each (with the closest branch if the style asks). schemaAt, when not nil, finds a schema object by the location the library reports, to name multiple types in the schema's order. Any other error is returned as nil.
func (ValidationError) Error ¶
func (v ValidationError) Error() string