diff --git a/jsonschema/infer.go b/jsonschema/infer.go index 8a0ec53..4785ef7 100644 --- a/jsonschema/infer.go +++ b/jsonschema/infer.go @@ -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 } diff --git a/jsonschema/infer_test.go b/jsonschema/infer_test.go index e99a273..d0cf0d1 100644 --- a/jsonschema/infer_test.go +++ b/jsonschema/infer_test.go @@ -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) } }) }