-
Notifications
You must be signed in to change notification settings - Fork 507
Enforce jsonschema formats and structural error path lookup #56325
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ func ExtractJSONPathFromValidationError(err error) []JSONPathInfo { | |
| Message: cause.Error(), | ||
| Location: cause.InstanceLocation, | ||
| ErrorKind: cause.ErrorKind, | ||
| Causes: cause.Causes, | ||
| } | ||
| paths = append(paths, path) | ||
| } | ||
|
|
@@ -49,10 +50,11 @@ func ExtractJSONPathFromValidationError(err error) []JSONPathInfo { | |
|
|
||
| // JSONPathInfo holds information about a validation error and its path | ||
| type JSONPathInfo struct { | ||
| Path string // JSON path like "/tools/1" or "/age" | ||
| Message string // Error message | ||
| Location []string // Instance location from jsonschema (e.g., ["tools", "1"]) | ||
| ErrorKind jsonschema.ErrorKind // Structural error kind; nil when built from a string-only context | ||
| Path string // JSON path like "/tools/1" or "/age" | ||
| Message string // Error message | ||
| Location []string // Instance location from jsonschema (e.g., ["tools", "1"]) | ||
| ErrorKind jsonschema.ErrorKind // Structural error kind; nil when built from a string-only context | ||
| Causes []*jsonschema.ValidationError // Nested validation errors from a composite error kind such as OneOf or Group | ||
| } | ||
|
|
||
| // convertInstanceLocationToJSONPath converts jsonschema InstanceLocation to JSON path string | ||
|
|
@@ -105,24 +107,40 @@ func LocateJSONPathForPathInfo(yamlContent string, info JSONPathInfo) JSONPathLo | |
| } | ||
|
|
||
| // additionalPropertyNamesFor returns the disallowed property names for a JSONPathInfo. | ||
| // It checks ErrorKind first (structural, no string parsing) and falls back to regex on Message. | ||
| // It checks ErrorKind first (structural, no string parsing) and falls back to regex on Message | ||
| // only when no structural error kind is available. | ||
| func additionalPropertyNamesFor(info JSONPathInfo) []string { | ||
| if info.ErrorKind != nil { | ||
| if ap, ok := info.ErrorKind.(*kind.AdditionalProperties); ok { | ||
| return ap.Properties | ||
| } | ||
| switch info.ErrorKind.(type) { | ||
| case *kind.OneOf, *kind.AnyOf, *kind.AllOf, *kind.Group: | ||
| // Composite errors may wrap an additional-properties leaf; regex fallback | ||
| // preserves the historical location behavior for these aggregate kinds. | ||
| return extractAdditionalPropertyNames(info.Message) | ||
| return additionalPropertyNamesFromCauses(info.Causes) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] Silent regression: when a composite error ( 💡 DetailsBefore this change,
Consider adding a note in the function doc that composite-kind errors with empty @copilot please address this. |
||
| default: | ||
| return nil | ||
| } | ||
| } | ||
| return extractAdditionalPropertyNames(info.Message) | ||
| } | ||
|
|
||
| func additionalPropertyNamesFromCauses(causes []*jsonschema.ValidationError) []string { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The new 💡 Suggested testfunc TestAdditionalPropertyNamesFromCausesHandlesNilCause(t *testing.T) {
info := JSONPathInfo{
Path: "/on",
Message: "irrelevant",
ErrorKind: &kind.Group{},
Causes: []*jsonschema.ValidationError{
nil,
{ErrorKind: &kind.AdditionalProperties{Properties: []string{"foobar"}}},
},
}
location := LocateJSONPathForPathInfo(yamlContent, info)
// expect foobar to be found, not a panic
...
}The guard is good, but without a test the intention is invisible and the guard could be removed in a cleanup pass. @copilot please address this. |
||
| var names []string | ||
| for _, cause := range causes { | ||
| if cause == nil { | ||
| continue | ||
| } | ||
| if ap, ok := cause.ErrorKind.(*kind.AdditionalProperties); ok { | ||
| names = append(names, ap.Properties...) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden: nil In Suggested fix: if cause == nil || cause.ErrorKind == nil {
continue
}@copilot please address this. |
||
| continue | ||
| } | ||
| if len(cause.Causes) > 0 { | ||
| names = append(names, additionalPropertyNamesFromCauses(cause.Causes)...) | ||
|
Comment on lines
+133
to
+138
|
||
| } | ||
| } | ||
| return names | ||
| } | ||
|
|
||
| func findPathInYAMLLines(yamlContent string, pathSegments []PathSegment) JSONPathLocation { | ||
| lines := strings.Split(yamlContent, "\n") | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -112,6 +112,7 @@ func CompileSchema(schemaJSON, schemaURL string) (*jsonschema.Schema, error) { | |
|
|
||
| // Create a new compiler | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden:
Consider adding a comment explaining the deliberate backward-compatibility trade-off, or audit existing schemas to confirm no valid values will now fail. @copilot please address this. |
||
| compiler := jsonschema.NewCompiler() | ||
| compiler.AssertFormat() | ||
|
|
||
|
|
||
| // Parse the schema JSON first | ||
| var schemaDoc any | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,3 +27,26 @@ func TestCompileSchema(t *testing.T) { | |
| t.Fatal("Validate(invalid) returned nil error, want validation failure") | ||
| } | ||
| } | ||
|
|
||
| func TestCompileSchemaAssertsFormat(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| schema, err := CompileSchema(`{ | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "type": "object", | ||
| "properties": { | ||
| "expires": {"type": "string", "format": "date"} | ||
| } | ||
| }`, "http://example.com/format-schema.json") | ||
| if err != nil { | ||
| t.Fatalf("CompileSchema returned error: %v", err) | ||
| } | ||
|
|
||
| if err := schema.Validate(map[string]any{"expires": "2026-08-28"}); err != nil { | ||
| t.Fatalf("Validate(valid date) returned error: %v", err) | ||
| } | ||
|
|
||
| if err := schema.Validate(map[string]any{"expires": "not-a-date"}); err == nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] 💡 SuggestionAdd a parallel sub-test (or a table-driven case) for {"type": "string", "format": "uri"}
// valid: "(example.com/redacted)"
// invalid: "not a uri"This mirrors the PR description's own claim: "This makes declared formats such as @copilot please address this. |
||
| t.Fatal("Validate(invalid date) returned nil error, want format validation failure") | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/codebase-design]
JSONPathInfo.Causesleaks an internal jsonschema type ([]*jsonschema.ValidationError) into a struct that is otherwise expressed in domain terms (Path,Message,Location,ErrorKind).💡 Suggestion
If
JSONPathInfois ever serialised, logged, or passed across package boundaries, callers will transitively depend on thejsonschemapackage. Consider whetheradditionalPropertyNamesFromCausesshould live as a closure or helper that accepts the raw*jsonschema.ValidationErrordirectly (bypassingJSONPathInfo) — keeping the struct focused on describing a single error rather than carrying a full cause tree.This is a minor coupling concern, not a blocker, but worth calling out given
JSONPathInfois already used in several other places in the package.@copilot please address this.