Skip to content

Commit

Permalink
openapi2,3: support array of types in type field (#912)
Browse files Browse the repository at this point in the history
Resolves #563
  • Loading branch information
brandonbloom committed Feb 25, 2024
1 parent 05ccac2 commit 672dc32
Show file tree
Hide file tree
Showing 30 changed files with 367 additions and 219 deletions.
2 changes: 1 addition & 1 deletion .github/docs/openapi2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type Parameter struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Expand Down
21 changes: 20 additions & 1 deletion .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
TypeNumber = "number"
TypeObject = "object"
TypeString = "string"
TypeNull = "null"
)
const (
// FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122
Expand Down Expand Up @@ -1197,7 +1198,7 @@ type Schema struct {
AnyOf SchemaRefs `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"`
Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Type *Types `json:"type,omitempty" yaml:"type,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Expand Down Expand Up @@ -1299,6 +1300,8 @@ func (schema Schema) MarshalJSON() ([]byte, error)

func (schema *Schema) NewRef() *SchemaRef

func (schema *Schema) PermitsNull() bool

func (schema *Schema) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Schema to a copy of data.

Expand Down Expand Up @@ -1721,6 +1724,22 @@ func (tags Tags) Get(name string) *Tag
func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Tags does not comply with the OpenAPI spec.

type Types []string

func (pTypes *Types) Includes(typ string) bool

func (types *Types) Is(typ string) bool

func (pTypes *Types) MarshalJSON() ([]byte, error)

func (pTypes *Types) MarshalYAML() (interface{}, error)

func (types *Types) Permits(typ string) bool

func (types *Types) Slice() []string

func (types *Types) UnmarshalJSON(data []byte) error

type ValidationOption func(options *ValidationOptions)
ValidationOption allows the modification of how the OpenAPI document is
validated.
Expand Down
4 changes: 2 additions & 2 deletions openapi2/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type Parameter struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Expand Down Expand Up @@ -76,7 +76,7 @@ func (parameter Parameter) MarshalJSON() ([]byte, error) {
if x := parameter.CollectionFormat; x != "" {
m["collectionFormat"] = x
}
if x := parameter.Type; x != "" {
if x := parameter.Type; x != nil {
m["type"] = x
}
if x := parameter.Format; x != "" {
Expand Down
14 changes: 7 additions & 7 deletions openapi2conv/openapi2_conv.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete

case "formData":
format, typ := parameter.Format, parameter.Type
if typ == "file" {
format, typ = "binary", "string"
if typ.Is("file") {
format, typ = "binary", &openapi3.Types{"string"}
}
if parameter.Extensions == nil {
parameter.Extensions = make(map[string]interface{}, 1)
Expand Down Expand Up @@ -347,7 +347,7 @@ func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool, c
}
}
schema := &openapi3.Schema{
Type: "object",
Type: &openapi3.Types{"object"},
Properties: ToV3Schemas(bodies),
Required: requireds,
}
Expand Down Expand Up @@ -772,8 +772,8 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
}

if schema.Value != nil {
if schema.Value.Type == "string" && schema.Value.Format == "binary" {
paramType := "file"
if schema.Value.Type.Is("string") && schema.Value.Format == "binary" {
paramType := &openapi3.Types{"file"}
required := false

value, _ := schema.Value.Extensions["x-formData-name"]
Expand Down Expand Up @@ -825,7 +825,7 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
for i, v := range schema.Value.AllOf {
schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
}
if schema.Value.Nullable {
if schema.Value.PermitsNull() {
schema.Value.Nullable = false
if schema.Value.Extensions == nil {
schema.Value.Extensions = make(map[string]interface{})
Expand Down Expand Up @@ -893,7 +893,7 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter
val := schemaRef.Value
typ := val.Type
if val.Format == "binary" {
typ = "file"
typ = &openapi3.Types{"file"}
}
required := false
for _, name := range val.Required {
Expand Down
4 changes: 2 additions & 2 deletions openapi3/issue301_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ func TestIssue301(t *testing.T) {
err = doc.Validate(sl.Context)
require.NoError(t, err)

require.Equal(t, "object", doc.
require.Equal(t, &Types{"object"}, doc.
Paths.Value("/trans").
Post.Callbacks["transactionCallback"].Value.
Value("http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}").
Post.RequestBody.Value.
Content["application/json"].Schema.Value.
Type)

require.Equal(t, "boolean", doc.
require.Equal(t, &Types{"boolean"}, doc.
Paths.Value("/other").
Post.Callbacks["myEvent"].Value.
Value("{$request.query.queryUrl}").
Expand Down
2 changes: 1 addition & 1 deletion openapi3/issue341_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestIssue341(t *testing.T) {
}
}`, string(bs))

require.Equal(t, "string", doc.
require.Equal(t, &Types{"string"}, doc.
Paths.Value("/testpath").
Get.
Responses.Value("200").Value.
Expand Down
2 changes: 1 addition & 1 deletion openapi3/issue344_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ func TestIssue344(t *testing.T) {
err = doc.Validate(sl.Context)
require.NoError(t, err)

require.Equal(t, "string", doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type)
require.Equal(t, &Types{"string"}, doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type)
}
2 changes: 1 addition & 1 deletion openapi3/issue376_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ info:
require.Equal(t, 2, len(doc.Components.Schemas))
require.Equal(t, 0, doc.Paths.Len())

require.Equal(t, "string", doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type)
require.Equal(t, &Types{"string"}, doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type)
}

func TestExclusiveValuesOfValuesAdditionalProperties(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion openapi3/issue495_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ paths:
err = doc.Validate(sl.Context)
require.NoError(t, err)

require.Equal(t, &Schema{Type: "object"}, doc.Components.Schemas["schemaArray"].Value.Items.Value)
require.Equal(t, &Schema{Type: &Types{"object"}}, doc.Components.Schemas["schemaArray"].Value.Items.Value)
}

func TestIssue495WithDraft04(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion openapi3/issue638_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ func TestIssue638(t *testing.T) {
// testdata/issue638/test1.yaml : reproduce
doc, err := loader.LoadFromFile("testdata/issue638/test1.yaml")
require.NoError(t, err)
require.Equal(t, "int", doc.Components.Schemas["test1d"].Value.Type)
require.Equal(t, &Types{"int"}, doc.Components.Schemas["test1d"].Value.Type)
}
}
2 changes: 1 addition & 1 deletion openapi3/issue652_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ func TestIssue652(t *testing.T) {

schema := spec.Components.Schemas[schemaName]
assert.Equal(t, schema.Ref, "../definitions.yml#/components/schemas/TestSchema")
assert.Equal(t, schema.Value.Type, "string")
assert.Equal(t, schema.Value.Type, &openapi3.Types{"string"})
})
}
16 changes: 8 additions & 8 deletions openapi3/issue689_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestIssue689(t *testing.T) {
{
name: "read-only property succeeds when read-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", ReadOnly: true}}),
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: true}}),
value: map[string]interface{}{"foo": true},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
Expand All @@ -32,7 +32,7 @@ func TestIssue689(t *testing.T) {
{
name: "non read-only property succeeds when read-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", ReadOnly: false}}),
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest()},
value: map[string]interface{}{"foo": true},
Expand All @@ -41,7 +41,7 @@ func TestIssue689(t *testing.T) {
{
name: "read-only property fails when read-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", ReadOnly: true}}),
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: true}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest()},
value: map[string]interface{}{"foo": true},
Expand All @@ -50,7 +50,7 @@ func TestIssue689(t *testing.T) {
{
name: "non read-only property succeeds when read-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", ReadOnly: false}}),
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest()},
value: map[string]interface{}{"foo": true},
Expand All @@ -60,7 +60,7 @@ func TestIssue689(t *testing.T) {
{
name: "write-only property succeeds when write-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", WriteOnly: true}}),
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: true}}),
value: map[string]interface{}{"foo": true},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse(),
Expand All @@ -70,7 +70,7 @@ func TestIssue689(t *testing.T) {
{
name: "non write-only property succeeds when write-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", WriteOnly: false}}),
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse()},
value: map[string]interface{}{"foo": true},
Expand All @@ -79,7 +79,7 @@ func TestIssue689(t *testing.T) {
{
name: "write-only property fails when write-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", WriteOnly: true}}),
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: true}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse()},
value: map[string]interface{}{"foo": true},
Expand All @@ -88,7 +88,7 @@ func TestIssue689(t *testing.T) {
{
name: "non write-only property succeeds when write-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", WriteOnly: false}}),
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse()},
value: map[string]interface{}{"foo": true},
Expand Down
16 changes: 8 additions & 8 deletions openapi3/issue767_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestIssue767(t *testing.T) {
{
name: "default values disabled should fail with minProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", Default: true}}).WithMinProperties(1),
"foo": {Type: &openapi3.Types{"boolean"}, Default: true}}).WithMinProperties(1),
value: map[string]interface{}{},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
Expand All @@ -31,7 +31,7 @@ func TestIssue767(t *testing.T) {
{
name: "default values enabled should pass with minProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", Default: true}}).WithMinProperties(1),
"foo": {Type: &openapi3.Types{"boolean"}, Default: true}}).WithMinProperties(1),
value: map[string]interface{}{},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
Expand All @@ -42,8 +42,8 @@ func TestIssue767(t *testing.T) {
{
name: "default values enabled should pass with minProps 2",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", Default: true},
"bar": {Type: "boolean"},
"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
"bar": {Type: &openapi3.Types{"boolean"}},
}).WithMinProperties(2),
value: map[string]interface{}{"bar": false},
opts: []openapi3.SchemaValidationOption{
Expand All @@ -55,8 +55,8 @@ func TestIssue767(t *testing.T) {
{
name: "default values enabled should fail with maxProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", Default: true},
"bar": {Type: "boolean"},
"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
"bar": {Type: &openapi3.Types{"boolean"}},
}).WithMaxProperties(1),
value: map[string]interface{}{"bar": false},
opts: []openapi3.SchemaValidationOption{
Expand All @@ -68,8 +68,8 @@ func TestIssue767(t *testing.T) {
{
name: "default values disabled should pass with maxProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: "boolean", Default: true},
"bar": {Type: "boolean"},
"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
"bar": {Type: &openapi3.Types{"boolean"}},
}).WithMaxProperties(1),
value: map[string]interface{}{"bar": false},
opts: []openapi3.SchemaValidationOption{
Expand Down
2 changes: 1 addition & 1 deletion openapi3/load_cicular_ref_with_external_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestLoadCircularRefFromFile(t *testing.T) {
Value: &openapi3.Schema{
Properties: map[string]*openapi3.SchemaRef{
"id": {
Value: &openapi3.Schema{Type: "string"}},
Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion openapi3/load_with_go_embed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ func Example() {
Properties["bar"].Value.
Type,
)
// Output: string
// Output: &[string]
}
6 changes: 3 additions & 3 deletions openapi3/loader_issue212_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ components:
require.NoError(t, err)

expected, err := json.Marshal(&Schema{
Type: "object",
Type: &Types{"object"},
Required: []string{"id", "uri"},
Properties: Schemas{
"id": {Value: &Schema{Type: "string"}},
"uri": {Value: &Schema{Type: "string"}},
"id": {Value: &Schema{Type: &Types{"string"}}},
"uri": {Value: &Schema{Type: &Types{"string"}}},
},
},
)
Expand Down
2 changes: 1 addition & 1 deletion openapi3/loader_issue220_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestIssue220(t *testing.T) {
err = doc.Validate(loader.Context)
require.NoError(t, err)

require.Equal(t, "integer", doc.
require.Equal(t, &Types{"integer"}, doc.
Paths.Value("/foo").
Get.Responses.Value("200").Value.
Content["application/json"].
Expand Down
2 changes: 1 addition & 1 deletion openapi3/loader_outside_refs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestLoadOutsideRefs(t *testing.T) {
err = doc.Validate(loader.Context)
require.NoError(t, err)

require.Equal(t, "string", doc.
require.Equal(t, &Types{"string"}, doc.
Paths.Value("/service").
Get.
Responses.Value("200").Value.
Expand Down
2 changes: 1 addition & 1 deletion openapi3/loader_recursive_ref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ components:
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, "object", doc.Components.
require.Equal(t, &Types{"object"}, doc.Components.
Schemas["Complex"].
Value.Properties["parent"].
Value.Properties["parent"].
Expand Down

0 comments on commit 672dc32

Please sign in to comment.