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 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) Validate(name string, instance 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.
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
}
Options choose how a Set made by NewSetWith validates. The zero value is what NewSet gives.
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.
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