internal/mcp/jsonschema: apply schema defaults to an instance

This CL handles only the limited case of defaults on properties.

This is just a start: I need to think more about defaults on other
schema keywords, and I need to set defaults recursively.

As part of this work, I realized that the Schema.Default field is
more usable when represented as a json.RawMessage instead of a *any.

Change-Id: Iedcedb405b52607a312dbd895fa61e7f7f94c6bc
Reviewed-on: https://go-review.googlesource.com/c/tools/+/676938
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-27 07:08:57 -04:00 • committed by Gopher Robot
commit c5a3591742

View file

@ -94,14 +94,12 @@ func TestValidateErrors(t *testing.T) {
}
func TestValidateDefaults(t *testing.T) {
anyptr := func(x any) *any { return &x }
s := &Schema{
Properties: map[string]*Schema{
"a": {Type: "integer", Default: anyptr(3)},
"b": {Type: "string", Default: anyptr("s")},
"a": {Type: "integer", Default: mustMarshal(1)},
"b": {Type: "string", Default: mustMarshal("s")},
},
Default: anyptr(map[string]any{"a": 1, "b": "two"}),
Default: mustMarshal(map[string]any{"a": 1, "b": "two"}),
}
if _, err := s.Resolve(&ResolveOptions{ValidateDefaults: true}); err != nil {
t.Fatal(err)
@ -109,10 +107,10 @@ func TestValidateDefaults(t *testing.T) {
s = &Schema{
Properties: map[string]*Schema{
"a": {Type: "integer", Default: anyptr(3)},
"b": {Type: "string", Default: anyptr("s")},
"a": {Type: "integer", Default: mustMarshal(3)},
"b": {Type: "string", Default: mustMarshal("s")},
},
Default: anyptr(map[string]any{"a": 1, "b": 2}),
Default: mustMarshal(map[string]any{"a": 1, "b": 2}),
}
_, err := s.Resolve(&ResolveOptions{ValidateDefaults: true})
want := `has type "integer", want "string"`
@ -121,6 +119,52 @@ func TestValidateDefaults(t *testing.T) {
}
}
func TestApplyDefaults(t *testing.T) {
schema := &Schema{
Properties: map[string]*Schema{
"A": {Default: mustMarshal(1)},
"B": {Default: mustMarshal(2)},
"C": {Default: mustMarshal(3)},
},
Required: []string{"C"},
}
rs, err := schema.Resolve(&ResolveOptions{ValidateDefaults: true})
if err != nil {
t.Fatal(err)
}
type S struct{ A, B, C int }
for _, tt := range []struct {
instancep any // pointer to instance value
want any // desired value (not a pointer)
}{
{
&map[string]any{"B": 0},
map[string]any{
"A": float64(1), // filled from default
"B": 0, // untouched: it was already there
// "C" not added: it is required (Validate will catch that)
},
},
{
&S{B: 1},
S{
A: 1, // filled from default
B: 1, // untouched: non-zero
C: 0, // untouched: required
},
},
} {
if err := rs.ApplyDefaults(tt.instancep); err != nil {
t.Fatal(err)
}
got := reflect.ValueOf(tt.instancep).Elem().Interface() // dereference the pointer
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("\ngot %#v\nwant %#v", got, tt.want)
}
}
}
func TestStructInstance(t *testing.T) {
instance := struct {
I int
@ -235,6 +279,14 @@ func TestJSONName(t *testing.T) {
}
}
func mustMarshal(x any) json.RawMessage {
data, err := json.Marshal(x)
if err != nil {
panic(err)
}
return json.RawMessage(data)
}
// loadRemote loads a remote reference used in the test suite.
func loadRemote(uri *url.URL) (*Schema, error) {
// Anything with localhost:1234 refers to the remotes directory in the test suite repo.