Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(draft2019-09): upgrading jsonschema to support draft2019-09 #63

Closed
wants to merge 12 commits into from
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
module github.com/qri-io/jsonschema

go 1.14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use go 1.13 for now. Apparently IPFS doesn't work with 1.14


require (
github.com/qri-io/jsonpointer v0.1.0
github.com/sergi/go-diff v1.0.0
github.com/stretchr/testify v1.3.0 // indirect
)

replace github.com/qri-io/jsonpointer => /Users/arqu/dev/qri/jsonpointer
19 changes: 19 additions & 0 deletions keywords/draft2019_09_keywords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package keywords

import (
// kw "github.com/qri-io/jsonschema/keywords"
)

func LoadDraft() {
// standard keywords
kw.RegisterKeyword("type", NewVoid)
kw.RegisterKeyword("enum", NewVoid)
kw.RegisterKeyword("const", NewVoid)

// numeric keywords
kw.RegisterKeyword("multipleOf", NewVoid)
kw.RegisterKeyword("maximum", NewVoid)
kw.RegisterKeyword("exclusiveMaximum", NewVoid)
kw.RegisterKeyword("minimum", NewVoid)
kw.RegisterKeyword("exclusiveMinimum", NewVoid)
}
20 changes: 20 additions & 0 deletions keywords/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package keywords

import (
"fmt"
js "github.com/qri-io/jsonschema"
)

type Id struct {}

func NewId() Keyword {
return &Id{}
}

func (i *Id) Validate(propPath string, data interface{}, errs *[]KeyError) {
fmt.Println("WARN: Using Id Validator - always True")
}

func (i *Id) RegisterSubschemas(uri string, registry *js.SchemaRegistry) {

}
177 changes: 177 additions & 0 deletions keywords/keywords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package keywords

import (
"bytes"
"encoding/json"
"fmt"
)

var notSupported = map[string]bool{
"$schema": true,
"$id": true,
"$anchor": true,
"$recursiveAnchor": true,
"$defs": true,
"$ref": true,
"$recursiveRef": true,
"title": true,
"description": true,
"default": true,
"examples": true,
"readOnly": true,
"writeOnly": true,
"$comment": true,

// string keywords
"maxLength": true,
"minLength": true,
"pattern": true,

// boolean keywords
"allOf": true,
"anyOf": true,
"oneOf": true,
"not": true,

// array keywords
"items": true,
"additionalItems": true,
"maxItems": true,
"minItems": true,
"uniqueItems": true,
"contains": true,

// object keywords
"maxProperties": true,
"minProperties": true,
"required": true,
"properties": true,
"patternProperties": true,
"additionalProperties": true,
"dependencies": true,
"propertyNames": true,

// conditional keywords
"if": true,
"then": true,
"else": true,

//optional formats
"format": true,
}

var KeywordRegistry = map[string]KeyMaker{}

func IsKeyword(prop string) bool {
_, ok := KeywordRegistry[prop]
return ok
}

func GetKeyword(prop string) Keyword {
if !IsKeyword(prop) {
return NewVoid()
}
return KeywordRegistry[prop]()
}

func RegisterKeyword(prop string, maker KeyMaker) {
KeywordRegistry[prop] = maker
}

// MaxValueErrStringLen sets how long a value can be before it's length is truncated
// when printing error strings
// a special value of -1 disables output trimming
var MaxKeywordErrStringLen = 20

// Validator is an interface for anything that can validate.
// JSON-Schema keywords are all examples of validators
type Keyword interface {
// Validate checks decoded JSON data and writes
// validation errors (if any) to an outparam slice of ValErrors
// propPath indicates the position of data in the json tree
Validate(propPath string, data interface{}, errs *[]KeyError)
// ValidateFromContext(schCtx *SchemaContext, errs *[]ValError)

RegisterSubschemas(uri string, registry *SchemaRegistry)
}

// BaseValidator is a foundation for building a validator
type BaseKeyword struct {
path string
}

// SetPath sets base validator's path
func (b *BaseKeyword) SetPath(path string) {
b.path = path
}

// Path gives this validator's path
func (b BaseKeyword) Path() string {
return b.path
}

