internal/mcp: pointerize all the things

Use pointers for the protocol's struct types, to future-proof against
them getting large.

Change-Id: I55b204080a41c8140539697ba264e502e912e613
Reviewed-on: https://go-review.googlesource.com/c/tools/+/672575
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Jonathan Amsterdam 2025-05-13 19:10:34 -04:00
commit 02fa1b3ea6

View file

@ -139,6 +139,10 @@ func main() {
if err := json.Unmarshal(data, &schema); err != nil {
log.Fatal(err)
}
// Resolve the schema so we have the referents of all the Refs.
if _, err := schema.Resolve("", nil); err != nil {
log.Fatal(err)
}
// Collect named types. Since we may create new type definitions while
// writing types, we collect definitions and concatenate them later. This
@ -253,7 +257,8 @@ func writeDecl(configName string, config typeConfig, def *jsonschema.Schema, nam
// be added during writeType, if they are extracted from inner fields.
func writeType(w io.Writer, config *typeConfig, def *jsonschema.Schema, named map[string]*bytes.Buffer) error {
// Use type names for Named types.
if name := strings.TrimPrefix(def.Ref, "#/definitions/"); name != "" {
name, resolved := deref(def)
if name != "" {
// TODO: this check is not quite right: we should really panic if the
// definition is missing, *but only if w is not io.Discard*. That's not a
// great API: see if we can do something more explicit than io.Discard.
@ -266,6 +271,9 @@ func writeType(w io.Writer, config *typeConfig, def *jsonschema.Schema, named ma
} else if cfg.Name != "" {
name = cfg.Name
}
if isStruct(resolved) {
w.Write([]byte{'*'})
}
}
w.Write([]byte(name))
return nil
@ -288,7 +296,7 @@ func writeType(w io.Writer, config *typeConfig, def *jsonschema.Schema, named ma
if slices.ContainsFunc(def.AnyOf, func(s *jsonschema.Schema) bool {
return s.Ref == "#/definitions/TextContent"
}) {
fmt.Fprintf(w, "Content")
fmt.Fprintf(w, "*Content")
} else {
// E.g. union types.
fmt.Fprintf(w, "json.RawMessage")
@ -322,14 +330,15 @@ func writeType(w io.Writer, config *typeConfig, def *jsonschema.Schema, named ma
required := slices.Contains(def.Required, name)
// If the field is not required, and is a struct type, indirect with a
// If the field is a struct type, indirect with a
// pointer so that it can be empty as defined by encoding/json.
//
// TODO: use omitzero when available.
needPointer := !required &&
(strings.HasPrefix(fieldDef.Ref, "#/definitions/") ||
fieldDef.Type == "object" && !canHaveAdditionalProperties(fieldDef))
// This also future-proofs against the struct getting large.
fieldTypeSchema := fieldDef
// If the schema is a reference, dereference it.
if _, rs := deref(fieldDef); rs != nil {
fieldTypeSchema = rs
}
needPointer := isStruct(fieldTypeSchema)
if config != nil && config.Fields[export] != nil {
r := config.Fields[export]
if r.Substitute != "" {
@ -348,13 +357,8 @@ func writeType(w io.Writer, config *typeConfig, def *jsonschema.Schema, named ma
}
fmt.Fprintf(w, typename)
}
} else {
if needPointer {
fmt.Fprintf(w, "*")
}
if err := writeType(w, nil, fieldDef, named); err != nil {
return fmt.Errorf("failed to write type for field %s: %v", export, err)
}
} else if err := writeType(w, nil, fieldDef, named); err != nil {
return fmt.Errorf("failed to write type for field %s: %v", export, err)
}
fmt.Fprintf(w, " `json:\"%s", name)
if !required {
@ -452,6 +456,33 @@ func exportName(s string) string {
return s
}
// deref dereferences s.Ref.
// If s.Ref refers to a schema in the Definitions section, deref
// returns the definition name and the associated schema.
// Otherwise, deref returns "", nil.
func deref(s *jsonschema.Schema) (name string, _ *jsonschema.Schema) {
name, ok := strings.CutPrefix(s.Ref, "#/definitions/")
if !ok {
return "", nil
}
return name, s.ResolvedRef()
}
// isStruct reports whether s should be translated to a struct.
func isStruct(s *jsonschema.Schema) bool {
return s.Type == "object" && s.Properties != nil && !canHaveAdditionalProperties(s)
}
// schemaJSON returns the JSON for s.
// For debugging.
func schemaJSON(s *jsonschema.Schema) string {
data, err := json.Marshal(s)
if err != nil {
return fmt.Sprintf("<jsonschema.Schema:%v>", err)
}
return string(data)
}
// Map from initialism to the regexp that matches it.
var initialisms = map[string]*regexp.Regexp{
"Id": nil,