mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
add common request structure feature support for openapi
This commit is contained in:
@ -8,6 +8,8 @@ package goai
|
||||
|
||||
// Config provides extra configuration feature for OpenApiV3 implements.
|
||||
type Config struct {
|
||||
CommonRequest interface{} // Common request structure for all paths.
|
||||
CommonRequestDataField string // Common request field name to be replaced with certain business request structure. Eg: `Data`, `Request.`.
|
||||
CommonResponse interface{} // Common response structure for all paths.
|
||||
CommonResponseDataField string // Common response field name to be replaced with certain business response structure. Eg: `Data`, `Response.`.
|
||||
ReadContentTypes []string // ReadContentTypes specifies the default MIME types for consuming if MIME types are not configured.
|
||||
|
||||
@ -137,7 +137,10 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
|
||||
return gerror.WrapCode(gcode.CodeInternalError, err, `mapping struct tags to Operation failed`)
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================================================
|
||||
// Request.
|
||||
// =================================================================================================================
|
||||
if operation.RequestBody == nil {
|
||||
operation.RequestBody = &RequestBodyRef{}
|
||||
}
|
||||
@ -160,10 +163,16 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
|
||||
if isInputStructEmpty {
|
||||
requestBody.Content[v] = MediaType{}
|
||||
} else {
|
||||
schemaRef, err := oai.getRequestSchemaRef(getRequestSchemaRefInput{
|
||||
BusinessStructName: outputStructTypeName,
|
||||
RequestObject: oai.Config.CommonRequest,
|
||||
RequestDataField: oai.Config.CommonRequestDataField,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestBody.Content[v] = MediaType{
|
||||
Schema: &SchemaRef{
|
||||
Ref: inputStructTypeName,
|
||||
},
|
||||
Schema: schemaRef,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,7 +198,9 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================================================
|
||||
// Response.
|
||||
// =================================================================================================================
|
||||
if _, ok := operation.Responses[responseOkKey]; !ok {
|
||||
var (
|
||||
response = Response{
|
||||
@ -272,71 +283,3 @@ func (oai *OpenApiV3) doesStructHasNoFields(s interface{}) bool {
|
||||
})
|
||||
return len(structFields) == 0
|
||||
}
|
||||
|
||||
type getResponseSchemaRefInput struct {
|
||||
BusinessStructName string
|
||||
ResponseObject interface{}
|
||||
ResponseDataField string
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*SchemaRef, error) {
|
||||
if oai.Config.CommonResponse == nil {
|
||||
return &SchemaRef{
|
||||
Ref: in.BusinessStructName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
dataFieldsPartsArray = gstr.Split(in.ResponseDataField, ".")
|
||||
bizResponseStructSchemaRef, bizResponseStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName]
|
||||
schema, err = oai.structToSchema(in.ResponseObject)
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.ResponseDataField == "" && bizResponseStructSchemaRefExist {
|
||||
for k, v := range bizResponseStructSchemaRef.Value.Properties {
|
||||
schema.Properties[k] = v
|
||||
}
|
||||
} else {
|
||||
structFields, _ := structs.Fields(structs.FieldsInput{
|
||||
Pointer: in.ResponseObject,
|
||||
RecursiveOption: structs.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] {
|
||||
schema.Properties[fieldName] = bizResponseStructSchemaRef
|
||||
break
|
||||
}
|
||||
default:
|
||||
if structField.Name() == dataFieldsPartsArray[0] {
|
||||
var (
|
||||
structFieldInstance = reflect.New(structField.Type().Type)
|
||||
)
|
||||
schemaRef, err := oai.getResponseSchemaRef(getResponseSchemaRefInput{
|
||||
BusinessStructName: in.BusinessStructName,
|
||||
ResponseObject: structFieldInstance,
|
||||
ResponseDataField: gstr.Join(dataFieldsPartsArray[1:], "."),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Properties[fieldName] = *schemaRef
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &SchemaRef{
|
||||
Value: schema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ package goai
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
|
||||
@ -28,3 +31,71 @@ func (r RequestBodyRef) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
|
||||
type getRequestSchemaRefInput struct {
|
||||
BusinessStructName string
|
||||
RequestObject interface{}
|
||||
RequestDataField string
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaRef, error) {
|
||||
if oai.Config.CommonRequest == nil {
|
||||
return &SchemaRef{
|
||||
Ref: in.BusinessStructName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
dataFieldsPartsArray = gstr.Split(in.RequestDataField, ".")
|
||||
bizRequestStructSchemaRef, bizRequestStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName]
|
||||
schema, err = oai.structToSchema(in.RequestObject)
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.RequestDataField == "" && bizRequestStructSchemaRefExist {
|
||||
for k, v := range bizRequestStructSchemaRef.Value.Properties {
|
||||
schema.Properties[k] = v
|
||||
}
|
||||
} else {
|
||||
structFields, _ := structs.Fields(structs.FieldsInput{
|
||||
Pointer: in.RequestObject,
|
||||
RecursiveOption: structs.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] {
|
||||
schema.Properties[fieldName] = bizRequestStructSchemaRef
|
||||
break
|
||||
}
|
||||
default:
|
||||
if structField.Name() == dataFieldsPartsArray[0] {
|
||||
var (
|
||||
structFieldInstance = reflect.New(structField.Type().Type)
|
||||
)
|
||||
schemaRef, err := oai.getRequestSchemaRef(getRequestSchemaRefInput{
|
||||
BusinessStructName: in.BusinessStructName,
|
||||
RequestObject: structFieldInstance,
|
||||
RequestDataField: gstr.Join(dataFieldsPartsArray[1:], "."),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Properties[fieldName] = *schemaRef
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &SchemaRef{
|
||||
Value: schema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ package goai
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Response is specified by OpenAPI/Swagger 3.0 standard.
|
||||
@ -32,3 +35,71 @@ func (r ResponseRef) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
|
||||
type getResponseSchemaRefInput struct {
|
||||
BusinessStructName string
|
||||
ResponseObject interface{}
|
||||
ResponseDataField string
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*SchemaRef, error) {
|
||||
if oai.Config.CommonResponse == nil {
|
||||
return &SchemaRef{
|
||||
Ref: in.BusinessStructName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
dataFieldsPartsArray = gstr.Split(in.ResponseDataField, ".")
|
||||
bizResponseStructSchemaRef, bizResponseStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName]
|
||||
schema, err = oai.structToSchema(in.ResponseObject)
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.ResponseDataField == "" && bizResponseStructSchemaRefExist {
|
||||
for k, v := range bizResponseStructSchemaRef.Value.Properties {
|
||||
schema.Properties[k] = v
|
||||
}
|
||||
} else {
|
||||
structFields, _ := structs.Fields(structs.FieldsInput{
|
||||
Pointer: in.ResponseObject,
|
||||
RecursiveOption: structs.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] {
|
||||
schema.Properties[fieldName] = bizResponseStructSchemaRef
|
||||
break
|
||||
}
|
||||
default:
|
||||
if structField.Name() == dataFieldsPartsArray[0] {
|
||||
var (
|
||||
structFieldInstance = reflect.New(structField.Type().Type)
|
||||
)
|
||||
schemaRef, err := oai.getResponseSchemaRef(getResponseSchemaRefInput{
|
||||
BusinessStructName: in.BusinessStructName,
|
||||
ResponseObject: structFieldInstance,
|
||||
ResponseDataField: gstr.Join(dataFieldsPartsArray[1:], "."),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Properties[fieldName] = *schemaRef
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &SchemaRef{
|
||||
Value: schema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -189,6 +189,192 @@ func TestOpenApiV3_Add_EmptyReqAndRes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonRequest(t *testing.T) {
|
||||
type CommonRequest struct {
|
||||
Code int `json:"code" description:"Error code"`
|
||||
Message string `json:"message" description:"Error message"`
|
||||
Data interface{} `json:"data" description:"Result data for certain request according API definition"`
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"PUT"`
|
||||
Product string `json:"product" v:"required" description:"Unique product key"`
|
||||
Name string `json:"name" v:"required" description:"Instance name"`
|
||||
}
|
||||
type Res struct {
|
||||
Product string `json:"product" v:"required" description:"Unique product key"`
|
||||
TemplateName string `json:"templateName" v:"required" description:"Workflow template name"`
|
||||
Version string `json:"version" v:"required" description:"Workflow template version"`
|
||||
TxID string `json:"txID" v:"required" description:"Transaction ID for creating instance"`
|
||||
Globals string `json:"globals" description:"Globals"`
|
||||
}
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
oai.Config.CommonRequest = CommonRequest{}
|
||||
oai.Config.CommonRequestDataField = `Data`
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
t.Assert(len(oai.Components.Schemas), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonRequest_WithoutDataField_Setting(t *testing.T) {
|
||||
type CommonRequest struct {
|
||||
Code int `json:"code" description:"Error code"`
|
||||
Message string `json:"message" description:"Error message"`
|
||||
Data interface{} `json:"data" description:"Result data for certain request according API definition"`
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"PUT"`
|
||||
Product string `json:"product" in:"query" v:"required" description:"Unique product key"`
|
||||
Name string `json:"name" in:"query" v:"required" description:"Instance name"`
|
||||
}
|
||||
type Res struct {
|
||||
Product string `json:"product" v:"required" description:"Unique product key"`
|
||||
TemplateName string `json:"templateName" v:"required" description:"Workflow template name"`
|
||||
Version string `json:"version" v:"required" description:"Workflow template version"`
|
||||
TxID string `json:"txID" v:"required" description:"Transaction ID for creating instance"`
|
||||
Globals string `json:"globals" description:"Globals"`
|
||||
}
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
oai.Config.CommonRequest = CommonRequest{}
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
fmt.Println(oai.String())
|
||||
t.Assert(len(oai.Components.Schemas), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 8)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonRequest_EmptyRequest(t *testing.T) {
|
||||
type CommonRequest struct {
|
||||
Code int `json:"code" description:"Error code"`
|
||||
Message string `json:"message" description:"Error message"`
|
||||
Data interface{} `json:"data" description:"Result data for certain request according API definition"`
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"Put"`
|
||||
Product string `json:"product" in:"query" v:"required" description:"Unique product key"`
|
||||
Name string `json:"name" in:"query" v:"required" description:"Instance name"`
|
||||
}
|
||||
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()
|
||||
)
|
||||
|
||||
oai.Config.CommonRequest = CommonRequest{}
|
||||
oai.Config.CommonRequestDataField = `Data`
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
//fmt.Println(oai.String())
|
||||
t.Assert(len(oai.Components.Schemas), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonRequest_SubDataField(t *testing.T) {
|
||||
type CommonReqError struct {
|
||||
Code string `description:"错误码"`
|
||||
Message string `description:"错误描述"`
|
||||
}
|
||||
|
||||
type CommonReqRequest struct {
|
||||
RequestId string `description:"RequestId"`
|
||||
Error *CommonReqError `json:",omitempty" description:"执行错误信息"`
|
||||
}
|
||||
|
||||
type CommonReq struct {
|
||||
Request CommonReqRequest
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"Put"`
|
||||
Product string `json:"product" in:"query" v:"required" description:"Unique product key"`
|
||||
Name string `json:"name" in:"query" v:"required" description:"Instance name"`
|
||||
}
|
||||
|
||||
type Res struct {
|
||||
Product string `json:"product" v:"required" description:"Unique product key"`
|
||||
TemplateName string `json:"templateName" v:"required" description:"Workflow template name"`
|
||||
Version string `json:"version" v:"required" description:"Workflow template version"`
|
||||
TxID string `json:"txID" v:"required" description:"Transaction ID for creating instance"`
|
||||
Globals string `json:"globals" description:"Globals"`
|
||||
}
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
oai.Config.CommonRequest = CommonReq{}
|
||||
oai.Config.CommonRequestDataField = `Request.`
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
//fmt.Println(oai.String())
|
||||
t.Assert(len(oai.Components.Schemas), 4)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties[`Request`].Value.Properties), 7)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonResponse(t *testing.T) {
|
||||
type CommonResponse struct {
|
||||
Code int `json:"code" description:"Error code"`
|
||||
|
||||
Reference in New Issue
Block a user