// AddError is a convenience method for appending a new error to an existing error slice
func (b BaseKeyword) AddError(errs *[]KeyError, propPath string, data interface{}, msg string) {
*errs = append(*errs, KeyError{
PropertyPath: propPath,
RulePath: b.Path(),
InvalidValue: data,
Message: msg,
})
}

// ValMaker is a function that generates instances of a validator.
// Calls to ValMaker will be passed directly to json.Marshal,
// so the returned value should be a pointer
type KeyMaker func() Keyword




// ValError represents a single error in an instance of a schema
// The only absolutely-required property is Message.
type KeyError struct {
// PropertyPath is a string path that leads to the
// property that produced the error
PropertyPath string `json:"propertyPath,omitempty"`
// InvalidValue is the value that returned the error
InvalidValue interface{} `json:"invalidValue,omitempty"`
// RulePath is the path to the rule that errored
RulePath string `json:"rulePath,omitempty"`
// Message is a human-readable description of the error
Message string `json:"message"`
}

// Error implements the error interface for ValError
func (v KeyError) Error() string {
// [propPath]: [value] [message]
if v.PropertyPath != "" && v.InvalidValue != nil {
return fmt.Sprintf("%s: %s %s", v.PropertyPath, InvalidValueString(v.InvalidValue), v.Message)
} else if v.PropertyPath != "" {
return fmt.Sprintf("%s: %s", v.PropertyPath, v.Message)
}
return v.Message
}

// InvalidValueString returns the errored value as a string
func InvalidValueString(data interface{}) string {
bt, err := json.Marshal(data)
if err != nil {
return ""
}
bt = bytes.Replace(bt, []byte{'\n', '\r'}, []byte{' '}, -1)
if MaxKeywordErrStringLen != -1 && len(bt) > MaxKeywordErrStringLen {
bt = append(bt[:MaxKeywordErrStringLen], []byte("...")...)
}
return string(bt)
}

// AddError creates and appends a ValError to errs
func AddError(errs *[]KeyError, propPath string, data interface{}, msg string) {
*errs = append(*errs, KeyError{
PropertyPath: propPath,
InvalidValue: data,
Message: msg,
})
}

19 changes: 19 additions & 0 deletions keywords/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package keywords

import (
"fmt"
)

type Template struct {}

func NewTemplate() Keyword {
return &Template{}
}

func (t *Template) Validate(propPath string, data interface{}, errs *[]KeyError) {
fmt.Println("WARN: Using Template Validator - always True")
}

func (t *Template) RegisterSubschemas(uri string, registry *SchemaRegistry) {

}
19 changes: 19 additions & 0 deletions keywords/void.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package keywords

import (
"fmt"
)

type Void struct {}

func NewVoid() Keyword {
return &Void{}
}

func (vo *Void) Validate(propPath string, data interface{}, errs *[]KeyError) {
fmt.Println("WARN: Using Void Validator - always True")
}

func (vo *Void) RegisterSubschemas(uri string, registry *SchemaRegistry) {

}
2 changes: 2 additions & 0 deletions keywords_booleans.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonschema

import (
// "fmt"
"encoding/json"
"strconv"
)
Expand All @@ -17,6 +18,7 @@ func NewAllOf() Validator {
// Validate implements the validator interface for AllOf
func (a AllOf) Validate(propPath string, data interface{}, errs *[]ValError) {
for _, sch := range a {
// fmt.Println(sch)
sch.Validate(propPath, data, errs)
}
}
Expand Down
2 changes: 1 addition & 1 deletion keywords_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (f Format) Validate(propPath string, data interface{}, errs *[]ValError) {
// from RFC 3339, section 5.6 [RFC3339]
// https://tools.ietf.org/html/rfc3339#section-5.6
func isValidDateTime(dateTime string) error {
if _, err := time.Parse(time.RFC3339, dateTime); err != nil {
if _, err := time.Parse(time.RFC3339, strings.ToUpper(dateTime)); err != nil {
return fmt.Errorf("date-time incorrectly Formatted: %s", err.Error())
}
return nil
Expand Down
Loading