Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openapi2,3: support array of types in type field #912

Merged
merged 1 commit into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's update this test so its output is serialized json (or yaml), so as to show that serialization turns single valued arrays of types into a string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's precisely what this comment change is doing, right? Not sure I follow the concern.

}
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
Loading
Loading