add common request structure feature support for openapi

This commit is contained in:
John Guo
2021-10-11 17:10:55 +08:00
parent 310e178206
commit cbfd92a21d
5 changed files with 344 additions and 71 deletions

View File

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

View File

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

View File

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

View File

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

View File

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