internal/mcp/jsonschema: remove shared structure from schema inference
Schemas must form a tree. This is important for Schema.Resolve, and also for modifying parts of the schema before resolution, as with mcp SchemaOptions. Remove the `seen` argument from inference, creating all schemas afresh. Change-Id: Ief8295e8470f3f575ef51cc2ba697901250bbe6f Reviewed-on: https://go-review.googlesource.com/c/tools/+/676937 Auto-Submit: Jonathan Amsterdam <jba@google.com> Reviewed-by: Hongxiang Jiang <hxjiang@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
d45330dd8f
commit
a9a69a03e4
2 changed files with 45 additions and 28 deletions
|
|
@ -44,21 +44,17 @@ func For[T any]() (*Schema, error) {
|
|||
//
|
||||
// TODO(rfindley): we could perhaps just skip these incompatible fields.
|
||||
func ForType(t reflect.Type) (*Schema, error) {
|
||||
return typeSchema(t, make(map[reflect.Type]*Schema))
|
||||
return typeSchema(t)
|
||||
}
|
||||
|
||||
func typeSchema(t reflect.Type, seen map[reflect.Type]*Schema) (*Schema, error) {
|
||||
func typeSchema(t reflect.Type) (*Schema, error) {
|
||||
if t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
if s := seen[t]; s != nil {
|
||||
return s, nil
|
||||
}
|
||||
var (
|
||||
s = new(Schema)
|
||||
err error
|
||||
)
|
||||
seen[t] = s
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
|
|
@ -80,14 +76,14 @@ func typeSchema(t reflect.Type, seen map[reflect.Type]*Schema) (*Schema, error)
|
|||
return nil, fmt.Errorf("unsupported map key type %v", t.Key().Kind())
|
||||
}
|
||||
s.Type = "object"
|
||||
s.AdditionalProperties, err = typeSchema(t.Elem(), seen)
|
||||
s.AdditionalProperties, err = typeSchema(t.Elem())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("computing map value schema: %v", err)
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
s.Type = "array"
|
||||
s.Items, err = typeSchema(t.Elem(), seen)
|
||||
s.Items, err = typeSchema(t.Elem())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("computing element schema: %v", err)
|
||||
}
|
||||
|
|
@ -113,7 +109,7 @@ func typeSchema(t reflect.Type, seen map[reflect.Type]*Schema) (*Schema, error)
|
|||
if s.Properties == nil {
|
||||
s.Properties = make(map[string]*Schema)
|
||||
}
|
||||
s.Properties[name], err = typeSchema(field.Type, seen)
|
||||
s.Properties[name], err = typeSchema(field.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,31 +41,52 @@ func TestForType(t *testing.T) {
|
|||
Type: "object",
|
||||
AdditionalProperties: &schema{},
|
||||
}},
|
||||
{"struct", forType[struct {
|
||||
F int `json:"f"`
|
||||
G []float64
|
||||
P *bool
|
||||
Skip string `json:"-"`
|
||||
NoSkip string `json:",omitempty"`
|
||||
unexported float64
|
||||
unexported2 int `json:"No"`
|
||||
}](), &schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*schema{
|
||||
"f": {Type: "integer"},
|
||||
"G": {Type: "array", Items: &schema{Type: "number"}},
|
||||
"P": {Type: "boolean"},
|
||||
"NoSkip": {Type: "string"},
|
||||
{
|
||||
"struct",
|
||||
forType[struct {
|
||||
F int `json:"f"`
|
||||
G []float64
|
||||
P *bool
|
||||
Skip string `json:"-"`
|
||||
NoSkip string `json:",omitempty"`
|
||||
unexported float64
|
||||
unexported2 int `json:"No"`
|
||||
}](),
|
||||
&schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*schema{
|
||||
"f": {Type: "integer"},
|
||||
"G": {Type: "array", Items: &schema{Type: "number"}},
|
||||
"P": {Type: "boolean"},
|
||||
"NoSkip": {Type: "string"},
|
||||
},
|
||||
Required: []string{"f", "G", "P"},
|
||||
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
|
||||
},
|
||||
Required: []string{"f", "G", "P"},
|
||||
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
"no sharing",
|
||||
forType[struct{ X, Y int }](),
|
||||
&schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*schema{
|
||||
"X": {Type: "integer"},
|
||||
"Y": {Type: "integer"},
|
||||
},
|
||||
Required: []string{"X", "Y"},
|
||||
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if diff := cmp.Diff(test.want, test.got, cmpopts.IgnoreUnexported(jsonschema.Schema{})); diff != "" {
|
||||
t.Errorf("ForType mismatch (-want +got):\n%s", diff)
|
||||
t.Fatalf("ForType mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
// These schemas should all resolve.
|
||||
if _, err := test.got.Resolve(nil); err != nil {
|
||||
t.Fatalf("Resolving: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue