diff --git a/dsl.go b/dsl.go index a9f80ea32..e8f7db32b 100644 --- a/dsl.go +++ b/dsl.go @@ -813,7 +813,13 @@ func (s *Schema) SetDescription(d string) *Schema { // SetType sets the Type of the Schema. func (s *Schema) SetType(t string) *Schema { - s.Type = t + s.Type = jsonschema.StringArray{t} + return s +} + +// AddType adds the Type to the Type array of the Schema. +func (s *Schema) AddType(t string) *Schema { + s.Type = append(s.Type, t) return s } @@ -1066,7 +1072,7 @@ func schema(t, f string) *Schema { // AsArray returns a new "array" Schema wrapping the receiver. func (s *Schema) AsArray() *Schema { return &Schema{ - Type: jsonschema.Array.String(), + Type: []string{jsonschema.Array.String()}, Items: &Items{ Item: s, }, diff --git a/dsl_test.go b/dsl_test.go index b12ea4d33..e4aff03de 100644 --- a/dsl_test.go +++ b/dsl_test.go @@ -129,7 +129,7 @@ func TestBuilder(t *testing.T) { In: "path", Description: "ID Parameter in path", Required: true, - Schema: &ogen.Schema{Type: "integer", Format: "int32"}, + Schema: &ogen.Schema{Type: []string{"integer"}, Format: "int32"}, }, }, Responses: ogen.Responses{ @@ -138,11 +138,11 @@ func TestBuilder(t *testing.T) { Description: "Success", Content: map[string]ogen.Media{ ir.EncodingJSON.String(): {Schema: &ogen.Schema{ - Type: "object", + Type: []string{"object"}, Description: "Success", Properties: []ogen.Property{ - {Name: "prop1", Schema: &ogen.Schema{Type: "integer", Format: "int32"}}, - {Name: "prop2", Schema: &ogen.Schema{Type: "string"}}, + {Name: "prop1", Schema: &ogen.Schema{Type: []string{"integer"}, Format: "int32"}}, + {Name: "prop2", Schema: &ogen.Schema{Type: []string{"string"}}}, }, }}, }, @@ -178,11 +178,11 @@ func TestBuilder(t *testing.T) { Description: "An Error Response", Content: map[string]ogen.Media{ ir.EncodingJSON.String(): {Schema: &ogen.Schema{ - Type: "object", + Type: []string{"object"}, Description: "Error Response Schema", Properties: []ogen.Property{ - {Name: "code", Schema: &ogen.Schema{Type: "integer", Format: "int32"}}, - {Name: "status", Schema: &ogen.Schema{Type: "string"}}, + {Name: "code", Schema: &ogen.Schema{Type: []string{"integer"}, Format: "int32"}}, + {Name: "status", Schema: &ogen.Schema{Type: []string{"string"}}}, }, }}, }, @@ -368,7 +368,7 @@ func TestBuilder(t *testing.T) { assert.Equal(t, &ogen.Schema{ Ref: "ref", Description: "desc", - Type: "object", + Type: []string{"object"}, Format: "", Properties: []ogen.Property{{Name: "prop"}}, Required: []string{"prop"}, diff --git a/go.mod b/go.mod index de8b2b958..37b29d209 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/go-faster/yaml v0.4.6 github.com/google/uuid v1.6.0 github.com/mattn/go-isatty v0.0.20 + github.com/samber/lo v1.52.0 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.11.1 github.com/valyala/fasthttp v1.69.0 diff --git a/go.sum b/go.sum index bdadf7c00..e9c2a0b5b 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= diff --git a/jsonschema/cross_type_constraints_test.go b/jsonschema/cross_type_constraints_test.go index 6165402d3..971903480 100644 --- a/jsonschema/cross_type_constraints_test.go +++ b/jsonschema/cross_type_constraints_test.go @@ -18,7 +18,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "maximum on string type - strict mode", schema: &RawSchema{ - Type: "string", + Type: StringArray{"string"}, Maximum: Num(`1000`), }, allowCrossType: false, @@ -28,7 +28,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "maximum on string type - default mode interprets as numeric constraint", schema: &RawSchema{ - Type: "string", + Type: StringArray{"string"}, Maximum: Num(`1000`), }, allowCrossType: true, @@ -41,7 +41,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "minimum on string type - strict mode", schema: &RawSchema{ - Type: "string", + Type: StringArray{"string"}, Minimum: Num(`0.001`), }, allowCrossType: false, @@ -51,7 +51,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "minimum on string type - default mode", schema: &RawSchema{ - Type: "string", + Type: StringArray{"string"}, Minimum: Num(`0.001`), }, allowCrossType: true, @@ -60,7 +60,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "pattern on number type - strict mode", schema: &RawSchema{ - Type: "number", + Type: StringArray{"number"}, Pattern: `^\d+(\.\d{1,2})?$`, }, allowCrossType: false, @@ -70,7 +70,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "pattern on number type - default mode", schema: &RawSchema{ - Type: "number", + Type: StringArray{"number"}, Pattern: `^\d+(\.\d{1,2})?$`, }, allowCrossType: true, @@ -79,7 +79,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "pattern on integer type - strict mode", schema: &RawSchema{ - Type: "integer", + Type: StringArray{"integer"}, Pattern: `^\d+$`, }, allowCrossType: false, @@ -89,7 +89,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "pattern on integer type - default mode", schema: &RawSchema{ - Type: "integer", + Type: StringArray{"integer"}, Pattern: `^\d+$`, }, allowCrossType: true, @@ -98,7 +98,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "maxLength on number type - strict mode", schema: &RawSchema{ - Type: "number", + Type: StringArray{"number"}, MaxLength: uint64Ptr(10), }, allowCrossType: false, @@ -108,7 +108,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "maxLength on number type - default mode", schema: &RawSchema{ - Type: "number", + Type: StringArray{"number"}, MaxLength: uint64Ptr(10), }, allowCrossType: true, @@ -117,7 +117,7 @@ func TestCrossTypeConstraints(t *testing.T) { { name: "valid schema - both modes", schema: &RawSchema{ - Type: "string", + Type: StringArray{"string"}, MaxLength: uint64Ptr(100), Pattern: `^\w+$`, }, @@ -151,12 +151,12 @@ func TestCrossTypeConstraints(t *testing.T) { func TestCrossTypeConstraintsComplex(t *testing.T) { // Test with a more complex schema like the one in the issue schema := &RawSchema{ - Type: "object", + Type: StringArray{"object"}, Properties: []RawProperty{ { Name: "weight", Schema: &RawSchema{ - Type: "string", + Type: StringArray{"string"}, Maximum: Num(`1000`), Minimum: Num(`0.001`), }, @@ -164,7 +164,7 @@ func TestCrossTypeConstraintsComplex(t *testing.T) { { Name: "quantity", Schema: &RawSchema{ - Type: "number", + Type: StringArray{"number"}, Pattern: `^\d+(\.\d{1,2})?$`, }, }, diff --git a/jsonschema/infer.go b/jsonschema/infer.go index cbf987a1a..ebdf56bc4 100644 --- a/jsonschema/infer.go +++ b/jsonschema/infer.go @@ -4,6 +4,8 @@ import ( "slices" "strings" + "github.com/samber/lo" + "github.com/go-faster/errors" "github.com/go-faster/jx" ) @@ -23,8 +25,8 @@ func (i *Infer) Apply(data []byte) error { return apply(&i.target, jx.DecodeBytes(data)) } -func applyType(s *RawSchema, tt string) { - if hasType(s, tt) { +func applyType(s *RawSchema, tt ...string) { + if hasType(s, tt...) { return } if len(s.OneOf) > 0 { @@ -32,7 +34,7 @@ func applyType(s *RawSchema, tt string) { return } - if s.Type == "" { + if s.Type == nil { s.Type = tt return } @@ -46,26 +48,28 @@ func applyType(s *RawSchema, tt string) { } } -func hasType(s *RawSchema, tt string) bool { - if s.Type == tt { +func hasType(s *RawSchema, tt ...string) bool { + if len(lo.Intersect(s.Type, tt)) > 0 { return true } + for _, v := range s.OneOf { - if v.Type == tt { + if len(lo.Intersect(v.Type, tt)) > 0 { return true } } return false } -func replaceType(s *RawSchema, from, to string) bool { - if s.Type == from { +func replaceType(s *RawSchema, from, to []string) bool { + if lo.ElementsMatch(s.Type, from) { s.Type = to return true } + for _, v := range s.OneOf { - if v.Type == from { - v.Type = to + if lo.ElementsMatch(v.Type, from) { + s.Type = to return true } } @@ -86,7 +90,7 @@ func apply(s *RawSchema, d *jx.Decoder) error { applyType(s, "integer") return nil } - if replaceType(s, "integer", "number") { + if replaceType(s, []string{"integer"}, []string{"number"}) { return nil } applyType(s, "number") diff --git a/jsonschema/infer_test.go b/jsonschema/infer_test.go index 8c96b8c8f..869ff886d 100644 --- a/jsonschema/infer_test.go +++ b/jsonschema/infer_test.go @@ -28,47 +28,47 @@ func TestInfer_Apply(t *testing.T) { result RawSchema inputs []string }{ - {RawSchema{Type: "integer"}, []string{"1", "2", "3"}}, - {RawSchema{Type: "number"}, []string{"1", "2.0", "3"}}, - {RawSchema{Type: "number"}, []string{"2.0"}}, - {RawSchema{Type: "number", Nullable: true}, []string{"2.0", "null"}}, + {RawSchema{Type: StringArray{"integer"}}, []string{"1", "2", "3"}}, + {RawSchema{Type: StringArray{"number"}}, []string{"1", "2.0", "3"}}, + {RawSchema{Type: StringArray{"number"}}, []string{"2.0"}}, + {RawSchema{Type: StringArray{"number"}, Nullable: true}, []string{"2.0", "null"}}, - {RawSchema{Type: "boolean"}, []string{"true", "false"}}, - {RawSchema{Type: "boolean", Nullable: true}, []string{"true", "null"}}, + {RawSchema{Type: StringArray{"boolean"}}, []string{"true", "false"}}, + {RawSchema{Type: StringArray{"boolean"}, Nullable: true}, []string{"true", "null"}}, - {RawSchema{Type: "array"}, []string{"[]"}}, + {RawSchema{Type: StringArray{"array"}}, []string{"[]"}}, {RawSchema{ - Type: "array", + Type: StringArray{"array"}, Items: &RawItems{ - Item: &RawSchema{Type: "integer"}, + Item: &RawSchema{Type: StringArray{"integer"}}, }, }, []string{"[1]"}}, {RawSchema{ - Type: "array", + Type: StringArray{"array"}, Items: &RawItems{ - Item: &RawSchema{Type: "number"}, + Item: &RawSchema{Type: StringArray{"number"}}, }, }, []string{"[1, 10, 5, 0.5]"}}, {RawSchema{ - Type: "array", + Type: StringArray{"array"}, Items: &RawItems{ Item: &RawSchema{ OneOf: []*RawSchema{ - {Type: "integer"}, - {Type: "boolean"}, - {Type: "string"}, + {Type: StringArray{"integer"}}, + {Type: StringArray{"boolean"}}, + {Type: StringArray{"string"}}, }, }, }, }, []string{`[1, true, "foo"]`}}, - {RawSchema{Type: "object", Properties: RawProperties{}}, []string{ + {RawSchema{Type: StringArray{"object"}, Properties: RawProperties{}}, []string{ `{}`, }}, {RawSchema{ - Type: "object", + Type: StringArray{"object"}, Properties: RawProperties{ - {"foo", &RawSchema{Type: "integer"}}, + {"foo", &RawSchema{Type: StringArray{"integer"}}}, }, }, []string{ `{}`, @@ -77,11 +77,11 @@ func TestInfer_Apply(t *testing.T) { `{"foo": 3}`, }}, {RawSchema{ - Type: "object", + Type: StringArray{"object"}, Required: []string{"foo"}, Properties: RawProperties{ - {"bar", &RawSchema{Type: "string"}}, - {"foo", &RawSchema{Type: "integer"}}, + {"bar", &RawSchema{Type: StringArray{"string"}}}, + {"foo", &RawSchema{Type: StringArray{"integer"}}}, }, }, []string{ `{"foo": 1}`, @@ -89,13 +89,13 @@ func TestInfer_Apply(t *testing.T) { `{"foo": 2, "bar": "baz"}`, }}, {RawSchema{ - Type: "object", + Type: StringArray{"object"}, Required: []string{"required", "required_nullable"}, Properties: RawProperties{ - {"optional", &RawSchema{Type: "integer"}}, - {"optional_nullable", &RawSchema{Type: "integer", Nullable: true}}, - {"required", &RawSchema{Type: "integer"}}, - {"required_nullable", &RawSchema{Type: "integer", Nullable: true}}, + {"optional", &RawSchema{Type: StringArray{"integer"}}}, + {"optional_nullable", &RawSchema{Type: StringArray{"integer"}, Nullable: true}}, + {"required", &RawSchema{Type: StringArray{"integer"}}}, + {"required_nullable", &RawSchema{Type: StringArray{"integer"}, Nullable: true}}, }, }, []string{ `{"required": 10, "required_nullable": null, "optional": 10, "optional_nullable": null}`, @@ -106,17 +106,19 @@ func TestInfer_Apply(t *testing.T) { {RawSchema{Nullable: true}, []string{"null"}}, {RawSchema{ OneOf: []*RawSchema{ - {Type: "boolean"}, - {Type: "string"}, - {Type: "number"}, + {Type: StringArray{"boolean"}}, + {Type: StringArray{"string"}}, + {Type: StringArray{"integer"}}, }, + Type: StringArray{"number"}, }, []string{"true", `"foo"`, "10", "1.0"}}, {RawSchema{ OneOf: []*RawSchema{ - {Type: "boolean"}, - {Type: "string"}, - {Type: "number"}, + {Type: StringArray{"boolean"}}, + {Type: StringArray{"string"}}, + {Type: StringArray{"number"}}, }, + Type: StringArray(nil), }, []string{"true", `"foo"`, "1.0", "10"}}, } for i, tt := range tests { diff --git a/jsonschema/parser.go b/jsonschema/parser.go index 1493ecae6..2ed60c65d 100644 --- a/jsonschema/parser.go +++ b/jsonschema/parser.go @@ -262,7 +262,7 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo return s, nil } - if schema.Type == "" && p.inferTypes { + if len(schema.Type) == 0 && p.inferTypes { switch { case len(schema.Default) > 0: schema.Type, err = inferJSONType(json.RawMessage(schema.Default)) @@ -283,45 +283,74 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo schema.PatternProperties != nil || schema.MaxProperties != nil || schema.MinProperties != nil: - schema.Type = "object" + schema.Type = []string{"object"} case schema.Items != nil || schema.UniqueItems || schema.MaxItems != nil || schema.MinItems != nil: - schema.Type = "array" + schema.Type = []string{"array"} case schema.Maximum != nil || schema.Minimum != nil || schema.ExclusiveMinimum || schema.ExclusiveMaximum || // FIXME(tdakkota): check for existence instead of true? schema.MultipleOf != nil: - schema.Type = "number" + schema.Type = []string{"number"} case schema.MaxLength != nil || schema.MinLength != nil || schema.Pattern != "": - schema.Type = "string" + schema.Type = []string{"string"} } } } - typ, ok := map[string]SchemaType{ - "object": Object, - "array": Array, - "string": String, - "integer": Integer, - "number": Number, - "boolean": Boolean, - "null": Null, - "": Empty, - }[schema.Type] - if !ok { - err := errors.Errorf("unexpected schema type: %q", schema.Type) - return nil, wrapField("type", err) + var ( + typ SchemaType + nullable = schema.Nullable + ) + for _, t := range schema.Type { + if t == "null" { + nullable = true + } + } + + if len(schema.Type) > 0 { + switch len(schema.Type) { + case 1: + typ = SchemaType(schema.Type[0]) + case 2: + var mainType string + for _, t := range schema.Type { + if t != "null" { + mainType = t + break + } + } + + if mainType != "" { + typ = SchemaType(mainType) + } else { + typ = Null + } + default: + // More than 2 types, just pick the first one for now. + typ = SchemaType(schema.Type[0]) + } + + if typ == "null" { + typ = Null + } + + if !typ.IsPrimitive() && typ != Empty && typ != Null { + return nil, wrapField("type", errors.Errorf("unexpected schema type: %q", typ)) + } + } else { + typ = Empty } - if schema.Type != "" { + if len(schema.Type) > 0 { allowed := map[string]map[string]struct{}{ "object": { "required": {}, @@ -373,7 +402,7 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo } } - if allowedFields, ok := allowed[schema.Type]; ok { + if allowedFields, ok := allowed[string(typ)]; ok { fields, err := getRawSchemaFields(schema) if err != nil { return nil, err @@ -391,7 +420,7 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo // They will be interpreted during validation code generation. continue } - return nil, wrapField(field, errors.Errorf("unexpected field for type %q", schema.Type)) + return nil, wrapField(field, errors.Errorf("unexpected field for type %q", typ)) } } } @@ -400,7 +429,7 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo s := hook(&Schema{ Type: typ, Format: schema.Format, - Nullable: typ == Null, + Nullable: nullable, Required: slices.Clone(schema.Required), // Object validators MaxProperties: schema.MaxProperties, @@ -605,7 +634,7 @@ func (p *Parser) extendInfo(schema *RawSchema, s *Schema, file location.File) *S // Nullable enums will be handled later. if len(s.Enum) < 1 { - s.Nullable = schema.Nullable + s.Nullable = s.Nullable || schema.Nullable } if x := schema.XML; x != nil { s.XML = &XML{ diff --git a/jsonschema/parser_enum.go b/jsonschema/parser_enum.go index e29093324..6fd71f90b 100644 --- a/jsonschema/parser_enum.go +++ b/jsonschema/parser_enum.go @@ -10,19 +10,19 @@ import ( "github.com/ogen-go/ogen/internal/xslices" ) -func inferJSONType(v json.RawMessage) (string, error) { +func inferJSONType(v json.RawMessage) ([]string, error) { d := jx.DecodeBytes(v) switch tt := d.Next(); tt { case jx.String: - return "string", nil + return []string{"string"}, nil case jx.Number: - return "number", nil + return []string{"number"}, nil case jx.Bool: - return "bool", nil + return []string{"boolean"}, nil case jx.Null: - return "", errors.Errorf("cannot infer type from %q", v) + return []string{"null"}, nil default: - return "", errors.Errorf("invalid value %q", v) + return nil, errors.Errorf("invalid value %q", v) } } @@ -102,7 +102,7 @@ func handleNullableEnum(s *Schema) { // Notice that nullable enum requires `null` in value list. // // Check that enum contains `null` value. - s.Nullable = s.Nullable || slices.ContainsFunc(s.Enum, func(v any) bool { + s.Nullable = s.Nullable || s.Type == Null || slices.ContainsFunc(s.Enum, func(v any) bool { return v == nil }) // Filter all `null`s. diff --git a/jsonschema/parser_test.go b/jsonschema/parser_test.go index 199bb5dde..091b33987 100644 --- a/jsonschema/parser_test.go +++ b/jsonschema/parser_test.go @@ -36,28 +36,44 @@ func testCtx() *jsonpointer.ResolveCtx { return jsonpointer.NewResolveCtx(&url.URL{Path: "/root.json"}, jsonpointer.DefaultDepthLimit) } +func TestSchemaNullableType(t *testing.T) { + parser := NewParser(Settings{}) + + out, err := parser.Parse(&RawSchema{ + Type: StringArray{"string", "null"}, + }, testCtx()) + require.NoError(t, err) + + expect := &Schema{ + Type: String, + Nullable: true, + } + + require.Equal(t, expect, out) +} + func TestSchemaSimple(t *testing.T) { parser := NewParser(Settings{}) out, err := parser.Parse(&RawSchema{ - Type: "object", + Type: StringArray{"object"}, Properties: []RawProperty{ { Name: "id", - Schema: &RawSchema{Type: "integer"}, + Schema: &RawSchema{Type: StringArray{"integer"}}, }, { Name: "name", - Schema: &RawSchema{Type: "string"}, + Schema: &RawSchema{Type: StringArray{"string"}}, }, }, - Required: []string{"id", "name"}, + Required: StringArray{"id", "name"}, }, testCtx()) require.NoError(t, err) expect := &Schema{ Type: Object, - Required: []string{"id", "name"}, + Required: StringArray{"id", "name"}, Properties: []Property{ { Name: "id", @@ -78,20 +94,20 @@ func TestSchemaSimple(t *testing.T) { func TestSchemaRecursive(t *testing.T) { components := components{ "Pet": { - Type: "object", + Type: StringArray{"object"}, Properties: []RawProperty{ { Name: "id", - Schema: &RawSchema{Type: "integer"}, + Schema: &RawSchema{Type: StringArray{"integer"}}, }, { Name: "name", - Schema: &RawSchema{Type: "string"}, + Schema: &RawSchema{Type: StringArray{"string"}}, }, { Name: "friends", Schema: &RawSchema{ - Type: "array", + Type: StringArray{"array"}, Items: &RawItems{ Item: &RawSchema{ Ref: "#/components/schemas/Pet", @@ -100,13 +116,13 @@ func TestSchemaRecursive(t *testing.T) { }, }, }, - Required: []string{"id", "name", "friends"}, + Required: StringArray{"id", "name", "friends"}, }, } pet := &Schema{ Type: Object, - Required: []string{"id", "name", "friends"}, + Required: StringArray{"id", "name", "friends"}, Ref: Ref{Loc: "/root.json", Ptr: "#/components/schemas/Pet"}, } pet.Properties = []Property{ @@ -134,7 +150,7 @@ func TestSchemaRecursive(t *testing.T) { {Loc: "/root.json", Ptr: "#/components/schemas/Pet"}: { Type: Object, Ref: Ref{Loc: "/root.json", Ptr: "#/components/schemas/Pet"}, - Required: []string{"id", "name", "friends"}, + Required: StringArray{"id", "name", "friends"}, Properties: []Property{ { Name: "id", @@ -173,7 +189,7 @@ func TestSchemaRecursive(t *testing.T) { func TestSchemaInfiniteRecursion(t *testing.T) { testCases := []RawSchema{ { - Type: "object", + Type: StringArray{"object"}, Ref: "#/components/schemas/Type", }, } @@ -207,10 +223,10 @@ func TestSchemaRefToRef(t *testing.T) { Ref: "#/components/schemas/actual", }, "actual": { - Type: "integer", + Type: StringArray{"integer"}, }, "referer": { - Type: "object", + Type: StringArray{"object"}, Properties: RawProperties{ {"Ref1", &RawSchema{Ref: "#/components/schemas/first"}}, {"Ref2", &RawSchema{Ref: "#/components/schemas/first"}}, @@ -231,7 +247,7 @@ func TestSchemaSideEffects(t *testing.T) { expectSide := []*Schema{ { Type: Object, - Required: []string{"name", "id", "age"}, + Required: StringArray{"name", "id", "age"}, Properties: []Property{ { Name: "name", @@ -254,7 +270,7 @@ func TestSchemaSideEffects(t *testing.T) { expect := &Schema{ Type: Object, - Required: []string{"id", "name", "owner"}, + Required: StringArray{"id", "name", "owner"}, Properties: []Property{ { Name: "name", @@ -272,35 +288,35 @@ func TestSchemaSideEffects(t *testing.T) { parser := NewParser(Settings{}) out, err := parser.Parse(&RawSchema{ - Type: "object", + Type: StringArray{"object"}, Properties: []RawProperty{ { Name: "name", - Schema: &RawSchema{Type: "string"}, + Schema: &RawSchema{Type: StringArray{"string"}}, }, { Name: "owner", Schema: &RawSchema{ - Type: "object", + Type: StringArray{"object"}, Properties: []RawProperty{ { Name: "name", - Schema: &RawSchema{Type: "string"}, + Schema: &RawSchema{Type: StringArray{"string"}}, }, { Name: "age", - Schema: &RawSchema{Type: "integer"}, + Schema: &RawSchema{Type: StringArray{"integer"}}, }, { Name: "id", - Schema: &RawSchema{Type: "integer"}, + Schema: &RawSchema{Type: StringArray{"integer"}}, }, }, - Required: []string{"name", "id", "age"}, + Required: StringArray{"name", "id", "age"}, }, }, }, - Required: []string{"id", "name", "owner"}, + Required: StringArray{"id", "name", "owner"}, }, testCtx()) require.NoError(t, err) @@ -310,10 +326,10 @@ func TestSchemaSideEffects(t *testing.T) { func TestSchemaReferencedArray(t *testing.T) { components := components{ "Pets": { - Type: "array", + Type: StringArray{"array"}, Items: &RawItems{ Item: &RawSchema{ - Type: "string", + Type: StringArray{"string"}, }, }, }, @@ -331,7 +347,7 @@ func TestSchemaReferencedArray(t *testing.T) { expect := &Schema{ Type: Object, - Required: []string{"pets"}, + Required: StringArray{"pets"}, Properties: []Property{ { Name: "pets", @@ -346,7 +362,7 @@ func TestSchemaReferencedArray(t *testing.T) { }) out, err := parser.Parse(&RawSchema{ - Type: "object", + Type: StringArray{"object"}, Properties: []RawProperty{ { Name: "pets", @@ -355,7 +371,7 @@ func TestSchemaReferencedArray(t *testing.T) { }, }, }, - Required: []string{"pets"}, + Required: StringArray{"pets"}, }, testCtx()) require.NoError(t, err) @@ -437,14 +453,14 @@ func TestInvalidMultipleOf(t *testing.T) { t.Run(typ, func(t *testing.T) { for _, v := range values { _, err := parser.Parse(&RawSchema{ - Type: typ, + Type: StringArray{typ}, MultipleOf: strconv.AppendInt(nil, int64(v), 10), }, testCtx()) require.Errorf(t, err, "%d", v) } }) _, err := parser.Parse(&RawSchema{ - Type: typ, + Type: StringArray{typ}, MultipleOf: []byte("true"), }, testCtx()) require.Error(t, err) @@ -457,7 +473,7 @@ func TestSchema_MinMaxObjectProp(t *testing.T) { parser := NewParser(Settings{}) out, err := parser.Parse(&RawSchema{ - Type: "object", + Type: StringArray{"object"}, MinLength: &minLength, MaxLength: &maxLength, }, testCtx()) diff --git a/jsonschema/raw_custom_test.go b/jsonschema/raw_custom_test.go index 0c30eca1f..219b3f836 100644 --- a/jsonschema/raw_custom_test.go +++ b/jsonschema/raw_custom_test.go @@ -83,8 +83,8 @@ func TestRawProperties(t *testing.T) { wantErr bool }{ {`{"foo":{"type":"string"}, "bar":{"type":"number"}}`, RawProperties{ - {Name: "foo", Schema: &RawSchema{Type: "string"}}, - {Name: "bar", Schema: &RawSchema{Type: "number"}}, + {Name: "foo", Schema: &RawSchema{Type: StringArray{"string"}}}, + {Name: "bar", Schema: &RawSchema{Type: StringArray{"number"}}}, }, false}, // Invalid YAML. {`{`, RawProperties{}, true}, @@ -109,7 +109,7 @@ func TestAdditionalProperties(t *testing.T) { value AdditionalProperties wantErr bool }{ - {`{"type":"string"}`, AdditionalProperties{Schema: RawSchema{Type: "string"}}, false}, + {`{"type":"string"}`, AdditionalProperties{Schema: RawSchema{Type: StringArray{"string"}}}, false}, {`false`, AdditionalProperties{Bool: new(bool)}, false}, // Invalid YAML. {`{`, AdditionalProperties{}, true}, @@ -136,8 +136,8 @@ func TestPatternProperties(t *testing.T) { wantErr bool }{ {`{"\\w+":{"type":"string"}, "\\d+":{"type":"number"}}`, RawPatternProperties{ - {Pattern: "\\w+", Schema: &RawSchema{Type: "string"}}, - {Pattern: "\\d+", Schema: &RawSchema{Type: "number"}}, + {Pattern: "\\w+", Schema: &RawSchema{Type: StringArray{"string"}}}, + {Pattern: "\\d+", Schema: &RawSchema{Type: StringArray{"number"}}}, }, false}, // Invalid JSON. {`{`, RawPatternProperties{}, true}, @@ -162,12 +162,12 @@ func TestItems(t *testing.T) { value RawItems wantErr bool }{ - {`{"type":"string"}`, RawItems{Item: &RawSchema{Type: "string"}}, false}, + {`{"type":"string"}`, RawItems{Item: &RawSchema{Type: StringArray{"string"}}}, false}, {`[]`, RawItems{}, false}, {`[{"type":"string"}, {"type":"integer"}]`, RawItems{ Items: []*RawSchema{ - {Type: "string"}, - {Type: "integer"}, + {Type: StringArray{"string"}}, + {Type: StringArray{"integer"}}, }, }, false}, // Invalid YAML. diff --git a/jsonschema/raw_schema.go b/jsonschema/raw_schema.go index aad1c75e3..ca7e6984c 100644 --- a/jsonschema/raw_schema.go +++ b/jsonschema/raw_schema.go @@ -1,11 +1,96 @@ package jsonschema +import ( + "encoding/json" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "github.com/go-faster/yaml" +) + +// MarshalJSON implements json.Marshaler. +func (s StringArray) MarshalJSON() ([]byte, error) { + if len(s) == 1 { + return json.Marshal(s[0]) + } + return json.Marshal([]string(s)) +} + +// MarshalYAML implements yaml.Marshaler. +func (s StringArray) MarshalYAML() (any, error) { + if len(s) == 1 { + return s[0], nil + } + return []string(s), nil +} + +// StringArray is a type that can be unmarshaled from a single string or an array of strings. +type StringArray []string + +// UnmarshalJSON implements json.Unmarshaler. +func (s *StringArray) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + switch t := d.Next(); t { + case jx.String: + v, err := d.Str() + if err != nil { + return err + } + *s = []string{v} + return nil + case jx.Array: + *s = (*s)[:0] + return d.Arr(func(d *jx.Decoder) error { + v, err := d.Str() + if err != nil { + return err + } + *s = append(*s, v) + return nil + }) + case jx.Null: + *s = nil + return nil + default: + return errors.Errorf("unexpected type %s", t) + } +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (s *StringArray) UnmarshalYAML(node *yaml.Node) error { + switch node.Kind { + case yaml.ScalarNode: + if node.Tag == "!!null" { + *s = nil + return nil + } + var v string + if err := node.Decode(&v); err != nil { + return err + } + *s = []string{v} + return nil + case yaml.SequenceNode: + *s = (*s)[:0] + for _, n := range node.Content { + var v string + if err := n.Decode(&v); err != nil { + return err + } + *s = append(*s, v) + } + return nil + default: + return errors.Errorf("unexpected kind %d", node.Kind) + } +} + // RawSchema is unparsed JSON Schema. type RawSchema struct { Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` - Type string `json:"type,omitempty" yaml:"type,omitempty"` + Type StringArray `json:"type,omitempty" yaml:"type,omitempty"` Format string `json:"format,omitempty" yaml:"format,omitempty"` Properties RawProperties `json:"properties,omitempty" yaml:"properties,omitempty"` AdditionalProperties *AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` diff --git a/jsonschema/resolve_test.go b/jsonschema/resolve_test.go index e85a8e89d..729d483b4 100644 --- a/jsonschema/resolve_test.go +++ b/jsonschema/resolve_test.go @@ -50,7 +50,7 @@ func TestExternalReference(t *testing.T) { remote := external{ "foo.json": components{ "RemoteSchema": { - Type: "object", + Type: StringArray{"object"}, Properties: RawProperties{ { Name: "relative", @@ -73,7 +73,7 @@ func TestExternalReference(t *testing.T) { }, }, "Property": { - Type: "number", + Type: StringArray{"number"}, }, }, "https://example.com/bar.json": components{ @@ -84,7 +84,7 @@ func TestExternalReference(t *testing.T) { Ref: "https://example.com/bar.json#/components/schemas/Property", }, "Property": { - Type: "boolean", + Type: StringArray{"boolean"}, }, }, } @@ -95,7 +95,7 @@ func TestExternalReference(t *testing.T) { }) out, err := parser.Parse(&RawSchema{ - Type: "array", + Type: StringArray{"array"}, Items: &RawItems{ Item: &RawSchema{ Ref: "#/components/schemas/LocalSchema", @@ -162,7 +162,7 @@ func TestLimitDepth(t *testing.T) { Ref: "#/components/schemas/Schema4", }, "Schema4": { - Type: "string", + Type: StringArray{"string"}, }, } diff --git a/jsonschema/schema.go b/jsonschema/schema.go index b460a95c9..826a525da 100644 --- a/jsonschema/schema.go +++ b/jsonschema/schema.go @@ -31,6 +31,16 @@ const ( // String implements fmt.Stringer. func (t SchemaType) String() string { return string(t) } +// IsPrimitive returns true if the type is a primitive type. +func (t SchemaType) IsPrimitive() bool { + switch t { + case Object, Array, Integer, Number, String, Boolean, Null: + return true + default: + return false + } +} + // Ref is a JSON Schema reference. type Ref = jsonpointer.RefKey diff --git a/jsonschema/utils_test.go b/jsonschema/utils_test.go index 088e3ad51..211417bf1 100644 --- a/jsonschema/utils_test.go +++ b/jsonschema/utils_test.go @@ -15,11 +15,11 @@ func TestGetRawSchemaFields(t *testing.T) { }{ { Schema: &RawSchema{ - Type: "object", + Type: StringArray{"object"}, Properties: RawProperties{ RawProperty{ Name: "foo", - Schema: &RawSchema{Type: "null"}, + Schema: &RawSchema{Type: StringArray{"null"}}, }, }, AdditionalProperties: &AdditionalProperties{Bool: &flse}, @@ -31,6 +31,6 @@ func TestGetRawSchemaFields(t *testing.T) { for _, test := range tests { fields, err := getRawSchemaFields(test.Schema) require.NoError(t, err) - require.Equal(t, test.Expect, fields) + require.ElementsMatch(t, test.Expect, fields) } } diff --git a/openapi/parser/expand.go b/openapi/parser/expand.go index f3c03e6a6..498cdca27 100644 --- a/openapi/parser/expand.go +++ b/openapi/parser/expand.go @@ -602,7 +602,10 @@ func (e *expander) Schema(schema *jsonschema.Schema, walked map[*jsonschema.Sche delete(walked, schema) }() - expanded.Type = schema.Type.String() + expanded.Type = jsonschema.StringArray{schema.Type.String()} + if schema.Type == jsonschema.Empty { + expanded.Type = nil + } expanded.Format = schema.Format expanded.ContentEncoding = schema.ContentEncoding expanded.ContentMediaType = schema.ContentMediaType diff --git a/openapi/parser/parse_path_item_test.go b/openapi/parser/parse_path_item_test.go index a8d63bbee..f48fa0bc7 100644 --- a/openapi/parser/parse_path_item_test.go +++ b/openapi/parser/parse_path_item_test.go @@ -153,7 +153,7 @@ func TestDuplicatePathsDifferentMethods(t *testing.T) { Name: "petId", In: "path", Required: true, - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, Responses: map[string]*ogen.Response{ @@ -169,7 +169,7 @@ func TestDuplicatePathsDifferentMethods(t *testing.T) { Name: "id", In: "path", Required: true, - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, Responses: map[string]*ogen.Response{ @@ -225,7 +225,7 @@ func TestDuplicatePathsDifferentMethodsDisabled(t *testing.T) { Name: "petId", In: "path", Required: true, - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, Responses: map[string]*ogen.Response{ @@ -241,7 +241,7 @@ func TestDuplicatePathsDifferentMethodsDisabled(t *testing.T) { Name: "id", In: "path", Required: true, - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, Responses: map[string]*ogen.Response{ @@ -281,7 +281,7 @@ func TestDuplicatePathsSameMethod(t *testing.T) { Name: "petId", In: "path", Required: true, - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, Responses: map[string]*ogen.Response{ @@ -297,7 +297,7 @@ func TestDuplicatePathsSameMethod(t *testing.T) { Name: "id", In: "path", Required: true, - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, Responses: map[string]*ogen.Response{ diff --git a/openapi/parser/parse_schema_test.go b/openapi/parser/parse_schema_test.go index 881b6d134..ed0c25b7e 100644 --- a/openapi/parser/parse_schema_test.go +++ b/openapi/parser/parse_schema_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ogen-go/ogen" + "github.com/ogen-go/ogen/jsonschema" ) func TestParseDiscriminator(t *testing.T) { @@ -87,27 +88,27 @@ func TestParseDiscriminator(t *testing.T) { }, }, "Cat": { - Type: "object", + Type: jsonschema.StringArray{"object"}, Required: []string{"petType", "meow"}, Properties: ogen.Properties{ - {Name: "petType", Schema: &ogen.Schema{Type: "string"}}, - {Name: "meow", Schema: &ogen.Schema{Type: "string"}}, + {Name: "petType", Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}}, + {Name: "meow", Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}}, }, }, "Dog": { - Type: "object", + Type: jsonschema.StringArray{"object"}, Required: []string{"petType", "bark"}, Properties: ogen.Properties{ - {Name: "petType", Schema: &ogen.Schema{Type: "string"}}, - {Name: "bark", Schema: &ogen.Schema{Type: "string"}}, + {Name: "petType", Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}}, + {Name: "bark", Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}}, }, }, "Cow": { - Type: "object", + Type: jsonschema.StringArray{"object"}, Required: []string{"petType", "moo"}, Properties: ogen.Properties{ - {Name: "petType", Schema: &ogen.Schema{Type: "string"}}, - {Name: "moo", Schema: &ogen.Schema{Type: "string"}}, + {Name: "petType", Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}}, + {Name: "moo", Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}}, }, }, }, diff --git a/openapi/parser/resolve_external_test.go b/openapi/parser/resolve_external_test.go index c188473c1..5d50048e7 100644 --- a/openapi/parser/resolve_external_test.go +++ b/openapi/parser/resolve_external_test.go @@ -190,7 +190,7 @@ func TestExternalReference(t *testing.T) { Content: map[string]ogen.Media{ "application/json": { Schema: &ogen.Schema{ - Type: "string", + Type: jsonschema.StringArray{"string"}, }, Examples: map[string]*ogen.Example{ "ref": { @@ -200,7 +200,7 @@ func TestExternalReference(t *testing.T) { }, }, }, - "schema_file.json": ogen.Schema{Type: "string"}, + "schema_file.json": ogen.Schema{Type: jsonschema.StringArray{"string"}}, } a := require.New(t) diff --git a/openapi/parser/resolve_test.go b/openapi/parser/resolve_test.go index 143476bde..da184ba79 100644 --- a/openapi/parser/resolve_test.go +++ b/openapi/parser/resolve_test.go @@ -52,13 +52,13 @@ func TestComplicatedReference(t *testing.T) { "201": { Headers: map[string]*ogen.Header{ "ResponseHeader": { - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, Style: "simple", }, }, Content: map[string]ogen.Media{ "application/json": { - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, }, @@ -75,7 +75,7 @@ func TestComplicatedReference(t *testing.T) { In: "query", Style: "form", Schema: &ogen.Schema{ - Type: "string", + Type: jsonschema.StringArray{"string"}, }, }, }, @@ -83,7 +83,7 @@ func TestComplicatedReference(t *testing.T) { Content: map[string]ogen.Media{ "application/json": { Schema: &ogen.Schema{ - Type: "string", + Type: jsonschema.StringArray{"string"}, }, }, }, @@ -92,7 +92,7 @@ func TestComplicatedReference(t *testing.T) { "200": { Headers: map[string]*ogen.Header{ "ResponseHeader": { - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, Style: "simple", }, }, @@ -124,7 +124,7 @@ func TestComplicatedReference(t *testing.T) { var ( responseHeader = &openapi.Header{ Name: "ResponseHeader", - Schema: &jsonschema.Schema{Type: "string"}, + Schema: &jsonschema.Schema{Type: jsonschema.String}, In: openapi.LocationHeader, Style: openapi.HeaderStyleSimple, } @@ -140,7 +140,7 @@ func TestComplicatedReference(t *testing.T) { Ref: refKey{Loc: "/root.json", Ptr: "#/paths/~1post/post/parameters/0"}, Name: "param", Schema: &jsonschema.Schema{ - Type: "string", + Type: jsonschema.String, }, In: openapi.LocationQuery, Style: openapi.QueryStyleForm, @@ -152,7 +152,7 @@ func TestComplicatedReference(t *testing.T) { Content: map[string]*openapi.MediaType{ "application/json": { Schema: &jsonschema.Schema{ - Type: "string", + Type: jsonschema.String, }, Examples: map[string]*openapi.Example{}, Encoding: map[string]*openapi.Encoding{}, @@ -171,7 +171,7 @@ func TestComplicatedReference(t *testing.T) { "application/json": { Schema: &jsonschema.Schema{ Ref: refKey{Loc: "/root.json", Ptr: "#/paths/~1post/post/requestBody/content/application~1json/schema"}, - Type: "string", + Type: jsonschema.String, }, Examples: map[string]*openapi.Example{}, Encoding: map[string]*openapi.Encoding{}, @@ -179,12 +179,13 @@ func TestComplicatedReference(t *testing.T) { }, }, 201: { + Ref: refKey{}, Headers: map[string]*openapi.Header{ "ResponseHeader": responseHeader, }, Content: map[string]*openapi.MediaType{ "application/json": { - Schema: &jsonschema.Schema{Type: "string"}, + Schema: &jsonschema.Schema{Type: jsonschema.String}, Examples: map[string]*openapi.Example{}, Encoding: map[string]*openapi.Encoding{}, }, @@ -204,7 +205,7 @@ func TestComplicatedReference(t *testing.T) { { Name: "param", Schema: &jsonschema.Schema{ - Type: "string", + Type: jsonschema.String, }, In: openapi.LocationQuery, Style: openapi.QueryStyleForm, @@ -215,7 +216,7 @@ func TestComplicatedReference(t *testing.T) { Content: map[string]*openapi.MediaType{ "application/json": { Schema: &jsonschema.Schema{ - Type: "string", + Type: jsonschema.String, }, Examples: map[string]*openapi.Example{}, Encoding: map[string]*openapi.Encoding{}, @@ -232,8 +233,7 @@ func TestComplicatedReference(t *testing.T) { Content: map[string]*openapi.MediaType{ "application/json": { Schema: &jsonschema.Schema{ - Ref: refKey{Loc: "/root.json", Ptr: "#/paths/~1post/post/requestBody/content/application~1json/schema"}, - Type: "string", + Type: jsonschema.String, }, Examples: map[string]*openapi.Example{}, Encoding: map[string]*openapi.Encoding{}, @@ -307,16 +307,16 @@ func TestParserNoPanic(t *testing.T) { AnyOf: []*ogen.Schema{nil}, }), schema(&ogen.Schema{ - Type: "array", + Type: jsonschema.StringArray{"array"}, }), schema(&ogen.Schema{ - Type: "object", + Type: jsonschema.StringArray{"object"}, Properties: ogen.Properties{ {Name: "foo", Schema: nil}, }, }), schema(&ogen.Schema{ - Type: "object", + Type: jsonschema.StringArray{"object"}, PatternProperties: ogen.PatternProperties{ {Pattern: "foo", Schema: nil}, }, diff --git a/schema.go b/schema.go index 334b95aa0..30eabbace 100644 --- a/schema.go +++ b/schema.go @@ -21,8 +21,7 @@ type Schema struct { // Additional external documentation for this schema. ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` - // Value MUST be a string. Multiple types via an array are not supported. - Type string `json:"type,omitempty" yaml:"type,omitempty"` + Type jsonschema.StringArray `json:"type,omitempty" yaml:"type,omitempty"` // See Data Type Formats for further details (https://swagger.io/specification/#data-type-format). // While relying on JSON Schema's defined formats, diff --git a/schema_test.go b/schema_test.go index bc2244677..3b2164c1f 100644 --- a/schema_test.go +++ b/schema_test.go @@ -83,8 +83,8 @@ func TestProperties(t *testing.T) { wantErr bool }{ {`{"foo":{"type":"string"}, "bar":{"type":"number"}}`, Properties{ - {Name: "foo", Schema: &Schema{Type: "string"}}, - {Name: "bar", Schema: &Schema{Type: "number"}}, + {Name: "foo", Schema: &Schema{Type: []string{"string"}}}, + {Name: "bar", Schema: &Schema{Type: []string{"number"}}}, }, false}, // Invalid YAML. {`{`, Properties{}, true}, @@ -109,7 +109,7 @@ func TestAdditionalProperties(t *testing.T) { value AdditionalProperties wantErr bool }{ - {`{"type":"string"}`, AdditionalProperties{Schema: Schema{Type: "string"}}, false}, + {`{"type":"string"}`, AdditionalProperties{Schema: Schema{Type: []string{"string"}}}, false}, {`false`, AdditionalProperties{Bool: new(bool)}, false}, // Invalid YAML. {`{`, AdditionalProperties{}, true}, @@ -136,8 +136,8 @@ func TestPatternProperties(t *testing.T) { wantErr bool }{ {`{"\\w+":{"type":"string"}, "\\d+":{"type":"number"}}`, PatternProperties{ - {Pattern: "\\w+", Schema: &Schema{Type: "string"}}, - {Pattern: "\\d+", Schema: &Schema{Type: "number"}}, + {Pattern: "\\w+", Schema: &Schema{Type: []string{"string"}}}, + {Pattern: "\\d+", Schema: &Schema{Type: []string{"number"}}}, }, false}, // Invalid JSON. {`{`, PatternProperties{}, true}, diff --git a/tools/mkformattest/main.go b/tools/mkformattest/main.go index 5f805cf5a..36332841e 100644 --- a/tools/mkformattest/main.go +++ b/tools/mkformattest/main.go @@ -12,6 +12,7 @@ import ( "github.com/ogen-go/ogen" "github.com/ogen-go/ogen/gen" "github.com/ogen-go/ogen/gen/ir" + "github.com/ogen-go/ogen/jsonschema" ) func generateSpec() *ogen.Spec { @@ -107,11 +108,11 @@ func generateSpec() *ogen.Spec { Description: "An Error Response", Content: map[string]ogen.Media{ ir.EncodingJSON.String(): {Schema: &ogen.Schema{ - Type: "object", + Type: jsonschema.StringArray{"object"}, Description: "Error Response Schema", Properties: []ogen.Property{ - {Name: "code", Schema: &ogen.Schema{Type: "integer", Format: "int32"}}, - {Name: "status", Schema: &ogen.Schema{Type: "string"}}, + {Name: "code", Schema: &ogen.Schema{Type: jsonschema.StringArray{"integer"}, Format: "int32"}}, + {Name: "status", Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}}, }, }}, }, @@ -122,7 +123,7 @@ func generateSpec() *ogen.Spec { Description: "Referenced RequestBody", Content: map[string]ogen.Media{ ir.EncodingJSON.String(): { - Schema: &ogen.Schema{Type: "string"}, + Schema: &ogen.Schema{Type: jsonschema.StringArray{"string"}}, }, }, Required: true, @@ -221,7 +222,7 @@ func generateSpec() *ogen.Spec { } s := &ogen.Schema{ - Type: typ, + Type: jsonschema.StringArray{typ}, Format: format, } add(name, s)