internal/mcp/jsonschema: clearer validations errors

Validation errors now more clearly indicate the path from the root
schema.

- Enhance schema iteration to yield the path, and generalize it by using
  reflection.

- Store each schema's path inside the schema.

- Remove the path arg from validate: we don't need it anymore.

- Improve the test for the iterators and add one for validation
  errors.

Change-Id: Ief55133ef4ae2eac0736208a3b9f78ed69933b31
Reviewed-on: https://go-review.googlesource.com/c/tools/+/676375
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Jonathan Amsterdam 2025-05-26 07:49:23 -04:00 • committed by Gopher Robot
commit e3938ce68c

View file

@ -28,7 +28,18 @@ import (
"strings"
)
var jsonPointerReplacer = strings.NewReplacer("~0", "~", "~1", "/")
var (
jsonPointerEscaper = strings.NewReplacer("~", "~0", "/", "~1")
jsonPointerUnescaper = strings.NewReplacer("~0", "~", "~1", "/")
)
func escapeJSONPointerSegment(s string) string {
return jsonPointerEscaper.Replace(s)
}
func unescapeJSONPointerSegment(s string) string {
return jsonPointerUnescaper.Replace(s)
}
// parseJSONPointer splits a JSON Pointer into a sequence of segments. It doesn't
// convert strings to numbers, because that depends on the traversal: a segment
@ -47,7 +58,7 @@ func parseJSONPointer(ptr string) (segments []string, err error) {
if strings.Contains(ptr, "~") {
// Undo the simple escaping rules that allow one to include a slash in a segment.
for i := range segments {
segments[i] = jsonPointerReplacer.Replace(segments[i])
segments[i] = unescapeJSONPointerSegment(segments[i])
}
}
return segments, nil
@ -121,17 +132,6 @@ func dereferenceJSONPointer(s *Schema, sptr string) (_ *Schema, err error) {
return nil, fmt.Errorf("does not refer to a schema, but to a %s", v.Type())
}
// map from JSON names for fields in a Schema to their indexes in the struct.
var schemaFields = map[string][]int{}
func init() {
for _, f := range reflect.VisibleFields(reflect.TypeFor[Schema]()) {
if name, ok := jsonName(f); ok {
schemaFields[name] = f.Index
}
}
}
// lookupSchemaField returns the value of the field with the given name in v,
// or the zero value if there is no such field or it is not of type Schema or *Schema.
func lookupSchemaField(v reflect.Value, name string) reflect.Value {
@ -143,8 +143,8 @@ func lookupSchemaField(v reflect.Value, name string) reflect.Value {
}
return v.FieldByName("Types")
}
if index := schemaFields[name]; index != nil {
return v.FieldByIndex(index)
if sf, ok := schemaFieldMap[name]; ok {
return v.FieldByIndex(sf.Index)
}
return reflect.Value{}
}