internal/mcp/jsonschema: check for structure separately

Redo iteration. It was too subtle.

Write a separate recursive check for whether the schemas form a tree and
have any invalid nils.

While doing that, assign paths to schemas for better error messages,
even as we check structure. This also lets us detect re-occurring
schemas without a separate map.

Everything else, including the iterators, can now assume valid structure
and populated paths.

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

View file

@ -9,8 +9,6 @@ import (
"fmt"
"math"
"regexp"
"slices"
"strings"
"testing"
)
@ -111,37 +109,6 @@ 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{
Type: "string",
PrefixItems: []*Schema{{Type: "int"}, {Items: &Schema{Type: "null"}}},
Contains: &Schema{Properties: map[string]*Schema{
"~1": {Type: "boolean"},
"p": {},
}},
}
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)
}
}
func mustUnmarshal(t *testing.T, data []byte, ptr any) {
t.Helper()
if err := json.Unmarshal(data, ptr); err != nil {