gf/protocol/goai/goai_path.go

284 lines
8.7 KiB
Go
Raw Normal View History

2021-10-02 18:54:06 +08:00
// 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.
2021-10-02 14:52:28 +08:00
package goai
import (
2021-11-13 23:34:16 +08:00
"reflect"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gstructs"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gmeta"
2021-10-02 14:52:28 +08:00
)
type Path struct {
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
2021-12-07 19:08:52 +08:00
// Paths are specified by OpenAPI/Swagger standard version 3.0.
2021-10-02 14:52:28 +08:00
type Paths map[string]Path
const (
responseOkKey = `200`
)
type addPathInput struct {
2021-10-08 11:40:47 +08:00
Path string // Precise route path.
Prefix string // Route path prefix.
Method string // Route method.
Function interface{} // Uniformed function.
2021-10-02 14:52:28 +08:00
}
2021-10-02 18:54:06 +08:00
func (oai *OpenApiV3) addPath(in addPathInput) error {
2021-10-02 14:52:28 +08:00
if oai.Paths == nil {
oai.Paths = map[string]Path{}
}
var (
reflectType = reflect.TypeOf(in.Function)
)
if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
2021-10-02 18:54:06 +08:00
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`unsupported function "%s" for OpenAPI Path register, there should be input & output structures`,
2021-10-02 14:52:28 +08:00
reflectType.String(),
)
}
var (
inputObject reflect.Value
outputObject reflect.Value
)
// Create instance according input/output types.
if reflectType.In(1).Kind() == reflect.Ptr {
2021-10-13 22:28:49 +08:00
inputObject = reflect.New(reflectType.In(1).Elem()).Elem()
2021-10-02 14:52:28 +08:00
} else {
2021-11-23 21:05:31 +08:00
inputObject = reflect.New(reflectType.In(1)).Elem()
2021-10-02 14:52:28 +08:00
}
if reflectType.Out(0).Kind() == reflect.Ptr {
outputObject = reflect.New(reflectType.Out(0).Elem()).Elem()
} else {
outputObject = reflect.New(reflectType.Out(0)).Elem()
}
2021-11-25 21:58:38 +08:00
2021-10-02 14:52:28 +08:00
var (
path = Path{}
inputMetaMap = gmeta.Data(inputObject.Interface())
outputMetaMap = gmeta.Data(outputObject.Interface())
2021-10-08 21:16:47 +08:00
isInputStructEmpty = oai.doesStructHasNoFields(inputObject.Interface())
2021-10-14 15:03:39 +08:00
inputStructTypeName = oai.golangTypeToSchemaName(inputObject.Type())
outputStructTypeName = oai.golangTypeToSchemaName(outputObject.Type())
2021-10-02 14:52:28 +08:00
operation = Operation{
Responses: map[string]ResponseRef{},
}
)
// Path check.
2021-10-02 14:52:28 +08:00
if in.Path == "" {
2021-10-02 18:54:06 +08:00
in.Path = gmeta.Get(inputObject.Interface(), TagNamePath).String()
2021-10-08 11:40:47 +08:00
if in.Prefix != "" {
in.Path = gstr.TrimRight(in.Prefix, "/") + "/" + gstr.TrimLeft(in.Path, "/")
}
2021-10-02 14:52:28 +08:00
}
if in.Path == "" {
return gerror.NewCodef(
2021-10-02 15:07:47 +08:00
gcode.CodeMissingParameter,
`missing necessary path parameter "%s" for input struct "%s", missing tag in attribute Meta?`,
2021-10-02 18:54:06 +08:00
TagNamePath, inputStructTypeName,
)
2021-10-02 14:52:28 +08:00
}
2021-10-08 11:40:47 +08:00
2021-10-08 14:44:10 +08:00
if v, ok := oai.Paths[in.Path]; ok {
path = v
}
// Method check.
2021-10-02 14:52:28 +08:00
if in.Method == "" {
2021-10-02 18:54:06 +08:00
in.Method = gmeta.Get(inputObject.Interface(), TagNameMethod).String()
2021-10-02 14:52:28 +08:00
}
if in.Method == "" {
return gerror.NewCodef(
2021-10-02 15:07:47 +08:00
gcode.CodeMissingParameter,
`missing necessary method parameter "%s" for input struct "%s", missing tag in attribute Meta?`,
TagNameMethod, inputStructTypeName,
)
2021-10-02 14:52:28 +08:00
}
2021-10-02 15:07:47 +08:00
2021-10-02 18:54:06 +08:00
if err := oai.addSchema(inputObject.Interface(), outputObject.Interface()); err != nil {
return err
}
2021-10-02 14:52:28 +08:00
if len(inputMetaMap) > 0 {
if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &path); err != nil {
return gerror.Wrap(err, `mapping struct tags to Path failed`)
2021-10-02 14:52:28 +08:00
}
if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &operation); err != nil {
return gerror.Wrap(err, `mapping struct tags to Operation failed`)
2021-10-02 14:52:28 +08:00
}
}
// =================================================================================================================
2021-10-02 14:52:28 +08:00
// Request.
// =================================================================================================================
2021-10-06 19:52:19 +08:00
if operation.RequestBody == nil {
operation.RequestBody = &RequestBodyRef{}
}
2021-10-02 14:52:28 +08:00
if operation.RequestBody.Value == nil {
var (
requestBody = RequestBody{
Required: true,
Content: map[string]MediaType{},
}
)
// Supported mime types of request.
2021-10-06 15:24:01 +08:00
var (
contentTypes = oai.Config.ReadContentTypes
tagMimeValue = gmeta.Get(inputObject.Interface(), TagNameMime).String()
)
if tagMimeValue != "" {
contentTypes = gstr.SplitAndTrim(tagMimeValue, ",")
}
for _, v := range contentTypes {
2021-10-06 19:52:19 +08:00
if isInputStructEmpty {
requestBody.Content[v] = MediaType{}
} else {
schemaRef, err := oai.getRequestSchemaRef(getRequestSchemaRefInput{
2021-10-11 20:39:13 +08:00
BusinessStructName: inputStructTypeName,
RequestObject: oai.Config.CommonRequest,
RequestDataField: oai.Config.CommonRequestDataField,
})
if err != nil {
return err
}
2021-10-06 19:52:19 +08:00
requestBody.Content[v] = MediaType{
Schema: schemaRef,
2021-10-06 19:52:19 +08:00
}
2021-10-02 14:52:28 +08:00
}
}
2021-10-06 19:52:19 +08:00
operation.RequestBody = &RequestBodyRef{
2021-10-02 14:52:28 +08:00
Value: &requestBody,
}
}
// It also sets request parameters.
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
2021-10-02 18:54:06 +08:00
Pointer: inputObject.Interface(),
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
2021-10-02 18:54:06 +08:00
})
for _, structField := range structFields {
if operation.Parameters == nil {
operation.Parameters = []ParameterRef{}
}
parameterRef, err := oai.newParameterRefWithStructMethod(structField, in.Path, in.Method)
2021-10-02 18:54:06 +08:00
if err != nil {
return err
}
if parameterRef != nil {
operation.Parameters = append(operation.Parameters, *parameterRef)
}
}
// =================================================================================================================
2021-10-02 14:52:28 +08:00
// Response.
// =================================================================================================================
2021-10-02 14:52:28 +08:00
if _, ok := operation.Responses[responseOkKey]; !ok {
var (
response = Response{
Content: map[string]MediaType{},
}
)
if len(outputMetaMap) > 0 {
if err := gconv.Struct(oai.fileMapWithShortTags(outputMetaMap), &response); err != nil {
return gerror.Wrap(err, `mapping struct tags to Response failed`)
2021-10-02 14:52:28 +08:00
}
}
// Supported mime types of response.
2021-10-06 15:24:01 +08:00
var (
contentTypes = oai.Config.ReadContentTypes
tagMimeValue = gmeta.Get(outputObject.Interface(), TagNameMime).String()
2021-10-25 20:36:09 +08:00
refInput = getResponseSchemaRefInput{
BusinessStructName: outputStructTypeName,
ResponseObject: oai.Config.CommonResponse,
ResponseDataField: oai.Config.CommonResponseDataField,
}
2021-10-06 15:24:01 +08:00
)
if tagMimeValue != "" {
contentTypes = gstr.SplitAndTrim(tagMimeValue, ",")
}
for _, v := range contentTypes {
2021-10-25 20:36:09 +08:00
// If customized response mime type, it then ignores common response feature.
if tagMimeValue != "" {
refInput.ResponseObject = nil
refInput.ResponseDataField = ""
}
schemaRef, err := oai.getResponseSchemaRef(refInput)
2021-10-08 21:23:30 +08:00
if err != nil {
return err
}
response.Content[v] = MediaType{
Schema: schemaRef,
2021-10-02 14:52:28 +08:00
}
}
operation.Responses[responseOkKey] = ResponseRef{Value: &response}
}
2021-10-02 18:54:06 +08:00
2021-10-02 14:52:28 +08:00
// Assign to certain operation attribute.
switch gstr.ToUpper(in.Method) {
case HttpMethodGet:
// GET operations cannot have a requestBody.
2021-10-06 19:52:19 +08:00
operation.RequestBody = nil
2021-10-02 14:52:28 +08:00
path.Get = &operation
2021-10-02 14:52:28 +08:00
case HttpMethodPut:
path.Put = &operation
2021-10-02 14:52:28 +08:00
case HttpMethodPost:
path.Post = &operation
2021-10-02 14:52:28 +08:00
case HttpMethodDelete:
// DELETE operations cannot have a requestBody.
2021-10-08 15:37:17 +08:00
operation.RequestBody = nil
2021-10-02 14:52:28 +08:00
path.Delete = &operation
2021-10-02 14:52:28 +08:00
case HttpMethodConnect:
// Nothing to do for Connect.
2021-10-02 14:52:28 +08:00
case HttpMethodHead:
path.Head = &operation
2021-10-02 14:52:28 +08:00
case HttpMethodOptions:
path.Options = &operation
2021-10-02 14:52:28 +08:00
case HttpMethodPatch:
path.Patch = &operation
2021-10-02 14:52:28 +08:00
case HttpMethodTrace:
path.Trace = &operation
2021-10-02 14:52:28 +08:00
default:
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid method "%s"`, in.Method)
2021-10-02 14:52:28 +08:00
}
oai.Paths[in.Path] = path
2021-10-02 18:54:06 +08:00
return nil
2021-10-02 14:52:28 +08:00
}
2021-10-06 21:11:16 +08:00
2021-10-08 21:16:47 +08:00
func (oai *OpenApiV3) doesStructHasNoFields(s interface{}) bool {
2021-10-12 17:52:31 +08:00
return reflect.TypeOf(s).NumField() == 0
2021-10-06 21:11:16 +08:00
}