@@ -8,12 +8,33 @@ package jsonschema
88
99import (
1010 "fmt"
11+ "log/slog"
12+ "math/big"
1113 "reflect"
1214 "regexp"
15+ "time"
1316
1417 "github.com/modelcontextprotocol/go-sdk/internal/util"
1518)
1619
20+ // ForOptions are options for the [For] function.
21+ type ForOptions struct {
22+ // If IgnoreBadTypes is true, fields that can't be represented as a JSON Schema
23+ // are ignored instead of causing an error.
24+ // This allows callers to adjust the resulting schema using custom knowledge.
25+ // For example, an interface type where all the possible implementations are
26+ // known can be described with "oneof".
27+ IgnoreBadTypes bool
28+
29+ // TypeSchemas maps types to their schemas.
30+ // If [For] encounters a type equal to a type of a key in this map, the
31+ // corresponding value is used as the resulting schema (after cloning to
32+ // ensure uniqueness).
33+ // Types in this map override the default translations, as described
34+ // in [For]'s documentation.
35+ TypeSchemas map [any ]* Schema
36+ }
37+
1738// For constructs a JSON schema object for the given type argument.
1839//
1940// It translates Go types into compatible JSON schema types, as follows:
@@ -29,48 +50,52 @@ import (
2950// Their properties are derived from exported struct fields, using the
3051// struct field JSON name. Fields that are marked "omitempty" are
3152// considered optional; all other fields become required properties.
53+ // - Some types in the standard library that implement json.Marshaler
54+ // translate to schemas that match the values to which they marshal.
55+ // For example, [time.Time] translates to the schema for strings.
3256//
33- // For returns an error if t contains (possibly recursively) any of the following Go
34- // types, as they are incompatible with the JSON schema spec.
57+ // By default, For returns an error if t contains (possibly recursively) any of the
58+ // following Go types, as they are incompatible with the JSON schema spec.
3559// - maps with key other than 'string'
3660// - function types
3761// - channel types
3862// - complex numbers
3963// - unsafe pointers
4064//
65+ // If [ForOptions.IgnoreBadTypes] is true, then these types are ignored instead.
66+ //
4167// It will return an error if there is a cycle in the types.
4268//
4369// This function recognizes struct field tags named "jsonschema".
4470// A jsonschema tag on a field is used as the description for the corresponding property.
4571// For future compatibility, descriptions must not start with "WORD=", where WORD is a
4672// sequence of non-whitespace characters.
47- func For [T any ]() (* Schema , error ) {
48- // TODO: consider skipping incompatible fields, instead of failing.
49- seen := make (map [reflect.Type ]bool )
50- s , err := forType (reflect .TypeFor [T ](), seen , false )
51- if err != nil {
52- var z T
53- return nil , fmt .Errorf ("For[%T](): %w" , z , err )
73+ func For [T any ](opts * ForOptions ) (* Schema , error ) {
74+ if opts == nil {
75+ opts = & ForOptions {}
5476 }
55- return s , nil
56- }
57-
58- // ForLax behaves like [For], except that it ignores struct fields with invalid types instead of
59- // returning an error. That allows callers to adjust the resulting schema using custom knowledge.
60- // For example, an interface type where all the possible implementations are known
61- // can be described with "oneof".
62- func ForLax [T any ]() (* Schema , error ) {
63- // TODO: consider skipping incompatible fields, instead of failing.
64- seen := make (map [reflect.Type ]bool )
65- s , err := forType (reflect .TypeFor [T ](), seen , true )
77+ schemas := make (map [reflect.Type ]* Schema )
78+ // Add types from the standard library that have MarshalJSON methods.
79+ ss := & Schema {Type : "string" }
80+ schemas [reflect .TypeFor [time.Time ]()] = ss
81+ schemas [reflect .TypeFor [slog.Level ]()] = ss
82+ schemas [reflect .TypeFor [big.Int ]()] = & Schema {Types : []string {"null" , "string" }}
83+ schemas [reflect .TypeFor [big.Rat ]()] = ss
84+ schemas [reflect .TypeFor [big.Float ]()] = ss
85+
86+ // Add types from the options. They override the default ones.
87+ for v , s := range opts .TypeSchemas {
88+ schemas [reflect .TypeOf (v )] = s
89+ }
90+ s , err := forType (reflect .TypeFor [T ](), map [reflect.Type ]bool {}, opts .IgnoreBadTypes , schemas )
6691 if err != nil {
6792 var z T
68- return nil , fmt .Errorf ("ForLax [%T](): %w" , z , err )
93+ return nil , fmt .Errorf ("For [%T](): %w" , z , err )
6994 }
7095 return s , nil
7196}
7297
73- func forType (t reflect.Type , seen map [reflect.Type ]bool , lax bool ) (* Schema , error ) {
98+ func forType (t reflect.Type , seen map [reflect.Type ]bool , ignore bool , schemas map [reflect. Type ] * Schema ) (* Schema , error ) {
7499 // Follow pointers: the schema for *T is almost the same as for T, except that
75100 // an explicit JSON "null" is allowed for the pointer.
76101 allowNull := false
@@ -89,6 +114,10 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
89114 defer delete (seen , t )
90115 }
91116
117+ if s := schemas [t ]; s != nil {
118+ return s .CloneSchemas (), nil
119+ }
120+
92121 var (
93122 s = new (Schema )
94123 err error
@@ -111,30 +140,30 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
111140
112141 case reflect .Map :
113142 if t .Key ().Kind () != reflect .String {
114- if lax {
143+ if ignore {
115144 return nil , nil // ignore
116145 }
117146 return nil , fmt .Errorf ("unsupported map key type %v" , t .Key ().Kind ())
118147 }
119148 if t .Key ().Kind () != reflect .String {
120149 }
121150 s .Type = "object"
122- s .AdditionalProperties , err = forType (t .Elem (), seen , lax )
151+ s .AdditionalProperties , err = forType (t .Elem (), seen , ignore , schemas )
123152 if err != nil {
124153 return nil , fmt .Errorf ("computing map value schema: %v" , err )
125154 }
126- if lax && s .AdditionalProperties == nil {
155+ if ignore && s .AdditionalProperties == nil {
127156 // Ignore if the element type is invalid.
128157 return nil , nil
129158 }
130159
131160 case reflect .Slice , reflect .Array :
132161 s .Type = "array"
133- s .Items , err = forType (t .Elem (), seen , lax )
162+ s .Items , err = forType (t .Elem (), seen , ignore , schemas )
134163 if err != nil {
135164 return nil , fmt .Errorf ("computing element schema: %v" , err )
136165 }
137- if lax && s .Items == nil {
166+ if ignore && s .Items == nil {
138167 // Ignore if the element type is invalid.
139168 return nil , nil
140169 }
@@ -160,11 +189,11 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
160189 if s .Properties == nil {
161190 s .Properties = make (map [string ]* Schema )
162191 }
163- fs , err := forType (field .Type , seen , lax )
192+ fs , err := forType (field .Type , seen , ignore , schemas )
164193 if err != nil {
165194 return nil , err
166195 }
167- if lax && fs == nil {
196+ if ignore && fs == nil {
168197 // Skip fields of invalid type.
169198 continue
170199 }
@@ -184,7 +213,7 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
184213 }
185214
186215 default :
187- if lax {
216+ if ignore {
188217 // Ignore.
189218 return nil , nil
190219 }
@@ -194,6 +223,7 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
194223 s .Types = []string {"null" , s .Type }
195224 s .Type = ""
196225 }
226+ schemas [t ] = s
197227 return s , nil
198228}
199229
0 commit comments