| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package mcp |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "reflect" |
| "sync" |
| |
| "golang.org/x/tools/internal/mcp/internal/util" |
| ) |
| |
| func assert(cond bool, msg string) { |
| if !cond { |
| panic(msg) |
| } |
| } |
| |
| // marshalStructWithMap marshals its first argument to JSON, treating the field named |
| // mapField as an embedded map. The first argument must be a pointer to |
| // a struct. The underlying type of mapField must be a map[string]any, and it must have |
| // an "omitempty" json tag. |
| // |
| // For example, given this struct: |
| // |
| // type S struct { |
| // A int |
| // Extra map[string] any `json:,omitempty` |
| // } |
| // |
| // and this value: |
| // |
| // s := S{A: 1, Extra: map[string]any{"B": 2}} |
| // |
| // the call marshalJSONWithMap(s, "Extra") would return |
| // |
| // {"A": 1, "B": 2} |
| // |
| // It is an error if the map contains the same key as another struct field's |
| // JSON name. |
| // |
| // marshalStructWithMap calls json.Marshal on a value of type T, so T must not |
| // have a MarshalJSON method that calls this function, on pain of infinite regress. |
| // |
| // TODO: avoid this restriction on T by forcing it to marshal in a default way. |
| // See https://go.dev/play/p/EgXKJHxEx_R. |
| func marshalStructWithMap[T any](s *T, mapField string) ([]byte, error) { |
| // Marshal the struct and the map separately, and concatenate the bytes. |
| // This strategy is dramatically less complicated than |
| // constructing a synthetic struct or map with the combined keys. |
| if s == nil { |
| return []byte("null"), nil |
| } |
| s2 := *s |
| vMapField := reflect.ValueOf(&s2).Elem().FieldByName(mapField) |
| mapVal := vMapField.Interface().(map[string]any) |
| |
| // Check for duplicates. |
| names := jsonNames(reflect.TypeFor[T]()) |
| for key := range mapVal { |
| if names[key] { |
| return nil, fmt.Errorf("map key %q duplicates struct field", key) |
| } |
| } |
| |
| // Clear the map field, relying on the omitempty tag to omit it. |
| vMapField.Set(reflect.Zero(vMapField.Type())) |
| structBytes, err := json.Marshal(s2) |
| if err != nil { |
| return nil, fmt.Errorf("marshalStructWithMap(%+v): %w", s, err) |
| } |
| if len(mapVal) == 0 { |
| return structBytes, nil |
| } |
| mapBytes, err := json.Marshal(mapVal) |
| if err != nil { |
| return nil, err |
| } |
| if len(structBytes) == 2 { // must be "{}" |
| return mapBytes, nil |
| } |
| // "{X}" + "{Y}" => "{X,Y}" |
| res := append(structBytes[:len(structBytes)-1], ',') |
| res = append(res, mapBytes[1:]...) |
| return res, nil |
| } |
| |
| // unmarshalStructWithMap is the inverse of marshalStructWithMap. |
| // T has the same restrictions as in that function. |
| func unmarshalStructWithMap[T any](data []byte, v *T, mapField string) error { |
| // Unmarshal into the struct, ignoring unknown fields. |
| if err := json.Unmarshal(data, v); err != nil { |
| return err |
| } |
| // Unmarshal into the map. |
| m := map[string]any{} |
| if err := json.Unmarshal(data, &m); err != nil { |
| return err |
| } |
| // Delete from the map the fields of the struct. |
| for n := range jsonNames(reflect.TypeFor[T]()) { |
| delete(m, n) |
| } |
| if len(m) != 0 { |
| reflect.ValueOf(v).Elem().FieldByName(mapField).Set(reflect.ValueOf(m)) |
| } |
| return nil |
| } |
| |
| var jsonNamesMap sync.Map // from reflect.Type to map[string]bool |
| |
| // jsonNames returns the set of JSON object keys that t will marshal into. |
| // t must be a struct type. |
| func jsonNames(t reflect.Type) map[string]bool { |
| // Lock not necessary: at worst we'll duplicate work. |
| if val, ok := jsonNamesMap.Load(t); ok { |
| return val.(map[string]bool) |
| } |
| m := map[string]bool{} |
| for i := range t.NumField() { |
| info := util.FieldJSONInfo(t.Field(i)) |
| if !info.Omit { |
| m[info.Name] = true |
| } |
| } |
| jsonNamesMap.Store(t, m) |
| return m |
| } |