diff --git a/.gitignore b/.gitignore index c378da3..4caec1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store coverage.txt -.vscode \ No newline at end of file +.vscode +.idea \ No newline at end of file diff --git a/keyword.go b/keyword.go index eb7b18c..efd78e5 100644 --- a/keyword.go +++ b/keyword.go @@ -179,6 +179,13 @@ type Keyword interface { Resolve(pointer jptr.Pointer, uri string) *Schema } +// SchemaKeyword is a kind of Keyword which exposes a GetSchema method for returning the underlying Schema. This should +// be implemented by other keywords whose value is a Schema (i.e. via a type definition like `type Not Schema`). +type SchemaKeyword interface { + Keyword + GetSchema() *Schema +} + // KeyMaker is a function that generates instances of a Keyword. // Calls to KeyMaker will be passed directly to json.Marshal, // so the returned value should be a pointer diff --git a/keywords_array.go b/keywords_array.go index 24c36ff..f365ecf 100644 --- a/keywords_array.go +++ b/keywords_array.go @@ -100,11 +100,18 @@ func (it Items) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for Items -func (it Items) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (it Items) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} + + if it.single { + res["."] = it.Schemas[0] + return + } + for i, sch := range it.Schemas { res[strconv.Itoa(i)] = sch } + return } @@ -265,13 +272,18 @@ func (c *Contains) ValidateKeyword(ctx context.Context, currentState *Validation } } +// GetSchema implements the SchemaKeyword for Contains +func (c *Contains) GetSchema() *Schema { + return (*Schema)(c) +} + // JSONProp implements the JSONPather for Contains func (c Contains) JSONProp(name string) interface{} { return Schema(c).JSONProp(name) } // JSONChildren implements the JSONContainer interface for Contains -func (c Contains) JSONChildren() (res map[string]JSONPather) { +func (c Contains) JSONChildren() (res map[string]interface{}) { return Schema(c).JSONChildren() } @@ -383,6 +395,11 @@ func (ai *AdditionalItems) ValidateKeyword(ctx context.Context, currentState *Va } } +// GetSchema implements the SchemaKeyword for AdditionalItems +func (ai *AdditionalItems) GetSchema() *Schema { + return (*Schema)(ai) +} + // UnmarshalJSON implements the json.Unmarshaler interface for AdditionalItems func (ai *AdditionalItems) UnmarshalJSON(data []byte) error { sch := &Schema{} @@ -433,6 +450,11 @@ func (ui *UnevaluatedItems) ValidateKeyword(ctx context.Context, currentState *V } } +// GetSchema implements the SchemaKeyword for UnevaluatedItems +func (ui *UnevaluatedItems) GetSchema() *Schema { + return (*Schema)(ui) +} + // UnmarshalJSON implements the json.Unmarshaler interface for UnevaluatedItems func (ui *UnevaluatedItems) UnmarshalJSON(data []byte) error { sch := &Schema{} diff --git a/keywords_boolean.go b/keywords_boolean.go index 9e32040..28c3058 100644 --- a/keywords_boolean.go +++ b/keywords_boolean.go @@ -84,8 +84,8 @@ func (a AllOf) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for AllOf -func (a AllOf) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (a AllOf) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} for i, sch := range a { res[strconv.Itoa(i)] = sch } @@ -163,8 +163,8 @@ func (a AnyOf) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for AnyOf -func (a AnyOf) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (a AnyOf) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} for i, sch := range a { res[strconv.Itoa(i)] = sch } @@ -252,8 +252,8 @@ func (o OneOf) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for OneOf -func (o OneOf) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (o OneOf) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} for i, sch := range o { res[strconv.Itoa(i)] = sch } @@ -293,13 +293,18 @@ func (n *Not) ValidateKeyword(ctx context.Context, currentState *ValidationState } } +// GetSchema implements the SchemaKeyword for Not +func (n *Not) GetSchema() *Schema { + return (*Schema)(n) +} + // JSONProp implements the JSONPather for Not func (n Not) JSONProp(name string) interface{} { return Schema(n).JSONProp(name) } // JSONChildren implements the JSONContainer interface for Not -func (n Not) JSONChildren() (res map[string]JSONPather) { +func (n Not) JSONChildren() (res map[string]interface{}) { return Schema(n).JSONChildren() } diff --git a/keywords_conditional.go b/keywords_conditional.go index 15559e7..9fcc4ff 100644 --- a/keywords_conditional.go +++ b/keywords_conditional.go @@ -49,13 +49,18 @@ func (f *If) ValidateKeyword(ctx context.Context, currentState *ValidationState, currentState.Misc["ifResult"] = subState.IsValid() } +// GetSchema implements the SchemaKeyword for If +func (f *If) GetSchema() *Schema { + return (*Schema)(f) +} + // JSONProp implements the JSONPather for If func (f If) JSONProp(name string) interface{} { return Schema(f).JSONProp(name) } // JSONChildren implements the JSONContainer interface for If -func (f If) JSONChildren() (res map[string]JSONPather) { +func (f If) JSONChildren() (res map[string]interface{}) { return Schema(f).JSONChildren() } @@ -116,13 +121,18 @@ func (t *Then) ValidateKeyword(ctx context.Context, currentState *ValidationStat currentState.UpdateEvaluatedPropsAndItems(subState) } +// GetSchema implements the SchemaKeyword for Then +func (t *Then) GetSchema() *Schema { + return (*Schema)(t) +} + // JSONProp implements the JSONPather for Then func (t Then) JSONProp(name string) interface{} { return Schema(t).JSONProp(name) } // JSONChildren implements the JSONContainer interface for Then -func (t Then) JSONChildren() (res map[string]JSONPather) { +func (t Then) JSONChildren() (res map[string]interface{}) { return Schema(t).JSONChildren() } @@ -180,13 +190,18 @@ func (e *Else) ValidateKeyword(ctx context.Context, currentState *ValidationStat sch.ValidateKeyword(ctx, subState, data) } +// GetSchema implements the SchemaKeyword for Else +func (e *Else) GetSchema() *Schema { + return (*Schema)(e) +} + // JSONProp implements the JSONPather for Else func (e Else) JSONProp(name string) interface{} { return Schema(e).JSONProp(name) } // JSONChildren implements the JSONContainer interface for Else -func (e Else) JSONChildren() (res map[string]JSONPather) { +func (e Else) JSONChildren() (res map[string]interface{}) { return Schema(e).JSONChildren() } diff --git a/keywords_core.go b/keywords_core.go index 1c61bdd..ac96cc6 100644 --- a/keywords_core.go +++ b/keywords_core.go @@ -661,8 +661,8 @@ func (d Defs) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for Defs -func (d Defs) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (d Defs) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} for key, sch := range d { res[key] = sch } diff --git a/keywords_object.go b/keywords_object.go index f4f4d55..9ba3d2a 100644 --- a/keywords_object.go +++ b/keywords_object.go @@ -72,8 +72,8 @@ func (p Properties) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for Properties -func (p Properties) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (p Properties) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} for key, sch := range p { res[key] = sch } @@ -252,8 +252,8 @@ func (p PatternProperties) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for PatternProperties -func (p PatternProperties) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (p PatternProperties) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} for i, pp := range p { res[strconv.Itoa(i)] = pp.schema } @@ -341,6 +341,11 @@ func (ap *AdditionalProperties) ValidateKeyword(ctx context.Context, currentStat } } +// GetSchema implements the SchemaKeyword for AdditionalProperties +func (ap *AdditionalProperties) GetSchema() *Schema { + return (*Schema)(ap) +} + // UnmarshalJSON implements the json.Unmarshaler interface for AdditionalProperties func (ap *AdditionalProperties) UnmarshalJSON(data []byte) error { sch := &Schema{} @@ -351,6 +356,16 @@ func (ap *AdditionalProperties) UnmarshalJSON(data []byte) error { return nil } +// JSONProp implements the JSONPather for AdditionalProperties +func (ap AdditionalProperties) JSONProp(name string) interface{} { + return Schema(ap).JSONProp(name) +} + +// JSONChildren implements the JSONContainer interface for AdditionalProperties +func (ap AdditionalProperties) JSONChildren() (res map[string]interface{}) { + return Schema(ap).JSONChildren() +} + // PropertyNames defines the propertyNames JSON Schema keyword type PropertyNames Schema @@ -383,13 +398,18 @@ func (p *PropertyNames) ValidateKeyword(ctx context.Context, currentState *Valid } } +// GetSchema implements the SchemaKeyword for PropertyNames +func (p *PropertyNames) GetSchema() *Schema { + return (*Schema)(p) +} + // JSONProp implements the JSONPather for PropertyNames func (p PropertyNames) JSONProp(name string) interface{} { return Schema(p).JSONProp(name) } // JSONChildren implements the JSONContainer interface for PropertyNames -func (p PropertyNames) JSONChildren() (res map[string]JSONPather) { +func (p PropertyNames) JSONChildren() (res map[string]interface{}) { return Schema(p).JSONChildren() } @@ -478,8 +498,8 @@ func (d DependentSchemas) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for DependentSchemas -func (d DependentSchemas) JSONChildren() (r map[string]JSONPather) { - r = map[string]JSONPather{} +func (d DependentSchemas) JSONChildren() (r map[string]interface{}) { + r = map[string]interface{}{} for key, val := range d { r[key] = val } @@ -519,6 +539,11 @@ func (d *SchemaDependency) ValidateKeyword(ctx context.Context, currentState *Va d.schema.ValidateKeyword(ctx, subState, data) } +// GetSchema implements the SchemaKeyword for SchemaDependency +func (d *SchemaDependency) GetSchema() *Schema { + return d.schema +} + // MarshalJSON implements the json.Marshaler interface for SchemaDependency func (d SchemaDependency) MarshalJSON() ([]byte, error) { return json.Marshal(d.schema) @@ -591,8 +616,8 @@ func (d DependentRequired) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for DependentRequired -func (d DependentRequired) JSONChildren() (r map[string]JSONPather) { - r = map[string]JSONPather{} +func (d DependentRequired) JSONChildren() (r map[string]interface{}) { + r = map[string]interface{}{} for key, val := range d { r[key] = val } @@ -681,6 +706,11 @@ func (up *UnevaluatedProperties) ValidateKeyword(ctx context.Context, currentSta } } +// GetSchema implements the SchemaKeyword for UnevaluatedProperties +func (up *UnevaluatedProperties) GetSchema() *Schema { + return (*Schema)(up) +} + // UnmarshalJSON implements the json.Unmarshaler interface for UnevaluatedProperties func (up *UnevaluatedProperties) UnmarshalJSON(data []byte) error { sch := &Schema{} diff --git a/keywords_standard.go b/keywords_standard.go index 271c8b7..650d15f 100644 --- a/keywords_standard.go +++ b/keywords_standard.go @@ -107,8 +107,8 @@ func (e Enum) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for Enum -func (e Enum) JSONChildren() (res map[string]JSONPather) { - res = map[string]JSONPather{} +func (e Enum) JSONChildren() (res map[string]interface{}) { + res = map[string]interface{}{} for i, bs := range e { res[strconv.Itoa(i)] = bs } @@ -256,6 +256,12 @@ func (t Type) JSONProp(name string) interface{} { return t.vals[idx] } +func (t Type) Values() []string { + values := make([]string, len(t.vals)) + copy(values, t.vals) + return values +} + // UnmarshalJSON implements the json.Unmarshaler interface for Type func (t *Type) UnmarshalJSON(data []byte) error { var single string diff --git a/schema.go b/schema.go index babce5b..a0c0e0e 100644 --- a/schema.go +++ b/schema.go @@ -146,20 +146,22 @@ func (s Schema) JSONProp(name string) interface{} { } // JSONChildren implements the JSONContainer interface for Schema -func (s Schema) JSONChildren() map[string]JSONPather { - ch := map[string]JSONPather{} +func (s Schema) JSONChildren() map[string]interface{} { + ch := map[string]interface{}{} if s.keywords != nil { - for key, val := range s.keywords { - if jp, ok := val.(JSONPather); ok { - ch[key] = jp - } + for _, key := range s.orderedkeywords { + ch[key] = s.keywords[key] } } return ch } +func (s *Schema) GetSchema() *Schema { + return s +} + // _schema is an internal struct for encoding & decoding purposes type _schema struct { ID string `json:"$id,omitempty"` diff --git a/traversal.go b/traversal.go index 224d99b..9570ac8 100644 --- a/traversal.go +++ b/traversal.go @@ -14,11 +14,11 @@ type JSONPather interface { // JSONContainer is an interface that enables tree traversal by listing // the immideate children of an object type JSONContainer interface { - // JSONChildren should return all immidiate children of this element - JSONChildren() map[string]JSONPather + // JSONChildren should return all immediate children of this element + JSONChildren() map[string]interface{} } -func walkJSON(elem JSONPather, fn func(elem JSONPather) error) error { +func walkJSON(elem interface{}, fn func(elem interface{}) error) error { if err := fn(elem); err != nil { return err } diff --git a/traversal_test.go b/traversal_test.go index 4ef0599..3f33f08 100644 --- a/traversal_test.go +++ b/traversal_test.go @@ -49,10 +49,10 @@ func TestReferenceTraversal(t *testing.T) { } elements := 0 - expectElements := 14 + expectElements := 35 refs := 0 - expectRefs := 6 - walkJSON(rs, func(elem JSONPather) error { + expectRefs := 7 + walkJSON(rs, func(elem interface{}) error { elements++ if sch, ok := elem.(*Schema); ok { if sch.HasKeyword("$ref") { @@ -74,7 +74,7 @@ func TestReferenceTraversal(t *testing.T) { elements int refs int }{ - {`{ "not" : { "$ref":"#" }}`, 2, 0}, + {`{ "not" : { "$ref":"#" }}`, 3, 1}, } for i, c := range cases { @@ -86,10 +86,10 @@ func TestReferenceTraversal(t *testing.T) { elements := 0 refs := 0 - walkJSON(rs, func(elem JSONPather) error { + walkJSON(rs, func(elem interface{}) error { elements++ - if sch, ok := elem.(*Schema); ok { - if sch.HasKeyword("$ref") { + if kw, ok := elem.(SchemaKeyword); ok { + if kw.GetSchema().HasKeyword("$ref") { refs++ } }