mirror of https://github.com/docker/cli.git
bump github.com/xeipuuv/gojsonschema v1.1.0
full diff: 93e72a773f...f971f3cd73
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
3e07fa728a
commit
06f34ba507
|
@ -18,15 +18,19 @@ const (
|
||||||
|
|
||||||
type portsFormatChecker struct{}
|
type portsFormatChecker struct{}
|
||||||
|
|
||||||
func (checker portsFormatChecker) IsFormat(input string) bool {
|
func (checker portsFormatChecker) IsFormat(input interface{}) bool {
|
||||||
// TODO: implement this
|
// TODO: implement this
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type durationFormatChecker struct{}
|
type durationFormatChecker struct{}
|
||||||
|
|
||||||
func (checker durationFormatChecker) IsFormat(input string) bool {
|
func (checker durationFormatChecker) IsFormat(input interface{}) bool {
|
||||||
_, err := time.ParseDuration(input)
|
value, ok := input.(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := time.ParseDuration(value)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ github.com/tonistiigi/fsutil 7f9f9232dd24c4c9c68ab3c8030c
|
||||||
github.com/tonistiigi/units 6950e57a87eaf136bbe44ef2ec8e75b9e3569de2
|
github.com/tonistiigi/units 6950e57a87eaf136bbe44ef2ec8e75b9e3569de2
|
||||||
github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6
|
github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6
|
||||||
github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b
|
github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b
|
||||||
github.com/xeipuuv/gojsonschema 93e72a773fade158921402d6a24c819b48aba29d
|
github.com/xeipuuv/gojsonschema f971f3cd73b2899de6923801c147f075263e0c50 # v1.1.0
|
||||||
golang.org/x/crypto 88737f569e3a9c7ab309cdc09a07fe7fc87233c3
|
golang.org/x/crypto 88737f569e3a9c7ab309cdc09a07fe7fc87233c3
|
||||||
golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
|
golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
|
||||||
golang.org/x/oauth2 ef147856a6ddbb60760db74283d2424e98c87bff
|
golang.org/x/oauth2 ef147856a6ddbb60760db74283d2424e98c87bff
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
[![GoDoc](https://godoc.org/github.com/xeipuuv/gojsonschema?status.svg)](https://godoc.org/github.com/xeipuuv/gojsonschema)
|
||||||
[![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema)
|
[![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema)
|
||||||
|
|
||||||
# gojsonschema
|
# gojsonschema
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
An implementation of JSON Schema, based on IETF's draft v4 - Go language
|
An implementation of JSON Schema for the Go programming language. Supports draft-04, draft-06 and draft-07.
|
||||||
|
|
||||||
References :
|
References :
|
||||||
|
|
||||||
|
@ -54,7 +55,6 @@ func main() {
|
||||||
fmt.Printf("- %s\n", desc)
|
fmt.Printf("- %s\n", desc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,6 +148,87 @@ To check the result :
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Loading local schemas
|
||||||
|
|
||||||
|
By default `file` and `http(s)` references to external schemas are loaded automatically via the file system or via http(s). An external schema can also be loaded using a `SchemaLoader`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
sl := gojsonschema.NewSchemaLoader()
|
||||||
|
loader1 := gojsonschema.NewStringLoader(`{ "type" : "string" }`)
|
||||||
|
err := sl.AddSchema("http://some_host.com/string.json", loader1)
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively if your schema already has an `$id` you can use the `AddSchemas` function
|
||||||
|
```go
|
||||||
|
loader2 := gojsonschema.NewStringLoader(`{
|
||||||
|
"$id" : "http://some_host.com/maxlength.json",
|
||||||
|
"maxLength" : 5
|
||||||
|
}`)
|
||||||
|
err = sl.AddSchemas(loader2)
|
||||||
|
```
|
||||||
|
|
||||||
|
The main schema should be passed to the `Compile` function. This main schema can then directly reference the added schemas without needing to download them.
|
||||||
|
```go
|
||||||
|
loader3 := gojsonschema.NewStringLoader(`{
|
||||||
|
"$id" : "http://some_host.com/main.json",
|
||||||
|
"allOf" : [
|
||||||
|
{ "$ref" : "http://some_host.com/string.json" },
|
||||||
|
{ "$ref" : "http://some_host.com/maxlength.json" }
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
schema, err := sl.Compile(loader3)
|
||||||
|
|
||||||
|
documentLoader := gojsonschema.NewStringLoader(`"hello world"`)
|
||||||
|
|
||||||
|
result, err := schema.Validate(documentLoader)
|
||||||
|
```
|
||||||
|
|
||||||
|
It's also possible to pass a `ReferenceLoader` to the `Compile` function that references a loaded schema.
|
||||||
|
|
||||||
|
```go
|
||||||
|
err = sl.AddSchemas(loader3)
|
||||||
|
schema, err := sl.Compile(gojsonschema.NewReferenceLoader("http://some_host.com/main.json"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Schemas added by `AddSchema` and `AddSchemas` are only validated when the entire schema is compiled, unless meta-schema validation is used.
|
||||||
|
|
||||||
|
## Using a specific draft
|
||||||
|
By default `gojsonschema` will try to detect the draft of a schema by using the `$schema` keyword and parse it in a strict draft-04, draft-06 or draft-07 mode. If `$schema` is missing, or the draft version is not explicitely set, a hybrid mode is used which merges together functionality of all drafts into one mode.
|
||||||
|
|
||||||
|
Autodectection can be turned off with the `AutoDetect` property. Specific draft versions can be specified with the `Draft` property.
|
||||||
|
|
||||||
|
```go
|
||||||
|
sl := gojsonschema.NewSchemaLoader()
|
||||||
|
sl.Draft = gojsonschema.Draft7
|
||||||
|
sl.AutoDetect = false
|
||||||
|
```
|
||||||
|
|
||||||
|
If autodetection is on (default), a draft-07 schema can savely reference draft-04 schemas and vice-versa, as long as `$schema` is specified in all schemas.
|
||||||
|
|
||||||
|
## Meta-schema validation
|
||||||
|
Schemas that are added using the `AddSchema`, `AddSchemas` and `Compile` can be validated against their meta-schema by setting the `Validate` property.
|
||||||
|
|
||||||
|
The following example will produce an error as `multipleOf` must be a number. If `Validate` is off (default), this error is only returned at the `Compile` step.
|
||||||
|
|
||||||
|
```go
|
||||||
|
sl := gojsonschema.NewSchemaLoader()
|
||||||
|
sl.Validate = true
|
||||||
|
err := sl.AddSchemas(gojsonschema.NewStringLoader(`{
|
||||||
|
$id" : "http://some_host.com/invalid.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"multipleOf" : true
|
||||||
|
}`))
|
||||||
|
```
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
Errors returned by meta-schema validation are more readable and contain more information, which helps significantly if you are developing a schema.
|
||||||
|
|
||||||
|
Meta-schema validation also works with a custom `$schema`. In case `$schema` is missing, or `AutoDetect` is set to `false`, the meta-schema of the used draft is used.
|
||||||
|
|
||||||
|
|
||||||
## Working with Errors
|
## Working with Errors
|
||||||
|
|
||||||
The library handles string error codes which you can customize by creating your own gojsonschema.locale and setting it
|
The library handles string error codes which you can customize by creating your own gojsonschema.locale and setting it
|
||||||
|
@ -157,6 +238,8 @@ gojsonschema.Locale = YourCustomLocale{}
|
||||||
|
|
||||||
However, each error contains additional contextual information.
|
However, each error contains additional contextual information.
|
||||||
|
|
||||||
|
Newer versions of `gojsonschema` may have new additional errors, so code that uses a custom locale will need to be updated when this happens.
|
||||||
|
|
||||||
**err.Type()**: *string* Returns the "type" of error that occurred. Note you can also type check. See below
|
**err.Type()**: *string* Returns the "type" of error that occurred. Note you can also type check. See below
|
||||||
|
|
||||||
Note: An error of RequiredType has an err.Type() return value of "required"
|
Note: An error of RequiredType has an err.Type() return value of "required"
|
||||||
|
@ -169,15 +252,18 @@ Note: An error of RequiredType has an err.Type() return value of "required"
|
||||||
"number_not": NumberNotError
|
"number_not": NumberNotError
|
||||||
"missing_dependency": MissingDependencyError
|
"missing_dependency": MissingDependencyError
|
||||||
"internal": InternalError
|
"internal": InternalError
|
||||||
|
"const": ConstEror
|
||||||
"enum": EnumError
|
"enum": EnumError
|
||||||
"array_no_additional_items": ArrayNoAdditionalItemsError
|
"array_no_additional_items": ArrayNoAdditionalItemsError
|
||||||
"array_min_items": ArrayMinItemsError
|
"array_min_items": ArrayMinItemsError
|
||||||
"array_max_items": ArrayMaxItemsError
|
"array_max_items": ArrayMaxItemsError
|
||||||
"unique": ItemsMustBeUniqueError
|
"unique": ItemsMustBeUniqueError
|
||||||
|
"contains" : ArrayContainsError
|
||||||
"array_min_properties": ArrayMinPropertiesError
|
"array_min_properties": ArrayMinPropertiesError
|
||||||
"array_max_properties": ArrayMaxPropertiesError
|
"array_max_properties": ArrayMaxPropertiesError
|
||||||
"additional_property_not_allowed": AdditionalPropertyNotAllowedError
|
"additional_property_not_allowed": AdditionalPropertyNotAllowedError
|
||||||
"invalid_property_pattern": InvalidPropertyPatternError
|
"invalid_property_pattern": InvalidPropertyPatternError
|
||||||
|
"invalid_property_name": InvalidPropertyNameError
|
||||||
"string_gte": StringLengthGTEError
|
"string_gte": StringLengthGTEError
|
||||||
"string_lte": StringLengthLTEError
|
"string_lte": StringLengthLTEError
|
||||||
"pattern": DoesNotMatchPatternError
|
"pattern": DoesNotMatchPatternError
|
||||||
|
@ -186,28 +272,78 @@ Note: An error of RequiredType has an err.Type() return value of "required"
|
||||||
"number_gt": NumberGTError
|
"number_gt": NumberGTError
|
||||||
"number_lte": NumberLTEError
|
"number_lte": NumberLTEError
|
||||||
"number_lt": NumberLTError
|
"number_lt": NumberLTError
|
||||||
|
"condition_then" : ConditionThenError
|
||||||
|
"condition_else" : ConditionElseError
|
||||||
|
|
||||||
**err.Value()**: *interface{}* Returns the value given
|
**err.Value()**: *interface{}* Returns the value given
|
||||||
|
|
||||||
**err.Context()**: *gojsonschema.jsonContext* Returns the context. This has a String() method that will print something like this: (root).firstName
|
**err.Context()**: *gojsonschema.JsonContext* Returns the context. This has a String() method that will print something like this: (root).firstName
|
||||||
|
|
||||||
**err.Field()**: *string* Returns the fieldname in the format firstName, or for embedded properties, person.firstName. This returns the same as the String() method on *err.Context()* but removes the (root). prefix.
|
**err.Field()**: *string* Returns the fieldname in the format firstName, or for embedded properties, person.firstName. This returns the same as the String() method on *err.Context()* but removes the (root). prefix.
|
||||||
|
|
||||||
**err.Description()**: *string* The error description. This is based on the locale you are using. See the beginning of this section for overwriting the locale with a custom implementation.
|
**err.Description()**: *string* The error description. This is based on the locale you are using. See the beginning of this section for overwriting the locale with a custom implementation.
|
||||||
|
|
||||||
|
**err.DescriptionFormat()**: *string* The error description format. This is relevant if you are adding custom validation errors afterwards to the result.
|
||||||
|
|
||||||
**err.Details()**: *gojsonschema.ErrorDetails* Returns a map[string]interface{} of additional error details specific to the error. For example, GTE errors will have a "min" value, LTE will have a "max" value. See errors.go for a full description of all the error details. Every error always contains a "field" key that holds the value of *err.Field()*
|
**err.Details()**: *gojsonschema.ErrorDetails* Returns a map[string]interface{} of additional error details specific to the error. For example, GTE errors will have a "min" value, LTE will have a "max" value. See errors.go for a full description of all the error details. Every error always contains a "field" key that holds the value of *err.Field()*
|
||||||
|
|
||||||
Note in most cases, the err.Details() will be used to generate replacement strings in your locales. and not used directly i.e.
|
Note in most cases, the err.Details() will be used to generate replacement strings in your locales, and not used directly. These strings follow the text/template format i.e.
|
||||||
```
|
```
|
||||||
%field% must be greater than or equal to %min%
|
{{.field}} must be greater than or equal to {{.min}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The library allows you to specify custom template functions, should you require more complex error message handling.
|
||||||
|
```go
|
||||||
|
gojsonschema.ErrorTemplateFuncs = map[string]interface{}{
|
||||||
|
"allcaps": func(s string) string {
|
||||||
|
return strings.ToUpper(s)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Given the above definition, you can use the custom function `"allcaps"` in your localization templates:
|
||||||
|
```
|
||||||
|
{{allcaps .field}} must be greater than or equal to {{.min}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above error message would then be rendered with the `field` value in capital letters. For example:
|
||||||
|
```
|
||||||
|
"PASSWORD must be greater than or equal to 8"
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more about what types of template functions you can use in `ErrorTemplateFuncs` by referring to Go's [text/template FuncMap](https://golang.org/pkg/text/template/#FuncMap) type.
|
||||||
|
|
||||||
## Formats
|
## Formats
|
||||||
JSON Schema allows for optional "format" property to validate strings against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this:
|
JSON Schema allows for optional "format" property to validate instances against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this:
|
||||||
|
|
||||||
````json
|
````json
|
||||||
{"type": "string", "format": "email"}
|
{"type": "string", "format": "email"}
|
||||||
````
|
````
|
||||||
Available formats: date-time, hostname, email, ipv4, ipv6, uri.
|
|
||||||
|
Not all formats defined in draft-07 are available. Implemented formats are:
|
||||||
|
|
||||||
|
* `date`
|
||||||
|
* `time`
|
||||||
|
* `date-time`
|
||||||
|
* `hostname`. Subdomains that start with a number are also supported, but this means that it doesn't strictly follow [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5) and has the implication that ipv4 addresses are also recognized as valid hostnames.
|
||||||
|
* `email`. Go's email parser deviates slightly from [RFC5322](https://tools.ietf.org/html/rfc5322). Includes unicode support.
|
||||||
|
* `idn-email`. Same caveat as `email`.
|
||||||
|
* `ipv4`
|
||||||
|
* `ipv6`
|
||||||
|
* `uri`. Includes unicode support.
|
||||||
|
* `uri-reference`. Includes unicode support.
|
||||||
|
* `iri`
|
||||||
|
* `iri-reference`
|
||||||
|
* `uri-template`
|
||||||
|
* `uuid`
|
||||||
|
* `regex`. Go uses the [RE2](https://github.com/google/re2/wiki/Syntax) engine and is not [ECMA262](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) compatible.
|
||||||
|
* `json-pointer`
|
||||||
|
* `relative-json-pointer`
|
||||||
|
|
||||||
|
`email`, `uri` and `uri-reference` use the same validation code as their unicode counterparts `idn-email`, `iri` and `iri-reference`. If you rely on unicode support you should use the specific
|
||||||
|
unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats.
|
||||||
|
|
||||||
|
The validation code for `uri`, `idn-email` and their relatives use mostly standard library code. Go 1.5 and 1.6 contain some minor bugs with handling URIs and unicode. You are encouraged to use Go 1.7+ if you rely on these formats.
|
||||||
|
|
||||||
For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this:
|
For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this:
|
||||||
|
|
||||||
|
@ -216,8 +352,14 @@ For repetitive or more complex formats, you can create custom format checkers an
|
||||||
type RoleFormatChecker struct {}
|
type RoleFormatChecker struct {}
|
||||||
|
|
||||||
// Ensure it meets the gojsonschema.FormatChecker interface
|
// Ensure it meets the gojsonschema.FormatChecker interface
|
||||||
func (f RoleFormatChecker) IsFormat(input string) bool {
|
func (f RoleFormatChecker) IsFormat(input interface{}) bool {
|
||||||
return strings.HasPrefix("ROLE_", input)
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.HasPrefix("ROLE_", asString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add it to the library
|
// Add it to the library
|
||||||
|
@ -229,6 +371,93 @@ Now to use in your json schema:
|
||||||
{"type": "string", "format": "role"}
|
{"type": "string", "format": "role"}
|
||||||
````
|
````
|
||||||
|
|
||||||
|
Another example would be to check if the provided integer matches an id on database:
|
||||||
|
|
||||||
|
JSON schema:
|
||||||
|
```json
|
||||||
|
{"type": "integer", "format": "ValidUserId"}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Define the format checker
|
||||||
|
type ValidUserIdFormatChecker struct {}
|
||||||
|
|
||||||
|
// Ensure it meets the gojsonschema.FormatChecker interface
|
||||||
|
func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asFloat64, ok := input.(float64) // Numbers are always float64 here
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX
|
||||||
|
// do the magic on the database looking for the int(asFloat64)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to the library
|
||||||
|
gojsonschema.FormatCheckers.Add("ValidUserId", ValidUserIdFormatChecker{})
|
||||||
|
````
|
||||||
|
|
||||||
|
Formats can also be removed, for example if you want to override one of the formats that is defined by default.
|
||||||
|
|
||||||
|
```go
|
||||||
|
gojsonschema.FormatCheckers.Remove("hostname")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Additional custom validation
|
||||||
|
After the validation has run and you have the results, you may add additional
|
||||||
|
errors using `Result.AddError`. This is useful to maintain the same format within the resultset instead
|
||||||
|
of having to add special exceptions for your own errors. Below is an example.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type AnswerInvalidError struct {
|
||||||
|
gojsonschema.ResultErrorFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnswerInvalidError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *AnswerInvalidError {
|
||||||
|
err := AnswerInvalidError{}
|
||||||
|
err.SetContext(context)
|
||||||
|
err.SetType("custom_invalid_error")
|
||||||
|
// it is important to use SetDescriptionFormat() as this is used to call SetDescription() after it has been parsed
|
||||||
|
// using the description of err will be overridden by this.
|
||||||
|
err.SetDescriptionFormat("Answer to the Ultimate Question of Life, the Universe, and Everything is {{.answer}}")
|
||||||
|
err.SetValue(value)
|
||||||
|
err.SetDetails(details)
|
||||||
|
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// ...
|
||||||
|
schema, err := gojsonschema.NewSchema(schemaLoader)
|
||||||
|
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||||
|
|
||||||
|
if true { // some validation
|
||||||
|
jsonContext := gojsonschema.NewJsonContext("question", nil)
|
||||||
|
errDetail := gojsonschema.ErrorDetails{
|
||||||
|
"answer": 42,
|
||||||
|
}
|
||||||
|
result.AddError(
|
||||||
|
newAnswerInvalidError(
|
||||||
|
gojsonschema.NewJsonContext("answer", jsonContext),
|
||||||
|
52,
|
||||||
|
errDetail,
|
||||||
|
),
|
||||||
|
errDetail,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is especially useful if you want to add validation beyond what the
|
||||||
|
json schema drafts can provide such business specific logic.
|
||||||
|
|
||||||
## Uses
|
## Uses
|
||||||
|
|
||||||
gojsonschema uses the following test suite :
|
gojsonschema uses the following test suite :
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright 2018 johandorland ( https://github.com/johandorland )
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gojsonschema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/xeipuuv/gojsonreference"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Draft int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Draft4 Draft = 4
|
||||||
|
Draft6 Draft = 6
|
||||||
|
Draft7 Draft = 7
|
||||||
|
Hybrid Draft = math.MaxInt32
|
||||||
|
)
|
||||||
|
|
||||||
|
type draftConfig struct {
|
||||||
|
Version Draft
|
||||||
|
MetaSchemaURL string
|
||||||
|
MetaSchema string
|
||||||
|
}
|
||||||
|
type draftConfigs []draftConfig
|
||||||
|
|
||||||
|
var drafts draftConfigs
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
drafts = []draftConfig{
|
||||||
|
draftConfig{
|
||||||
|
Version: Draft4,
|
||||||
|
MetaSchemaURL: "http://json-schema.org/draft-04/schema",
|
||||||
|
MetaSchema: `{"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}`,
|
||||||
|
},
|
||||||
|
draftConfig{
|
||||||
|
Version: Draft6,
|
||||||
|
MetaSchemaURL: "http://json-schema.org/draft-06/schema",
|
||||||
|
MetaSchema: `{"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}`,
|
||||||
|
},
|
||||||
|
draftConfig{
|
||||||
|
Version: Draft7,
|
||||||
|
MetaSchemaURL: "http://json-schema.org/draft-07/schema",
|
||||||
|
MetaSchema: `{"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc draftConfigs) GetMetaSchema(url string) string {
|
||||||
|
for _, config := range dc {
|
||||||
|
if config.MetaSchemaURL == url {
|
||||||
|
return config.MetaSchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
func (dc draftConfigs) GetDraftVersion(url string) *Draft {
|
||||||
|
for _, config := range dc {
|
||||||
|
if config.MetaSchemaURL == url {
|
||||||
|
return &config.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (dc draftConfigs) GetSchemaURL(draft Draft) string {
|
||||||
|
for _, config := range dc {
|
||||||
|
if config.Version == draft {
|
||||||
|
return config.MetaSchemaURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSchemaURL(documentNode interface{}) (string, *Draft, error) {
|
||||||
|
|
||||||
|
if isKind(documentNode, reflect.Bool) {
|
||||||
|
return "", nil, nil
|
||||||
|
}
|
||||||
|
m := documentNode.(map[string]interface{})
|
||||||
|
|
||||||
|
if existsMapKey(m, KEY_SCHEMA) {
|
||||||
|
if !isKind(m[KEY_SCHEMA], reflect.String) {
|
||||||
|
return "", nil, errors.New(formatErrorDescription(
|
||||||
|
Locale.MustBeOfType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"key": KEY_SCHEMA,
|
||||||
|
"type": TYPE_STRING,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaReference, err := gojsonreference.NewJsonReference(m[KEY_SCHEMA].(string))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := schemaReference.String()
|
||||||
|
|
||||||
|
return schema, drafts.GetDraftVersion(schema), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil, nil
|
||||||
|
}
|
|
@ -1,10 +1,20 @@
|
||||||
package gojsonschema
|
package gojsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"bytes"
|
||||||
"strings"
|
"sync"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errorTemplates errorTemplate = errorTemplate{template.New("errors-new"), sync.RWMutex{}}
|
||||||
|
|
||||||
|
// template.Template is not thread-safe for writing, so some locking is done
|
||||||
|
// sync.RWMutex is used for efficiently locking when new templates are created
|
||||||
|
type errorTemplate struct {
|
||||||
|
*template.Template
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// RequiredError. ErrorDetails: property string
|
// RequiredError. ErrorDetails: property string
|
||||||
RequiredError struct {
|
RequiredError struct {
|
||||||
|
@ -46,6 +56,11 @@ type (
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConstError. ErrorDetails: allowed
|
||||||
|
ConstError struct {
|
||||||
|
ResultErrorFields
|
||||||
|
}
|
||||||
|
|
||||||
// EnumError. ErrorDetails: allowed
|
// EnumError. ErrorDetails: allowed
|
||||||
EnumError struct {
|
EnumError struct {
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
|
@ -66,11 +81,16 @@ type (
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
}
|
}
|
||||||
|
|
||||||
// ItemsMustBeUniqueError. ErrorDetails: type
|
// ItemsMustBeUniqueError. ErrorDetails: type, i, j
|
||||||
ItemsMustBeUniqueError struct {
|
ItemsMustBeUniqueError struct {
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArrayContainsError. ErrorDetails:
|
||||||
|
ArrayContainsError struct {
|
||||||
|
ResultErrorFields
|
||||||
|
}
|
||||||
|
|
||||||
// ArrayMinPropertiesError. ErrorDetails: min
|
// ArrayMinPropertiesError. ErrorDetails: min
|
||||||
ArrayMinPropertiesError struct {
|
ArrayMinPropertiesError struct {
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
|
@ -91,6 +111,11 @@ type (
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvalidPopertyNameError. ErrorDetails: property
|
||||||
|
InvalidPropertyNameError struct {
|
||||||
|
ResultErrorFields
|
||||||
|
}
|
||||||
|
|
||||||
// StringLengthGTEError. ErrorDetails: min
|
// StringLengthGTEError. ErrorDetails: min
|
||||||
StringLengthGTEError struct {
|
StringLengthGTEError struct {
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
|
@ -135,10 +160,20 @@ type (
|
||||||
NumberLTError struct {
|
NumberLTError struct {
|
||||||
ResultErrorFields
|
ResultErrorFields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConditionThenError. ErrorDetails: -
|
||||||
|
ConditionThenError struct {
|
||||||
|
ResultErrorFields
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConditionElseError. ErrorDetails: -
|
||||||
|
ConditionElseError struct {
|
||||||
|
ResultErrorFields
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// newError takes a ResultError type and sets the type, context, description, details, value, and field
|
// newError takes a ResultError type and sets the type, context, description, details, value, and field
|
||||||
func newError(err ResultError, context *jsonContext, value interface{}, locale locale, details ErrorDetails) {
|
func newError(err ResultError, context *JsonContext, value interface{}, locale locale, details ErrorDetails) {
|
||||||
var t string
|
var t string
|
||||||
var d string
|
var d string
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
|
@ -166,6 +201,9 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l
|
||||||
case *InternalError:
|
case *InternalError:
|
||||||
t = "internal"
|
t = "internal"
|
||||||
d = locale.Internal()
|
d = locale.Internal()
|
||||||
|
case *ConstError:
|
||||||
|
t = "const"
|
||||||
|
d = locale.Const()
|
||||||
case *EnumError:
|
case *EnumError:
|
||||||
t = "enum"
|
t = "enum"
|
||||||
d = locale.Enum()
|
d = locale.Enum()
|
||||||
|
@ -181,6 +219,9 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l
|
||||||
case *ItemsMustBeUniqueError:
|
case *ItemsMustBeUniqueError:
|
||||||
t = "unique"
|
t = "unique"
|
||||||
d = locale.Unique()
|
d = locale.Unique()
|
||||||
|
case *ArrayContainsError:
|
||||||
|
t = "contains"
|
||||||
|
d = locale.ArrayContains()
|
||||||
case *ArrayMinPropertiesError:
|
case *ArrayMinPropertiesError:
|
||||||
t = "array_min_properties"
|
t = "array_min_properties"
|
||||||
d = locale.ArrayMinProperties()
|
d = locale.ArrayMinProperties()
|
||||||
|
@ -193,6 +234,9 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l
|
||||||
case *InvalidPropertyPatternError:
|
case *InvalidPropertyPatternError:
|
||||||
t = "invalid_property_pattern"
|
t = "invalid_property_pattern"
|
||||||
d = locale.InvalidPropertyPattern()
|
d = locale.InvalidPropertyPattern()
|
||||||
|
case *InvalidPropertyNameError:
|
||||||
|
t = "invalid_property_name"
|
||||||
|
d = locale.InvalidPropertyName()
|
||||||
case *StringLengthGTEError:
|
case *StringLengthGTEError:
|
||||||
t = "string_gte"
|
t = "string_gte"
|
||||||
d = locale.StringGTE()
|
d = locale.StringGTE()
|
||||||
|
@ -220,23 +264,61 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l
|
||||||
case *NumberLTError:
|
case *NumberLTError:
|
||||||
t = "number_lt"
|
t = "number_lt"
|
||||||
d = locale.NumberLT()
|
d = locale.NumberLT()
|
||||||
|
case *ConditionThenError:
|
||||||
|
t = "condition_then"
|
||||||
|
d = locale.ConditionThen()
|
||||||
|
case *ConditionElseError:
|
||||||
|
t = "condition_else"
|
||||||
|
d = locale.ConditionElse()
|
||||||
}
|
}
|
||||||
|
|
||||||
err.SetType(t)
|
err.SetType(t)
|
||||||
err.SetContext(context)
|
err.SetContext(context)
|
||||||
err.SetValue(value)
|
err.SetValue(value)
|
||||||
err.SetDetails(details)
|
err.SetDetails(details)
|
||||||
|
err.SetDescriptionFormat(d)
|
||||||
details["field"] = err.Field()
|
details["field"] = err.Field()
|
||||||
err.SetDescription(formatErrorDescription(d, details))
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatErrorDescription takes a string in this format: %field% is required
|
if _, exists := details["context"]; !exists && context != nil {
|
||||||
// and converts it to a string with replacements. The fields come from
|
details["context"] = context.String()
|
||||||
// the ErrorDetails struct and vary for each type of error.
|
|
||||||
func formatErrorDescription(s string, details ErrorDetails) string {
|
|
||||||
for name, val := range details {
|
|
||||||
s = strings.Replace(s, "%"+strings.ToLower(name)+"%", fmt.Sprintf("%v", val), -1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details))
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatErrorDescription takes a string in the default text/template
|
||||||
|
// format and converts it to a string with replacements. The fields come
|
||||||
|
// from the ErrorDetails struct and vary for each type of error.
|
||||||
|
func formatErrorDescription(s string, details ErrorDetails) string {
|
||||||
|
|
||||||
|
var tpl *template.Template
|
||||||
|
var descrAsBuffer bytes.Buffer
|
||||||
|
var err error
|
||||||
|
|
||||||
|
errorTemplates.RLock()
|
||||||
|
tpl = errorTemplates.Lookup(s)
|
||||||
|
errorTemplates.RUnlock()
|
||||||
|
|
||||||
|
if tpl == nil {
|
||||||
|
errorTemplates.Lock()
|
||||||
|
tpl = errorTemplates.New(s)
|
||||||
|
|
||||||
|
if ErrorTemplateFuncs != nil {
|
||||||
|
tpl.Funcs(ErrorTemplateFuncs)
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl, err = tpl.Parse(s)
|
||||||
|
errorTemplates.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tpl.Execute(&descrAsBuffer, details)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return descrAsBuffer.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,18 @@ package gojsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
|
// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
|
||||||
FormatChecker interface {
|
FormatChecker interface {
|
||||||
IsFormat(input string) bool
|
IsFormat(input interface{}) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatCheckerChain holds the formatters
|
// FormatCheckerChain holds the formatters
|
||||||
|
@ -52,14 +53,33 @@ type (
|
||||||
// http://tools.ietf.org/html/rfc3339#section-5.6
|
// http://tools.ietf.org/html/rfc3339#section-5.6
|
||||||
DateTimeFormatChecker struct{}
|
DateTimeFormatChecker struct{}
|
||||||
|
|
||||||
// URIFormatCheckers validates a URI with a valid Scheme per RFC3986
|
DateFormatChecker struct{}
|
||||||
|
|
||||||
|
TimeFormatChecker struct{}
|
||||||
|
|
||||||
|
// URIFormatChecker validates a URI with a valid Scheme per RFC3986
|
||||||
URIFormatChecker struct{}
|
URIFormatChecker struct{}
|
||||||
|
|
||||||
|
// URIReferenceFormatChecker validates a URI or relative-reference per RFC3986
|
||||||
|
URIReferenceFormatChecker struct{}
|
||||||
|
|
||||||
|
// URITemplateFormatChecker validates a URI template per RFC6570
|
||||||
|
URITemplateFormatChecker struct{}
|
||||||
|
|
||||||
// HostnameFormatChecker validates a hostname is in the correct format
|
// HostnameFormatChecker validates a hostname is in the correct format
|
||||||
HostnameFormatChecker struct{}
|
HostnameFormatChecker struct{}
|
||||||
|
|
||||||
// UUIDFormatChecker validates a UUID is in the correct format
|
// UUIDFormatChecker validates a UUID is in the correct format
|
||||||
UUIDFormatChecker struct{}
|
UUIDFormatChecker struct{}
|
||||||
|
|
||||||
|
// RegexFormatChecker validates a regex is in the correct format
|
||||||
|
RegexFormatChecker struct{}
|
||||||
|
|
||||||
|
// JSONPointerFormatChecker validates a JSON Pointer per RFC6901
|
||||||
|
JSONPointerFormatChecker struct{}
|
||||||
|
|
||||||
|
// RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format
|
||||||
|
RelativeJSONPointerFormatChecker struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -67,43 +87,65 @@ var (
|
||||||
// so library users can add custom formatters
|
// so library users can add custom formatters
|
||||||
FormatCheckers = FormatCheckerChain{
|
FormatCheckers = FormatCheckerChain{
|
||||||
formatters: map[string]FormatChecker{
|
formatters: map[string]FormatChecker{
|
||||||
"date-time": DateTimeFormatChecker{},
|
"date": DateFormatChecker{},
|
||||||
"hostname": HostnameFormatChecker{},
|
"time": TimeFormatChecker{},
|
||||||
"email": EmailFormatChecker{},
|
"date-time": DateTimeFormatChecker{},
|
||||||
"ipv4": IPV4FormatChecker{},
|
"hostname": HostnameFormatChecker{},
|
||||||
"ipv6": IPV6FormatChecker{},
|
"email": EmailFormatChecker{},
|
||||||
"uri": URIFormatChecker{},
|
"idn-email": EmailFormatChecker{},
|
||||||
"uuid": UUIDFormatChecker{},
|
"ipv4": IPV4FormatChecker{},
|
||||||
|
"ipv6": IPV6FormatChecker{},
|
||||||
|
"uri": URIFormatChecker{},
|
||||||
|
"uri-reference": URIReferenceFormatChecker{},
|
||||||
|
"iri": URIFormatChecker{},
|
||||||
|
"iri-reference": URIReferenceFormatChecker{},
|
||||||
|
"uri-template": URITemplateFormatChecker{},
|
||||||
|
"uuid": UUIDFormatChecker{},
|
||||||
|
"regex": RegexFormatChecker{},
|
||||||
|
"json-pointer": JSONPointerFormatChecker{},
|
||||||
|
"relative-json-pointer": RelativeJSONPointerFormatChecker{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regex credit: https://github.com/asaskevich/govalidator
|
|
||||||
rxEmail = regexp.MustCompile("^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$")
|
|
||||||
|
|
||||||
// Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
|
// Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
|
||||||
rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`)
|
rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`)
|
||||||
|
|
||||||
|
// Use a regex to make sure curly brackets are balanced properly after validating it as a AURI
|
||||||
|
rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$")
|
||||||
|
|
||||||
rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
|
rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
|
||||||
|
|
||||||
|
rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$")
|
||||||
|
|
||||||
|
rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$")
|
||||||
|
|
||||||
|
lock = new(sync.Mutex)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add adds a FormatChecker to the FormatCheckerChain
|
// Add adds a FormatChecker to the FormatCheckerChain
|
||||||
// The name used will be the value used for the format key in your json schema
|
// The name used will be the value used for the format key in your json schema
|
||||||
func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
|
func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
|
||||||
|
lock.Lock()
|
||||||
c.formatters[name] = f
|
c.formatters[name] = f
|
||||||
|
lock.Unlock()
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
|
// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
|
||||||
func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
|
func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
|
||||||
|
lock.Lock()
|
||||||
delete(c.formatters, name)
|
delete(c.formatters, name)
|
||||||
|
lock.Unlock()
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
|
// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
|
||||||
func (c *FormatCheckerChain) Has(name string) bool {
|
func (c *FormatCheckerChain) Has(name string) bool {
|
||||||
|
lock.Lock()
|
||||||
_, ok := c.formatters[name]
|
_, ok := c.formatters[name]
|
||||||
|
lock.Unlock()
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
@ -117,32 +159,52 @@ func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isKind(input, reflect.String) {
|
return f.IsFormat(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f EmailFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
inputString := input.(string)
|
_, err := mail.ParseAddress(asString)
|
||||||
|
|
||||||
return f.IsFormat(inputString)
|
return err == nil
|
||||||
}
|
|
||||||
|
|
||||||
func (f EmailFormatChecker) IsFormat(input string) bool {
|
|
||||||
return rxEmail.MatchString(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Credit: https://github.com/asaskevich/govalidator
|
// Credit: https://github.com/asaskevich/govalidator
|
||||||
func (f IPV4FormatChecker) IsFormat(input string) bool {
|
func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
|
||||||
ip := net.ParseIP(input)
|
|
||||||
return ip != nil && strings.Contains(input, ".")
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(asString)
|
||||||
|
return ip != nil && strings.Contains(asString, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Credit: https://github.com/asaskevich/govalidator
|
// Credit: https://github.com/asaskevich/govalidator
|
||||||
func (f IPV6FormatChecker) IsFormat(input string) bool {
|
func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
|
||||||
ip := net.ParseIP(input)
|
|
||||||
return ip != nil && strings.Contains(input, ":")
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(asString)
|
||||||
|
return ip != nil && strings.Contains(asString, ":")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f DateTimeFormatChecker) IsFormat(input string) bool {
|
func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
formats := []string{
|
formats := []string{
|
||||||
"15:04:05",
|
"15:04:05",
|
||||||
"15:04:05Z07:00",
|
"15:04:05Z07:00",
|
||||||
|
@ -152,7 +214,7 @@ func (f DateTimeFormatChecker) IsFormat(input string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, format := range formats {
|
for _, format := range formats {
|
||||||
if _, err := time.Parse(format, input); err == nil {
|
if _, err := time.Parse(format, asString); err == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,19 +222,122 @@ func (f DateTimeFormatChecker) IsFormat(input string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f URIFormatChecker) IsFormat(input string) bool {
|
func (f DateFormatChecker) IsFormat(input interface{}) bool {
|
||||||
u, err := url.Parse(input)
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := time.Parse("2006-01-02", asString)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f TimeFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := time.Parse("15:04:05Z07:00", asString); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := time.Parse("15:04:05", asString)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f URIFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(asString)
|
||||||
|
|
||||||
if err != nil || u.Scheme == "" {
|
if err != nil || u.Scheme == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !strings.Contains(asString, `\`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := url.Parse(asString)
|
||||||
|
return err == nil && !strings.Contains(asString, `\`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(asString)
|
||||||
|
if err != nil || strings.Contains(asString, `\`) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rxURITemplate.MatchString(u.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rxHostname.MatchString(asString) && len(asString) < 256
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rxUUID.MatchString(asString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFormat implements FormatChecker interface.
|
||||||
|
func (f RegexFormatChecker) IsFormat(input interface{}) bool {
|
||||||
|
|
||||||
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if asString == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, err := regexp.Compile(asString)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f HostnameFormatChecker) IsFormat(input string) bool {
|
func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
|
||||||
return rxHostname.MatchString(input) && len(input) < 256
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rxJSONPointer.MatchString(asString)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f UUIDFormatChecker) IsFormat(input string) bool {
|
func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool {
|
||||||
return rxUUID.MatchString(input)
|
asString, ok := input.(string)
|
||||||
|
if ok == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rxRelJSONPointer.MatchString(asString)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,20 +26,20 @@ package gojsonschema
|
||||||
|
|
||||||
import "bytes"
|
import "bytes"
|
||||||
|
|
||||||
// jsonContext implements a persistent linked-list of strings
|
// JsonContext implements a persistent linked-list of strings
|
||||||
type jsonContext struct {
|
type JsonContext struct {
|
||||||
head string
|
head string
|
||||||
tail *jsonContext
|
tail *JsonContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func newJsonContext(head string, tail *jsonContext) *jsonContext {
|
func NewJsonContext(head string, tail *JsonContext) *JsonContext {
|
||||||
return &jsonContext{head, tail}
|
return &JsonContext{head, tail}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String displays the context in reverse.
|
// String displays the context in reverse.
|
||||||
// This plays well with the data structure's persistent nature with
|
// This plays well with the data structure's persistent nature with
|
||||||
// Cons and a json document's tree structure.
|
// Cons and a json document's tree structure.
|
||||||
func (c *jsonContext) String(del ...string) string {
|
func (c *JsonContext) String(del ...string) string {
|
||||||
byteArr := make([]byte, 0, c.stringLen())
|
byteArr := make([]byte, 0, c.stringLen())
|
||||||
buf := bytes.NewBuffer(byteArr)
|
buf := bytes.NewBuffer(byteArr)
|
||||||
c.writeStringToBuffer(buf, del)
|
c.writeStringToBuffer(buf, del)
|
||||||
|
@ -47,7 +47,7 @@ func (c *jsonContext) String(del ...string) string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *jsonContext) stringLen() int {
|
func (c *JsonContext) stringLen() int {
|
||||||
length := 0
|
length := 0
|
||||||
if c.tail != nil {
|
if c.tail != nil {
|
||||||
length = c.tail.stringLen() + 1 // add 1 for "."
|
length = c.tail.stringLen() + 1 // add 1 for "."
|
||||||
|
@ -57,7 +57,7 @@ func (c *jsonContext) stringLen() int {
|
||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *jsonContext) writeStringToBuffer(buf *bytes.Buffer, del []string) {
|
func (c *JsonContext) writeStringToBuffer(buf *bytes.Buffer, del []string) {
|
||||||
if c.tail != nil {
|
if c.tail != nil {
|
||||||
c.tail.writeStringToBuffer(buf, del)
|
c.tail.writeStringToBuffer(buf, del)
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -40,34 +41,92 @@ import (
|
||||||
"github.com/xeipuuv/gojsonreference"
|
"github.com/xeipuuv/gojsonreference"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var osFS = osFileSystem(os.Open)
|
||||||
|
|
||||||
// JSON loader interface
|
// JSON loader interface
|
||||||
|
|
||||||
type JSONLoader interface {
|
type JSONLoader interface {
|
||||||
jsonSource() interface{}
|
JsonSource() interface{}
|
||||||
loadJSON() (interface{}, error)
|
LoadJSON() (interface{}, error)
|
||||||
loadSchema() (*Schema, error)
|
JsonReference() (gojsonreference.JsonReference, error)
|
||||||
|
LoaderFactory() JSONLoaderFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONLoaderFactory interface {
|
||||||
|
New(source string) JSONLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultJSONLoaderFactory struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileSystemJSONLoaderFactory struct {
|
||||||
|
fs http.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DefaultJSONLoaderFactory) New(source string) JSONLoader {
|
||||||
|
return &jsonReferenceLoader{
|
||||||
|
fs: osFS,
|
||||||
|
source: source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader {
|
||||||
|
return &jsonReferenceLoader{
|
||||||
|
fs: f.fs,
|
||||||
|
source: source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// osFileSystem is a functional wrapper for os.Open that implements http.FileSystem.
|
||||||
|
type osFileSystem func(string) (*os.File, error)
|
||||||
|
|
||||||
|
func (o osFileSystem) Open(name string) (http.File, error) {
|
||||||
|
return o(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON Reference loader
|
// JSON Reference loader
|
||||||
// references are used to load JSONs from files and HTTP
|
// references are used to load JSONs from files and HTTP
|
||||||
|
|
||||||
type jsonReferenceLoader struct {
|
type jsonReferenceLoader struct {
|
||||||
|
fs http.FileSystem
|
||||||
source string
|
source string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonReferenceLoader) jsonSource() interface{} {
|
func (l *jsonReferenceLoader) JsonSource() interface{} {
|
||||||
return l.source
|
return l.source
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReferenceLoader(source string) *jsonReferenceLoader {
|
func (l *jsonReferenceLoader) JsonReference() (gojsonreference.JsonReference, error) {
|
||||||
return &jsonReferenceLoader{source: source}
|
return gojsonreference.NewJsonReference(l.JsonSource().(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonReferenceLoader) loadJSON() (interface{}, error) {
|
func (l *jsonReferenceLoader) LoaderFactory() JSONLoaderFactory {
|
||||||
|
return &FileSystemJSONLoaderFactory{
|
||||||
|
fs: l.fs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system.
|
||||||
|
func NewReferenceLoader(source string) JSONLoader {
|
||||||
|
return &jsonReferenceLoader{
|
||||||
|
fs: osFS,
|
||||||
|
source: source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReferenceLoaderFileSystem returns a JSON reference loader using the given source and file system.
|
||||||
|
func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) JSONLoader {
|
||||||
|
return &jsonReferenceLoader{
|
||||||
|
fs: fs,
|
||||||
|
source: source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
reference, err := gojsonreference.NewJsonReference(l.jsonSource().(string))
|
reference, err := gojsonreference.NewJsonReference(l.JsonSource().(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -79,15 +138,12 @@ func (l *jsonReferenceLoader) loadJSON() (interface{}, error) {
|
||||||
|
|
||||||
if reference.HasFileScheme {
|
if reference.HasFileScheme {
|
||||||
|
|
||||||
filename := strings.Replace(refToUrl.String(), "file://", "", -1)
|
filename := strings.TrimPrefix(refToUrl.String(), "file://")
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// on Windows, a file URL may have an extra leading slash, use slashes
|
// on Windows, a file URL may have an extra leading slash, use slashes
|
||||||
// instead of backslashes, and have spaces escaped
|
// instead of backslashes, and have spaces escaped
|
||||||
if strings.HasPrefix(filename, "/") {
|
filename = strings.TrimPrefix(filename, "/")
|
||||||
filename = filename[1:]
|
|
||||||
}
|
|
||||||
filename = filepath.FromSlash(filename)
|
filename = filepath.FromSlash(filename)
|
||||||
filename = strings.Replace(filename, "%20", " ", -1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document, err = l.loadFromFile(filename)
|
document, err = l.loadFromFile(filename)
|
||||||
|
@ -108,35 +164,14 @@ func (l *jsonReferenceLoader) loadJSON() (interface{}, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonReferenceLoader) loadSchema() (*Schema, error) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
d := Schema{}
|
|
||||||
d.pool = newSchemaPool()
|
|
||||||
d.referencePool = newSchemaReferencePool()
|
|
||||||
|
|
||||||
d.documentReference, err = gojsonreference.NewJsonReference(l.jsonSource().(string))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
spd, err := d.pool.GetDocument(d.documentReference)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.parse(spd.Document)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &d, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) {
|
func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) {
|
||||||
|
|
||||||
|
// returned cached versions for metaschemas for drafts 4, 6 and 7
|
||||||
|
// for performance and allow for easier offline use
|
||||||
|
if metaSchema := drafts.GetMetaSchema(address); metaSchema != "" {
|
||||||
|
return decodeJsonUsingNumber(strings.NewReader(metaSchema))
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.Get(address)
|
resp, err := http.Get(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -144,7 +179,7 @@ func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error)
|
||||||
|
|
||||||
// must return HTTP Status 200 OK
|
// must return HTTP Status 200 OK
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, errors.New(formatErrorDescription(Locale.httpBadStatus(), ErrorDetails{"status": resp.Status}))
|
return nil, errors.New(formatErrorDescription(Locale.HttpBadStatus(), ErrorDetails{"status": resp.Status}))
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyBuff, err := ioutil.ReadAll(resp.Body)
|
bodyBuff, err := ioutil.ReadAll(resp.Body)
|
||||||
|
@ -153,12 +188,16 @@ func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodeJsonUsingNumber(bytes.NewReader(bodyBuff))
|
return decodeJsonUsingNumber(bytes.NewReader(bodyBuff))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) {
|
func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) {
|
||||||
|
f, err := l.fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
bodyBuff, err := ioutil.ReadFile(path)
|
bodyBuff, err := ioutil.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -173,45 +212,52 @@ type jsonStringLoader struct {
|
||||||
source string
|
source string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonStringLoader) jsonSource() interface{} {
|
func (l *jsonStringLoader) JsonSource() interface{} {
|
||||||
return l.source
|
return l.source
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStringLoader(source string) *jsonStringLoader {
|
func (l *jsonStringLoader) JsonReference() (gojsonreference.JsonReference, error) {
|
||||||
|
return gojsonreference.NewJsonReference("#")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *jsonStringLoader) LoaderFactory() JSONLoaderFactory {
|
||||||
|
return &DefaultJSONLoaderFactory{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStringLoader(source string) JSONLoader {
|
||||||
return &jsonStringLoader{source: source}
|
return &jsonStringLoader{source: source}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonStringLoader) loadJSON() (interface{}, error) {
|
func (l *jsonStringLoader) LoadJSON() (interface{}, error) {
|
||||||
|
|
||||||
return decodeJsonUsingNumber(strings.NewReader(l.jsonSource().(string)))
|
return decodeJsonUsingNumber(strings.NewReader(l.JsonSource().(string)))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonStringLoader) loadSchema() (*Schema, error) {
|
// JSON bytes loader
|
||||||
|
|
||||||
var err error
|
type jsonBytesLoader struct {
|
||||||
|
source []byte
|
||||||
|
}
|
||||||
|
|
||||||
document, err := l.loadJSON()
|
func (l *jsonBytesLoader) JsonSource() interface{} {
|
||||||
if err != nil {
|
return l.source
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
d := Schema{}
|
func (l *jsonBytesLoader) JsonReference() (gojsonreference.JsonReference, error) {
|
||||||
d.pool = newSchemaPool()
|
return gojsonreference.NewJsonReference("#")
|
||||||
d.referencePool = newSchemaReferencePool()
|
}
|
||||||
d.documentReference, err = gojsonreference.NewJsonReference("#")
|
|
||||||
d.pool.SetStandaloneDocument(document)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.parse(document)
|
func (l *jsonBytesLoader) LoaderFactory() JSONLoaderFactory {
|
||||||
if err != nil {
|
return &DefaultJSONLoaderFactory{}
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return &d, nil
|
func NewBytesLoader(source []byte) JSONLoader {
|
||||||
|
return &jsonBytesLoader{source: source}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *jsonBytesLoader) LoadJSON() (interface{}, error) {
|
||||||
|
return decodeJsonUsingNumber(bytes.NewReader(l.JsonSource().([]byte)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON Go (types) loader
|
// JSON Go (types) loader
|
||||||
|
@ -221,19 +267,27 @@ type jsonGoLoader struct {
|
||||||
source interface{}
|
source interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonGoLoader) jsonSource() interface{} {
|
func (l *jsonGoLoader) JsonSource() interface{} {
|
||||||
return l.source
|
return l.source
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGoLoader(source interface{}) *jsonGoLoader {
|
func (l *jsonGoLoader) JsonReference() (gojsonreference.JsonReference, error) {
|
||||||
|
return gojsonreference.NewJsonReference("#")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *jsonGoLoader) LoaderFactory() JSONLoaderFactory {
|
||||||
|
return &DefaultJSONLoaderFactory{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGoLoader(source interface{}) JSONLoader {
|
||||||
return &jsonGoLoader{source: source}
|
return &jsonGoLoader{source: source}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonGoLoader) loadJSON() (interface{}, error) {
|
func (l *jsonGoLoader) LoadJSON() (interface{}, error) {
|
||||||
|
|
||||||
// convert it to a compliant JSON first to avoid types "mismatches"
|
// convert it to a compliant JSON first to avoid types "mismatches"
|
||||||
|
|
||||||
jsonBytes, err := json.Marshal(l.jsonSource())
|
jsonBytes, err := json.Marshal(l.JsonSource())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -242,31 +296,58 @@ func (l *jsonGoLoader) loadJSON() (interface{}, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonGoLoader) loadSchema() (*Schema, error) {
|
type jsonIOLoader struct {
|
||||||
|
buf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf)
|
||||||
|
}
|
||||||
|
|
||||||
document, err := l.loadJSON()
|
func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) {
|
||||||
if err != nil {
|
buf := &bytes.Buffer{}
|
||||||
return nil, err
|
return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
d := Schema{}
|
func (l *jsonIOLoader) JsonSource() interface{} {
|
||||||
d.pool = newSchemaPool()
|
return l.buf.String()
|
||||||
d.referencePool = newSchemaReferencePool()
|
}
|
||||||
d.documentReference, err = gojsonreference.NewJsonReference("#")
|
|
||||||
d.pool.SetStandaloneDocument(document)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.parse(document)
|
func (l *jsonIOLoader) LoadJSON() (interface{}, error) {
|
||||||
if err != nil {
|
return decodeJsonUsingNumber(l.buf)
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return &d, nil
|
func (l *jsonIOLoader) JsonReference() (gojsonreference.JsonReference, error) {
|
||||||
|
return gojsonreference.NewJsonReference("#")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *jsonIOLoader) LoaderFactory() JSONLoaderFactory {
|
||||||
|
return &DefaultJSONLoaderFactory{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON raw loader
|
||||||
|
// In case the JSON is already marshalled to interface{} use this loader
|
||||||
|
// This is used for testing as otherwise there is no guarantee the JSON is marshalled
|
||||||
|
// "properly" by using https://golang.org/pkg/encoding/json/#Decoder.UseNumber
|
||||||
|
type jsonRawLoader struct {
|
||||||
|
source interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRawLoader(source interface{}) *jsonRawLoader {
|
||||||
|
return &jsonRawLoader{source: source}
|
||||||
|
}
|
||||||
|
func (l *jsonRawLoader) JsonSource() interface{} {
|
||||||
|
return l.source
|
||||||
|
}
|
||||||
|
func (l *jsonRawLoader) LoadJSON() (interface{}, error) {
|
||||||
|
return l.source, nil
|
||||||
|
}
|
||||||
|
func (l *jsonRawLoader) JsonReference() (gojsonreference.JsonReference, error) {
|
||||||
|
return gojsonreference.NewJsonReference("#")
|
||||||
|
}
|
||||||
|
func (l *jsonRawLoader) LoaderFactory() JSONLoaderFactory {
|
||||||
|
return &DefaultJSONLoaderFactory{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeJsonUsingNumber(r io.Reader) (interface{}, error) {
|
func decodeJsonUsingNumber(r io.Reader) (interface{}, error) {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
package gojsonschema
|
package gojsonschema
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// locale is an interface for definining custom error strings
|
// locale is an interface for defining custom error strings
|
||||||
locale interface {
|
locale interface {
|
||||||
Required() string
|
Required() string
|
||||||
InvalidType() string
|
InvalidType() string
|
||||||
|
@ -36,15 +36,19 @@ type (
|
||||||
NumberNot() string
|
NumberNot() string
|
||||||
MissingDependency() string
|
MissingDependency() string
|
||||||
Internal() string
|
Internal() string
|
||||||
|
Const() string
|
||||||
Enum() string
|
Enum() string
|
||||||
|
ArrayNotEnoughItems() string
|
||||||
ArrayNoAdditionalItems() string
|
ArrayNoAdditionalItems() string
|
||||||
ArrayMinItems() string
|
ArrayMinItems() string
|
||||||
ArrayMaxItems() string
|
ArrayMaxItems() string
|
||||||
Unique() string
|
Unique() string
|
||||||
|
ArrayContains() string
|
||||||
ArrayMinProperties() string
|
ArrayMinProperties() string
|
||||||
ArrayMaxProperties() string
|
ArrayMaxProperties() string
|
||||||
AdditionalPropertyNotAllowed() string
|
AdditionalPropertyNotAllowed() string
|
||||||
InvalidPropertyPattern() string
|
InvalidPropertyPattern() string
|
||||||
|
InvalidPropertyName() string
|
||||||
StringGTE() string
|
StringGTE() string
|
||||||
StringLTE() string
|
StringLTE() string
|
||||||
DoesNotMatchPattern() string
|
DoesNotMatchPattern() string
|
||||||
|
@ -72,7 +76,11 @@ type (
|
||||||
ReferenceMustBeCanonical() string
|
ReferenceMustBeCanonical() string
|
||||||
NotAValidType() string
|
NotAValidType() string
|
||||||
Duplicated() string
|
Duplicated() string
|
||||||
httpBadStatus() string
|
HttpBadStatus() string
|
||||||
|
ParseError() string
|
||||||
|
|
||||||
|
ConditionThen() string
|
||||||
|
ConditionElse() string
|
||||||
|
|
||||||
// ErrorFormat
|
// ErrorFormat
|
||||||
ErrorFormat() string
|
ErrorFormat() string
|
||||||
|
@ -83,11 +91,11 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l DefaultLocale) Required() string {
|
func (l DefaultLocale) Required() string {
|
||||||
return `%property% is required`
|
return `{{.property}} is required`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) InvalidType() string {
|
func (l DefaultLocale) InvalidType() string {
|
||||||
return `Invalid type. Expected: %expected%, given: %given%`
|
return `Invalid type. Expected: {{.expected}}, given: {{.given}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) NumberAnyOf() string {
|
func (l DefaultLocale) NumberAnyOf() string {
|
||||||
|
@ -107,164 +115,194 @@ func (l DefaultLocale) NumberNot() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MissingDependency() string {
|
func (l DefaultLocale) MissingDependency() string {
|
||||||
return `Has a dependency on %dependency%`
|
return `Has a dependency on {{.dependency}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) Internal() string {
|
func (l DefaultLocale) Internal() string {
|
||||||
return `Internal Error %error%`
|
return `Internal Error {{.error}}`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l DefaultLocale) Const() string {
|
||||||
|
return `{{.field}} does not match: {{.allowed}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) Enum() string {
|
func (l DefaultLocale) Enum() string {
|
||||||
return `%field% must be one of the following: %allowed%`
|
return `{{.field}} must be one of the following: {{.allowed}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) ArrayNoAdditionalItems() string {
|
func (l DefaultLocale) ArrayNoAdditionalItems() string {
|
||||||
return `No additional items allowed on array`
|
return `No additional items allowed on array`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l DefaultLocale) ArrayNotEnoughItems() string {
|
||||||
|
return `Not enough items on array to match positional list of schema`
|
||||||
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) ArrayMinItems() string {
|
func (l DefaultLocale) ArrayMinItems() string {
|
||||||
return `Array must have at least %min% items`
|
return `Array must have at least {{.min}} items`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) ArrayMaxItems() string {
|
func (l DefaultLocale) ArrayMaxItems() string {
|
||||||
return `Array must have at most %max% items`
|
return `Array must have at most {{.max}} items`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) Unique() string {
|
func (l DefaultLocale) Unique() string {
|
||||||
return `%type% items must be unique`
|
return `{{.type}} items[{{.i}},{{.j}}] must be unique`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l DefaultLocale) ArrayContains() string {
|
||||||
|
return `At least one of the items must match`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) ArrayMinProperties() string {
|
func (l DefaultLocale) ArrayMinProperties() string {
|
||||||
return `Must have at least %min% properties`
|
return `Must have at least {{.min}} properties`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) ArrayMaxProperties() string {
|
func (l DefaultLocale) ArrayMaxProperties() string {
|
||||||
return `Must have at most %max% properties`
|
return `Must have at most {{.max}} properties`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) AdditionalPropertyNotAllowed() string {
|
func (l DefaultLocale) AdditionalPropertyNotAllowed() string {
|
||||||
return `Additional property %property% is not allowed`
|
return `Additional property {{.property}} is not allowed`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) InvalidPropertyPattern() string {
|
func (l DefaultLocale) InvalidPropertyPattern() string {
|
||||||
return `Property "%property%" does not match pattern %pattern%`
|
return `Property "{{.property}}" does not match pattern {{.pattern}}`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l DefaultLocale) InvalidPropertyName() string {
|
||||||
|
return `Property name of "{{.property}}" does not match`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) StringGTE() string {
|
func (l DefaultLocale) StringGTE() string {
|
||||||
return `String length must be greater than or equal to %min%`
|
return `String length must be greater than or equal to {{.min}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) StringLTE() string {
|
func (l DefaultLocale) StringLTE() string {
|
||||||
return `String length must be less than or equal to %max%`
|
return `String length must be less than or equal to {{.max}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) DoesNotMatchPattern() string {
|
func (l DefaultLocale) DoesNotMatchPattern() string {
|
||||||
return `Does not match pattern '%pattern%'`
|
return `Does not match pattern '{{.pattern}}'`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) DoesNotMatchFormat() string {
|
func (l DefaultLocale) DoesNotMatchFormat() string {
|
||||||
return `Does not match format '%format%'`
|
return `Does not match format '{{.format}}'`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MultipleOf() string {
|
func (l DefaultLocale) MultipleOf() string {
|
||||||
return `Must be a multiple of %multiple%`
|
return `Must be a multiple of {{.multiple}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) NumberGTE() string {
|
func (l DefaultLocale) NumberGTE() string {
|
||||||
return `Must be greater than or equal to %min%`
|
return `Must be greater than or equal to {{.min}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) NumberGT() string {
|
func (l DefaultLocale) NumberGT() string {
|
||||||
return `Must be greater than %min%`
|
return `Must be greater than {{.min}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) NumberLTE() string {
|
func (l DefaultLocale) NumberLTE() string {
|
||||||
return `Must be less than or equal to %max%`
|
return `Must be less than or equal to {{.max}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) NumberLT() string {
|
func (l DefaultLocale) NumberLT() string {
|
||||||
return `Must be less than %max%`
|
return `Must be less than {{.max}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schema validators
|
// Schema validators
|
||||||
func (l DefaultLocale) RegexPattern() string {
|
func (l DefaultLocale) RegexPattern() string {
|
||||||
return `Invalid regex pattern '%pattern%'`
|
return `Invalid regex pattern '{{.pattern}}'`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) GreaterThanZero() string {
|
func (l DefaultLocale) GreaterThanZero() string {
|
||||||
return `%number% must be strictly greater than 0`
|
return `{{.number}} must be strictly greater than 0`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MustBeOfA() string {
|
func (l DefaultLocale) MustBeOfA() string {
|
||||||
return `%x% must be of a %y%`
|
return `{{.x}} must be of a {{.y}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MustBeOfAn() string {
|
func (l DefaultLocale) MustBeOfAn() string {
|
||||||
return `%x% must be of an %y%`
|
return `{{.x}} must be of an {{.y}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) CannotBeUsedWithout() string {
|
func (l DefaultLocale) CannotBeUsedWithout() string {
|
||||||
return `%x% cannot be used without %y%`
|
return `{{.x}} cannot be used without {{.y}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) CannotBeGT() string {
|
func (l DefaultLocale) CannotBeGT() string {
|
||||||
return `%x% cannot be greater than %y%`
|
return `{{.x}} cannot be greater than {{.y}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MustBeOfType() string {
|
func (l DefaultLocale) MustBeOfType() string {
|
||||||
return `%key% must be of type %type%`
|
return `{{.key}} must be of type {{.type}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MustBeValidRegex() string {
|
func (l DefaultLocale) MustBeValidRegex() string {
|
||||||
return `%key% must be a valid regex`
|
return `{{.key}} must be a valid regex`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MustBeValidFormat() string {
|
func (l DefaultLocale) MustBeValidFormat() string {
|
||||||
return `%key% must be a valid format %given%`
|
return `{{.key}} must be a valid format {{.given}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) MustBeGTEZero() string {
|
func (l DefaultLocale) MustBeGTEZero() string {
|
||||||
return `%key% must be greater than or equal to 0`
|
return `{{.key}} must be greater than or equal to 0`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) KeyCannotBeGreaterThan() string {
|
func (l DefaultLocale) KeyCannotBeGreaterThan() string {
|
||||||
return `%key% cannot be greater than %y%`
|
return `{{.key}} cannot be greater than {{.y}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) KeyItemsMustBeOfType() string {
|
func (l DefaultLocale) KeyItemsMustBeOfType() string {
|
||||||
return `%key% items must be %type%`
|
return `{{.key}} items must be {{.type}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) KeyItemsMustBeUnique() string {
|
func (l DefaultLocale) KeyItemsMustBeUnique() string {
|
||||||
return `%key% items must be unique`
|
return `{{.key}} items must be unique`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) ReferenceMustBeCanonical() string {
|
func (l DefaultLocale) ReferenceMustBeCanonical() string {
|
||||||
return `Reference %reference% must be canonical`
|
return `Reference {{.reference}} must be canonical`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) NotAValidType() string {
|
func (l DefaultLocale) NotAValidType() string {
|
||||||
return `%type% is not a valid type -- `
|
return `has a primitive type that is NOT VALID -- given: {{.given}} Expected valid values are:{{.expected}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) Duplicated() string {
|
func (l DefaultLocale) Duplicated() string {
|
||||||
return `%type% type is duplicated`
|
return `{{.type}} type is duplicated`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l DefaultLocale) httpBadStatus() string {
|
func (l DefaultLocale) HttpBadStatus() string {
|
||||||
return `Could not read schema from HTTP, response status is %status%`
|
return `Could not read schema from HTTP, response status is {{.status}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replacement options: field, description, context, value
|
// Replacement options: field, description, context, value
|
||||||
func (l DefaultLocale) ErrorFormat() string {
|
func (l DefaultLocale) ErrorFormat() string {
|
||||||
return `%field%: %description%`
|
return `{{.field}}: {{.description}}`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse error
|
||||||
|
func (l DefaultLocale) ParseError() string {
|
||||||
|
return `Expected: {{.expected}}, given: Invalid JSON`
|
||||||
|
}
|
||||||
|
|
||||||
|
//If/Else
|
||||||
|
func (l DefaultLocale) ConditionThen() string {
|
||||||
|
return `Must validate "then" as "if" was valid`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l DefaultLocale) ConditionElse() string {
|
||||||
|
return `Must validate "else" as "if" was not valid`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
STRING_NUMBER = "number"
|
STRING_NUMBER = "number"
|
||||||
STRING_ARRAY_OF_STRINGS = "array of strings"
|
STRING_ARRAY_OF_STRINGS = "array of strings"
|
||||||
STRING_ARRAY_OF_SCHEMAS = "array of schemas"
|
STRING_ARRAY_OF_SCHEMAS = "array of schemas"
|
||||||
STRING_SCHEMA = "schema"
|
STRING_SCHEMA = "valid schema"
|
||||||
STRING_SCHEMA_OR_ARRAY_OF_STRINGS = "schema or array of strings"
|
STRING_SCHEMA_OR_ARRAY_OF_STRINGS = "schema or array of strings"
|
||||||
STRING_PROPERTIES = "properties"
|
STRING_PROPERTIES = "properties"
|
||||||
STRING_DEPENDENCY = "dependency"
|
STRING_DEPENDENCY = "dependency"
|
||||||
|
|
|
@ -40,25 +40,29 @@ type (
|
||||||
Field() string
|
Field() string
|
||||||
SetType(string)
|
SetType(string)
|
||||||
Type() string
|
Type() string
|
||||||
SetContext(*jsonContext)
|
SetContext(*JsonContext)
|
||||||
Context() *jsonContext
|
Context() *JsonContext
|
||||||
SetDescription(string)
|
SetDescription(string)
|
||||||
Description() string
|
Description() string
|
||||||
|
SetDescriptionFormat(string)
|
||||||
|
DescriptionFormat() string
|
||||||
SetValue(interface{})
|
SetValue(interface{})
|
||||||
Value() interface{}
|
Value() interface{}
|
||||||
SetDetails(ErrorDetails)
|
SetDetails(ErrorDetails)
|
||||||
Details() ErrorDetails
|
Details() ErrorDetails
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResultErrorFields holds the fields for each ResultError implementation.
|
// ResultErrorFields holds the fields for each ResultError implementation.
|
||||||
// ResultErrorFields implements the ResultError interface, so custom errors
|
// ResultErrorFields implements the ResultError interface, so custom errors
|
||||||
// can be defined by just embedding this type
|
// can be defined by just embedding this type
|
||||||
ResultErrorFields struct {
|
ResultErrorFields struct {
|
||||||
errorType string // A string with the type of error (i.e. invalid_type)
|
errorType string // A string with the type of error (i.e. invalid_type)
|
||||||
context *jsonContext // Tree like notation of the part that failed the validation. ex (root).a.b ...
|
context *JsonContext // Tree like notation of the part that failed the validation. ex (root).a.b ...
|
||||||
description string // A human readable error message
|
description string // A human readable error message
|
||||||
value interface{} // Value given by the JSON file that is the source of the error
|
descriptionFormat string // A format for human readable error message
|
||||||
details ErrorDetails
|
value interface{} // Value given by the JSON file that is the source of the error
|
||||||
|
details ErrorDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
Result struct {
|
Result struct {
|
||||||
|
@ -72,12 +76,6 @@ type (
|
||||||
// Field outputs the field name without the root context
|
// Field outputs the field name without the root context
|
||||||
// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName
|
// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName
|
||||||
func (v *ResultErrorFields) Field() string {
|
func (v *ResultErrorFields) Field() string {
|
||||||
if p, ok := v.Details()["property"]; ok {
|
|
||||||
if str, isString := p.(string); isString {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".")
|
return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +87,11 @@ func (v *ResultErrorFields) Type() string {
|
||||||
return v.errorType
|
return v.errorType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ResultErrorFields) SetContext(context *jsonContext) {
|
func (v *ResultErrorFields) SetContext(context *JsonContext) {
|
||||||
v.context = context
|
v.context = context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ResultErrorFields) Context() *jsonContext {
|
func (v *ResultErrorFields) Context() *JsonContext {
|
||||||
return v.context
|
return v.context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +103,14 @@ func (v *ResultErrorFields) Description() string {
|
||||||
return v.description
|
return v.description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *ResultErrorFields) SetDescriptionFormat(descriptionFormat string) {
|
||||||
|
v.descriptionFormat = descriptionFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ResultErrorFields) DescriptionFormat() string {
|
||||||
|
return v.descriptionFormat
|
||||||
|
}
|
||||||
|
|
||||||
func (v *ResultErrorFields) SetValue(value interface{}) {
|
func (v *ResultErrorFields) SetValue(value interface{}) {
|
||||||
v.value = value
|
v.value = value
|
||||||
}
|
}
|
||||||
|
@ -154,7 +160,19 @@ func (v *Result) Errors() []ResultError {
|
||||||
return v.errors
|
return v.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Result) addError(err ResultError, context *jsonContext, value interface{}, details ErrorDetails) {
|
// Add a fully filled error to the error set
|
||||||
|
// SetDescription() will be called with the result of the parsed err.DescriptionFormat()
|
||||||
|
func (v *Result) AddError(err ResultError, details ErrorDetails) {
|
||||||
|
if _, exists := details["context"]; !exists && err.Context() != nil {
|
||||||
|
details["context"] = err.Context().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details))
|
||||||
|
|
||||||
|
v.errors = append(v.errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Result) addInternalError(err ResultError, context *JsonContext, value interface{}, details ErrorDetails) {
|
||||||
newError(err, context, value, Locale, details)
|
newError(err, context, value, Locale, details)
|
||||||
v.errors = append(v.errors, err)
|
v.errors = append(v.errors, err)
|
||||||
v.score -= 2 // results in a net -1 when added to the +1 we get at the end of the validation function
|
v.score -= 2 // results in a net -1 when added to the +1 we get at the end of the validation function
|
||||||
|
|
|
@ -27,10 +27,11 @@
|
||||||
package gojsonschema
|
package gojsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/xeipuuv/gojsonreference"
|
"github.com/xeipuuv/gojsonreference"
|
||||||
)
|
)
|
||||||
|
@ -39,10 +40,13 @@ var (
|
||||||
// Locale is the default locale to use
|
// Locale is the default locale to use
|
||||||
// Library users can overwrite with their own implementation
|
// Library users can overwrite with their own implementation
|
||||||
Locale locale = DefaultLocale{}
|
Locale locale = DefaultLocale{}
|
||||||
|
|
||||||
|
// ErrorTemplateFuncs allows you to define custom template funcs for use in localization.
|
||||||
|
ErrorTemplateFuncs template.FuncMap
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewSchema(l JSONLoader) (*Schema, error) {
|
func NewSchema(l JSONLoader) (*Schema, error) {
|
||||||
return l.loadSchema()
|
return NewSchemaLoader().Compile(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Schema struct {
|
type Schema struct {
|
||||||
|
@ -52,8 +56,8 @@ type Schema struct {
|
||||||
referencePool *schemaReferencePool
|
referencePool *schemaReferencePool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Schema) parse(document interface{}) error {
|
func (d *Schema) parse(document interface{}, draft Draft) error {
|
||||||
d.rootSchema = &subSchema{property: STRING_ROOT_SCHEMA_PROPERTY}
|
d.rootSchema = &subSchema{property: STRING_ROOT_SCHEMA_PROPERTY, draft: &draft}
|
||||||
return d.parseSchema(document, d.rootSchema)
|
return d.parseSchema(document, d.rootSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,80 +73,95 @@ func (d *Schema) SetRootSchemaName(name string) {
|
||||||
//
|
//
|
||||||
func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) error {
|
func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) error {
|
||||||
|
|
||||||
|
if currentSchema.draft == nil {
|
||||||
|
if currentSchema.parent == nil {
|
||||||
|
return errors.New("Draft not set")
|
||||||
|
}
|
||||||
|
currentSchema.draft = currentSchema.parent.draft
|
||||||
|
}
|
||||||
|
|
||||||
|
// As of draft 6 "true" is equivalent to an empty schema "{}" and false equals "{"not":{}}"
|
||||||
|
if *currentSchema.draft >= Draft6 && isKind(documentNode, reflect.Bool) {
|
||||||
|
b := documentNode.(bool)
|
||||||
|
if b {
|
||||||
|
documentNode = map[string]interface{}{}
|
||||||
|
} else {
|
||||||
|
documentNode = map[string]interface{}{"not": true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !isKind(documentNode, reflect.Map) {
|
if !isKind(documentNode, reflect.Map) {
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
Locale.InvalidType(),
|
Locale.ParseError(),
|
||||||
ErrorDetails{
|
ErrorDetails{
|
||||||
"expected": TYPE_OBJECT,
|
"expected": STRING_SCHEMA,
|
||||||
"given": STRING_SCHEMA,
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
m := documentNode.(map[string]interface{})
|
m := documentNode.(map[string]interface{})
|
||||||
|
|
||||||
if currentSchema == d.rootSchema {
|
if currentSchema.parent == nil {
|
||||||
currentSchema.ref = &d.documentReference
|
currentSchema.ref = &d.documentReference
|
||||||
|
currentSchema.id = &d.documentReference
|
||||||
}
|
}
|
||||||
|
|
||||||
// $subSchema
|
if currentSchema.id == nil && currentSchema.parent != nil {
|
||||||
if existsMapKey(m, KEY_SCHEMA) {
|
currentSchema.id = currentSchema.parent.id
|
||||||
if !isKind(m[KEY_SCHEMA], reflect.String) {
|
|
||||||
return errors.New(formatErrorDescription(
|
|
||||||
Locale.InvalidType(),
|
|
||||||
ErrorDetails{
|
|
||||||
"expected": TYPE_STRING,
|
|
||||||
"given": KEY_SCHEMA,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
schemaRef := m[KEY_SCHEMA].(string)
|
|
||||||
schemaReference, err := gojsonreference.NewJsonReference(schemaRef)
|
|
||||||
currentSchema.subSchema = &schemaReference
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// $ref
|
// In draft 6 the id keyword was renamed to $id
|
||||||
if existsMapKey(m, KEY_REF) && !isKind(m[KEY_REF], reflect.String) {
|
// Hybrid mode uses the old id by default
|
||||||
|
var keyID string
|
||||||
|
|
||||||
|
switch *currentSchema.draft {
|
||||||
|
case Draft4:
|
||||||
|
keyID = KEY_ID
|
||||||
|
case Hybrid:
|
||||||
|
keyID = KEY_ID_NEW
|
||||||
|
if existsMapKey(m, KEY_ID) {
|
||||||
|
keyID = KEY_ID
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
keyID = KEY_ID_NEW
|
||||||
|
}
|
||||||
|
if existsMapKey(m, keyID) && !isKind(m[keyID], reflect.String) {
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
Locale.InvalidType(),
|
Locale.InvalidType(),
|
||||||
ErrorDetails{
|
ErrorDetails{
|
||||||
"expected": TYPE_STRING,
|
"expected": TYPE_STRING,
|
||||||
"given": KEY_REF,
|
"given": keyID,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
if k, ok := m[KEY_REF].(string); ok {
|
if k, ok := m[keyID].(string); ok {
|
||||||
|
jsonReference, err := gojsonreference.NewJsonReference(k)
|
||||||
if sch, ok := d.referencePool.Get(currentSchema.ref.String() + k); ok {
|
if err != nil {
|
||||||
|
return err
|
||||||
currentSchema.refSchema = sch
|
}
|
||||||
|
if currentSchema == d.rootSchema {
|
||||||
|
currentSchema.id = &jsonReference
|
||||||
} else {
|
} else {
|
||||||
|
ref, err := currentSchema.parent.id.Inherits(jsonReference)
|
||||||
var err error
|
|
||||||
err = d.parseReference(documentNode, currentSchema, k)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
currentSchema.id = ref
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// definitions
|
// definitions
|
||||||
if existsMapKey(m, KEY_DEFINITIONS) {
|
if existsMapKey(m, KEY_DEFINITIONS) {
|
||||||
if isKind(m[KEY_DEFINITIONS], reflect.Map) {
|
if isKind(m[KEY_DEFINITIONS], reflect.Map, reflect.Bool) {
|
||||||
currentSchema.definitions = make(map[string]*subSchema)
|
for _, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) {
|
||||||
for dk, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) {
|
if isKind(dv, reflect.Map, reflect.Bool) {
|
||||||
if isKind(dv, reflect.Map) {
|
|
||||||
newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema, ref: currentSchema.ref}
|
newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema}
|
||||||
currentSchema.definitions[dk] = newSchema
|
|
||||||
err := d.parseSchema(dv, newSchema)
|
err := d.parseSchema(dv, newSchema)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(err.Error())
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
|
@ -166,20 +185,6 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// id
|
|
||||||
if existsMapKey(m, KEY_ID) && !isKind(m[KEY_ID], reflect.String) {
|
|
||||||
return errors.New(formatErrorDescription(
|
|
||||||
Locale.InvalidType(),
|
|
||||||
ErrorDetails{
|
|
||||||
"expected": TYPE_STRING,
|
|
||||||
"given": KEY_ID,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
if k, ok := m[KEY_ID].(string); ok {
|
|
||||||
currentSchema.id = &k
|
|
||||||
}
|
|
||||||
|
|
||||||
// title
|
// title
|
||||||
if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) {
|
if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) {
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
|
@ -208,6 +213,39 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
currentSchema.description = &k
|
currentSchema.description = &k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $ref
|
||||||
|
if existsMapKey(m, KEY_REF) && !isKind(m[KEY_REF], reflect.String) {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": TYPE_STRING,
|
||||||
|
"given": KEY_REF,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if k, ok := m[KEY_REF].(string); ok {
|
||||||
|
|
||||||
|
jsonReference, err := gojsonreference.NewJsonReference(k)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSchema.ref = &jsonReference
|
||||||
|
|
||||||
|
if sch, ok := d.referencePool.Get(currentSchema.ref.String()); ok {
|
||||||
|
currentSchema.refSchema = sch
|
||||||
|
} else {
|
||||||
|
err := d.parseReference(documentNode, currentSchema)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// type
|
// type
|
||||||
if existsMapKey(m, KEY_TYPE) {
|
if existsMapKey(m, KEY_TYPE) {
|
||||||
if isKind(m[KEY_TYPE], reflect.String) {
|
if isKind(m[KEY_TYPE], reflect.String) {
|
||||||
|
@ -309,6 +347,26 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// propertyNames
|
||||||
|
if existsMapKey(m, KEY_PROPERTY_NAMES) && *currentSchema.draft >= Draft6 {
|
||||||
|
if isKind(m[KEY_PROPERTY_NAMES], reflect.Map, reflect.Bool) {
|
||||||
|
newSchema := &subSchema{property: KEY_PROPERTY_NAMES, parent: currentSchema, ref: currentSchema.ref}
|
||||||
|
currentSchema.propertyNames = newSchema
|
||||||
|
err := d.parseSchema(m[KEY_PROPERTY_NAMES], newSchema)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": STRING_SCHEMA,
|
||||||
|
"given": KEY_PATTERN_PROPERTIES,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dependencies
|
// dependencies
|
||||||
if existsMapKey(m, KEY_DEPENDENCIES) {
|
if existsMapKey(m, KEY_DEPENDENCIES) {
|
||||||
err := d.parseDependencies(m[KEY_DEPENDENCIES], currentSchema)
|
err := d.parseDependencies(m[KEY_DEPENDENCIES], currentSchema)
|
||||||
|
@ -321,7 +379,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
if existsMapKey(m, KEY_ITEMS) {
|
if existsMapKey(m, KEY_ITEMS) {
|
||||||
if isKind(m[KEY_ITEMS], reflect.Slice) {
|
if isKind(m[KEY_ITEMS], reflect.Slice) {
|
||||||
for _, itemElement := range m[KEY_ITEMS].([]interface{}) {
|
for _, itemElement := range m[KEY_ITEMS].([]interface{}) {
|
||||||
if isKind(itemElement, reflect.Map) {
|
if isKind(itemElement, reflect.Map, reflect.Bool) {
|
||||||
newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS}
|
newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS}
|
||||||
newSchema.ref = currentSchema.ref
|
newSchema.ref = currentSchema.ref
|
||||||
currentSchema.AddItemsChild(newSchema)
|
currentSchema.AddItemsChild(newSchema)
|
||||||
|
@ -340,7 +398,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
}
|
}
|
||||||
currentSchema.itemsChildrenIsSingleSchema = false
|
currentSchema.itemsChildrenIsSingleSchema = false
|
||||||
}
|
}
|
||||||
} else if isKind(m[KEY_ITEMS], reflect.Map) {
|
} else if isKind(m[KEY_ITEMS], reflect.Map, reflect.Bool) {
|
||||||
newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS}
|
newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS}
|
||||||
newSchema.ref = currentSchema.ref
|
newSchema.ref = currentSchema.ref
|
||||||
currentSchema.AddItemsChild(newSchema)
|
currentSchema.AddItemsChild(newSchema)
|
||||||
|
@ -395,7 +453,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
if *multipleOfValue <= 0 {
|
if multipleOfValue.Cmp(big.NewRat(0, 1)) <= 0 {
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
Locale.GreaterThanZero(),
|
Locale.GreaterThanZero(),
|
||||||
ErrorDetails{"number": KEY_MULTIPLE_OF},
|
ErrorDetails{"number": KEY_MULTIPLE_OF},
|
||||||
|
@ -416,20 +474,62 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
if existsMapKey(m, KEY_EXCLUSIVE_MINIMUM) {
|
if existsMapKey(m, KEY_EXCLUSIVE_MINIMUM) {
|
||||||
if isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) {
|
switch *currentSchema.draft {
|
||||||
|
case Draft4:
|
||||||
|
if !isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": TYPE_BOOLEAN,
|
||||||
|
"given": KEY_EXCLUSIVE_MINIMUM,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
if currentSchema.minimum == nil {
|
if currentSchema.minimum == nil {
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
Locale.CannotBeUsedWithout(),
|
Locale.CannotBeUsedWithout(),
|
||||||
ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM},
|
ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
exclusiveMinimumValue := m[KEY_EXCLUSIVE_MINIMUM].(bool)
|
if m[KEY_EXCLUSIVE_MINIMUM].(bool) {
|
||||||
currentSchema.exclusiveMinimum = exclusiveMinimumValue
|
currentSchema.exclusiveMinimum = currentSchema.minimum
|
||||||
} else {
|
currentSchema.minimum = nil
|
||||||
return errors.New(formatErrorDescription(
|
}
|
||||||
Locale.MustBeOfA(),
|
case Hybrid:
|
||||||
ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": TYPE_BOOLEAN},
|
if isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) {
|
||||||
))
|
if currentSchema.minimum == nil {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.CannotBeUsedWithout(),
|
||||||
|
ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if m[KEY_EXCLUSIVE_MINIMUM].(bool) {
|
||||||
|
currentSchema.exclusiveMinimum = currentSchema.minimum
|
||||||
|
currentSchema.minimum = nil
|
||||||
|
}
|
||||||
|
} else if isJsonNumber(m[KEY_EXCLUSIVE_MINIMUM]) {
|
||||||
|
currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM])
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER,
|
||||||
|
"given": KEY_EXCLUSIVE_MINIMUM,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if isJsonNumber(m[KEY_EXCLUSIVE_MINIMUM]) {
|
||||||
|
currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM])
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": TYPE_NUMBER,
|
||||||
|
"given": KEY_EXCLUSIVE_MINIMUM,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,29 +545,62 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
if existsMapKey(m, KEY_EXCLUSIVE_MAXIMUM) {
|
if existsMapKey(m, KEY_EXCLUSIVE_MAXIMUM) {
|
||||||
if isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) {
|
switch *currentSchema.draft {
|
||||||
|
case Draft4:
|
||||||
|
if !isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": TYPE_BOOLEAN,
|
||||||
|
"given": KEY_EXCLUSIVE_MAXIMUM,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
if currentSchema.maximum == nil {
|
if currentSchema.maximum == nil {
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
Locale.CannotBeUsedWithout(),
|
Locale.CannotBeUsedWithout(),
|
||||||
ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM},
|
ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
exclusiveMaximumValue := m[KEY_EXCLUSIVE_MAXIMUM].(bool)
|
if m[KEY_EXCLUSIVE_MAXIMUM].(bool) {
|
||||||
currentSchema.exclusiveMaximum = exclusiveMaximumValue
|
currentSchema.exclusiveMaximum = currentSchema.maximum
|
||||||
} else {
|
currentSchema.maximum = nil
|
||||||
return errors.New(formatErrorDescription(
|
}
|
||||||
Locale.MustBeOfA(),
|
case Hybrid:
|
||||||
ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": STRING_NUMBER},
|
if isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) {
|
||||||
))
|
if currentSchema.maximum == nil {
|
||||||
}
|
return errors.New(formatErrorDescription(
|
||||||
}
|
Locale.CannotBeUsedWithout(),
|
||||||
|
ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM},
|
||||||
if currentSchema.minimum != nil && currentSchema.maximum != nil {
|
))
|
||||||
if *currentSchema.minimum > *currentSchema.maximum {
|
}
|
||||||
return errors.New(formatErrorDescription(
|
if m[KEY_EXCLUSIVE_MAXIMUM].(bool) {
|
||||||
Locale.CannotBeGT(),
|
currentSchema.exclusiveMaximum = currentSchema.maximum
|
||||||
ErrorDetails{"x": KEY_MINIMUM, "y": KEY_MAXIMUM},
|
currentSchema.maximum = nil
|
||||||
))
|
}
|
||||||
|
} else if isJsonNumber(m[KEY_EXCLUSIVE_MAXIMUM]) {
|
||||||
|
currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM])
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER,
|
||||||
|
"given": KEY_EXCLUSIVE_MAXIMUM,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if isJsonNumber(m[KEY_EXCLUSIVE_MAXIMUM]) {
|
||||||
|
currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM])
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.InvalidType(),
|
||||||
|
ErrorDetails{
|
||||||
|
"expected": TYPE_NUMBER,
|
||||||
|
"given": KEY_EXCLUSIVE_MAXIMUM,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,11 +671,6 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
formatString, ok := m[KEY_FORMAT].(string)
|
formatString, ok := m[KEY_FORMAT].(string)
|
||||||
if ok && FormatCheckers.Has(formatString) {
|
if ok && FormatCheckers.Has(formatString) {
|
||||||
currentSchema.format = formatString
|
currentSchema.format = formatString
|
||||||
} else {
|
|
||||||
return errors.New(formatErrorDescription(
|
|
||||||
Locale.MustBeValidFormat(),
|
|
||||||
ErrorDetails{"key": KEY_FORMAT, "given": m[KEY_FORMAT]},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,8 +790,24 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if existsMapKey(m, KEY_CONTAINS) && *currentSchema.draft >= Draft6 {
|
||||||
|
newSchema := &subSchema{property: KEY_CONTAINS, parent: currentSchema, ref: currentSchema.ref}
|
||||||
|
currentSchema.contains = newSchema
|
||||||
|
err := d.parseSchema(m[KEY_CONTAINS], newSchema)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// validation : all
|
// validation : all
|
||||||
|
|
||||||
|
if existsMapKey(m, KEY_CONST) && *currentSchema.draft >= Draft6 {
|
||||||
|
err := currentSchema.AddConst(m[KEY_CONST])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if existsMapKey(m, KEY_ENUM) {
|
if existsMapKey(m, KEY_ENUM) {
|
||||||
if isKind(m[KEY_ENUM], reflect.Slice) {
|
if isKind(m[KEY_ENUM], reflect.Slice) {
|
||||||
for _, v := range m[KEY_ENUM].([]interface{}) {
|
for _, v := range m[KEY_ENUM].([]interface{}) {
|
||||||
|
@ -737,7 +881,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
if existsMapKey(m, KEY_NOT) {
|
if existsMapKey(m, KEY_NOT) {
|
||||||
if isKind(m[KEY_NOT], reflect.Map) {
|
if isKind(m[KEY_NOT], reflect.Map, reflect.Bool) {
|
||||||
newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref}
|
newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref}
|
||||||
currentSchema.SetNot(newSchema)
|
currentSchema.SetNot(newSchema)
|
||||||
err := d.parseSchema(m[KEY_NOT], newSchema)
|
err := d.parseSchema(m[KEY_NOT], newSchema)
|
||||||
|
@ -752,71 +896,91 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *currentSchema.draft >= Draft7 {
|
||||||
|
if existsMapKey(m, KEY_IF) {
|
||||||
|
if isKind(m[KEY_IF], reflect.Map, reflect.Bool) {
|
||||||
|
newSchema := &subSchema{property: KEY_IF, parent: currentSchema, ref: currentSchema.ref}
|
||||||
|
currentSchema.SetIf(newSchema)
|
||||||
|
err := d.parseSchema(m[KEY_IF], newSchema)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.MustBeOfAn(),
|
||||||
|
ErrorDetails{"x": KEY_IF, "y": TYPE_OBJECT},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if existsMapKey(m, KEY_THEN) {
|
||||||
|
if isKind(m[KEY_THEN], reflect.Map, reflect.Bool) {
|
||||||
|
newSchema := &subSchema{property: KEY_THEN, parent: currentSchema, ref: currentSchema.ref}
|
||||||
|
currentSchema.SetThen(newSchema)
|
||||||
|
err := d.parseSchema(m[KEY_THEN], newSchema)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.MustBeOfAn(),
|
||||||
|
ErrorDetails{"x": KEY_THEN, "y": TYPE_OBJECT},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if existsMapKey(m, KEY_ELSE) {
|
||||||
|
if isKind(m[KEY_ELSE], reflect.Map, reflect.Bool) {
|
||||||
|
newSchema := &subSchema{property: KEY_ELSE, parent: currentSchema, ref: currentSchema.ref}
|
||||||
|
currentSchema.SetElse(newSchema)
|
||||||
|
err := d.parseSchema(m[KEY_ELSE], newSchema)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New(formatErrorDescription(
|
||||||
|
Locale.MustBeOfAn(),
|
||||||
|
ErrorDetails{"x": KEY_ELSE, "y": TYPE_OBJECT},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema, reference string) (e error) {
|
func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema) error {
|
||||||
|
var (
|
||||||
|
refdDocumentNode interface{}
|
||||||
|
dsp *schemaPoolDocument
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
var err error
|
newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref}
|
||||||
|
|
||||||
|
d.referencePool.Add(currentSchema.ref.String(), newSchema)
|
||||||
|
|
||||||
|
dsp, err = d.pool.GetDocument(*currentSchema.ref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newSchema.id = currentSchema.ref
|
||||||
|
|
||||||
|
refdDocumentNode = dsp.Document
|
||||||
|
newSchema.draft = dsp.Draft
|
||||||
|
|
||||||
jsonReference, err := gojsonreference.NewJsonReference(reference)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneDocument := d.pool.GetStandaloneDocument()
|
if !isKind(refdDocumentNode, reflect.Map, reflect.Bool) {
|
||||||
|
|
||||||
if jsonReference.HasFullUrl {
|
|
||||||
currentSchema.ref = &jsonReference
|
|
||||||
} else {
|
|
||||||
inheritedReference, err := currentSchema.ref.Inherits(jsonReference)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
currentSchema.ref = inheritedReference
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonPointer := currentSchema.ref.GetPointer()
|
|
||||||
|
|
||||||
var refdDocumentNode interface{}
|
|
||||||
|
|
||||||
if standaloneDocument != nil {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
refdDocumentNode, _, err = jsonPointer.Get(standaloneDocument)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
dsp, err := d.pool.GetDocument(*currentSchema.ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
refdDocumentNode, _, err = jsonPointer.Get(dsp.Document)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isKind(refdDocumentNode, reflect.Map) {
|
|
||||||
return errors.New(formatErrorDescription(
|
return errors.New(formatErrorDescription(
|
||||||
Locale.MustBeOfType(),
|
Locale.MustBeOfType(),
|
||||||
ErrorDetails{"key": STRING_SCHEMA, "type": TYPE_OBJECT},
|
ErrorDetails{"key": STRING_SCHEMA, "type": TYPE_OBJECT},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the loaded referenced subSchema for the caller to update its current subSchema
|
err = d.parseSchema(refdDocumentNode, newSchema)
|
||||||
newSchemaDocument := refdDocumentNode.(map[string]interface{})
|
|
||||||
|
|
||||||
newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref}
|
|
||||||
d.referencePool.Add(currentSchema.ref.String()+reference, newSchema)
|
|
||||||
|
|
||||||
err = d.parseSchema(newSchemaDocument, newSchema)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -884,7 +1048,7 @@ func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *subS
|
||||||
currentSchema.dependencies[k] = valuesToRegister
|
currentSchema.dependencies[k] = valuesToRegister
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.Map:
|
case reflect.Map, reflect.Bool:
|
||||||
depSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref}
|
depSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref}
|
||||||
err := d.parseSchema(m[k], depSchema)
|
err := d.parseSchema(m[k], depSchema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
// Copyright 2018 johandorland ( https://github.com/johandorland )
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gojsonschema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/xeipuuv/gojsonreference"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SchemaLoader struct {
|
||||||
|
pool *schemaPool
|
||||||
|
AutoDetect bool
|
||||||
|
Validate bool
|
||||||
|
Draft Draft
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSchemaLoader() *SchemaLoader {
|
||||||
|
|
||||||
|
ps := &SchemaLoader{
|
||||||
|
pool: &schemaPool{
|
||||||
|
schemaPoolDocuments: make(map[string]*schemaPoolDocument),
|
||||||
|
},
|
||||||
|
AutoDetect: true,
|
||||||
|
Validate: false,
|
||||||
|
Draft: Hybrid,
|
||||||
|
}
|
||||||
|
ps.pool.autoDetect = &ps.AutoDetect
|
||||||
|
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SchemaLoader) validateMetaschema(documentNode interface{}) error {
|
||||||
|
|
||||||
|
var (
|
||||||
|
schema string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if sl.AutoDetect {
|
||||||
|
schema, _, err = parseSchemaURL(documentNode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no explicit "$schema" is used, use the default metaschema associated with the draft used
|
||||||
|
if schema == "" {
|
||||||
|
if sl.Draft == Hybrid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
schema = drafts.GetSchemaURL(sl.Draft)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Disable validation when loading the metaschema to prevent an infinite recursive loop
|
||||||
|
sl.Validate = false
|
||||||
|
|
||||||
|
metaSchema, err := sl.Compile(NewReferenceLoader(schema))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sl.Validate = true
|
||||||
|
|
||||||
|
result := metaSchema.validateDocument(documentNode)
|
||||||
|
|
||||||
|
if !result.Valid() {
|
||||||
|
var res bytes.Buffer
|
||||||
|
for _, err := range result.Errors() {
|
||||||
|
res.WriteString(err.String())
|
||||||
|
res.WriteString("\n")
|
||||||
|
}
|
||||||
|
return errors.New(res.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSchemas adds an arbritrary amount of schemas to the schema cache. As this function does not require
|
||||||
|
// an explicit URL, every schema should contain an $id, so that it can be referenced by the main schema
|
||||||
|
func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error {
|
||||||
|
emptyRef, _ := gojsonreference.NewJsonReference("")
|
||||||
|
|
||||||
|
for _, loader := range loaders {
|
||||||
|
doc, err := loader.LoadJSON()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sl.Validate {
|
||||||
|
if err := sl.validateMetaschema(doc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly use the Recursive function, so that it get only added to the schema pool by $id
|
||||||
|
// and not by the ref of the document as it's empty
|
||||||
|
if err = sl.pool.parseReferences(doc, emptyRef, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddSchema adds a schema under the provided URL to the schema cache
|
||||||
|
func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error {
|
||||||
|
|
||||||
|
ref, err := gojsonreference.NewJsonReference(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := loader.LoadJSON()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sl.Validate {
|
||||||
|
if err := sl.validateMetaschema(doc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sl.pool.parseReferences(doc, ref, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) {
|
||||||
|
|
||||||
|
ref, err := rootSchema.JsonReference()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := Schema{}
|
||||||
|
d.pool = sl.pool
|
||||||
|
d.pool.jsonLoaderFactory = rootSchema.LoaderFactory()
|
||||||
|
d.documentReference = ref
|
||||||
|
d.referencePool = newSchemaReferencePool()
|
||||||
|
|
||||||
|
var doc interface{}
|
||||||
|
if ref.String() != "" {
|
||||||
|
// Get document from schema pool
|
||||||
|
spd, err := d.pool.GetDocument(d.documentReference)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
doc = spd.Document
|
||||||
|
} else {
|
||||||
|
// Load JSON directly
|
||||||
|
doc, err = rootSchema.LoadJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// References need only be parsed if loading JSON directly
|
||||||
|
// as pool.GetDocument already does this for us if loading by reference
|
||||||
|
err = sl.pool.parseReferences(doc, ref, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sl.Validate {
|
||||||
|
if err := sl.validateMetaschema(doc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draft := sl.Draft
|
||||||
|
if sl.AutoDetect {
|
||||||
|
_, detectedDraft, err := parseSchemaURL(doc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if detectedDraft != nil {
|
||||||
|
draft = *detectedDraft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.parse(doc, draft)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &d, nil
|
||||||
|
}
|
|
@ -28,80 +28,188 @@ package gojsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/xeipuuv/gojsonreference"
|
"github.com/xeipuuv/gojsonreference"
|
||||||
)
|
)
|
||||||
|
|
||||||
type schemaPoolDocument struct {
|
type schemaPoolDocument struct {
|
||||||
Document interface{}
|
Document interface{}
|
||||||
|
Draft *Draft
|
||||||
}
|
}
|
||||||
|
|
||||||
type schemaPool struct {
|
type schemaPool struct {
|
||||||
schemaPoolDocuments map[string]*schemaPoolDocument
|
schemaPoolDocuments map[string]*schemaPoolDocument
|
||||||
standaloneDocument interface{}
|
jsonLoaderFactory JSONLoaderFactory
|
||||||
|
autoDetect *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSchemaPool() *schemaPool {
|
func (p *schemaPool) parseReferences(document interface{}, ref gojsonreference.JsonReference, pooled bool) error {
|
||||||
|
|
||||||
p := &schemaPool{}
|
var (
|
||||||
p.schemaPoolDocuments = make(map[string]*schemaPoolDocument)
|
draft *Draft
|
||||||
p.standaloneDocument = nil
|
err error
|
||||||
|
reference = ref.String()
|
||||||
|
)
|
||||||
|
// Only the root document should be added to the schema pool if pooled is true
|
||||||
|
if _, ok := p.schemaPoolDocuments[reference]; pooled && ok {
|
||||||
|
return fmt.Errorf("Reference already exists: \"%s\"", reference)
|
||||||
|
}
|
||||||
|
|
||||||
return p
|
if *p.autoDetect {
|
||||||
|
_, draft, err = parseSchemaURL(document)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.parseReferencesRecursive(document, ref, draft)
|
||||||
|
|
||||||
|
if pooled {
|
||||||
|
p.schemaPoolDocuments[reference] = &schemaPoolDocument{Document: document, Draft: draft}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *schemaPool) SetStandaloneDocument(document interface{}) {
|
func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference, draft *Draft) error {
|
||||||
p.standaloneDocument = document
|
// parseReferencesRecursive parses a JSON document and resolves all $id and $ref references.
|
||||||
}
|
// For $ref references it takes into account the $id scope it is in and replaces
|
||||||
|
// the reference by the absolute resolved reference
|
||||||
|
|
||||||
func (p *schemaPool) GetStandaloneDocument() (document interface{}) {
|
// When encountering errors it fails silently. Error handling is done when the schema
|
||||||
return p.standaloneDocument
|
// is syntactically parsed and any error encountered here should also come up there.
|
||||||
|
switch m := document.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
for _, v := range m {
|
||||||
|
p.parseReferencesRecursive(v, ref, draft)
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
localRef := &ref
|
||||||
|
|
||||||
|
keyID := KEY_ID_NEW
|
||||||
|
if existsMapKey(m, KEY_ID) {
|
||||||
|
keyID = KEY_ID
|
||||||
|
}
|
||||||
|
if existsMapKey(m, keyID) && isKind(m[keyID], reflect.String) {
|
||||||
|
jsonReference, err := gojsonreference.NewJsonReference(m[keyID].(string))
|
||||||
|
if err == nil {
|
||||||
|
localRef, err = ref.Inherits(jsonReference)
|
||||||
|
if err == nil {
|
||||||
|
if _, ok := p.schemaPoolDocuments[localRef.String()]; ok {
|
||||||
|
return fmt.Errorf("Reference already exists: \"%s\"", localRef.String())
|
||||||
|
}
|
||||||
|
p.schemaPoolDocuments[localRef.String()] = &schemaPoolDocument{Document: document, Draft: draft}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if existsMapKey(m, KEY_REF) && isKind(m[KEY_REF], reflect.String) {
|
||||||
|
jsonReference, err := gojsonreference.NewJsonReference(m[KEY_REF].(string))
|
||||||
|
if err == nil {
|
||||||
|
absoluteRef, err := localRef.Inherits(jsonReference)
|
||||||
|
if err == nil {
|
||||||
|
m[KEY_REF] = absoluteRef.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range m {
|
||||||
|
// const and enums should be interpreted literally, so ignore them
|
||||||
|
if k == KEY_CONST || k == KEY_ENUM {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Something like a property or a dependency is not a valid schema, as it might describe properties named "$ref", "$id" or "const", etc
|
||||||
|
// Therefore don't treat it like a schema.
|
||||||
|
if k == KEY_PROPERTIES || k == KEY_DEPENDENCIES || k == KEY_PATTERN_PROPERTIES {
|
||||||
|
if child, ok := v.(map[string]interface{}); ok {
|
||||||
|
for _, v := range child {
|
||||||
|
p.parseReferencesRecursive(v, *localRef, draft)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.parseReferencesRecursive(v, *localRef, draft)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) {
|
func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
spd *schemaPoolDocument
|
||||||
|
draft *Draft
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog("Get Document ( %s )", reference.String())
|
internalLog("Get Document ( %s )", reference.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
// Create a deep copy, so we can remove the fragment part later on without altering the original
|
||||||
|
refToUrl, _ := gojsonreference.NewJsonReference(reference.String())
|
||||||
|
|
||||||
// It is not possible to load anything that is not canonical...
|
// First check if the given fragment is a location independent identifier
|
||||||
if !reference.IsCanonical() {
|
// http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3
|
||||||
return nil, errors.New(formatErrorDescription(
|
|
||||||
Locale.ReferenceMustBeCanonical(),
|
|
||||||
ErrorDetails{"reference": reference},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
refToUrl := reference
|
if spd, ok = p.schemaPoolDocuments[refToUrl.String()]; ok {
|
||||||
refToUrl.GetUrl().Fragment = ""
|
|
||||||
|
|
||||||
var spd *schemaPoolDocument
|
|
||||||
|
|
||||||
// Try to find the requested document in the pool
|
|
||||||
for k := range p.schemaPoolDocuments {
|
|
||||||
if k == refToUrl.String() {
|
|
||||||
spd = p.schemaPoolDocuments[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if spd != nil {
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog(" From pool")
|
internalLog(" From pool")
|
||||||
}
|
}
|
||||||
return spd, nil
|
return spd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonReferenceLoader := NewReferenceLoader(reference.String())
|
// If the given reference is not a location independent identifier,
|
||||||
document, err := jsonReferenceLoader.loadJSON()
|
// strip the fragment and look for a document with it's base URI
|
||||||
|
|
||||||
|
refToUrl.GetUrl().Fragment = ""
|
||||||
|
|
||||||
|
if cachedSpd, ok := p.schemaPoolDocuments[refToUrl.String()]; ok {
|
||||||
|
document, _, err := reference.GetPointer().Get(cachedSpd.Document)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if internalLogEnabled {
|
||||||
|
internalLog(" From pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
spd = &schemaPoolDocument{Document: document, Draft: cachedSpd.Draft}
|
||||||
|
p.schemaPoolDocuments[reference.String()] = spd
|
||||||
|
|
||||||
|
return spd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is not possible to load anything remotely that is not canonical...
|
||||||
|
if !reference.IsCanonical() {
|
||||||
|
return nil, errors.New(formatErrorDescription(
|
||||||
|
Locale.ReferenceMustBeCanonical(),
|
||||||
|
ErrorDetails{"reference": reference.String()},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonReferenceLoader := p.jsonLoaderFactory.New(reference.String())
|
||||||
|
document, err := jsonReferenceLoader.LoadJSON()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
spd = &schemaPoolDocument{Document: document}
|
// add the whole document to the pool for potential re-use
|
||||||
// add the document to the pool for potential later use
|
p.parseReferences(document, refToUrl, true)
|
||||||
p.schemaPoolDocuments[refToUrl.String()] = spd
|
|
||||||
|
|
||||||
return spd, nil
|
_, draft, _ = parseSchemaURL(document)
|
||||||
|
|
||||||
|
// resolve the potential fragment and also cache it
|
||||||
|
document, _, err = reference.GetPointer().Get(document)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &schemaPoolDocument{Document: document, Draft: draft}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ func (p *schemaReferencePool) Add(ref string, sch *subSchema) {
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog(fmt.Sprintf("Add Schema Reference %s to pool", ref))
|
internalLog(fmt.Sprintf("Add Schema Reference %s to pool", ref))
|
||||||
}
|
}
|
||||||
|
if _, ok := p.documents[ref]; !ok {
|
||||||
p.documents[ref] = sch
|
p.documents[ref] = sch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ func (t *jsonSchemaType) IsTyped() bool {
|
||||||
func (t *jsonSchemaType) Add(etype string) error {
|
func (t *jsonSchemaType) Add(etype string) error {
|
||||||
|
|
||||||
if !isStringInSlice(JSON_TYPES, etype) {
|
if !isStringInSlice(JSON_TYPES, etype) {
|
||||||
return errors.New(formatErrorDescription(Locale.NotAValidType(), ErrorDetails{"type": etype}))
|
return errors.New(formatErrorDescription(Locale.NotAValidType(), ErrorDetails{"given": "/" + etype + "/", "expected": JSON_TYPES}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Contains(etype) {
|
if t.Contains(etype) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ package gojsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -35,8 +36,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
KEY_SCHEMA = "$subSchema"
|
KEY_SCHEMA = "$schema"
|
||||||
KEY_ID = "$id"
|
KEY_ID = "id"
|
||||||
|
KEY_ID_NEW = "$id"
|
||||||
KEY_REF = "$ref"
|
KEY_REF = "$ref"
|
||||||
KEY_TITLE = "title"
|
KEY_TITLE = "title"
|
||||||
KEY_DESCRIPTION = "description"
|
KEY_DESCRIPTION = "description"
|
||||||
|
@ -46,6 +48,7 @@ const (
|
||||||
KEY_PROPERTIES = "properties"
|
KEY_PROPERTIES = "properties"
|
||||||
KEY_PATTERN_PROPERTIES = "patternProperties"
|
KEY_PATTERN_PROPERTIES = "patternProperties"
|
||||||
KEY_ADDITIONAL_PROPERTIES = "additionalProperties"
|
KEY_ADDITIONAL_PROPERTIES = "additionalProperties"
|
||||||
|
KEY_PROPERTY_NAMES = "propertyNames"
|
||||||
KEY_DEFINITIONS = "definitions"
|
KEY_DEFINITIONS = "definitions"
|
||||||
KEY_MULTIPLE_OF = "multipleOf"
|
KEY_MULTIPLE_OF = "multipleOf"
|
||||||
KEY_MINIMUM = "minimum"
|
KEY_MINIMUM = "minimum"
|
||||||
|
@ -63,17 +66,23 @@ const (
|
||||||
KEY_MIN_ITEMS = "minItems"
|
KEY_MIN_ITEMS = "minItems"
|
||||||
KEY_MAX_ITEMS = "maxItems"
|
KEY_MAX_ITEMS = "maxItems"
|
||||||
KEY_UNIQUE_ITEMS = "uniqueItems"
|
KEY_UNIQUE_ITEMS = "uniqueItems"
|
||||||
|
KEY_CONTAINS = "contains"
|
||||||
|
KEY_CONST = "const"
|
||||||
KEY_ENUM = "enum"
|
KEY_ENUM = "enum"
|
||||||
KEY_ONE_OF = "oneOf"
|
KEY_ONE_OF = "oneOf"
|
||||||
KEY_ANY_OF = "anyOf"
|
KEY_ANY_OF = "anyOf"
|
||||||
KEY_ALL_OF = "allOf"
|
KEY_ALL_OF = "allOf"
|
||||||
KEY_NOT = "not"
|
KEY_NOT = "not"
|
||||||
|
KEY_IF = "if"
|
||||||
|
KEY_THEN = "then"
|
||||||
|
KEY_ELSE = "else"
|
||||||
)
|
)
|
||||||
|
|
||||||
type subSchema struct {
|
type subSchema struct {
|
||||||
|
draft *Draft
|
||||||
|
|
||||||
// basic subSchema meta properties
|
// basic subSchema meta properties
|
||||||
id *string
|
id *gojsonreference.JsonReference
|
||||||
title *string
|
title *string
|
||||||
description *string
|
description *string
|
||||||
|
|
||||||
|
@ -86,23 +95,19 @@ type subSchema struct {
|
||||||
ref *gojsonreference.JsonReference
|
ref *gojsonreference.JsonReference
|
||||||
// Schema referenced
|
// Schema referenced
|
||||||
refSchema *subSchema
|
refSchema *subSchema
|
||||||
// Json reference
|
|
||||||
subSchema *gojsonreference.JsonReference
|
|
||||||
|
|
||||||
// hierarchy
|
// hierarchy
|
||||||
parent *subSchema
|
parent *subSchema
|
||||||
definitions map[string]*subSchema
|
|
||||||
definitionsChildren []*subSchema
|
|
||||||
itemsChildren []*subSchema
|
itemsChildren []*subSchema
|
||||||
itemsChildrenIsSingleSchema bool
|
itemsChildrenIsSingleSchema bool
|
||||||
propertiesChildren []*subSchema
|
propertiesChildren []*subSchema
|
||||||
|
|
||||||
// validation : number / integer
|
// validation : number / integer
|
||||||
multipleOf *float64
|
multipleOf *big.Rat
|
||||||
maximum *float64
|
maximum *big.Rat
|
||||||
exclusiveMaximum bool
|
exclusiveMaximum *big.Rat
|
||||||
minimum *float64
|
minimum *big.Rat
|
||||||
exclusiveMinimum bool
|
exclusiveMinimum *big.Rat
|
||||||
|
|
||||||
// validation : string
|
// validation : string
|
||||||
minLength *int
|
minLength *int
|
||||||
|
@ -118,27 +123,43 @@ type subSchema struct {
|
||||||
dependencies map[string]interface{}
|
dependencies map[string]interface{}
|
||||||
additionalProperties interface{}
|
additionalProperties interface{}
|
||||||
patternProperties map[string]*subSchema
|
patternProperties map[string]*subSchema
|
||||||
|
propertyNames *subSchema
|
||||||
|
|
||||||
// validation : array
|
// validation : array
|
||||||
minItems *int
|
minItems *int
|
||||||
maxItems *int
|
maxItems *int
|
||||||
uniqueItems bool
|
uniqueItems bool
|
||||||
|
contains *subSchema
|
||||||
|
|
||||||
additionalItems interface{}
|
additionalItems interface{}
|
||||||
|
|
||||||
// validation : all
|
// validation : all
|
||||||
enum []string
|
_const *string //const is a golang keyword
|
||||||
|
enum []string
|
||||||
|
|
||||||
// validation : subSchema
|
// validation : subSchema
|
||||||
oneOf []*subSchema
|
oneOf []*subSchema
|
||||||
anyOf []*subSchema
|
anyOf []*subSchema
|
||||||
allOf []*subSchema
|
allOf []*subSchema
|
||||||
not *subSchema
|
not *subSchema
|
||||||
|
_if *subSchema // if/else are golang keywords
|
||||||
|
_then *subSchema
|
||||||
|
_else *subSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *subSchema) AddConst(i interface{}) error {
|
||||||
|
|
||||||
|
is, err := marshalWithoutNumber(i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s._const = is
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *subSchema) AddEnum(i interface{}) error {
|
func (s *subSchema) AddEnum(i interface{}) error {
|
||||||
|
|
||||||
is, err := marshalToJsonString(i)
|
is, err := marshalWithoutNumber(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -157,7 +178,7 @@ func (s *subSchema) AddEnum(i interface{}) error {
|
||||||
|
|
||||||
func (s *subSchema) ContainsEnum(i interface{}) (bool, error) {
|
func (s *subSchema) ContainsEnum(i interface{}) (bool, error) {
|
||||||
|
|
||||||
is, err := marshalToJsonString(i)
|
is, err := marshalWithoutNumber(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -181,6 +202,18 @@ func (s *subSchema) SetNot(subSchema *subSchema) {
|
||||||
s.not = subSchema
|
s.not = subSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *subSchema) SetIf(subSchema *subSchema) {
|
||||||
|
s._if = subSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *subSchema) SetThen(subSchema *subSchema) {
|
||||||
|
s._then = subSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *subSchema) SetElse(subSchema *subSchema) {
|
||||||
|
s._else = subSchema
|
||||||
|
}
|
||||||
|
|
||||||
func (s *subSchema) AddRequired(value string) error {
|
func (s *subSchema) AddRequired(value string) error {
|
||||||
|
|
||||||
if isStringInSlice(s.required, value) {
|
if isStringInSlice(s.required, value) {
|
||||||
|
@ -195,10 +228,6 @@ func (s *subSchema) AddRequired(value string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *subSchema) AddDefinitionChild(child *subSchema) {
|
|
||||||
s.definitionsChildren = append(s.definitionsChildren, child)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *subSchema) AddItemsChild(child *subSchema) {
|
func (s *subSchema) AddItemsChild(child *subSchema) {
|
||||||
s.itemsChildren = append(s.itemsChildren, child)
|
s.itemsChildren = append(s.itemsChildren, child)
|
||||||
}
|
}
|
||||||
|
@ -214,7 +243,7 @@ func (s *subSchema) PatternPropertiesString() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
patternPropertiesKeySlice := []string{}
|
patternPropertiesKeySlice := []string{}
|
||||||
for pk, _ := range s.patternProperties {
|
for pk := range s.patternProperties {
|
||||||
patternPropertiesKeySlice = append(patternPropertiesKeySlice, `"`+pk+`"`)
|
patternPropertiesKeySlice = append(patternPropertiesKeySlice, `"`+pk+`"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,23 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func isKind(what interface{}, kind reflect.Kind) bool {
|
func isKind(what interface{}, kinds ...reflect.Kind) bool {
|
||||||
return reflect.ValueOf(what).Kind() == kind
|
target := what
|
||||||
|
if isJsonNumber(what) {
|
||||||
|
// JSON Numbers are strings!
|
||||||
|
target = *mustBeNumber(what)
|
||||||
|
}
|
||||||
|
targetKind := reflect.ValueOf(target).Kind()
|
||||||
|
for _, kind := range kinds {
|
||||||
|
if targetKind == kind {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func existsMapKey(m map[string]interface{}, k string) bool {
|
func existsMapKey(m map[string]interface{}, k string) bool {
|
||||||
|
@ -51,6 +62,16 @@ func isStringInSlice(s []string, what string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// indexStringInSlice returns the index of the first instance of 'what' in s or -1 if it is not found in s.
|
||||||
|
func indexStringInSlice(s []string, what string) int {
|
||||||
|
for i := range s {
|
||||||
|
if s[i] == what {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
func marshalToJsonString(value interface{}) (*string, error) {
|
func marshalToJsonString(value interface{}) (*string, error) {
|
||||||
|
|
||||||
mBytes, err := json.Marshal(value)
|
mBytes, err := json.Marshal(value)
|
||||||
|
@ -62,6 +83,28 @@ func marshalToJsonString(value interface{}) (*string, error) {
|
||||||
return &sBytes, nil
|
return &sBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func marshalWithoutNumber(value interface{}) (*string, error) {
|
||||||
|
|
||||||
|
// The JSON is decoded using https://golang.org/pkg/encoding/json/#Decoder.UseNumber
|
||||||
|
// This means the numbers are internally still represented as strings and therefore 1.00 is unequal to 1
|
||||||
|
// One way to eliminate these differences is to decode and encode the JSON one more time without Decoder.UseNumber
|
||||||
|
// so that these differences in representation are removed
|
||||||
|
|
||||||
|
jsonString, err := marshalToJsonString(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var document interface{}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(*jsonString), &document)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return marshalToJsonString(document)
|
||||||
|
}
|
||||||
|
|
||||||
func isJsonNumber(what interface{}) bool {
|
func isJsonNumber(what interface{}) bool {
|
||||||
|
|
||||||
switch what.(type) {
|
switch what.(type) {
|
||||||
|
@ -73,20 +116,13 @@ func isJsonNumber(what interface{}) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkJsonNumber(what interface{}) (isValidFloat64 bool, isValidInt64 bool, isValidInt32 bool) {
|
func checkJsonInteger(what interface{}) (isInt bool) {
|
||||||
|
|
||||||
jsonNumber := what.(json.Number)
|
jsonNumber := what.(json.Number)
|
||||||
|
|
||||||
_, errFloat64 := jsonNumber.Float64()
|
bigFloat, isValidNumber := new(big.Rat).SetString(string(jsonNumber))
|
||||||
_, errInt64 := jsonNumber.Int64()
|
|
||||||
|
|
||||||
isValidFloat64 = errFloat64 == nil
|
return isValidNumber && bigFloat.IsInt()
|
||||||
isValidInt64 = errInt64 == nil
|
|
||||||
|
|
||||||
_, errInt32 := strconv.ParseInt(jsonNumber.String(), 10, 32)
|
|
||||||
isValidInt32 = isValidInt64 && errInt32 == nil
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,9 +147,9 @@ func mustBeInteger(what interface{}) *int {
|
||||||
|
|
||||||
number := what.(json.Number)
|
number := what.(json.Number)
|
||||||
|
|
||||||
_, _, isValidInt32 := checkJsonNumber(number)
|
isInt := checkJsonInteger(number)
|
||||||
|
|
||||||
if isValidInt32 {
|
if isInt {
|
||||||
|
|
||||||
int64Value, err := number.Int64()
|
int64Value, err := number.Int64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -132,15 +168,13 @@ func mustBeInteger(what interface{}) *int {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustBeNumber(what interface{}) *float64 {
|
func mustBeNumber(what interface{}) *big.Rat {
|
||||||
|
|
||||||
if isJsonNumber(what) {
|
if isJsonNumber(what) {
|
||||||
|
|
||||||
number := what.(json.Number)
|
number := what.(json.Number)
|
||||||
float64Value, err := number.Float64()
|
float64Value, success := new(big.Rat).SetString(string(number))
|
||||||
|
if success {
|
||||||
if err == nil {
|
return float64Value
|
||||||
return &float64Value
|
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ package gojsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -55,29 +56,32 @@ func (v *Schema) Validate(l JSONLoader) (*Result, error) {
|
||||||
|
|
||||||
// load document
|
// load document
|
||||||
|
|
||||||
root, err := l.loadJSON()
|
root, err := l.LoadJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return v.validateDocument(root), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Schema) validateDocument(root interface{}) *Result {
|
||||||
// begin validation
|
// begin validation
|
||||||
|
|
||||||
result := &Result{}
|
result := &Result{}
|
||||||
context := newJsonContext(STRING_CONTEXT_ROOT, nil)
|
context := NewJsonContext(STRING_CONTEXT_ROOT, nil)
|
||||||
v.rootSchema.validateRecursive(v.rootSchema, root, result, context)
|
v.rootSchema.validateRecursive(v.rootSchema, root, result, context)
|
||||||
|
|
||||||
return result, nil
|
return result
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *subSchema) subValidateWithContext(document interface{}, context *jsonContext) *Result {
|
func (v *subSchema) subValidateWithContext(document interface{}, context *JsonContext) *Result {
|
||||||
result := &Result{}
|
result := &Result{}
|
||||||
v.validateRecursive(v, document, result, context)
|
v.validateRecursive(v, document, result, context)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walker function to validate the json recursively against the subSchema
|
// Walker function to validate the json recursively against the subSchema
|
||||||
func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *jsonContext) {
|
func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) {
|
||||||
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog("validateRecursive %s", context.String())
|
internalLog("validateRecursive %s", context.String())
|
||||||
|
@ -93,7 +97,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
// Check for null value
|
// Check for null value
|
||||||
if currentNode == nil {
|
if currentNode == nil {
|
||||||
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_NULL) {
|
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_NULL) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(InvalidTypeError),
|
new(InvalidTypeError),
|
||||||
context,
|
context,
|
||||||
currentNode,
|
currentNode,
|
||||||
|
@ -114,18 +118,18 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
|
|
||||||
value := currentNode.(json.Number)
|
value := currentNode.(json.Number)
|
||||||
|
|
||||||
_, isValidInt64, _ := checkJsonNumber(value)
|
isInt := checkJsonInteger(value)
|
||||||
|
|
||||||
validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isValidInt64 && currentSubSchema.types.Contains(TYPE_INTEGER))
|
validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isInt && currentSubSchema.types.Contains(TYPE_INTEGER))
|
||||||
|
|
||||||
if currentSubSchema.types.IsTyped() && !validType {
|
if currentSubSchema.types.IsTyped() && !validType {
|
||||||
|
|
||||||
givenType := TYPE_INTEGER
|
givenType := TYPE_INTEGER
|
||||||
if !isValidInt64 {
|
if !isInt {
|
||||||
givenType = TYPE_NUMBER
|
givenType = TYPE_NUMBER
|
||||||
}
|
}
|
||||||
|
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(InvalidTypeError),
|
new(InvalidTypeError),
|
||||||
context,
|
context,
|
||||||
currentNode,
|
currentNode,
|
||||||
|
@ -154,7 +158,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
|
|
||||||
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_ARRAY) {
|
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_ARRAY) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(InvalidTypeError),
|
new(InvalidTypeError),
|
||||||
context,
|
context,
|
||||||
currentNode,
|
currentNode,
|
||||||
|
@ -177,7 +181,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_OBJECT) {
|
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_OBJECT) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(InvalidTypeError),
|
new(InvalidTypeError),
|
||||||
context,
|
context,
|
||||||
currentNode,
|
currentNode,
|
||||||
|
@ -202,7 +206,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
for _, pSchema := range currentSubSchema.propertiesChildren {
|
for _, pSchema := range currentSubSchema.propertiesChildren {
|
||||||
nextNode, ok := castCurrentNode[pSchema.property]
|
nextNode, ok := castCurrentNode[pSchema.property]
|
||||||
if ok {
|
if ok {
|
||||||
subContext := newJsonContext(pSchema.property, context)
|
subContext := NewJsonContext(pSchema.property, context)
|
||||||
v.validateRecursive(pSchema, nextNode, result, subContext)
|
v.validateRecursive(pSchema, nextNode, result, subContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +216,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
|
|
||||||
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_BOOLEAN) {
|
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_BOOLEAN) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(InvalidTypeError),
|
new(InvalidTypeError),
|
||||||
context,
|
context,
|
||||||
currentNode,
|
currentNode,
|
||||||
|
@ -234,7 +238,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
|
|
||||||
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_STRING) {
|
if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_STRING) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(InvalidTypeError),
|
new(InvalidTypeError),
|
||||||
context,
|
context,
|
||||||
currentNode,
|
currentNode,
|
||||||
|
@ -263,7 +267,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Different kinds of validation there, subSchema / common / array / object / string...
|
// Different kinds of validation there, subSchema / common / array / object / string...
|
||||||
func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *jsonContext) {
|
func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) {
|
||||||
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog("validateSchema %s", context.String())
|
internalLog("validateSchema %s", context.String())
|
||||||
|
@ -287,7 +291,7 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte
|
||||||
}
|
}
|
||||||
if !validatedAnyOf {
|
if !validatedAnyOf {
|
||||||
|
|
||||||
result.addError(new(NumberAnyOfError), context, currentNode, ErrorDetails{})
|
result.addInternalError(new(NumberAnyOfError), context, currentNode, ErrorDetails{})
|
||||||
|
|
||||||
if bestValidationResult != nil {
|
if bestValidationResult != nil {
|
||||||
// add error messages of closest matching subSchema as
|
// add error messages of closest matching subSchema as
|
||||||
|
@ -313,7 +317,7 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte
|
||||||
|
|
||||||
if nbValidated != 1 {
|
if nbValidated != 1 {
|
||||||
|
|
||||||
result.addError(new(NumberOneOfError), context, currentNode, ErrorDetails{})
|
result.addInternalError(new(NumberOneOfError), context, currentNode, ErrorDetails{})
|
||||||
|
|
||||||
if nbValidated == 0 {
|
if nbValidated == 0 {
|
||||||
// add error messages of closest matching subSchema as
|
// add error messages of closest matching subSchema as
|
||||||
|
@ -336,14 +340,14 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte
|
||||||
}
|
}
|
||||||
|
|
||||||
if nbValidated != len(currentSubSchema.allOf) {
|
if nbValidated != len(currentSubSchema.allOf) {
|
||||||
result.addError(new(NumberAllOfError), context, currentNode, ErrorDetails{})
|
result.addInternalError(new(NumberAllOfError), context, currentNode, ErrorDetails{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentSubSchema.not != nil {
|
if currentSubSchema.not != nil {
|
||||||
validationResult := currentSubSchema.not.subValidateWithContext(currentNode, context)
|
validationResult := currentSubSchema.not.subValidateWithContext(currentNode, context)
|
||||||
if validationResult.Valid() {
|
if validationResult.Valid() {
|
||||||
result.addError(new(NumberNotError), context, currentNode, ErrorDetails{})
|
result.addInternalError(new(NumberNotError), context, currentNode, ErrorDetails{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +360,7 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte
|
||||||
case []string:
|
case []string:
|
||||||
for _, dependOnKey := range dependency {
|
for _, dependOnKey := range dependency {
|
||||||
if _, dependencyResolved := currentNode.(map[string]interface{})[dependOnKey]; !dependencyResolved {
|
if _, dependencyResolved := currentNode.(map[string]interface{})[dependOnKey]; !dependencyResolved {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(MissingDependencyError),
|
new(MissingDependencyError),
|
||||||
context,
|
context,
|
||||||
currentNode,
|
currentNode,
|
||||||
|
@ -367,31 +371,65 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte
|
||||||
|
|
||||||
case *subSchema:
|
case *subSchema:
|
||||||
dependency.validateRecursive(dependency, currentNode, result, context)
|
dependency.validateRecursive(dependency, currentNode, result, context)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if currentSubSchema._if != nil {
|
||||||
|
validationResultIf := currentSubSchema._if.subValidateWithContext(currentNode, context)
|
||||||
|
if currentSubSchema._then != nil && validationResultIf.Valid() {
|
||||||
|
validationResultThen := currentSubSchema._then.subValidateWithContext(currentNode, context)
|
||||||
|
if !validationResultThen.Valid() {
|
||||||
|
result.addInternalError(new(ConditionThenError), context, currentNode, ErrorDetails{})
|
||||||
|
result.mergeErrors(validationResultThen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if currentSubSchema._else != nil && !validationResultIf.Valid() {
|
||||||
|
validationResultElse := currentSubSchema._else.subValidateWithContext(currentNode, context)
|
||||||
|
if !validationResultElse.Valid() {
|
||||||
|
result.addInternalError(new(ConditionElseError), context, currentNode, ErrorDetails{})
|
||||||
|
result.mergeErrors(validationResultElse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.incrementScore()
|
result.incrementScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) {
|
func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
|
||||||
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog("validateCommon %s", context.String())
|
internalLog("validateCommon %s", context.String())
|
||||||
internalLog(" %v", value)
|
internalLog(" %v", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const:
|
||||||
|
if currentSubSchema._const != nil {
|
||||||
|
vString, err := marshalWithoutNumber(value)
|
||||||
|
if err != nil {
|
||||||
|
result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err})
|
||||||
|
}
|
||||||
|
if *vString != *currentSubSchema._const {
|
||||||
|
result.addInternalError(new(ConstError),
|
||||||
|
context,
|
||||||
|
value,
|
||||||
|
ErrorDetails{
|
||||||
|
"allowed": *currentSubSchema._const,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// enum:
|
// enum:
|
||||||
if len(currentSubSchema.enum) > 0 {
|
if len(currentSubSchema.enum) > 0 {
|
||||||
has, err := currentSubSchema.ContainsEnum(value)
|
has, err := currentSubSchema.ContainsEnum(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.addError(new(InternalError), context, value, ErrorDetails{"error": err})
|
result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err})
|
||||||
}
|
}
|
||||||
if !has {
|
if !has {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(EnumError),
|
new(EnumError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -405,19 +443,19 @@ func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{
|
||||||
result.incrementScore()
|
result.incrementScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface{}, result *Result, context *jsonContext) {
|
func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface{}, result *Result, context *JsonContext) {
|
||||||
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog("validateArray %s", context.String())
|
internalLog("validateArray %s", context.String())
|
||||||
internalLog(" %v", value)
|
internalLog(" %v", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
nbItems := len(value)
|
nbValues := len(value)
|
||||||
|
|
||||||
// TODO explain
|
// TODO explain
|
||||||
if currentSubSchema.itemsChildrenIsSingleSchema {
|
if currentSubSchema.itemsChildrenIsSingleSchema {
|
||||||
for i := range value {
|
for i := range value {
|
||||||
subContext := newJsonContext(strconv.Itoa(i), context)
|
subContext := NewJsonContext(strconv.Itoa(i), context)
|
||||||
validationResult := currentSubSchema.itemsChildren[0].subValidateWithContext(value[i], subContext)
|
validationResult := currentSubSchema.itemsChildren[0].subValidateWithContext(value[i], subContext)
|
||||||
result.mergeErrors(validationResult)
|
result.mergeErrors(validationResult)
|
||||||
}
|
}
|
||||||
|
@ -425,24 +463,27 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface
|
||||||
if currentSubSchema.itemsChildren != nil && len(currentSubSchema.itemsChildren) > 0 {
|
if currentSubSchema.itemsChildren != nil && len(currentSubSchema.itemsChildren) > 0 {
|
||||||
|
|
||||||
nbItems := len(currentSubSchema.itemsChildren)
|
nbItems := len(currentSubSchema.itemsChildren)
|
||||||
nbValues := len(value)
|
|
||||||
|
|
||||||
if nbItems == nbValues {
|
// while we have both schemas and values, check them against each other
|
||||||
for i := 0; i != nbItems; i++ {
|
for i := 0; i != nbItems && i != nbValues; i++ {
|
||||||
subContext := newJsonContext(strconv.Itoa(i), context)
|
subContext := NewJsonContext(strconv.Itoa(i), context)
|
||||||
validationResult := currentSubSchema.itemsChildren[i].subValidateWithContext(value[i], subContext)
|
validationResult := currentSubSchema.itemsChildren[i].subValidateWithContext(value[i], subContext)
|
||||||
result.mergeErrors(validationResult)
|
result.mergeErrors(validationResult)
|
||||||
}
|
}
|
||||||
} else if nbItems < nbValues {
|
|
||||||
|
if nbItems < nbValues {
|
||||||
|
// we have less schemas than elements in the instance array,
|
||||||
|
// but that might be ok if "additionalItems" is specified.
|
||||||
|
|
||||||
switch currentSubSchema.additionalItems.(type) {
|
switch currentSubSchema.additionalItems.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
if !currentSubSchema.additionalItems.(bool) {
|
if !currentSubSchema.additionalItems.(bool) {
|
||||||
result.addError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{})
|
result.addInternalError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{})
|
||||||
}
|
}
|
||||||
case *subSchema:
|
case *subSchema:
|
||||||
additionalItemSchema := currentSubSchema.additionalItems.(*subSchema)
|
additionalItemSchema := currentSubSchema.additionalItems.(*subSchema)
|
||||||
for i := nbItems; i != nbValues; i++ {
|
for i := nbItems; i != nbValues; i++ {
|
||||||
subContext := newJsonContext(strconv.Itoa(i), context)
|
subContext := NewJsonContext(strconv.Itoa(i), context)
|
||||||
validationResult := additionalItemSchema.subValidateWithContext(value[i], subContext)
|
validationResult := additionalItemSchema.subValidateWithContext(value[i], subContext)
|
||||||
result.mergeErrors(validationResult)
|
result.mergeErrors(validationResult)
|
||||||
}
|
}
|
||||||
|
@ -453,8 +494,8 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface
|
||||||
|
|
||||||
// minItems & maxItems
|
// minItems & maxItems
|
||||||
if currentSubSchema.minItems != nil {
|
if currentSubSchema.minItems != nil {
|
||||||
if nbItems < int(*currentSubSchema.minItems) {
|
if nbValues < int(*currentSubSchema.minItems) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(ArrayMinItemsError),
|
new(ArrayMinItemsError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -463,8 +504,8 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if currentSubSchema.maxItems != nil {
|
if currentSubSchema.maxItems != nil {
|
||||||
if nbItems > int(*currentSubSchema.maxItems) {
|
if nbValues > int(*currentSubSchema.maxItems) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(ArrayMaxItemsError),
|
new(ArrayMaxItemsError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -476,27 +517,59 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface
|
||||||
// uniqueItems:
|
// uniqueItems:
|
||||||
if currentSubSchema.uniqueItems {
|
if currentSubSchema.uniqueItems {
|
||||||
var stringifiedItems []string
|
var stringifiedItems []string
|
||||||
for _, v := range value {
|
for j, v := range value {
|
||||||
vString, err := marshalToJsonString(v)
|
vString, err := marshalWithoutNumber(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.addError(new(InternalError), context, value, ErrorDetails{"err": err})
|
result.addInternalError(new(InternalError), context, value, ErrorDetails{"err": err})
|
||||||
}
|
}
|
||||||
if isStringInSlice(stringifiedItems, *vString) {
|
if i := indexStringInSlice(stringifiedItems, *vString); i > -1 {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(ItemsMustBeUniqueError),
|
new(ItemsMustBeUniqueError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
ErrorDetails{"type": TYPE_ARRAY},
|
ErrorDetails{"type": TYPE_ARRAY, "i": i, "j": j},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
stringifiedItems = append(stringifiedItems, *vString)
|
stringifiedItems = append(stringifiedItems, *vString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// contains:
|
||||||
|
|
||||||
|
if currentSubSchema.contains != nil {
|
||||||
|
validatedOne := false
|
||||||
|
var bestValidationResult *Result
|
||||||
|
|
||||||
|
for i, v := range value {
|
||||||
|
subContext := NewJsonContext(strconv.Itoa(i), context)
|
||||||
|
|
||||||
|
validationResult := currentSubSchema.contains.subValidateWithContext(v, subContext)
|
||||||
|
if validationResult.Valid() {
|
||||||
|
validatedOne = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if bestValidationResult == nil || validationResult.score > bestValidationResult.score {
|
||||||
|
bestValidationResult = validationResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !validatedOne {
|
||||||
|
result.addInternalError(
|
||||||
|
new(ArrayContainsError),
|
||||||
|
context,
|
||||||
|
value,
|
||||||
|
ErrorDetails{},
|
||||||
|
)
|
||||||
|
if bestValidationResult != nil {
|
||||||
|
result.mergeErrors(bestValidationResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.incrementScore()
|
result.incrementScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string]interface{}, result *Result, context *jsonContext) {
|
func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string]interface{}, result *Result, context *JsonContext) {
|
||||||
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog("validateObject %s", context.String())
|
internalLog("validateObject %s", context.String())
|
||||||
|
@ -506,7 +579,7 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
|
||||||
// minProperties & maxProperties:
|
// minProperties & maxProperties:
|
||||||
if currentSubSchema.minProperties != nil {
|
if currentSubSchema.minProperties != nil {
|
||||||
if len(value) < int(*currentSubSchema.minProperties) {
|
if len(value) < int(*currentSubSchema.minProperties) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(ArrayMinPropertiesError),
|
new(ArrayMinPropertiesError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -516,7 +589,7 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
|
||||||
}
|
}
|
||||||
if currentSubSchema.maxProperties != nil {
|
if currentSubSchema.maxProperties != nil {
|
||||||
if len(value) > int(*currentSubSchema.maxProperties) {
|
if len(value) > int(*currentSubSchema.maxProperties) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(ArrayMaxPropertiesError),
|
new(ArrayMaxPropertiesError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -531,7 +604,7 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
|
||||||
if ok {
|
if ok {
|
||||||
result.incrementScore()
|
result.incrementScore()
|
||||||
} else {
|
} else {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(RequiredError),
|
new(RequiredError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -562,10 +635,10 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
|
||||||
if found {
|
if found {
|
||||||
|
|
||||||
if pp_has && !pp_match {
|
if pp_has && !pp_match {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(AdditionalPropertyNotAllowedError),
|
new(AdditionalPropertyNotAllowedError),
|
||||||
context,
|
context,
|
||||||
value,
|
value[pk],
|
||||||
ErrorDetails{"property": pk},
|
ErrorDetails{"property": pk},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -573,10 +646,10 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if !pp_has || !pp_match {
|
if !pp_has || !pp_match {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(AdditionalPropertyNotAllowedError),
|
new(AdditionalPropertyNotAllowedError),
|
||||||
context,
|
context,
|
||||||
value,
|
value[pk],
|
||||||
ErrorDetails{"property": pk},
|
ErrorDetails{"property": pk},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -625,10 +698,10 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
|
||||||
|
|
||||||
if pp_has && !pp_match {
|
if pp_has && !pp_match {
|
||||||
|
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(InvalidPropertyPatternError),
|
new(InvalidPropertyPatternError),
|
||||||
context,
|
context,
|
||||||
value,
|
value[pk],
|
||||||
ErrorDetails{
|
ErrorDetails{
|
||||||
"property": pk,
|
"property": pk,
|
||||||
"pattern": currentSubSchema.PatternPropertiesString(),
|
"pattern": currentSubSchema.PatternPropertiesString(),
|
||||||
|
@ -639,10 +712,25 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// propertyNames:
|
||||||
|
if currentSubSchema.propertyNames != nil {
|
||||||
|
for pk := range value {
|
||||||
|
validationResult := currentSubSchema.propertyNames.subValidateWithContext(pk, context)
|
||||||
|
if !validationResult.Valid() {
|
||||||
|
result.addInternalError(new(InvalidPropertyNameError),
|
||||||
|
context,
|
||||||
|
value, ErrorDetails{
|
||||||
|
"property": pk,
|
||||||
|
})
|
||||||
|
result.mergeErrors(validationResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.incrementScore()
|
result.incrementScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *jsonContext) (has bool, matched bool) {
|
func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *JsonContext) (has bool, matched bool) {
|
||||||
|
|
||||||
if internalLogEnabled {
|
if internalLogEnabled {
|
||||||
internalLog("validatePatternProperty %s", context.String())
|
internalLog("validatePatternProperty %s", context.String())
|
||||||
|
@ -656,12 +744,10 @@ func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key str
|
||||||
for pk, pv := range currentSubSchema.patternProperties {
|
for pk, pv := range currentSubSchema.patternProperties {
|
||||||
if matches, _ := regexp.MatchString(pk, key); matches {
|
if matches, _ := regexp.MatchString(pk, key); matches {
|
||||||
has = true
|
has = true
|
||||||
subContext := newJsonContext(key, context)
|
subContext := NewJsonContext(key, context)
|
||||||
validationResult := pv.subValidateWithContext(value, subContext)
|
validationResult := pv.subValidateWithContext(value, subContext)
|
||||||
result.mergeErrors(validationResult)
|
result.mergeErrors(validationResult)
|
||||||
if validationResult.Valid() {
|
validatedkey = true
|
||||||
validatedkey = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,7 +760,7 @@ func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key str
|
||||||
return has, true
|
return has, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) {
|
func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
|
||||||
|
|
||||||
// Ignore JSON numbers
|
// Ignore JSON numbers
|
||||||
if isJsonNumber(value) {
|
if isJsonNumber(value) {
|
||||||
|
@ -696,7 +782,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{
|
||||||
// minLength & maxLength:
|
// minLength & maxLength:
|
||||||
if currentSubSchema.minLength != nil {
|
if currentSubSchema.minLength != nil {
|
||||||
if utf8.RuneCount([]byte(stringValue)) < int(*currentSubSchema.minLength) {
|
if utf8.RuneCount([]byte(stringValue)) < int(*currentSubSchema.minLength) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(StringLengthGTEError),
|
new(StringLengthGTEError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -706,7 +792,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{
|
||||||
}
|
}
|
||||||
if currentSubSchema.maxLength != nil {
|
if currentSubSchema.maxLength != nil {
|
||||||
if utf8.RuneCount([]byte(stringValue)) > int(*currentSubSchema.maxLength) {
|
if utf8.RuneCount([]byte(stringValue)) > int(*currentSubSchema.maxLength) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(StringLengthLTEError),
|
new(StringLengthLTEError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -718,7 +804,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{
|
||||||
// pattern:
|
// pattern:
|
||||||
if currentSubSchema.pattern != nil {
|
if currentSubSchema.pattern != nil {
|
||||||
if !currentSubSchema.pattern.MatchString(stringValue) {
|
if !currentSubSchema.pattern.MatchString(stringValue) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(DoesNotMatchPatternError),
|
new(DoesNotMatchPatternError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -731,7 +817,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{
|
||||||
// format
|
// format
|
||||||
if currentSubSchema.format != "" {
|
if currentSubSchema.format != "" {
|
||||||
if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) {
|
if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(DoesNotMatchFormatError),
|
new(DoesNotMatchFormatError),
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
|
@ -743,7 +829,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{
|
||||||
result.incrementScore()
|
result.incrementScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) {
|
func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
|
||||||
|
|
||||||
// Ignore non numbers
|
// Ignore non numbers
|
||||||
if !isJsonNumber(value) {
|
if !isJsonNumber(value) {
|
||||||
|
@ -756,72 +842,82 @@ func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{
|
||||||
}
|
}
|
||||||
|
|
||||||
number := value.(json.Number)
|
number := value.(json.Number)
|
||||||
float64Value, _ := number.Float64()
|
float64Value, _ := new(big.Rat).SetString(string(number))
|
||||||
|
|
||||||
// multipleOf:
|
// multipleOf:
|
||||||
if currentSubSchema.multipleOf != nil {
|
if currentSubSchema.multipleOf != nil {
|
||||||
|
if q := new(big.Rat).Quo(float64Value, currentSubSchema.multipleOf); !q.IsInt() {
|
||||||
if !isFloat64AnInteger(float64Value / *currentSubSchema.multipleOf) {
|
result.addInternalError(
|
||||||
result.addError(
|
|
||||||
new(MultipleOfError),
|
new(MultipleOfError),
|
||||||
context,
|
context,
|
||||||
resultErrorFormatJsonNumber(number),
|
resultErrorFormatJsonNumber(number),
|
||||||
ErrorDetails{"multiple": *currentSubSchema.multipleOf},
|
ErrorDetails{"multiple": new(big.Float).SetRat(currentSubSchema.multipleOf)},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//maximum & exclusiveMaximum:
|
//maximum & exclusiveMaximum:
|
||||||
if currentSubSchema.maximum != nil {
|
if currentSubSchema.maximum != nil {
|
||||||
if currentSubSchema.exclusiveMaximum {
|
if float64Value.Cmp(currentSubSchema.maximum) == 1 {
|
||||||
if float64Value >= *currentSubSchema.maximum {
|
result.addInternalError(
|
||||||
result.addError(
|
new(NumberLTEError),
|
||||||
new(NumberLTError),
|
context,
|
||||||
context,
|
resultErrorFormatJsonNumber(number),
|
||||||
resultErrorFormatJsonNumber(number),
|
ErrorDetails{
|
||||||
ErrorDetails{
|
"max": currentSubSchema.maximum,
|
||||||
"max": resultErrorFormatNumber(*currentSubSchema.maximum),
|
},
|
||||||
},
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if currentSubSchema.exclusiveMaximum != nil {
|
||||||
if float64Value > *currentSubSchema.maximum {
|
if float64Value.Cmp(currentSubSchema.exclusiveMaximum) >= 0 {
|
||||||
result.addError(
|
result.addInternalError(
|
||||||
new(NumberLTEError),
|
new(NumberLTError),
|
||||||
context,
|
context,
|
||||||
resultErrorFormatJsonNumber(number),
|
resultErrorFormatJsonNumber(number),
|
||||||
ErrorDetails{
|
ErrorDetails{
|
||||||
"max": resultErrorFormatNumber(*currentSubSchema.maximum),
|
"max": currentSubSchema.exclusiveMaximum,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//minimum & exclusiveMinimum:
|
//minimum & exclusiveMinimum:
|
||||||
if currentSubSchema.minimum != nil {
|
if currentSubSchema.minimum != nil {
|
||||||
if currentSubSchema.exclusiveMinimum {
|
if float64Value.Cmp(currentSubSchema.minimum) == -1 {
|
||||||
if float64Value <= *currentSubSchema.minimum {
|
result.addInternalError(
|
||||||
result.addError(
|
new(NumberGTEError),
|
||||||
new(NumberGTError),
|
context,
|
||||||
context,
|
resultErrorFormatJsonNumber(number),
|
||||||
resultErrorFormatJsonNumber(number),
|
ErrorDetails{
|
||||||
ErrorDetails{
|
"min": currentSubSchema.minimum,
|
||||||
"min": resultErrorFormatNumber(*currentSubSchema.minimum),
|
},
|
||||||
},
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if currentSubSchema.exclusiveMinimum != nil {
|
||||||
if float64Value < *currentSubSchema.minimum {
|
if float64Value.Cmp(currentSubSchema.exclusiveMinimum) <= 0 {
|
||||||
result.addError(
|
// if float64Value <= *currentSubSchema.minimum {
|
||||||
new(NumberGTEError),
|
result.addInternalError(
|
||||||
context,
|
new(NumberGTError),
|
||||||
resultErrorFormatJsonNumber(number),
|
context,
|
||||||
ErrorDetails{
|
resultErrorFormatJsonNumber(number),
|
||||||
"min": resultErrorFormatNumber(*currentSubSchema.minimum),
|
ErrorDetails{
|
||||||
},
|
"min": currentSubSchema.exclusiveMinimum,
|
||||||
)
|
},
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format
|
||||||
|
if currentSubSchema.format != "" {
|
||||||
|
if !FormatCheckers.IsFormat(currentSubSchema.format, float64Value) {
|
||||||
|
result.addInternalError(
|
||||||
|
new(DoesNotMatchFormatError),
|
||||||
|
context,
|
||||||
|
value,
|
||||||
|
ErrorDetails{"format": currentSubSchema.format},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue