Skip to content
Open
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
122 changes: 84 additions & 38 deletions operationv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func (o *OperationV3) ParseAcceptComment(commentLine string) error {
schema := spec.NewSchemaSpec()

switch value {
case "application/json", "multipart/form-data", "text/xml":
case "application/json", "multipart/form-data", "text/xml", "application/x-www-form-urlencoded":
schema.Spec.Type = &spec.SingleOrArray[string]{OBJECT}
case "image/png",
"image/jpeg",
Expand Down Expand Up @@ -424,14 +424,24 @@ func (o *OperationV3) ParseParamComment(commentLine string, astFile *ast.File) e
}
case "body", "formData":
if objectType == PRIMITIVE {
schema := PrimitiveSchemaV3(refType)
var schema *spec.RefOrSpec[spec.Schema]
if paramType == "formData" && refType == "file" {
schema = spec.NewSchemaSpec()
schema.Spec.Type = &spec.SingleOrArray[string]{STRING}
schema.Spec.Format = "binary"
} else {
schema = PrimitiveSchemaV3(refType)
}

err := o.parseParamAttributeForBody(commentLine, objectType, refType, schema.Spec)
if err != nil {
return err
}

o.fillRequestBody(name, schema, required, description, true, paramType == "formData")
err = o.fillRequestBody(name, schema, required, description, true, paramType == "formData")
if err != nil {
return err
}

return nil

Expand All @@ -446,7 +456,10 @@ func (o *OperationV3) ParseParamComment(commentLine string, astFile *ast.File) e
if err != nil {
return err
}
o.fillRequestBody(name, schema, required, description, false, paramType == "formData")
err = o.fillRequestBody(name, schema, required, description, false, paramType == "formData")
if err != nil {
return err
}

return nil

Expand All @@ -468,60 +481,93 @@ func (o *OperationV3) ParseParamComment(commentLine string, astFile *ast.File) e
return nil
}

func (o *OperationV3) fillRequestBody(name string, schema *spec.RefOrSpec[spec.Schema], required bool, description string, primitive, formData bool) {
if o.RequestBody == nil {
o.RequestBody = spec.NewRequestBodySpec()
o.RequestBody.Spec.Spec.Content = make(map[string]*spec.Extendable[spec.MediaType])
func isBinarySchema(schema *spec.RefOrSpec[spec.Schema]) bool {
if schema == nil || schema.Spec == nil {
return false
}
return schema.Spec.Format == "binary"
}

if primitive && !formData {
o.RequestBody.Spec.Spec.Content["text/plain"] = spec.NewMediaType()
} else if formData {
o.RequestBody.Spec.Spec.Content["application/x-www-form-urlencoded"] = spec.NewMediaType()
} else {
o.RequestBody.Spec.Spec.Content["application/json"] = spec.NewMediaType()
func (o *OperationV3) formDataContentType(schema *spec.RefOrSpec[spec.Schema]) string {
if o.RequestBody != nil && o.RequestBody.Spec != nil && o.RequestBody.Spec.Spec.Content != nil {
content := o.RequestBody.Spec.Spec.Content

if content["multipart/form-data"] != nil {
return "multipart/form-data"
}

if content["application/x-www-form-urlencoded"] != nil {
return "application/x-www-form-urlencoded"
}
}

o.RequestBody.Spec.Spec.Required = required
if isBinarySchema(schema) {
return "multipart/form-data"
}

// Append description to existing description if this is not the first body
if o.RequestBody.Spec.Spec.Description != "" && description != "" {
o.RequestBody.Spec.Spec.Description += " | " + description
} else if description != "" {
o.RequestBody.Spec.Spec.Description = description
return "application/x-www-form-urlencoded"
}

func (o *OperationV3) fillRequestBody(
name string,
schema *spec.RefOrSpec[spec.Schema],
required bool,
description string,
primitive, formData bool,
) error {
if o.RequestBody == nil {
o.RequestBody = spec.NewRequestBodySpec()
o.RequestBody.Spec.Spec.Content = make(map[string]*spec.Extendable[spec.MediaType])
}

// Handle oneOf merging for request body schemas
contentType := "application/json"
if primitive && !formData {
contentType = "text/plain"
} else if formData {
contentType = "application/x-www-form-urlencoded"
contentType = o.formDataContentType(schema)
}

mediaType := o.RequestBody.Spec.Spec.Content[contentType]
if mediaType == nil {
mediaType = spec.NewMediaType()
o.RequestBody.Spec.Spec.Content[contentType] = mediaType
}
if schema.Ref != nil {
schema.Ref.Summary = name
schema.Ref.Description = description
}
if schema.Spec != nil {
schema.Spec.Title = name

o.RequestBody.Spec.Spec.Required = required
if description != "" && o.RequestBody.Spec.Spec.Description == "" {
o.RequestBody.Spec.Spec.Description = description
}
if mediaType.Spec.Schema == nil {
mediaType.Spec.Schema = schema
} else if mediaType.Spec.Schema.Ref != nil || mediaType.Spec.Schema.Spec.OneOf == nil {
// If there's an existing schema that doesn't have oneOf, create a oneOf schema
oneOfSchema := spec.NewSchemaSpec()
oneOfSchema.Spec.OneOf = []*spec.RefOrSpec[spec.Schema]{mediaType.Spec.Schema, schema}
mediaType.Spec.Schema = oneOfSchema
} else {
// If there's already a oneOf schema, append to it
mediaType.Spec.Schema.Spec.OneOf = append(mediaType.Spec.Schema.Spec.OneOf, schema)

if formData {
if mediaType.Spec.Schema == nil {
mediaType.Spec.Schema = spec.NewSchemaSpec()
mediaType.Spec.Schema.Spec.Type = &spec.SingleOrArray[string]{OBJECT}
mediaType.Spec.Schema.Spec.Properties = map[string]*spec.RefOrSpec[spec.Schema]{}
}

if mediaType.Spec.Schema.Ref != nil {
return fmt.Errorf("form request body schema cannot be a ref")
}

if mediaType.Spec.Schema.Spec.Properties == nil {
mediaType.Spec.Schema.Spec.Properties = map[string]*spec.RefOrSpec[spec.Schema]{}
}

if schema != nil && schema.Spec != nil && schema.Spec.Description == "" && description != "" {
schema.Spec.Description = description
}

mediaType.Spec.Schema.Spec.Properties[name] = schema
if required && !findInSlice(mediaType.Spec.Schema.Spec.Required, name) {
mediaType.Spec.Schema.Spec.Required =
append(mediaType.Spec.Schema.Spec.Required, name)
}

return nil
}

mediaType.Spec.Schema = schema
return nil
}

func (o *OperationV3) parseParamAttribute(comment, objectType, schemaType string, param *spec.Parameter) error {
Expand Down
35 changes: 32 additions & 3 deletions operationv3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1204,11 +1204,33 @@ func TestParseParamCommentByFormDataTypeV3(t *testing.T) {
requestBody := operation.RequestBody
assert.True(t, requestBody.Spec.Spec.Required)
assert.Equal(t, "this is a test file", requestBody.Spec.Spec.Description)
assert.NotNil(t, requestBody)

requestBodySpec := requestBody.Spec.Spec
assert.NotNil(t, requestBodySpec)
assert.Equal(t, &typeFile, requestBodySpec.Content["application/x-www-form-urlencoded"].Spec.Schema.Spec.Type)

media := requestBodySpec.Content["multipart/form-data"]
if assert.NotNil(t, media) {
if assert.NotNil(t, media.Spec.Schema) && assert.NotNil(t, media.Spec.Schema.Spec) {
assert.Equal(
t,
&spec.SingleOrArray[string]{OBJECT},
media.Spec.Schema.Spec.Type,
)

prop := media.Spec.Schema.Spec.Properties["file"]
if assert.NotNil(t, prop) && assert.NotNil(t, prop.Spec) {
assert.Equal(
t,
&spec.SingleOrArray[string]{STRING},
prop.Spec.Type,
)
assert.Equal(t, "binary", prop.Spec.Format)
assert.Equal(t, "this is a test file", prop.Spec.Description)
}

assert.Contains(t, media.Spec.Schema.Spec.Required, "file")
}
}
}

func TestParseParamCommentByFormDataTypeUint64V3(t *testing.T) {
Expand All @@ -1228,7 +1250,14 @@ func TestParseParamCommentByFormDataTypeUint64V3(t *testing.T) {

requestBodySpec := requestBody.Spec.Spec.Content["application/x-www-form-urlencoded"].Spec
assert.NotNil(t, requestBodySpec)
assert.Equal(t, &typeInteger, requestBodySpec.Schema.Spec.Type)
assert.Equal(t, &typeObject, requestBodySpec.Schema.Spec.Type)

prop := requestBodySpec.Schema.Spec.Properties["file"]
if assert.NotNil(t, prop) && assert.NotNil(t, prop.Spec) {
assert.Equal(t, &typeInteger, prop.Spec.Type)
}

assert.Contains(t, requestBodySpec.Schema.Spec.Required, "file")
}

func TestParseParamCommentByNotSupportedTypeV3(t *testing.T) {
Expand Down