From 23591c6dd33848cc9b8912bf06d83d65a296476f Mon Sep 17 00:00:00 2001 From: yoyofx Date: Fri, 2 Feb 2024 18:22:46 +0800 Subject: [PATCH] support fromData parameters for swagger json --- .../simpleweb/contollers/usercontroller.go | 18 ++- pkg/swagger/swagger.go | 73 ++++++++++++ pkg/swagger/swagger_path.go | 2 +- web/endpoints/swagger.go | 108 +++++++++--------- web/mvc/controller_descriptor.go | 14 +-- 5 files changed, 147 insertions(+), 68 deletions(-) create mode 100644 pkg/swagger/swagger.go diff --git a/examples/simpleweb/contollers/usercontroller.go b/examples/simpleweb/contollers/usercontroller.go index 8c6dc36..49ab19f 100644 --- a/examples/simpleweb/contollers/usercontroller.go +++ b/examples/simpleweb/contollers/usercontroller.go @@ -9,6 +9,7 @@ import ( "github.com/yoyofx/yoyogo/web/captcha" "github.com/yoyofx/yoyogo/web/context" "github.com/yoyofx/yoyogo/web/mvc" + "gorm.io/gorm/utils" "mime/multipart" "simpleweb/models" ) @@ -66,11 +67,6 @@ func (controller UserController) GetHtmlBody() actionresult.IActionResult { }) } -func (controller UserController) GetDoc() mvc.ApiDocResult[string] { - - return mvc.ApiDocumentResult[string]().Success().Data("ok").Message("hello").Build() -} - func (controller UserController) GetInfo() mvc.ApiResult { return controller.OK(controller.userAction.Login("zhang")) @@ -158,6 +154,7 @@ func (controller UserController) Upload(form *UploadForm) mvc.ApiResult { } +// TestFunc attribute routing @route("/v1/user/{id}/test") func (controller UserController) TestFunc(request *struct { mvc.RequestGET `route:"/v1/user/:id/test" doc:"测试接口"` Name string `uri:"name" doc:"测试用户名"` @@ -166,3 +163,14 @@ func (controller UserController) TestFunc(request *struct { return mvc.Success(request) } + +// GetDocumentById TestFunc attribute routing @route("/v1/user/doc/{id}") +func (controller UserController) GetDocumentById(request *struct { + mvc.RequestGET `route:"/v1/user/doc/:id" doc:"根据ID获取文档"` + Id uint64 `path:"id" doc:"文档ID"` +}) mvc.ApiDocResult[string] { + + return mvc.ApiDocumentResult[string]().Success(). + Data("Document Id:" + utils.ToString(request.Id)). + Message("GetDocumentById").Build() +} diff --git a/pkg/swagger/swagger.go b/pkg/swagger/swagger.go new file mode 100644 index 0000000..dd50201 --- /dev/null +++ b/pkg/swagger/swagger.go @@ -0,0 +1,73 @@ +package swagger + +import ( + "reflect" + "strings" +) + +// GetSwaggerType returns the type of the swagger type that corresponds to the go type. +func GetSwaggerType(goType string) string { + if strings.Contains(goType, "file") { + return "file" + } + switch goType { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64": + return "integer" + case "float32", "float64": + return "number" + case "string": + return "string" + case "bool": + return "boolean" + default: + return "object" + } +} + +func getArrayElementType(goType string) string { + return goType[2 : len(goType)-1] +} + +// ConvertToSwaggerResponse converts a struct to a swagger response. +func ConvertToSwaggerResponse(data interface{}) map[string]interface{} { + response := make(map[string]interface{}) + response["type"] = "object" + response["properties"] = make(map[string]interface{}) + + dataType := reflect.TypeOf(data) + dataValue := reflect.ValueOf(data) + + for i := 0; i < dataType.NumField(); i++ { + field := dataType.Field(i) + fieldName := field.Tag.Get("json") + if fieldName == "" { + fieldName = field.Name + } + fieldValue := dataValue.Field(i).Interface() + + fieldType := field.Type.String() + swaggerType := GetSwaggerType(fieldType) + //if swaggerType == "object" && fieldValue == nil { + // fieldValue = reflect.New(field.Type).Elem().Interface() + //} + + if swaggerType == "array" { + response["properties"].(map[string]interface{})[fieldName] = map[string]interface{}{ + "type": "array", + "items": map[string]interface{}{"type": GetSwaggerType(getArrayElementType(fieldType))}, + } + } else { + response["properties"].(map[string]interface{})[fieldName] = map[string]interface{}{ + "type": swaggerType, + } + } + + if swaggerType == "object" { + if fieldValue != nil { + response["properties"].(map[string]interface{})[fieldName].(map[string]interface{})["properties"] = ConvertToSwaggerResponse(fieldValue) + } + } + } + + return response +} diff --git a/pkg/swagger/swagger_path.go b/pkg/swagger/swagger_path.go index c80822d..7a8d24b 100644 --- a/pkg/swagger/swagger_path.go +++ b/pkg/swagger/swagger_path.go @@ -41,7 +41,7 @@ type Property struct { type ResponsesItem struct { Description string `json:"description"` - Content map[string]interface{} `json:"content"` + Content map[string]interface{} `json:"content,omitempty"` } type Security struct { diff --git a/web/endpoints/swagger.go b/web/endpoints/swagger.go index ab43332..fd4d4f4 100644 --- a/web/endpoints/swagger.go +++ b/web/endpoints/swagger.go @@ -100,33 +100,31 @@ func FilterValidParams(controller mvc.ControllerDescriptor, openapi *swagger.Ope paramSourceData := param.ParameterType.Elem() fieldNum := paramSourceData.NumField() //根据请求方法分类 - if act.ActionMethod == "post" || act.ActionMethod == "any" { - for i := 0; i < fieldNum; i++ { - // 获取方法注释 - filed := paramSourceData.Field(i) - if strings.HasPrefix(filed.Type.Name(), "Request") { - if filed.Type.Name() == "RequestBody" || filed.Type.Name() == "RequestPOST" { - act.ActionMethod = "post" - } else { - actionMethodDef := filed.Type.Name() - actionMethodDef = strings.ReplaceAll(actionMethodDef, "Request", "") - act.ActionMethod = strings.ToLower(actionMethodDef) // get / head / delete / options / patch / put - } - // 获取BODY参数注释 RequestBody or RequestGET or RequestPOST - body, parameters := RequestBody(param) - if body.Content != nil { - pathInfo.RequestBody = body - } - if len(parameters) > 0 { - pathInfo.Parameters = parameters - } - - pathInfo.Description = filed.Tag.Get("doc") - pathInfo.Summary = filed.Tag.Get("doc") - break + for i := 0; i < fieldNum; i++ { + // 获取方法注释 + filed := paramSourceData.Field(i) + if strings.HasPrefix(filed.Type.Name(), "Request") { + if filed.Type.Name() == "RequestBody" || filed.Type.Name() == "RequestPOST" { + act.ActionMethod = "post" + } else { + actionMethodDef := filed.Type.Name() + actionMethodDef = strings.ReplaceAll(actionMethodDef, "Request", "") + act.ActionMethod = strings.ToLower(actionMethodDef) // get / head / delete / options / patch / put + } + // 获取BODY参数注释 RequestBody or RequestGET or RequestPOST + body, parameters := RequestBody(param) + if body.Content != nil { + pathInfo.RequestBody = body + } + if len(parameters) > 0 { + pathInfo.Parameters = parameters } + pathInfo.Description = filed.Tag.Get("doc") + pathInfo.Summary = filed.Tag.Get("doc") + break } + } } @@ -150,6 +148,13 @@ func FilterValidParams(controller mvc.ControllerDescriptor, openapi *swagger.Ope if responseType != nil && responseType.Kind() == reflect.Struct { // struct , ApiResult , ApiDocResult[?] println(responseType.Name()) + // new struct type to object + responseObject := reflect.New(responseType).Elem().Interface() + + swaggerResponse := swagger.ConvertToSwaggerResponse(responseObject) + responseItem := swagger.ResponsesItem{Description: "OK", Content: make(map[string]interface{})} + responseItem.Content["application/json"] = map[string]interface{}{"schema": swaggerResponse} + pathInfo.Responses["200"] = responseItem } else { pathInfo.Responses["200"] = swagger.ResponsesItem{Description: "OK"} } @@ -172,7 +177,11 @@ func RequestBody(param reflectx.MethodParameterInfo) (swagger.RequestBody, []swa schema.Properties = schemaProperties for i := 0; i < fieldNum; i++ { filed := paramSourceData.Field(i) - if strings.HasPrefix(filed.Type.Name(), "Request") { + fieldTypeName := strings.ToLower(filed.Type.Name()) + if fieldTypeName == "" { + fieldTypeName = strings.ToLower(filed.Type.Elem().Name()) + } + if strings.HasPrefix(fieldTypeName, "Request") { continue } uriField := filed.Tag.Get("uri") @@ -181,7 +190,7 @@ func RequestBody(param reflectx.MethodParameterInfo) (swagger.RequestBody, []swa pathField := filed.Tag.Get("path") headerField := filed.Tag.Get("header") - if uriField != "" || pathField != "" || headerField != "" { + if uriField != "" || pathField != "" || headerField != "" || formField != "" { // 构建参数 params := swagger.Parameters{} params.In = "query" @@ -194,28 +203,37 @@ func RequestBody(param reflectx.MethodParameterInfo) (swagger.RequestBody, []swa fieldName = headerField params.In = "header" } + if fieldName == "" { + fieldName = formField + params.In = "formData" + } + params.Name = fieldName + params.Schema = struct { + Type string `json:"type"` + }(struct{ Type string }{ + Type: swagger.GetSwaggerType(fieldTypeName), + }) + params.Description = filed.Tag.Get("doc") parameterList = append(parameterList, params) } - if formField != "" || jsonField != "" { + if jsonField != "" { //if contentTypeStr == "" { // contentTypeStr = "application/x-www-form-urlencoded" //} - fieldName := formField - if fieldName == "" { - fieldName = jsonField - } + fieldName := jsonField + property := swagger.Property{} property.Type = strings.ToLower(filed.Type.Name()) if property.Type == "" { property.Type = strings.ToLower(filed.Type.Elem().Name()) } - property.Type = getSwaggerType(property.Type) - if property.Type == "file" { - property.Format = "binary" - } + property.Type = swagger.GetSwaggerType(property.Type) + //if property.Type == "file" { + // property.Format = "binary" + //} property.Description = filed.Tag.Get("doc") schemaProperties[fieldName] = property @@ -227,7 +245,7 @@ func RequestBody(param reflectx.MethodParameterInfo) (swagger.RequestBody, []swa if len(schemaProperties) > 0 { //if contentTypeStr == "" { contentTypeStr = "application/json" - //} + content := swagger.ContentType{Schema: schema} contentType[contentTypeStr] = content } else { @@ -235,21 +253,3 @@ func RequestBody(param reflectx.MethodParameterInfo) (swagger.RequestBody, []swa } return swagger.RequestBody{Content: contentType}, parameterList } - -func getSwaggerType(goType string) string { - if strings.Contains(goType, "file") { - return "string" - } - switch goType { - case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64": - return "integer" - case "float32", "float64": - return "number" - case "string": - return "string" - case "bool": - return "boolean" - default: - return "object" - } -} diff --git a/web/mvc/controller_descriptor.go b/web/mvc/controller_descriptor.go index f286467..6fbcf33 100644 --- a/web/mvc/controller_descriptor.go +++ b/web/mvc/controller_descriptor.go @@ -41,15 +41,13 @@ func NewControllerDescriptor(name string, controllerType reflect.Type, controlle MethodInfo: action, } // Action Descriptors - attributeRoute, err := addAttributeRouteActionDescriptor(name, actionDescriptor) - if err != nil { - logger.Error(err.Error()) - } else { - if attributeRoute != nil { - actionDescriptor.IsAttributeRoute = true - actionDescriptor.Route = attributeRoute - } + attributeRoute, _ := addAttributeRouteActionDescriptor(name, actionDescriptor) + + if attributeRoute != nil { + actionDescriptor.IsAttributeRoute = true + actionDescriptor.Route = attributeRoute } + actionDescriptors[actionName] = actionDescriptor } }