From e8a2629b19ddbd4144bbd69533ef4e6dd00ace5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E4=BA=AE?= <739476267@qq.com> Date: Tue, 25 Jun 2024 21:01:58 +0800 Subject: [PATCH] net/goai: fix: #3660, support multiple file upload parameters for OpenAPIv3 (#3662) --- .../httpserver/swagger-set-template/main.go | 50 +++++++++++++++++++ net/goai/goai_shema.go | 2 +- net/goai/goai_shema_ref.go | 9 ++-- net/goai/goai_shemas.go | 6 +-- net/goai/goai_z_unit_test.go | 40 +++++++++++++-- 5 files changed, 95 insertions(+), 12 deletions(-) diff --git a/example/httpserver/swagger-set-template/main.go b/example/httpserver/swagger-set-template/main.go index 1277de461..d14988b86 100644 --- a/example/httpserver/swagger-set-template/main.go +++ b/example/httpserver/swagger-set-template/main.go @@ -31,7 +31,42 @@ func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) return } +// upload file request +type UploadReq struct { + g.Meta `path:"/upload" method:"POST" tags:"Upload" mime:"multipart/form-data" summary:"上传文件"` + Files []*ghttp.UploadFile `json:"files" type:"file" dc:"选择上传多文件"` + File *ghttp.UploadFile `p:"file" type:"file" dc:"选择上传文件"` + Msg string `dc:"消息"` +} + +// upload file response +type UploadRes struct { + FilesName []string `json:"files_name"` + FileName string `json:"file_name"` + Msg string `json:"msg"` +} + +// upload file +func (Hello) Upload(ctx context.Context, req *UploadReq) (res *UploadRes, err error) { + g.Log().Debugf(ctx, `receive say: %+v`, req) + res = &UploadRes{ + Msg: req.Msg, + } + if req.File != nil { + res.FileName = req.File.Filename + } + if len(req.Files) > 0 { + var filesName []string + for _, file := range req.Files { + filesName = append(filesName, file.Filename) + } + res.FilesName = filesName + } + return +} + const ( + // MySwaggerUITemplate is the custom Swagger UI template. MySwaggerUITemplate = ` @@ -55,6 +90,20 @@ const ( +` + // OpenapiUITemplate is the OpenAPI UI template. + OpenapiUITemplate = ` + + + + + test + + +
+ + + ` ) @@ -67,5 +116,6 @@ func main() { ) }) s.SetSwaggerUITemplate(MySwaggerUITemplate) + // s.SetSwaggerUITemplate(OpenapiUITemplate) // files support s.Run() } diff --git a/net/goai/goai_shema.go b/net/goai/goai_shema.go index 340474166..f8a6be2d8 100644 --- a/net/goai/goai_shema.go +++ b/net/goai/goai_shema.go @@ -54,7 +54,7 @@ type Schema struct { MaxItems *uint64 `json:"maxItems,omitempty"` Items *SchemaRef `json:"items,omitempty"` Required []string `json:"required,omitempty"` - Properties Schemas `json:"properties,omitempty"` + Properties *Schemas `json:"properties,omitempty"` MinProps uint64 `json:"minProperties,omitempty"` MaxProps *uint64 `json:"maxProperties,omitempty"` AdditionalProperties *SchemaRef `json:"additionalProperties,omitempty"` diff --git a/net/goai/goai_shema_ref.go b/net/goai/goai_shema_ref.go index cfcfb09a6..1147618ca 100644 --- a/net/goai/goai_shema_ref.go +++ b/net/goai/goai_shema_ref.go @@ -73,6 +73,9 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap if err := oai.tagMapToSchema(tagMap, schema); err != nil { return nil, err } + if oaiType == TypeArray && schema.Type == TypeFile { + schema.Type = TypeArray + } } schemaRef.Value = schema switch oaiType { @@ -111,8 +114,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap schemaRef.Value.Example = gconv.Bool(schemaRef.Value.Example) } // keep the example value as nil. - case - TypeArray: + case TypeArray: subSchemaRef, err := oai.newSchemaRefWithGolangType(golangType.Elem(), nil) if err != nil { return nil, err @@ -123,8 +125,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap schema.Enum = nil } - case - TypeObject: + case TypeObject: for golangType.Kind() == reflect.Ptr { golangType = golangType.Elem() } diff --git a/net/goai/goai_shemas.go b/net/goai/goai_shemas.go index 87fb4995c..d2e3020f8 100644 --- a/net/goai/goai_shemas.go +++ b/net/goai/goai_shemas.go @@ -14,8 +14,8 @@ type Schemas struct { refs *gmap.ListMap // map[string]SchemaRef } -func createSchemas() Schemas { - return Schemas{ +func createSchemas() *Schemas { + return &Schemas{ refs: gmap.NewListMap(), } } @@ -26,7 +26,7 @@ func (s *Schemas) init() { } } -func (s *Schemas) Clone() Schemas { +func (s *Schemas) Clone() *Schemas { newSchemas := createSchemas() newSchemas.refs = s.refs.Clone() return newSchemas diff --git a/net/goai/goai_z_unit_test.go b/net/goai/goai_z_unit_test.go index a59edafec..323be669b 100644 --- a/net/goai/goai_z_unit_test.go +++ b/net/goai/goai_z_unit_test.go @@ -15,6 +15,7 @@ import ( "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/net/goai" "github.com/gogf/gf/v2/test/gtest" "github.com/gogf/gf/v2/util/gmeta" @@ -466,6 +467,37 @@ func TestOpenApiV3_CommonRequest_SubDataField(t *testing.T) { }) } +func TestOpenApiV3_CommonRequest_Files(t *testing.T) { + type Req struct { + g.Meta `path:"/upload" method:"POST" tags:"Upload" mime:"multipart/form-data" summary:"上传文件"` + Files []*ghttp.UploadFile `json:"files" type:"file" dc:"选择上传文件"` + File *ghttp.UploadFile `json:"file" type:"file" dc:"选择上传文件"` + } + type Res struct { + } + + f := func(ctx context.Context, req *Req) (res *Res, err error) { + return + } + + gtest.C(t, func(t *gtest.T) { + var ( + err error + oai = goai.New() + ) + err = oai.Add(goai.AddInput{ + Path: "/upload", + Method: http.MethodGet, + Object: f, + }) + t.AssertNil(err) + + // fmt.Println(oai.String()) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.net.goai_test.Req`).Value.Properties.Get("files").Value.Type, goai.TypeArray) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.net.goai_test.Req`).Value.Properties.Get("file").Value.Type, goai.TypeFile) + }) +} + func TestOpenApiV3_CommonResponse(t *testing.T) { type CommonResponse struct { Code int `json:"code" description:"Error code"` @@ -1007,7 +1039,7 @@ func Test_EmbeddedStructAttribute(t *testing.T) { b, err := json.Marshal(oai) t.AssertNil(err) - t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateResourceReq":{"properties":{"Name":{"description":"This is name.","format":"string","properties":{},"type":"string"},"Embedded":{"properties":{"Age":{"description":"This is embedded age.","format":"uint","properties":{},"type":"integer"}},"type":"object"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`) + t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateResourceReq":{"properties":{"Name":{"description":"This is name.","format":"string","type":"string"},"Embedded":{"properties":{"Age":{"description":"This is embedded age.","format":"uint","type":"integer"}},"type":"object"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`) }) } @@ -1031,7 +1063,7 @@ func Test_NameFromJsonTag(t *testing.T) { b, err := json.Marshal(oai) t.AssertNil(err) - t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateReq":{"properties":{"nick_name":{"format":"string","properties":{},"type":"string"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`) + t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateReq":{"properties":{"nick_name":{"format":"string","type":"string"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`) }) // GET gtest.C(t, func(t *gtest.T) { @@ -1052,7 +1084,7 @@ func Test_NameFromJsonTag(t *testing.T) { b, err := json.Marshal(oai) t.AssertNil(err) fmt.Println(string(b)) - t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateReq":{"properties":{"nick_name":{"format":"string","properties":{},"type":"string"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`) + t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateReq":{"properties":{"nick_name":{"format":"string","type":"string"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`) }) } @@ -1188,6 +1220,6 @@ func Test_XExtension(t *testing.T) { Object: req, }) t.AssertNil(err) - t.Assert(oai.String(), `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.GetListReq":{"properties":{"Page":{"default":1,"description":"Page number","format":"int","properties":{},"type":"integer","x-sort":"1"},"Size":{"default":10,"description":"Size for per page.","format":"int","properties":{},"type":"integer","x-sort":"2"}},"type":"object","x-group":"User/Info"}}},"info":{"title":"","version":""},"paths":null}`) + t.Assert(oai.String(), `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.GetListReq":{"properties":{"Page":{"default":1,"description":"Page number","format":"int","type":"integer","x-sort":"1"},"Size":{"default":10,"description":"Size for per page.","format":"int","type":"integer","x-sort":"2"}},"type":"object","x-group":"User/Info"}}},"info":{"title":"","version":""},"paths":null}`) }) }