internal/mcp/jsonschema: make Schema.every fully recursive

s.every now visits the entire subtree of schemas at s, not only the
immediate children of s.

This re-introduces a tower: now of closures instead of iterators.
We could remove it having everyChild call every recursively instead
of returning, just passing f down all the way unwrapped.
But this is going to complicate the recursive function inside
resolveURIs, which needs to pass parent context down. It would have
the same problem that ast.Inspect has, and we'd have to adopt the same
solution, maintaining a stack explicitly.

I feel it's cleaner to keep the recursion simple at the cost of building
several closures. To do otherwise would be premature optimization.

Change-Id: Ic453cdaac482da8147dafc34b0117d25458d1c9d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/670675
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Jonathan Amsterdam 2025-05-07 09:27:59 -04:00
commit a88ab0dadc
2 changed files with 16 additions and 1 deletions

View file

@ -293,7 +293,7 @@ func Ptr[T any](x T) *T { return &x }
// It stops when f returns false.
func (s *Schema) every(f func(*Schema) bool) bool {
return s == nil ||
f(s) && s.everyChild(f)
f(s) && s.everyChild(func(s *Schema) bool { return s.every(f) })
}
// everyChild reports whether f is true for every immediate child schema of s.

View file

@ -111,3 +111,18 @@ 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{},
},
}
want := 3
got := 0
s.every(func(*Schema) bool { got++; return true })
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}