Skip to content

Commit

Permalink
feat(draft2019-09): support for unevaluatedProperties
Browse files Browse the repository at this point in the history
  • Loading branch information
Arqu committed Apr 15, 2020
1 parent 17c33bf commit 138d555
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 1 deletion.
173 changes: 173 additions & 0 deletions keywords_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ func (p Properties) JSONChildren() (res map[string]JSONPather) {
return
}

func (p *Properties) AppendItems(items *Properties) {
for k, sch := range *items {
(*p)[k] = sch
}
}

// PatternProperties determines how child instances validate for objects, and does not directly validate the immediate instance itself.
// Validation of the primitive instance type against this keyword always succeeds.
// Validation succeeds if, for each instance name that matches any regular expressions that appear as a property name in this
Expand Down Expand Up @@ -220,6 +226,12 @@ func (p PatternProperties) MarshalJSON() ([]byte, error) {
return json.Marshal(obj)
}

func (p *PatternProperties) AppendItems(items *PatternProperties) {
for _, pProp := range *items {
*p = append(*p, pProp)
}
}

// AdditionalProperties determines how child instances validate for objects, and does not directly validate the immediate instance itself.
// Validation with "AdditionalProperties" applies only to the child values of instance names that do not match any names in "Properties",
// and do not match any regular expression in "PatternProperties".
Expand All @@ -244,24 +256,30 @@ func (ap AdditionalProperties) Validate(propPath string, data interface{}, errs
return
}

evaluatedProperties := make(map[string]bool)

