Skip to content

Commit

Permalink
Fix file uploads in 3.1, remove file uploads nullability in 3.0 (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored Dec 8, 2023
1 parent fb05447 commit ac93ece
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 18 deletions.
31 changes: 27 additions & 4 deletions internal/json_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func sanitizeDefName(rc *jsonschema.ReflectContext) {

// ReflectRequestBody reflects JSON schema of request body.
func ReflectRequestBody(
is31 bool, // True if OpenAPI 3.1
r *jsonschema.Reflector,
cu openapi.ContentUnit,
httpMethod string,
Expand Down Expand Up @@ -121,21 +122,43 @@ func ReflectRequestBody(
jsonschema.PropertyNameMapping(mapping),
jsonschema.PropertyNameTag(tag, additionalTags...),
sanitizeDefName,
jsonschema.InterceptNullability(func(params jsonschema.InterceptNullabilityParams) {
if params.NullAdded {
vv := reflect.Zero(params.Schema.ReflectType).Interface()

foundFiles := false
if _, ok := vv.([]multipart.File); ok {
foundFiles = true
}

if _, ok := vv.([]*multipart.FileHeader); ok {
foundFiles = true
}

if foundFiles {
params.Schema.RemoveType(jsonschema.Null)
}
}
}),
jsonschema.InterceptSchema(func(params jsonschema.InterceptSchemaParams) (stop bool, err error) {
vv := params.Value.Interface()

found := false
foundFile := false
if _, ok := vv.(*multipart.File); ok {
found = true
foundFile = true
}

if _, ok := vv.(*multipart.FileHeader); ok {
found = true
foundFile = true
}

if found {
if foundFile {
params.Schema.AddType(jsonschema.String)
params.Schema.RemoveType(jsonschema.Null)
params.Schema.WithFormat("binary")
if is31 {
params.Schema.WithExtraPropertiesItem("contentMediaType", "application/octet-stream")
}

hasFileUpload = true

Expand Down
1 change: 1 addition & 0 deletions openapi3/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ func (r *Reflector) parseRequestBody(
additionalTags ...string,
) error {
schema, hasFileUpload, err := internal.ReflectRequestBody(
false,
r.JSONSchemaReflector(),
cu,
httpMethod,
Expand Down
4 changes: 2 additions & 2 deletions openapi3/reflect_deprecated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestReflector_SetRequest_uploadInterface(t *testing.T) {
"type":"object",
"properties":{"upload1":{"$ref":"#/components/schemas/MultipartFile"}}
},
"MultipartFile":{"type":"string","format":"binary","nullable":true}
"MultipartFile":{"type":"string","format":"binary"}
}
}
}`, s)
Expand Down Expand Up @@ -342,7 +342,7 @@ func TestReflector_SetupRequest(t *testing.T) {
}
},
"components":{
"schemas":{"MultipartFile":{"type":"string","format":"binary","nullable":true}}
"schemas":{"MultipartFile":{"type":"string","format":"binary"}}
}
}`, s)
}
Expand Down
4 changes: 2 additions & 2 deletions openapi3/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func TestReflector_AddOperation_uploadInterface(t *testing.T) {
"type":"object",
"properties":{"upload1":{"$ref":"#/components/schemas/MultipartFile"}}
},
"MultipartFile":{"type":"string","format":"binary","nullable":true}
"MultipartFile":{"type":"string","format":"binary"}
}
}
}`, reflector.Spec)
Expand Down Expand Up @@ -460,7 +460,7 @@ func TestReflector_AddOperation_setup_request(t *testing.T) {
}
},
"components":{
"schemas":{"MultipartFile":{"type":"string","format":"binary","nullable":true}}
"schemas":{"MultipartFile":{"type":"string","format":"binary"}}
}
}`, s)
}
Expand Down
4 changes: 2 additions & 2 deletions openapi3/testdata/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@
"upload2":{"$ref":"#/components/schemas/MultipartFileHeader"}
}
},
"MultipartFile":{"type":"string","format":"binary","nullable":true},
"MultipartFileHeader":{"type":"string","format":"binary","nullable":true},
"MultipartFile":{"type":"string","format":"binary"},
"MultipartFileHeader":{"type":"string","format":"binary"},
"Openapi3TestReq":{"type":"object","properties":{"in_body1":{"type":"integer"},"in_body2":{"type":"string"}}},
"Openapi3TestResp":{
"title":"Sample Response","type":"object",
Expand Down
4 changes: 2 additions & 2 deletions openapi3/testdata/req_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"type":"object",
"components":{
"schemas":{
"MultipartFile":{"type":["string","null"],"format":"binary"},
"MultipartFileHeader":{"type":["string","null"],"format":"binary"}
"MultipartFile":{"type":"string","format":"binary"},
"MultipartFileHeader":{"type":"string","format":"binary"}
}
}
}
25 changes: 25 additions & 0 deletions openapi3/testdata/uploads.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"openapi":"3.0.3","info":{"title":"","version":""},
"paths":{
"/upload":{
"post":{
"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/FormDataOpenapi3TestReq"}}}},
"responses":{"204":{"description":"No Content"}}
}
}
},
"components":{
"schemas":{
"FormDataOpenapi3TestReq":{
"type":"object",
"properties":{
"upload1":{"$ref":"#/components/schemas/MultipartFile"},
"upload2":{"$ref":"#/components/schemas/MultipartFileHeader"},
"uploads3":{"type":"array","items":{"$ref":"#/components/schemas/MultipartFile"}},
"uploads4":{"type":"array","items":{"$ref":"#/components/schemas/MultipartFileHeader"}}
}
},
"MultipartFile":{"type":"string","format":"binary"},"MultipartFileHeader":{"type":"string","format":"binary"}
}
}
}
40 changes: 40 additions & 0 deletions openapi3/upload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package openapi3_test

