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

@ -9,6 +9,8 @@ import (
"fmt"
"math"
"regexp"
"slices"
"strings"
"testing"
)
@ -112,15 +114,31 @@ func TestUnmarshalErrors(t *testing.T) {
func TestEvery(t *testing.T) {
// Schema.every should visit all descendants of a schema, not just the immediate ones.
s := &Schema{
Items: &Schema{
Items: &Schema{},
},
Type: "string",
PrefixItems: []*Schema{{Type: "int"}, {Items: &Schema{Type: "null"}}},
Contains: &Schema{Properties: map[string]*Schema{
"~1": {Type: "boolean"},
"p": {},
}},
}
want := 3
got := 0
s.every(func(*Schema) bool { got++; return true })
if got != want {
t.Errorf("got %d, want %d", got, want)
type item struct {
s *Schema
p string
}
want := []item{
{s, ""},
{s.Contains, "contains"},
{s.Contains.Properties["p"], "contains/properties/p"},
{s.Contains.Properties["~1"], "contains/properties/~01"},
{s.PrefixItems[0], "prefixItems/0"},
{s.PrefixItems[1], "prefixItems/1"},
{s.PrefixItems[1].Items, "prefixItems/1/items"},
}
var got []item
s.every(func(s *Schema, p []string) bool { got = append(got, item{s, strings.Join(p, "/")}); return true }, nil)
if !slices.Equal(got, want) {
t.Errorf("\n got %v\nwant %v", got, want)
}
}
@ -130,3 +148,21 @@ func mustUnmarshal(t *testing.T, data []byte, ptr any) {
t.Fatal(err)
}
}
// json returns the schema in json format.
func (s *Schema) json() string {
data, err := json.Marshal(s)
if err != nil {
return fmt.Sprintf("<jsonschema.Schema:%v>", err)
}
return string(data)
}
// json returns the schema in json format, indented.
func (s *Schema) jsonIndent() string {
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
return fmt.Sprintf("<jsonschema.Schema:%v>", err)
}
return string(data)
}