From e5ee36d055a08b8f90d06c2870671adb1cb8fb00 Mon Sep 17 00:00:00 2001 From: Vassil Kovatchev Date: Fri, 26 Jul 2024 10:07:27 -0400 Subject: [PATCH 1/4] Propagate recursive parent descriptor with ValidationContext --- compiler.go | 2 +- extension.go | 6 ++++++ schema.go | 27 ++++++++++++++++++++------- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/compiler.go b/compiler.go index 4101925..f102d4c 100644 --- a/compiler.go +++ b/compiler.go @@ -852,7 +852,7 @@ func (c *Compiler) validateSchema(r *resource, v interface{}, vloc string) error } validate := func(meta *Schema) error { - return meta.validateValue(v, v, vloc) + return meta.validateValue(v, v, vloc, ParentDescriptor{}) } meta := r.draft.meta diff --git a/extension.go b/extension.go index 38a9b2b..ee42ff4 100644 --- a/extension.go +++ b/extension.go @@ -92,6 +92,7 @@ type ValidationContext struct { result validationResult doc interface{} vloc string + parent ParentDescriptor validate func(sch *Schema, schPath string, v interface{}, vpath string) error validateInplace func(sch *Schema, schPath string) error validationError func(keywordPath string, format string, a ...interface{}) *ValidationError @@ -137,6 +138,11 @@ func (ctx ValidationContext) GetDoc() interface{} { return ctx.doc } +// GetDoc returns the parent descriptor of the value being validated. +func (ctx ValidationContext) GetParent() ParentDescriptor { + return ctx.parent +} + // Group is used by extensions to group multiple errors as causes to parent error. // This is useful in implementing keywords like allOf where each schema specified // in allOf can result a validationError. diff --git a/schema.go b/schema.go index fa2d63e..aeadf3e 100644 --- a/schema.go +++ b/schema.go @@ -104,6 +104,11 @@ type Schema struct { Extensions map[string]ExtSchema } +type ParentDescriptor struct { + parent *ParentDescriptor + value interface{} +} + func (s *Schema) String() string { return s.Location } @@ -165,10 +170,10 @@ func (s *Schema) hasVocab(name string) bool { // returns InfiniteLoopError if it detects loop during validation. // returns InvalidJSONTypeError if it detects any non json value in v. func (s *Schema) Validate(v interface{}) (err error) { - return s.validateValue(v, v, "") + return s.validateValue(v, v, "", ParentDescriptor{}) } -func (s *Schema) validateValue(doc interface{}, v interface{}, vloc string) (err error) { +func (s *Schema) validateValue(doc interface{}, v interface{}, vloc string, parent ParentDescriptor) (err error) { defer func() { if r := recover(); r != nil { switch r := r.(type) { @@ -179,7 +184,7 @@ func (s *Schema) validateValue(doc interface{}, v interface{}, vloc string) (err } } }() - if _, err := s.validate(nil, 0, "", doc, v, vloc); err != nil { + if _, err := s.validate(nil, 0, "", doc, v, vloc, parent); err != nil { ve := ValidationError{ KeywordLocation: "", AbsoluteKeywordLocation: s.Location, @@ -192,7 +197,7 @@ func (s *Schema) validateValue(doc interface{}, v interface{}, vloc string) (err } // validate validates given value v with this schema. -func (s *Schema) validate(scope []schemaRef, vscope int, spath string, doc interface{}, v interface{}, vloc string) (result validationResult, err error) { +func (s *Schema) validate(scope []schemaRef, vscope int, spath string, doc interface{}, v interface{}, vloc string, parent ParentDescriptor) (result validationResult, err error) { validationError := func(keywordPath string, format string, a ...interface{}) *ValidationError { return &ValidationError{ KeywordLocation: keywordLocation(scope, keywordPath), @@ -202,6 +207,7 @@ func (s *Schema) validate(scope []schemaRef, vscope int, spath string, doc inter } } + thisValue := v sref := schemaRef{spath, s, false} if err := checkLoop(scope[len(scope)-vscope:], sref); err != nil { panic(err) @@ -225,15 +231,22 @@ func (s *Schema) validate(scope []schemaRef, vscope int, spath string, doc inter validate := func(sch *Schema, schPath string, v interface{}, vpath string) error { vloc := vloc + subParent := parent + if vpath != "" { vloc += "/" + vpath + subParent = ParentDescriptor{ + parent: &parent, + value: thisValue, + } + } - _, err := sch.validate(scope, 0, schPath, doc, v, vloc) + _, err := sch.validate(scope, 0, schPath, doc, v, vloc, subParent) return err } validateInplace := func(sch *Schema, schPath string) error { - vr, err := sch.validate(scope, vscope, schPath, doc, v, vloc) + vr, err := sch.validate(scope, vscope, schPath, doc, v, vloc, parent) if err == nil { // update result for pname := range result.unevalProps { @@ -735,7 +748,7 @@ func (s *Schema) validate(scope []schemaRef, vscope int, spath string, doc inter } for _, ext := range s.Extensions { - if err := ext.Validate(ValidationContext{result, doc, vloc, validate, validateInplace, validationError}, v); err != nil { + if err := ext.Validate(ValidationContext{result, doc, vloc, parent, validate, validateInplace, validationError}, v); err != nil { errors = append(errors, err) } } From 0a211997049b93a2e9c6ee9e23ee7d7786cdb623 Mon Sep 17 00:00:00 2001 From: Vassil Kovatchev Date: Fri, 26 Jul 2024 10:44:05 -0400 Subject: [PATCH 2/4] Switch from typed parent descriptor to map[string]interface{} for parent implementation --- compiler.go | 2 +- schema.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler.go b/compiler.go index f102d4c..4d783d4 100644 --- a/compiler.go +++ b/compiler.go @@ -852,7 +852,7 @@ func (c *Compiler) validateSchema(r *resource, v interface{}, vloc string) error } validate := func(meta *Schema) error { - return meta.validateValue(v, v, vloc, ParentDescriptor{}) + return meta.validateValue(v, v, vloc, NewParentDescriptor(nil, nil)) } meta := r.draft.meta diff --git a/schema.go b/schema.go index aeadf3e..edf6007 100644 --- a/schema.go +++ b/schema.go @@ -104,9 +104,13 @@ type Schema struct { Extensions map[string]ExtSchema } -type ParentDescriptor struct { - parent *ParentDescriptor - value interface{} +type ParentDescriptor map[string]interface{} + +func NewParentDescriptor(parent ParentDescriptor, value interface{}) ParentDescriptor { + return ParentDescriptor{ + "parent": parent, + "value": value, + } } func (s *Schema) String() string { @@ -170,7 +174,7 @@ func (s *Schema) hasVocab(name string) bool { // returns InfiniteLoopError if it detects loop during validation. // returns InvalidJSONTypeError if it detects any non json value in v. func (s *Schema) Validate(v interface{}) (err error) { - return s.validateValue(v, v, "", ParentDescriptor{}) + return s.validateValue(v, v, "", NewParentDescriptor(nil, nil)) } func (s *Schema) validateValue(doc interface{}, v interface{}, vloc string, parent ParentDescriptor) (err error) { @@ -235,11 +239,7 @@ func (s *Schema) validate(scope []schemaRef, vscope int, spath string, doc inter if vpath != "" { vloc += "/" + vpath - subParent = ParentDescriptor{ - parent: &parent, - value: thisValue, - } - + subParent = NewParentDescriptor(parent, thisValue) } _, err := sch.validate(scope, 0, schPath, doc, v, vloc, subParent) return err From 20e1d9d67e2bcca27434420d297fd1245cf2cdbf Mon Sep 17 00:00:00 2001 From: Vassil Kovatchev Date: Fri, 26 Jul 2024 12:15:58 -0400 Subject: [PATCH 3/4] Make sure no parent and value exist at the top level --- schema.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/schema.go b/schema.go index edf6007..86b8ecc 100644 --- a/schema.go +++ b/schema.go @@ -107,10 +107,14 @@ type Schema struct { type ParentDescriptor map[string]interface{} func NewParentDescriptor(parent ParentDescriptor, value interface{}) ParentDescriptor { - return ParentDescriptor{ - "parent": parent, - "value": value, + pd := ParentDescriptor{} + + if parent != nil { + pd["parent"] = parent + pd["value"] = value } + + return pd } func (s *Schema) String() string { From 401cf673d4ea78794379d24093290a9ccff08fa6 Mon Sep 17 00:00:00 2001 From: Vassil Kovatchev Date: Fri, 26 Jul 2024 14:25:48 -0400 Subject: [PATCH 4/4] Update extension.go with @emla9's suggestion Co-authored-by: Elizabeth Labor <138024120+emla9@users.noreply.github.com> --- extension.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension.go b/extension.go index ee42ff4..4fb5030 100644 --- a/extension.go +++ b/extension.go @@ -138,7 +138,7 @@ func (ctx ValidationContext) GetDoc() interface{} { return ctx.doc } -// GetDoc returns the parent descriptor of the value being validated. +// GetParent returns the parent descriptor of the value being validated. func (ctx ValidationContext) GetParent() ParentDescriptor { return ctx.parent }