diff --git a/cmd/gf/internal/cmd/cmd_gen_dao.go b/cmd/gf/internal/cmd/cmd_gen_dao.go index 4528da444..5fa9ed691 100644 --- a/cmd/gf/internal/cmd/cmd_gen_dao.go +++ b/cmd/gf/internal/cmd/cmd_gen_dao.go @@ -21,8 +21,8 @@ import ( _ "github.com/gogf/gf/contrib/drivers/mssql/v2" _ "github.com/gogf/gf/contrib/drivers/pgsql/v2" - _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" - //_ "github.com/gogf/gf/contrib/drivers/oracle/v2" + // _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" + // _ "github.com/gogf/gf/contrib/drivers/oracle/v2" ) const ( @@ -35,7 +35,7 @@ const ( cGenDaoEg = ` gf gen dao gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" -gf gen dao -p ./model -c config.yaml -g user-center -t user,user_detail,user_login +gf gen dao -p ./model -g user-center -t user,user_detail,user_login gf gen dao -r user_ ` diff --git a/net/ghttp/ghttp_request_middleware.go b/net/ghttp/ghttp_request_middleware.go index d66536528..0b5cf8454 100644 --- a/net/ghttp/ghttp_request_middleware.go +++ b/net/ghttp/ghttp_request_middleware.go @@ -133,9 +133,7 @@ func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) { reflect.ValueOf(m.request.Context()), } if funcInfo.Type.NumIn() == 2 { - var ( - inputObject reflect.Value - ) + var inputObject reflect.Value if funcInfo.Type.In(1).Kind() == reflect.Ptr { inputObject = reflect.New(funcInfo.Type.In(1).Elem()) m.request.error = m.request.Parse(inputObject.Interface()) diff --git a/net/ghttp/ghttp_server_router.go b/net/ghttp/ghttp_server_router.go index 015adf97b..5f4a24dae 100644 --- a/net/ghttp/ghttp_server_router.go +++ b/net/ghttp/ghttp_server_router.go @@ -126,7 +126,7 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) { } // Repeated router checks, this feature can be disabled by server configuration. - routerKey := s.routerMapKey(handler.HookName, method, uri, domain) + var routerKey = s.routerMapKey(handler.HookName, method, uri, domain) if !s.config.RouteOverWrite { switch handler.Type { case HandlerTypeHandler, HandlerTypeObject: @@ -164,8 +164,10 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) { } // List array, very important for router registering. // There may be multiple lists adding into this array when searching from root to leaf. - lists := make([]*glist.List, 0) - array := ([]string)(nil) + var ( + array []string + lists = make([]*glist.List, 0) + ) if strings.EqualFold("/", uri) { array = []string{"/"} } else { @@ -182,7 +184,7 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) { // priorities from high to low. // 3. There may be repeated router items in the router lists. The lists' priorities // from root to leaf are from low to high. - p := s.serveTree[domain] + var p = s.serveTree[domain] for i, part := range array { // Ignore empty URI part, like: /user//index if part == "" { @@ -223,7 +225,7 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) { } // It iterates the list array of `lists`, compares priorities and inserts the new router item in // the proper position of each list. The priority of the list is ordered from high to low. - item := (*handlerItem)(nil) + var item *handlerItem for _, l := range lists { pushed := false for e := l.Front(); e != nil; e = e.Next() { @@ -247,7 +249,7 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) { s.routesMap[routerKey] = make([]registeredRouteItem, 0) } - routeItem := registeredRouteItem{ + var routeItem = registeredRouteItem{ Source: handler.Source, Handler: handler, } @@ -391,7 +393,7 @@ func (s *Server) patternToRegular(rule string) (regular string, names []string) return rule, nil } regular = "^" - array := strings.Split(rule[1:], "/") + var array = strings.Split(rule[1:], "/") for _, v := range array { if len(v) == 0 { continue diff --git a/net/ghttp/ghttp_server_service_handler.go b/net/ghttp/ghttp_server_service_handler.go index 458aed082..35e15c988 100644 --- a/net/ghttp/ghttp_server_service_handler.go +++ b/net/ghttp/ghttp_server_service_handler.go @@ -151,13 +151,13 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth if pkgPath != "" { err = gerror.NewCodef( gcode.CodeInvalidParameter, - `invalid handler: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, BizRequest)(BizResponse, error)" is required`, + `invalid handler: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizRequest)(*BizResponse, error)" is required`, pkgPath, structName, methodName, reflect.TypeOf(f).String(), ) } else { err = gerror.NewCodef( gcode.CodeInvalidParameter, - `invalid handler: defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, BizRequest)(BizResponse, error)" is required`, + `invalid handler: defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizRequest)(*BizResponse, error)" is required`, reflect.TypeOf(f).String(), ) } @@ -186,7 +186,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth if !gstr.HasSuffix(reflectType.In(1).String(), `Req`) { err = gerror.NewCodef( gcode.CodeInvalidParameter, - `invalid struct naming for request: defined as "%s", but it should be named with "Req" suffix like "xxxReq"`, + `invalid struct naming for request: defined as "%s", but it should be named with "Req" suffix like "XxxReq"`, reflectType.In(1).String(), ) return @@ -196,7 +196,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth if !gstr.HasSuffix(reflectType.Out(0).String(), `Res`) { err = gerror.NewCodef( gcode.CodeInvalidParameter, - `invalid struct naming for response: defined as "%s", but it should be named with "Res" suffix like "xxxRes"`, + `invalid struct naming for response: defined as "%s", but it should be named with "Res" suffix like "XxxRes"`, reflectType.Out(0).String(), ) return diff --git a/net/ghttp/ghttp_server_service_object.go b/net/ghttp/ghttp_server_service_object.go index bae4bfe7d..9af28b97e 100644 --- a/net/ghttp/ghttp_server_service_object.go +++ b/net/ghttp/ghttp_server_service_object.go @@ -22,9 +22,7 @@ import ( // The optional parameter `method` is used to specify the method to be registered, which // supports multiple method names; multiple methods are separated by char ',', case-sensitive. func (s *Server) BindObject(pattern string, object interface{}, method ...string) { - var ( - bindMethod = "" - ) + var bindMethod = "" if len(method) > 0 { bindMethod = method[0] } diff --git a/os/gstructs/gstructs_tag.go b/os/gstructs/gstructs_tag.go index fdc563df8..a59b59945 100644 --- a/os/gstructs/gstructs_tag.go +++ b/os/gstructs/gstructs_tag.go @@ -208,7 +208,7 @@ func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap tagFields = append(tagFields, tagField) } // If this is an embedded attribute, it retrieves the tags recursively. - if field.IsEmbedded() { + if field.IsEmbedded() && field.OriginalKind() == reflect.Struct { if subTagFields, err := getFieldValuesByTagPriority(field.Value, priority, tagMap); err != nil { return nil, err } else { diff --git a/protocol/goai/goai_operation.go b/protocol/goai/goai_operation.go index 7f7ee5a4b..7c717e80f 100644 --- a/protocol/goai/goai_operation.go +++ b/protocol/goai/goai_operation.go @@ -6,6 +6,12 @@ package goai +import ( + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/util/gconv" +) + // Operation represents "operation" specified by OpenAPI/Swagger 3.0 standard. type Operation struct { Tags []string `json:"tags,omitempty"` @@ -20,5 +26,36 @@ type Operation struct { Security *SecurityRequirements `json:"security,omitempty"` Servers *Servers `json:"servers,omitempty"` ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` - Sort int `json:"sort"` + XExtensions `json:"-"` +} + +func (oai *OpenApiV3) tagMapToOperation(tagMap map[string]string, operation *Operation) error { + var mergedTagMap = oai.fileMapWithShortTags(tagMap) + if err := gconv.Struct(mergedTagMap, operation); err != nil { + return gerror.Wrap(err, `mapping struct tags to Operation failed`) + } + oai.tagMapToXExtensions(mergedTagMap, operation.XExtensions) + return nil +} + +func (o Operation) MarshalJSON() ([]byte, error) { + var ( + b []byte + m map[string]json.RawMessage + err error + ) + type tempOperation Operation // To prevent JSON marshal recursion error. + if b, err = json.Marshal(tempOperation(o)); err != nil { + return nil, err + } + if err = json.Unmarshal(b, &m); err != nil { + return nil, err + } + for k, v := range o.XExtensions { + if b, err = json.Marshal(v); err != nil { + return nil, err + } + m[k] = b + } + return json.Marshal(m) } diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index 3b16ed49b..09d1a08be 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -7,14 +7,8 @@ package goai import ( - "fmt" - - "github.com/gogf/gf/v2/container/gset" - "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/os/gstructs" - "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" ) @@ -34,81 +28,36 @@ type Parameter struct { Example interface{} `json:"example,omitempty"` Examples *Examples `json:"examples,omitempty"` Content *Content `json:"content,omitempty"` + XExtensions `json:"-"` } -// Parameters is specified by OpenAPI/Swagger 3.0 standard. -type Parameters []ParameterRef - -type ParameterRef struct { - Ref string - Value *Parameter +func (oai *OpenApiV3) tagMapToParameter(tagMap map[string]string, parameter *Parameter) error { + var mergedTagMap = oai.fileMapWithShortTags(tagMap) + if err := gconv.Struct(mergedTagMap, parameter); err != nil { + return gerror.Wrap(err, `mapping struct tags to Parameter failed`) + } + oai.tagMapToXExtensions(mergedTagMap, parameter.XExtensions) + return nil } -func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path, method string) (*ParameterRef, error) { +func (p Parameter) MarshalJSON() ([]byte, error) { var ( - tagMap = field.TagMap() - parameter = &Parameter{ - Name: field.TagJsonName(), - } + b []byte + m map[string]json.RawMessage + err error ) - if parameter.Name == "" { - parameter.Name = field.Name() - } - if len(tagMap) > 0 { - err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter) - if err != nil { - return nil, gerror.Wrap(err, `mapping struct tags to Parameter failed`) - } - } - if parameter.In == "" { - // Automatically detect its "in" attribute. - if gstr.ContainsI(path, fmt.Sprintf(`{%s}`, parameter.Name)) { - parameter.In = ParameterInPath - } else { - // Default the parameter input to "query" if method is "GET/DELETE". - switch gstr.ToUpper(method) { - case HttpMethodGet, HttpMethodDelete: - parameter.In = ParameterInQuery - - default: - return nil, nil - } - } - } - - switch parameter.In { - case ParameterInPath: - // Required for path parameter. - parameter.Required = true - - case ParameterInCookie, ParameterInHeader, ParameterInQuery: - - default: - return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid tag value "%s" for In`, parameter.In) - } - // Necessary schema or content. - schemaRef, err := oai.newSchemaRefWithGolangType(field.Type().Type, tagMap) - if err != nil { + type tempParameter Parameter // To prevent JSON marshal recursion error. + if b, err = json.Marshal(tempParameter(p)); err != nil { return nil, err } - parameter.Schema = schemaRef - - // Required check. - if parameter.Schema.Value != nil && parameter.Schema.Value.Pattern != "" { - if gset.NewStrSetFrom(gstr.Split(parameter.Schema.Value.Pattern, "|")).Contains(patternKeyForRequired) { - parameter.Required = true + if err = json.Unmarshal(b, &m); err != nil { + return nil, err + } + for k, v := range p.XExtensions { + if b, err = json.Marshal(v); err != nil { + return nil, err } + m[k] = b } - - return &ParameterRef{ - Ref: "", - Value: parameter, - }, nil -} - -func (r ParameterRef) MarshalJSON() ([]byte, error) { - if r.Ref != "" { - return formatRefToBytes(r.Ref), nil - } - return json.Marshal(r.Value) + return json.Marshal(m) } diff --git a/protocol/goai/goai_parameter_ref.go b/protocol/goai/goai_parameter_ref.go new file mode 100644 index 000000000..bbbc3b932 --- /dev/null +++ b/protocol/goai/goai_parameter_ref.go @@ -0,0 +1,95 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package goai + +import ( + "fmt" + + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/os/gstructs" + "github.com/gogf/gf/v2/text/gstr" +) + +// Parameters is specified by OpenAPI/Swagger 3.0 standard. +type Parameters []ParameterRef + +type ParameterRef struct { + Ref string + Value *Parameter +} + +func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path, method string) (*ParameterRef, error) { + var ( + tagMap = field.TagMap() + parameter = &Parameter{ + Name: field.TagJsonName(), + XExtensions: make(XExtensions), + } + ) + if parameter.Name == "" { + parameter.Name = field.Name() + } + if len(tagMap) > 0 { + if err := oai.tagMapToParameter(tagMap, parameter); err != nil { + return nil, err + } + } + if parameter.In == "" { + // Automatically detect its "in" attribute. + if gstr.ContainsI(path, fmt.Sprintf(`{%s}`, parameter.Name)) { + parameter.In = ParameterInPath + } else { + // Default the parameter input to "query" if method is "GET/DELETE". + switch gstr.ToUpper(method) { + case HttpMethodGet, HttpMethodDelete: + parameter.In = ParameterInQuery + + default: + return nil, nil + } + } + } + + switch parameter.In { + case ParameterInPath: + // Required for path parameter. + parameter.Required = true + + case ParameterInCookie, ParameterInHeader, ParameterInQuery: + + default: + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid tag value "%s" for In`, parameter.In) + } + // Necessary schema or content. + schemaRef, err := oai.newSchemaRefWithGolangType(field.Type().Type, tagMap) + if err != nil { + return nil, err + } + parameter.Schema = schemaRef + + // Required check. + if parameter.Schema.Value != nil && parameter.Schema.Value.Pattern != "" { + if gset.NewStrSetFrom(gstr.Split(parameter.Schema.Value.Pattern, "|")).Contains(patternKeyForRequired) { + parameter.Required = true + } + } + + return &ParameterRef{ + Ref: "", + Value: parameter, + }, nil +} + +func (r ParameterRef) MarshalJSON() ([]byte, error) { + if r.Ref != "" { + return formatRefToBytes(r.Ref), nil + } + return json.Marshal(r.Value) +} diff --git a/protocol/goai/goai_path.go b/protocol/goai/goai_path.go index 7755b249a..314a670b8 100644 --- a/protocol/goai/goai_path.go +++ b/protocol/goai/goai_path.go @@ -11,6 +11,7 @@ import ( "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/os/gstructs" "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" @@ -32,7 +33,7 @@ type Path struct { Trace *Operation `json:"trace,omitempty"` Servers Servers `json:"servers,omitempty"` Parameters Parameters `json:"parameters,omitempty"` - Sort int `json:"sort"` + XExtensions `json:"-"` } // Paths are specified by OpenAPI/Swagger standard version 3.0. @@ -82,14 +83,15 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { var ( mime string - path = Path{} + path = Path{XExtensions: make(XExtensions)} inputMetaMap = gmeta.Data(inputObject.Interface()) outputMetaMap = gmeta.Data(outputObject.Interface()) isInputStructEmpty = oai.doesStructHasNoFields(inputObject.Interface()) inputStructTypeName = oai.golangTypeToSchemaName(inputObject.Type()) outputStructTypeName = oai.golangTypeToSchemaName(outputObject.Type()) operation = Operation{ - Responses: map[string]ResponseRef{}, + Responses: map[string]ResponseRef{}, + XExtensions: make(XExtensions), } ) // Path check. @@ -128,12 +130,11 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { } if len(inputMetaMap) > 0 { - inputMetaMap = oai.fileMapWithShortTags(inputMetaMap) - if err := gconv.Struct(inputMetaMap, &path); err != nil { - return gerror.Wrap(err, `mapping struct tags to Path failed`) + if err := oai.tagMapToPath(inputMetaMap, &path); err != nil { + return err } - if err := gconv.Struct(inputMetaMap, &operation); err != nil { - return gerror.Wrap(err, `mapping struct tags to Operation failed`) + if err := oai.tagMapToOperation(inputMetaMap, &operation); err != nil { + return err } // Allowed request mime. if mime = inputMetaMap[TagNameMime]; mime == "" { @@ -207,12 +208,13 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { if _, ok := operation.Responses[responseOkKey]; !ok { var ( response = Response{ - Content: map[string]MediaType{}, + Content: map[string]MediaType{}, + XExtensions: make(XExtensions), } ) if len(outputMetaMap) > 0 { - if err := gconv.Struct(oai.fileMapWithShortTags(outputMetaMap), &response); err != nil { - return gerror.Wrap(err, `mapping struct tags to Response failed`) + if err := oai.tagMapToResponse(outputMetaMap, &response); err != nil { + return err } } // Supported mime types of response. @@ -288,3 +290,34 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { func (oai *OpenApiV3) doesStructHasNoFields(s interface{}) bool { return reflect.TypeOf(s).NumField() == 0 } + +func (oai *OpenApiV3) tagMapToPath(tagMap map[string]string, path *Path) error { + var mergedTagMap = oai.fileMapWithShortTags(tagMap) + if err := gconv.Struct(mergedTagMap, path); err != nil { + return gerror.Wrap(err, `mapping struct tags to Path failed`) + } + oai.tagMapToXExtensions(mergedTagMap, path.XExtensions) + return nil +} + +func (p Path) MarshalJSON() ([]byte, error) { + var ( + b []byte + m map[string]json.RawMessage + err error + ) + type tempPath Path // To prevent JSON marshal recursion error. + if b, err = json.Marshal(tempPath(p)); err != nil { + return nil, err + } + if err = json.Unmarshal(b, &m); err != nil { + return nil, err + } + for k, v := range p.XExtensions { + if b, err = json.Marshal(v); err != nil { + return nil, err + } + m[k] = b + } + return json.Marshal(m) +} diff --git a/protocol/goai/goai_response.go b/protocol/goai/goai_response.go index c5225b649..cdf0c10a6 100644 --- a/protocol/goai/goai_response.go +++ b/protocol/goai/goai_response.go @@ -7,11 +7,9 @@ package goai import ( - "reflect" - + "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/os/gstructs" - "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" ) // Response is specified by OpenAPI/Swagger 3.0 standard. @@ -20,90 +18,36 @@ type Response struct { Headers Headers `json:"headers,omitempty"` Content Content `json:"content,omitempty"` Links Links `json:"links,omitempty"` + XExtensions `json:"-"` } -// Responses is specified by OpenAPI/Swagger 3.0 standard. -type Responses map[string]ResponseRef - -type ResponseRef struct { - Ref string - Value *Response -} - -func (r ResponseRef) MarshalJSON() ([]byte, error) { - if r.Ref != "" { - return formatRefToBytes(r.Ref), nil +func (oai *OpenApiV3) tagMapToResponse(tagMap map[string]string, response *Response) error { + var mergedTagMap = oai.fileMapWithShortTags(tagMap) + if err := gconv.Struct(mergedTagMap, response); err != nil { + return gerror.Wrap(err, `mapping struct tags to Response failed`) } - return json.Marshal(r.Value) + oai.tagMapToXExtensions(mergedTagMap, response.XExtensions) + return nil } -type getResponseSchemaRefInput struct { - BusinessStructName string // The business struct name. - CommonResponseObject interface{} // Common response object. - CommonResponseDataField string // Common response data field. -} - -func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*SchemaRef, error) { - if in.CommonResponseObject == nil { - return &SchemaRef{ - Ref: in.BusinessStructName, - }, nil - } - +func (r Response) MarshalJSON() ([]byte, error) { var ( - dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".") - bizResponseStructSchemaRef = oai.Components.Schemas.Get(in.BusinessStructName) - schema, err = oai.structToSchema(in.CommonResponseObject) + b []byte + m map[string]json.RawMessage + err error ) - if err != nil { + type tempResponse Response // To prevent JSON marshal recursion error. + if b, err = json.Marshal(tempResponse(r)); err != nil { return nil, err } - if in.CommonResponseDataField == "" && bizResponseStructSchemaRef != nil { - // Normal response. - bizResponseStructSchemaRef.Value.Properties.Iterator(func(key string, ref SchemaRef) bool { - schema.Properties.Set(key, ref) - return true - }) - } else { - // Common response. - structFields, _ := gstructs.Fields(gstructs.FieldsInput{ - Pointer: in.CommonResponseObject, - RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag, - }) - for _, structField := range structFields { - var fieldName = structField.Name() - if jsonName := structField.TagJsonName(); jsonName != "" { - fieldName = jsonName - } - switch len(dataFieldsPartsArray) { - case 1: - if structField.Name() == dataFieldsPartsArray[0] { - if err = oai.tagMapToSchema(structField.TagMap(), bizResponseStructSchemaRef.Value); err != nil { - return nil, err - } - schema.Properties.Set(fieldName, *bizResponseStructSchemaRef) - break - } - default: - // Recursively creating common response object schema. - if structField.Name() == dataFieldsPartsArray[0] { - var structFieldInstance = reflect.New(structField.Type().Type).Elem() - schemaRef, err := oai.getResponseSchemaRef(getResponseSchemaRefInput{ - BusinessStructName: in.BusinessStructName, - CommonResponseObject: structFieldInstance, - CommonResponseDataField: gstr.Join(dataFieldsPartsArray[1:], "."), - }) - if err != nil { - return nil, err - } - schema.Properties.Set(fieldName, *schemaRef) - break - } - } - } + if err = json.Unmarshal(b, &m); err != nil { + return nil, err } - - return &SchemaRef{ - Value: schema, - }, nil + for k, v := range r.XExtensions { + if b, err = json.Marshal(v); err != nil { + return nil, err + } + m[k] = b + } + return json.Marshal(m) } diff --git a/protocol/goai/goai_response_ref.go b/protocol/goai/goai_response_ref.go new file mode 100644 index 000000000..495aada71 --- /dev/null +++ b/protocol/goai/goai_response_ref.go @@ -0,0 +1,101 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package goai + +import ( + "reflect" + + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/os/gstructs" + "github.com/gogf/gf/v2/text/gstr" +) + +type ResponseRef struct { + Ref string + Value *Response +} + +// Responses is specified by OpenAPI/Swagger 3.0 standard. +type Responses map[string]ResponseRef + +func (r ResponseRef) MarshalJSON() ([]byte, error) { + if r.Ref != "" { + return formatRefToBytes(r.Ref), nil + } + return json.Marshal(r.Value) +} + +type getResponseSchemaRefInput struct { + BusinessStructName string // The business struct name. + CommonResponseObject interface{} // Common response object. + CommonResponseDataField string // Common response data field. +} + +func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*SchemaRef, error) { + if in.CommonResponseObject == nil { + return &SchemaRef{ + Ref: in.BusinessStructName, + }, nil + } + + var ( + dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".") + bizResponseStructSchemaRef = oai.Components.Schemas.Get(in.BusinessStructName) + schema, err = oai.structToSchema(in.CommonResponseObject) + ) + if err != nil { + return nil, err + } + if in.CommonResponseDataField == "" && bizResponseStructSchemaRef != nil { + // Normal response. + bizResponseStructSchemaRef.Value.Properties.Iterator(func(key string, ref SchemaRef) bool { + schema.Properties.Set(key, ref) + return true + }) + } else { + // Common response. + structFields, _ := gstructs.Fields(gstructs.FieldsInput{ + Pointer: in.CommonResponseObject, + RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag, + }) + for _, structField := range structFields { + var fieldName = structField.Name() + if jsonName := structField.TagJsonName(); jsonName != "" { + fieldName = jsonName + } + switch len(dataFieldsPartsArray) { + case 1: + if structField.Name() == dataFieldsPartsArray[0] { + if err = oai.tagMapToSchema(structField.TagMap(), bizResponseStructSchemaRef.Value); err != nil { + return nil, err + } + schema.Properties.Set(fieldName, *bizResponseStructSchemaRef) + break + } + default: + // Recursively creating common response object schema. + if structField.Name() == dataFieldsPartsArray[0] { + var structFieldInstance = reflect.New(structField.Type().Type).Elem() + schemaRef, err := oai.getResponseSchemaRef(getResponseSchemaRefInput{ + BusinessStructName: in.BusinessStructName, + CommonResponseObject: structFieldInstance, + CommonResponseDataField: gstr.Join(dataFieldsPartsArray[1:], "."), + }) + if err != nil { + return nil, err + } + schema.Properties.Set(fieldName, *schemaRef) + break + } + } + } + } + + return &SchemaRef{ + Value: schema, + }, nil +} diff --git a/protocol/goai/goai_shema.go b/protocol/goai/goai_shema.go index 8cda06727..398d2475d 100644 --- a/protocol/goai/goai_shema.go +++ b/protocol/goai/goai_shema.go @@ -12,6 +12,7 @@ import ( "github.com/gogf/gf/v2/container/gmap" "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/os/gstructs" "github.com/gogf/gf/v2/text/gstr" @@ -58,6 +59,29 @@ type Schema struct { MaxProps *uint64 `json:"maxProperties,omitempty"` AdditionalProperties *SchemaRef `json:"additionalProperties,omitempty"` Discriminator *Discriminator `json:"discriminator,omitempty"` + XExtensions `json:"-"` +} + +func (s Schema) MarshalJSON() ([]byte, error) { + var ( + b []byte + m map[string]json.RawMessage + err error + ) + type tempSchema Schema // To prevent JSON marshal recursion error. + if b, err = json.Marshal(tempSchema(s)); err != nil { + return nil, err + } + if err = json.Unmarshal(b, &m); err != nil { + return nil, err + } + for k, v := range s.XExtensions { + if b, err = json.Marshal(v); err != nil { + return nil, err + } + m[k] = b + } + return json.Marshal(m) } // Discriminator is specified by OpenAPI/Swagger standard version 3.0. @@ -111,7 +135,8 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) { var ( tagMap = gmeta.Data(object) schema = &Schema{ - Properties: createSchemas(), + Properties: createSchemas(), + XExtensions: make(XExtensions), } ) if len(tagMap) > 0 { @@ -173,6 +198,7 @@ func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) e if err := gconv.Struct(mergedTagMap, schema); err != nil { return gerror.Wrap(err, `mapping struct tags to Schema failed`) } + oai.tagMapToXExtensions(mergedTagMap, schema.XExtensions) // Validation info to OpenAPI schema pattern. for _, tag := range gvalid.GetTags() { if validationTagValue, ok := tagMap[tag]; ok { diff --git a/protocol/goai/goai_shemaref.go b/protocol/goai/goai_shema_ref.go similarity index 96% rename from protocol/goai/goai_shemaref.go rename to protocol/goai/goai_shema_ref.go index 005818db7..7480b8e67 100644 --- a/protocol/goai/goai_shemaref.go +++ b/protocol/goai/goai_shema_ref.go @@ -25,8 +25,9 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap oaiFormat = oai.golangTypeToOAIFormat(golangType) schemaRef = &SchemaRef{} schema = &Schema{ - Type: oaiType, - Format: oaiFormat, + Type: oaiType, + Format: oaiFormat, + XExtensions: make(XExtensions), } ) if len(tagMap) > 0 { diff --git a/protocol/goai/goai_xextensions.go b/protocol/goai/goai_xextensions.go new file mode 100644 index 000000000..cc11ef6a6 --- /dev/null +++ b/protocol/goai/goai_xextensions.go @@ -0,0 +1,22 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package goai + +import ( + "github.com/gogf/gf/v2/text/gstr" +) + +// XExtensions stores the `x-` custom extensions. +type XExtensions map[string]interface{} + +func (oai *OpenApiV3) tagMapToXExtensions(tagMap map[string]string, extensions XExtensions) { + for k, v := range tagMap { + if gstr.HasPrefix(k, "x-") || gstr.HasPrefix(k, "X-") { + extensions[k] = v + } + } +} diff --git a/protocol/goai/goai_z_unit_test.go b/protocol/goai/goai_z_unit_test.go index 9c025b9e0..bb209d6b4 100644 --- a/protocol/goai/goai_z_unit_test.go +++ b/protocol/goai/goai_z_unit_test.go @@ -802,7 +802,7 @@ func Test_Required_In_Schema(t *testing.T) { func Test_Properties_In_Sequence(t *testing.T) { type ResourceCreateReq struct { - g.Meta `path:"/resource" tags:"OSS Resource" method:"put" summary:"创建实例(发货)"` + g.Meta `path:"/resource" tags:"OSS Resource" method:"put" x-sort:"1" summary:"创建实例(发货)"` AppId uint64 `v:"required" dc:"应用Id"` Uin string `v:"required" dc:"主用户账号,该资源隶属于的账号"` CreateUin string `v:"required" dc:"创建实例的用户账号"` diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index b24d9d711..0a3072bb0 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -194,7 +194,8 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string var ( tempName string elemFieldType reflect.StructField - elemFieldValue reflect.Value + elemTempValue reflect.Value + elemOriginKind reflect.Kind elemType = pointerElemReflectValue.Type() attrMap = make(map[string]string) // Attribute name to its check name which has no symbols. ) @@ -204,17 +205,15 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string if !utils.IsLetterUpper(elemFieldType.Name[0]) { continue } + elemTempValue = pointerElemReflectValue.Field(i) + elemOriginKind = elemTempValue.Kind() + for elemOriginKind == reflect.Ptr || elemOriginKind == reflect.Interface { + elemTempValue = elemTempValue.Elem() + elemOriginKind = elemTempValue.Kind() + } // Maybe it's struct/*struct embedded. - if elemFieldType.Anonymous { - elemFieldValue = pointerElemReflectValue.Field(i) - // Ignore the interface attribute if it's nil. - if elemFieldValue.Kind() == reflect.Interface { - elemFieldValue = elemFieldValue.Elem() - if !elemFieldValue.IsValid() { - continue - } - } - if err = doStruct(paramsMap, elemFieldValue, mapping, priorityTag); err != nil { + if elemFieldType.Anonymous && elemOriginKind == reflect.Struct { + if err = doStruct(paramsMap, pointerElemReflectValue.Field(i), mapping, priorityTag); err != nil { return err } } else {