diff --git a/protocol/goai/goai_config.go b/protocol/goai/goai_config.go index 5df16ef09..4dde471f5 100644 --- a/protocol/goai/goai_config.go +++ b/protocol/goai/goai_config.go @@ -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. diff --git a/protocol/goai/goai_path.go b/protocol/goai/goai_path.go index 9ba1889bb..00b40efb6 100644 --- a/protocol/goai/goai_path.go +++ b/protocol/goai/goai_path.go @@ -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 -} diff --git a/protocol/goai/goai_requestbody.go b/protocol/goai/goai_requestbody.go index 30fb0e3c1..8f653c7b2 100644 --- a/protocol/goai/goai_requestbody.go +++ b/protocol/goai/goai_requestbody.go @@ -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 +} diff --git a/protocol/goai/goai_response.go b/protocol/goai/goai_response.go index 78ec3ae3b..5240bb283 100644 --- a/protocol/goai/goai_response.go +++ b/protocol/goai/goai_response.go @@ -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 +} diff --git a/protocol/goai/goai_test.go b/protocol/goai/goai_test.go index 70f527692..015fcce8f 100644 --- a/protocol/goai/goai_test.go +++ b/protocol/goai/goai_test.go @@ -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"`