import (
"mime/multipart"
"net/http"
"os"
"testing"

"github.com/stretchr/testify/require"
"github.com/swaggest/assertjson"
"github.com/swaggest/openapi-go/openapi3"
)

func TestNewReflector_uploads(t *testing.T) {
r := openapi3.NewReflector()

oc, err := r.NewOperationContext(http.MethodPost, "/upload")
require.NoError(t, err)

type req struct {
Upload1 multipart.File `formData:"upload1"`
Upload2 *multipart.FileHeader `formData:"upload2"`
Uploads3 []multipart.File `formData:"uploads3"`
Uploads4 []*multipart.FileHeader `formData:"uploads4"`
}

oc.AddReqStructure(req{})

require.NoError(t, r.AddOperation(oc))

schema, err := assertjson.MarshalIndentCompact(r.SpecSchema(), "", " ", 120)
require.NoError(t, err)

require.NoError(t, os.WriteFile("testdata/uploads_last_run.json", schema, 0o600))

expected, err := os.ReadFile("testdata/uploads.json")
require.NoError(t, err)

assertjson.Equal(t, expected, schema)
}
1 change: 1 addition & 0 deletions openapi31/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ func (r *Reflector) parseRequestBody(
additionalTags ...string,
) error {
schema, hasFileUpload, err := internal.ReflectRequestBody(
true,
r.JSONSchemaReflector(),
cu,
httpMethod,
Expand Down
4 changes: 2 additions & 2 deletions openapi31/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func TestReflector_AddOperation_uploadInterface(t *testing.T) {
"properties":{"upload1":{"$ref":"#/components/schemas/MultipartFile"}},
"type":"object"
},
"MultipartFile":{"format":"binary","type":["null","string"]}
"MultipartFile":{"format":"binary","type":"string","contentMediaType": "application/octet-stream"}
}
}
}`, reflector.Spec)
Expand Down Expand Up @@ -477,7 +477,7 @@ func TestReflector_AddOperation_setup_request(t *testing.T) {
}
}
},
"components":{"schemas":{"MultipartFile":{"format":"binary","type":["null","string"]}}}
"components":{"schemas":{"MultipartFile":{"format":"binary","type":"string", "contentMediaType": "application/octet-stream"}}}
}`, s)
}

Expand Down
4 changes: 2 additions & 2 deletions openapi31/testdata/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@
},
"type":"object"
},
"MultipartFile":{"format":"binary","type":["null","string"]},
"MultipartFileHeader":{"format":"binary","type":["null","string"]},
"MultipartFile":{"contentMediaType":"application/octet-stream","format":"binary","type":"string"},
"MultipartFileHeader":{"contentMediaType":"application/octet-stream","format":"binary","type":"string"},
"Openapi31TestReq":{"properties":{"in_body1":{"type":"integer"},"in_body2":{"type":"string"}},"type":"object"},
"Openapi31TestResp":{
"description":"This is a sample response.",
Expand Down
4 changes: 2 additions & 2 deletions openapi31/testdata/req_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"type":"object",
"components":{
"schemas":{
"MultipartFile":{"type":["null","string"],"format":"binary"},
"MultipartFileHeader":{"type":["null","string"],"format":"binary"}
"MultipartFile":{"type":"string","format":"binary","contentMediaType": "application/octet-stream"},
"MultipartFileHeader":{"type":"string","format":"binary","contentMediaType": "application/octet-stream"}
}
}
}
26 changes: 26 additions & 0 deletions openapi31/testdata/uploads.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"openapi":"3.1.0","info":{"title":"","version":""},
"paths":{
"/upload":{
"post":{
"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/FormDataOpenapi31TestReq"}}}},
"responses":{"204":{"description":"No Content"}}
}
}
},
"components":{
"schemas":{
"FormDataOpenapi31TestReq":{
"properties":{
"upload1":{"$ref":"#/components/schemas/MultipartFile"},
"upload2":{"$ref":"#/components/schemas/MultipartFileHeader"},
"uploads3":{"items":{"$ref":"#/components/schemas/MultipartFile"},"type":"array"},
"uploads4":{"items":{"$ref":"#/components/schemas/MultipartFileHeader"},"type":"array"}
},
"type":"object"
},
"MultipartFile":{"contentMediaType":"application/octet-stream","format":"binary","type":"string"},
"MultipartFileHeader":{"contentMediaType":"application/octet-stream","format":"binary","type":"string"}
}
}
}
40 changes: 40 additions & 0 deletions openapi31/upload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package openapi31_test

import (
"mime/multipart"
"net/http"
"os"
"testing"

"github.com/stretchr/testify/require"
"github.com/swaggest/assertjson"
"github.com/swaggest/openapi-go/openapi31"
)

func TestNewReflector_uploads(t *testing.T) {
r := openapi31.NewReflector()

oc, err := r.NewOperationContext(http.MethodPost, "/upload")
require.NoError(t, err)

type req struct {
Upload1 multipart.File `formData:"upload1"`
Upload2 *multipart.FileHeader `formData:"upload2"`
Uploads3 []multipart.File `formData:"uploads3"`
Uploads4 []*multipart.FileHeader `formData:"uploads4"`
}

oc.AddReqStructure(req{})

require.NoError(t, r.AddOperation(oc))

schema, err := assertjson.MarshalIndentCompact(r.SpecSchema(), "", " ", 120)
require.NoError(t, err)

require.NoError(t, os.WriteFile("testdata/uploads_last_run.json", schema, 0o600))

expected, err := os.ReadFile("testdata/uploads.json")
require.NoError(t, err)

assertjson.Equal(t, expected, schema)
}

0 comments on commit ac93ece

Please sign in to comment.