if obj, ok := data.(map[string]interface{}); ok {
KEYS:
for key, val := range obj {
if ap.Properties != nil {
for propKey := range *ap.Properties {
if propKey == key {
evaluatedProperties[key] = true
continue KEYS
}
}
}
if ap.patterns != nil {
for _, ptn := range *ap.patterns {
if ptn.re.Match([]byte(key)) {
evaluatedProperties[key] = true
continue KEYS
}
}
}
// fmt.Println("TEST3")
// c := len(*errs)
// d, _ := jp.Descendant(key)
d, _ := jp.Descendant(key)
ap.Schema.Validate(d.String(), val, errs)
// if len(*errs) > c {
Expand Down Expand Up @@ -452,3 +470,158 @@ func (p *PropertyNames) UnmarshalJSON(data []byte) error {
func (p PropertyNames) MarshalJSON() ([]byte, error) {
return json.Marshal(Schema(p))
}

type UnevaluatedProperties struct {
AdditionalProperties *AdditionalProperties
Properties *Properties
patterns *PatternProperties
Schema *Schema
If *If
}

// NewPropertyNames allocates a new PropertyNames validator
func NewUnevaluatedProperties() Validator {
return &UnevaluatedProperties{}
}

// Validate implements the validator interface for PropertyNames
func (p UnevaluatedProperties) Validate(propPath string, data interface{}, errs *[]ValError) {
jp, err := jsonpointer.Parse(propPath)
if err != nil {
AddError(errs, propPath, nil, "invalid property path")
return
}

sch := p.Schema
if sch.schemaType == schemaTypeFalse {
return
}

if p.AdditionalProperties != nil {
return
}

if p.If != nil {
ifSchema := p.If.Schema
test := &[]ValError{}
ifSchema.Validate(propPath, data, test)
if len(*test) == 0 {
if ifSchema.Validators["properties"] != nil {
if p.Properties != nil {
p.Properties.AppendItems(ifSchema.Validators["properties"].(*Properties))
} else {
p.Properties = ifSchema.Validators["properties"].(*Properties)
}
}
if ifSchema.Validators["patternProperties"] != nil {
if p.patterns != nil {
p.patterns.AppendItems(ifSchema.Validators["patternProperties"].(*PatternProperties))
} else {
p.patterns = ifSchema.Validators["patternProperties"].(*PatternProperties)
}
}
if ifSchema.Validators["additionalProperties"] != nil {
return
}

if p.If.Then != nil {
thenSchema := *p.If.Then
if thenSchema.Validators["properties"] != nil {
if p.Properties != nil {
p.Properties.AppendItems(thenSchema.Validators["properties"].(*Properties))
} else {
p.Properties = thenSchema.Validators["properties"].(*Properties)
}
}
if thenSchema.Validators["patternProperties"] != nil {
if p.patterns != nil {
p.patterns.AppendItems(thenSchema.Validators["patternProperties"].(*PatternProperties))
} else {
p.patterns = thenSchema.Validators["patternProperties"].(*PatternProperties)
}
}
if thenSchema.Validators["additionalProperties"] != nil {
return
}
}
} else if p.If.Else != nil {
elseSchema := *p.If.Else
if elseSchema.Validators["properties"] != nil {
if p.Properties != nil {
p.Properties.AppendItems(elseSchema.Validators["properties"].(*Properties))
} else {
p.Properties = elseSchema.Validators["properties"].(*Properties)
}
}
if elseSchema.Validators["patternProperties"] != nil {
if p.patterns != nil {
p.patterns.AppendItems(elseSchema.Validators["patternProperties"].(*PatternProperties))
} else {
p.patterns = elseSchema.Validators["patternProperties"].(*PatternProperties)
}
}
if elseSchema.Validators["additionalProperties"] != nil {
return
}
}
}

evaluatedProperties := make(map[string]bool)

if obj, ok := data.(map[string]interface{}); ok {
for key, val := range obj {
if p.Properties != nil {
for propKey := range *p.Properties {
if propKey == key {
evaluatedProperties[key] = true
continue
}
}
}
if p.patterns != nil {
for _, ptn := range *p.patterns {
if ptn.re.Match([]byte(key)) {
evaluatedProperties[key] = true
continue
}
}
}

if evaluatedProperties[key] {
continue
}

d, _ := jp.Descendant(key)
p.Schema.Validate(d.String(), val, errs)
}
}
}

// UnmarshalJSON implements the json.Unmarshaler interface for AdditionalProperties
func (ap *UnevaluatedProperties) UnmarshalJSON(data []byte) error {
sch := &Schema{}
if err := json.Unmarshal(data, sch); err != nil {
return err
}
// fmt.Println("unmarshal:", sch.Ref)
*ap = UnevaluatedProperties{Schema: sch}
return nil
}

// JSONProp implements JSON property name indexing for AdditionalProperties
func (ap *UnevaluatedProperties) JSONProp(name string) interface{} {
return ap.Schema.JSONProp(name)
}

// JSONChildren implements the JSONContainer interface for AdditionalProperties
func (ap *UnevaluatedProperties) JSONChildren() (res map[string]JSONPather) {
if ap.Schema.Ref != "" {
return map[string]JSONPather{"$ref": ap.Schema}
}
return ap.Schema.JSONChildren()
}

// MarshalJSON implements json.Marshaler for AdditionalProperties
func (ap UnevaluatedProperties) MarshalJSON() ([]byte, error) {
return json.Marshal(ap.Schema)
}
13 changes: 13 additions & 0 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,19 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
sch.Validators["additionalProperties"].(*AdditionalProperties).patterns = sch.Validators["patternProperties"].(*PatternProperties)
}

if sch.Validators["properties"] != nil && sch.Validators["unevaluatedProperties"] != nil {
sch.Validators["unevaluatedProperties"].(*UnevaluatedProperties).Properties = sch.Validators["properties"].(*Properties)
}
if sch.Validators["patternProperties"] != nil && sch.Validators["unevaluatedProperties"] != nil {
sch.Validators["unevaluatedProperties"].(*UnevaluatedProperties).patterns = sch.Validators["patternProperties"].(*PatternProperties)
}
if sch.Validators["additionalProperties"] != nil && sch.Validators["unevaluatedProperties"] != nil {
sch.Validators["unevaluatedProperties"].(*UnevaluatedProperties).AdditionalProperties = sch.Validators["additionalProperties"].(*AdditionalProperties)
}
if sch.Validators["if"] != nil && sch.Validators["unevaluatedProperties"] != nil {
sch.Validators["unevaluatedProperties"].(*UnevaluatedProperties).If = sch.Validators["if"].(*If)
}

*s = Schema(*sch)
return nil
}
Expand Down
4 changes: 3 additions & 1 deletion schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ func TestDraft2019_09(t *testing.T) {
"testdata/draft2019-09/propertyNames.json",
"testdata/draft2019-09/required.json",
"testdata/draft2019-09/type.json",
"testdata/draft2019-09/unevaluatedProperties.json",
"testdata/draft2019-09/uniqueItems.json",

"testdata/draft2019-09/optional/refOfUnknownKeyword.json",
Expand Down Expand Up @@ -481,7 +482,8 @@ func TestDraft2019_09(t *testing.T) {
// "testdata/draft2019-09/ref.json",
// "testdata/draft2019-09/refRemote.json",
// "testdata/draft2019-09/unevaluatedItems.json",
// "testdata/draft2019-09/unevaluatedProperties.json",

// TODO: implement support
// "testdata/draft2019-09/optional/bignum.json",
// "testdata/draft2019-09/optional/content.json",
// "testdata/draft2019-09/optional/ecmascript-regex.json",
Expand Down
1 change: 1 addition & 0 deletions validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ var DefaultValidators = map[string]ValMaker{
"properties": NewProperties,
"patternProperties": NewPatternProperties,
"additionalProperties": NewAdditionalProperties,
"unevaluatedProperties": NewUnevaluatedProperties,
"dependencies": NewDependencies,
"propertyNames": NewPropertyNames,

Expand Down

0 comments on commit 138d555

Please sign in to comment.