add xextensions feature for package goai

This commit is contained in:
John Guo 2022-03-29 20:31:00 +08:00
parent c1c86c026f
commit 707dc6b346
17 changed files with 406 additions and 201 deletions

View File

@ -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_
`

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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]
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}
}
}

View File

@ -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:"创建实例的用户账号"`

View File

@ -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 {