Skip to content

Commit c474fee

Browse files
authored
Merge pull request #23 from arduino/per1234/logic-inversion-keyword-pointers
Add full schema pointer matching capability to schema.ValidationErrorMatch()
2 parents dbdc5a0 + cd34d32 commit c474fee

File tree

3 files changed

+70
-13
lines changed

3 files changed

+70
-13
lines changed

check/checkdata/schema/parsevalidationresult.go

+59-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package schema
1717

1818
import (
1919
"encoding/json"
20+
"fmt"
2021
"regexp"
2122

2223
"github.com/arduino/go-paths-helper"
@@ -76,10 +77,10 @@ func validationErrorMatch(
7677
logrus.Tracef("Checking instance pointer: %s match with regexp: %s", validationError.InstancePtr, instancePointerRegexp)
7778
if instancePointerRegexp.MatchString(validationError.InstancePtr) {
7879
logrus.Tracef("Matched!")
79-
logrus.Tracef("Checking schema pointer: %s match with regexp: %s", validationError.SchemaPtr, schemaPointerRegexp)
80-
if schemaPointerRegexp.MatchString(validationError.SchemaPtr) {
80+
matchedSchemaPointer := validationErrorSchemaPointerMatch(schemaPointerRegexp, validationError, schemasPath)
81+
if matchedSchemaPointer != "" {
8182
logrus.Tracef("Matched!")
82-
if validationErrorSchemaPointerValueMatch(schemaPointerValueRegexp, validationError, schemasPath) {
83+
if validationErrorSchemaPointerValueMatch(schemaPointerValueRegexp, validationError.SchemaURL, matchedSchemaPointer, schemasPath) {
8384
logrus.Tracef("Matched!")
8485
logrus.Tracef("Checking failure context: %v match with regexp: %s", validationError.Context, failureContextRegexp)
8586
if validationErrorContextMatch(failureContextRegexp, validationError) {
@@ -107,14 +108,67 @@ func validationErrorMatch(
107108
return false
108109
}
109110

111+
// validationErrorSchemaPointerMatch matches the JSON schema pointer related to the validation failure against a regular expression.
112+
func validationErrorSchemaPointerMatch(
113+
schemaPointerRegexp *regexp.Regexp,
114+
validationError *jsonschema.ValidationError,
115+
schemasPath *paths.Path,
116+
) string {
117+
logrus.Tracef("Checking schema pointer: %s match with regexp: %s", validationError.SchemaPtr, schemaPointerRegexp)
118+
if schemaPointerRegexp.MatchString(validationError.SchemaPtr) {
119+
return validationError.SchemaPtr
120+
}
121+
122+
// The schema validator does not provide full pointer past logic inversion keywords to the lowest level keywords related to the validation error cause.
123+
// Therefore the sub-keywords must be checked for matches in order to be able to interpret the exact cause of the failure.
124+
if regexp.MustCompile("(/not)|(/oneOf)$").MatchString(validationError.SchemaPtr) {
125+
return validationErrorSchemaSubPointerMatch(schemaPointerRegexp, validationError.SchemaPtr, validationErrorSchemaPointerValue(validationError, schemasPath))
126+
}
127+
128+
return ""
129+
}
130+
131+
// validationErrorSchemaSubPointerMatch recursively checks JSON pointers of all keywords under the parent pointer for match against a regular expression.
132+
// The matching JSON pointer is returned.
133+
func validationErrorSchemaSubPointerMatch(schemaPointerRegexp *regexp.Regexp, parentPointer string, pointerValueObject interface{}) string {
134+
// Recurse through iterable objects.
135+
switch assertedObject := pointerValueObject.(type) {
136+
case []interface{}:
137+
for index, element := range assertedObject {
138+
// Append index to JSON pointer and check for match.
139+
matchingPointer := validationErrorSchemaSubPointerMatch(schemaPointerRegexp, fmt.Sprintf("%s/%d", parentPointer, index), element)
140+
if matchingPointer != "" {
141+
return matchingPointer
142+
}
143+
}
144+
case map[string]interface{}:
145+
for key := range assertedObject {
146+
// Append key to JSON pointer and check for match.
147+
matchingPointer := validationErrorSchemaSubPointerMatch(schemaPointerRegexp, parentPointer+"/"+key, assertedObject[key])
148+
if matchingPointer != "" {
149+
return matchingPointer
150+
}
151+
// TODO: Follow references. For now, the schema code must be written so that the problematic keywords are after the reference.
152+
}
153+
}
154+
155+
// pointerValueObject is not further iterable. Check for match against the parent JSON pointer.
156+
logrus.Tracef("Checking schema pointer: %s match with regexp: %s", parentPointer, schemaPointerRegexp)
157+
if schemaPointerRegexp.MatchString(parentPointer) {
158+
return parentPointer
159+
}
160+
return ""
161+
}
162+
110163
// validationErrorSchemaPointerValueMatch marshalls the data in the schema at the given JSON pointer and returns whether
111164
// it matches against the given regular expression.
112165
func validationErrorSchemaPointerValueMatch(
113166
schemaPointerValueRegexp *regexp.Regexp,
114-
validationError *jsonschema.ValidationError,
167+
schemaURL,
168+
schemaPointer string,
115169
schemasPath *paths.Path,
116170
) bool {
117-
marshalledSchemaPointerValue, err := json.Marshal(schemaPointerValue(validationError, schemasPath))
171+
marshalledSchemaPointerValue, err := json.Marshal(schemaPointerValue(schemaURL, schemaPointer, schemasPath))
118172
logrus.Tracef("Checking schema pointer value: %s match with regexp: %s", marshalledSchemaPointerValue, schemaPointerValueRegexp)
119173
if err != nil {
120174
panic(err)

check/checkdata/schema/schema.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func logValidationError(validationError *jsonschema.ValidationError, schemasPath
155155
logrus.Tracef("Instance pointer: %v", validationError.InstancePtr)
156156
logrus.Tracef("Schema URL: %s", validationError.SchemaURL)
157157
logrus.Tracef("Schema pointer: %s", validationError.SchemaPtr)
158-
logrus.Tracef("Schema pointer value: %v", schemaPointerValue(validationError, schemasPath))
158+
logrus.Tracef("Schema pointer value: %v", validationErrorSchemaPointerValue(validationError, schemasPath))
159159
logrus.Tracef("Failure context: %v", validationError.Context)
160160
logrus.Tracef("Failure context type: %T", validationError.Context)
161161

@@ -165,10 +165,15 @@ func logValidationError(validationError *jsonschema.ValidationError, schemasPath
165165
}
166166
}
167167

168+
// validationErrorSchemaPointerValue returns the object identified by the validation error's schema JSON pointer.
169+
func validationErrorSchemaPointerValue(validationError *jsonschema.ValidationError, schemasPath *paths.Path) interface{} {
170+
return schemaPointerValue(validationError.SchemaURL, validationError.SchemaPtr, schemasPath)
171+
}
172+
168173
// schemaPointerValue returns the object identified by the given JSON pointer from the schema file.
169-
func schemaPointerValue(validationError *jsonschema.ValidationError, schemasPath *paths.Path) interface{} {
170-
schemaPath := schemasPath.Join(path.Base(validationError.SchemaURL))
171-
return jsonPointerValue(validationError.SchemaPtr, schemaPath)
174+
func schemaPointerValue(schemaURL, schemaPointer string, schemasPath *paths.Path) interface{} {
175+
schemaPath := schemasPath.Join(path.Base(schemaURL))
176+
return jsonPointerValue(schemaPointer, schemaPath)
172177
}
173178

174179
// jsonPointerValue returns the object identified by the given JSON pointer from the JSON file.

check/checkdata/schema/schema_test.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package schema
22

33
import (
4-
"fmt"
54
"os"
65
"regexp"
76
"runtime"
@@ -155,14 +154,13 @@ func Test_pathURI(t *testing.T) {
155154
}
156155
}
157156

158-
func Test_schemaPointerValue(t *testing.T) {
157+
func Test_validationErrorSchemaPointerValue(t *testing.T) {
159158
validationError := jsonschema.ValidationError{
160159
SchemaURL: "https://raw.githubusercontent.com/arduino/arduino-check/main/check/checkdata/schema/testdata/referenced-schema-1.json",
161160
SchemaPtr: "#/definitions/patternObject/pattern",
162161
}
163162

164-
schemaPointerValueInterface := schemaPointerValue(&validationError, schemasPath)
165-
fmt.Printf("%T", schemaPointerValueInterface)
163+
schemaPointerValueInterface := validationErrorSchemaPointerValue(&validationError, schemasPath)
166164
schemaPointerValue, ok := schemaPointerValueInterface.(string)
167165
require.True(t, ok)
168166
require.Equal(t, "^[a-z]+$", schemaPointerValue)

0 commit comments

Comments
 (0)