mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
move package goai from protocol to net
This commit is contained in:
@ -16,10 +16,10 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/net/goai"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
"github.com/gogf/gf/v2/os/gsession"
|
||||
"github.com/gogf/gf/v2/protocol/goai"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@ -25,6 +25,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/net/ghttp/internal/swaggerui"
|
||||
"github.com/gogf/gf/v2/net/goai"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
"github.com/gogf/gf/v2/os/genv"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
@ -32,7 +33,6 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/os/gsession"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
"github.com/gogf/gf/v2/protocol/goai"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/protocol/goai"
|
||||
"github.com/gogf/gf/v2/net/goai"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"github.com/gogf/gf/v2/protocol/goai"
|
||||
"github.com/gogf/gf/v2/net/goai"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
|
||||
249
net/goai/goai.go
Normal file
249
net/goai/goai.go
Normal file
@ -0,0 +1,249 @@
|
||||
// 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 implements and provides document generating for OpenApi specification.
|
||||
//
|
||||
// https://editor.swagger.io/
|
||||
package goai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// OpenApiV3 is the structure defined from:
|
||||
// https://swagger.io/specification/
|
||||
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md
|
||||
type OpenApiV3 struct {
|
||||
Config Config `json:"-"`
|
||||
OpenAPI string `json:"openapi"`
|
||||
Components Components `json:"components,omitempty"`
|
||||
Info Info `json:"info"`
|
||||
Paths Paths `json:"paths"`
|
||||
Security *SecurityRequirements `json:"security,omitempty"`
|
||||
Servers *Servers `json:"servers,omitempty"`
|
||||
Tags *Tags `json:"tags,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
HttpMethodGet = `GET`
|
||||
HttpMethodPut = `PUT`
|
||||
HttpMethodPost = `POST`
|
||||
HttpMethodDelete = `DELETE`
|
||||
HttpMethodConnect = `CONNECT`
|
||||
HttpMethodHead = `HEAD`
|
||||
HttpMethodOptions = `OPTIONS`
|
||||
HttpMethodPatch = `PATCH`
|
||||
HttpMethodTrace = `TRACE`
|
||||
)
|
||||
|
||||
const (
|
||||
TypeInteger = `integer`
|
||||
TypeNumber = `number`
|
||||
TypeBoolean = `boolean`
|
||||
TypeArray = `array`
|
||||
TypeString = `string`
|
||||
TypeObject = `object`
|
||||
FormatInt32 = `int32`
|
||||
FormatInt64 = `int64`
|
||||
FormatDouble = `double`
|
||||
FormatByte = `byte`
|
||||
FormatBinary = `binary`
|
||||
FormatDate = `date`
|
||||
FormatDateTime = `date-time`
|
||||
FormatPassword = `password`
|
||||
)
|
||||
|
||||
const (
|
||||
ParameterInHeader = `header`
|
||||
ParameterInPath = `path`
|
||||
ParameterInQuery = `query`
|
||||
ParameterInCookie = `cookie`
|
||||
)
|
||||
|
||||
const (
|
||||
TagNamePath = `path`
|
||||
TagNameMethod = `method`
|
||||
TagNameMime = `mime`
|
||||
TagNameConsumes = `consumes`
|
||||
TagNameType = `type`
|
||||
TagNameDomain = `domain`
|
||||
)
|
||||
|
||||
const (
|
||||
validationRuleKeyForRequired = `required`
|
||||
validationRuleKeyForIn = `in:`
|
||||
)
|
||||
|
||||
var (
|
||||
defaultReadContentTypes = []string{`application/json`}
|
||||
defaultWriteContentTypes = []string{`application/json`}
|
||||
shortTypeMapForTag = map[string]string{
|
||||
"d": "Default",
|
||||
"sum": "Summary",
|
||||
"sm": "Summary",
|
||||
"des": "Description",
|
||||
"dc": "Description",
|
||||
"eg": "Example",
|
||||
"egs": "Examples",
|
||||
"ed": "ExternalDocs",
|
||||
}
|
||||
)
|
||||
|
||||
// New creates and returns a OpenApiV3 implements object.
|
||||
func New() *OpenApiV3 {
|
||||
oai := &OpenApiV3{}
|
||||
oai.fillWithDefaultValue()
|
||||
return oai
|
||||
}
|
||||
|
||||
// AddInput is the structured parameter for function OpenApiV3.Add.
|
||||
type AddInput struct {
|
||||
Path string // Path specifies the custom path if this is not configured in Meta of struct tag.
|
||||
Prefix string // Prefix specifies the custom route path prefix, which will be added with the path tag in Meta of struct tag.
|
||||
Method string // Method specifies the custom HTTP method if this is not configured in Meta of struct tag.
|
||||
Object interface{} // Object can be an instance of struct or a route function.
|
||||
}
|
||||
|
||||
// Add adds an instance of struct or a route function to OpenApiV3 definition implements.
|
||||
func (oai *OpenApiV3) Add(in AddInput) error {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(in.Object)
|
||||
)
|
||||
for reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
switch reflectValue.Kind() {
|
||||
case reflect.Struct:
|
||||
return oai.addSchema(in.Object)
|
||||
|
||||
case reflect.Func:
|
||||
return oai.addPath(addPathInput{
|
||||
Path: in.Path,
|
||||
Prefix: in.Prefix,
|
||||
Method: in.Method,
|
||||
Function: in.Object,
|
||||
})
|
||||
|
||||
default:
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported parameter type "%s", only struct/function type is supported`,
|
||||
reflect.TypeOf(in.Object).String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (oai OpenApiV3) String() string {
|
||||
b, err := json.Marshal(oai)
|
||||
if err != nil {
|
||||
intlog.Errorf(context.TODO(), `%+v`, err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) golangTypeToOAIType(t reflect.Type) string {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return TypeString
|
||||
|
||||
case reflect.Struct:
|
||||
switch t.String() {
|
||||
case `time.Time`, `gtime.Time`:
|
||||
return TypeString
|
||||
}
|
||||
return TypeObject
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
switch t.String() {
|
||||
case `[]uint8`:
|
||||
return TypeString
|
||||
}
|
||||
return TypeArray
|
||||
|
||||
case reflect.Bool:
|
||||
return TypeBoolean
|
||||
|
||||
case
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return TypeInteger
|
||||
|
||||
case
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.Complex64, reflect.Complex128:
|
||||
return TypeNumber
|
||||
|
||||
default:
|
||||
return TypeObject
|
||||
}
|
||||
}
|
||||
|
||||
// golangTypeToOAIFormat converts and returns OpenAPI parameter format for given golang type `t`.
|
||||
// Note that it does not return standard OpenAPI parameter format but custom format in golang type.
|
||||
func (oai *OpenApiV3) golangTypeToOAIFormat(t reflect.Type) string {
|
||||
format := t.String()
|
||||
switch gstr.TrimLeft(format, "*") {
|
||||
case `[]uint8`:
|
||||
return FormatBinary
|
||||
|
||||
default:
|
||||
return format
|
||||
}
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) golangTypeToSchemaName(t reflect.Type) string {
|
||||
var (
|
||||
pkgPath = ""
|
||||
schemaName = gstr.TrimLeft(t.String(), "*")
|
||||
)
|
||||
// Pointer type has no PkgPath.
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if pkgPath = t.PkgPath(); pkgPath != "" && pkgPath != "." {
|
||||
if !oai.Config.IgnorePkgPath {
|
||||
schemaName = gstr.Replace(pkgPath, `/`, `.`) + gstr.SubStrFrom(schemaName, ".")
|
||||
}
|
||||
}
|
||||
schemaName = gstr.ReplaceByMap(schemaName, map[string]string{
|
||||
` `: ``,
|
||||
`{`: ``,
|
||||
`}`: ``,
|
||||
})
|
||||
return schemaName
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) fileMapWithShortTags(m map[string]string) map[string]string {
|
||||
for k, v := range shortTypeMapForTag {
|
||||
if m[v] == "" && m[k] != "" {
|
||||
m[v] = m[k]
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func formatRefToBytes(ref string) []byte {
|
||||
return []byte(fmt.Sprintf(`{"$ref":"#/components/schemas/%s"}`, ref))
|
||||
}
|
||||
|
||||
func isValidParameterName(key string) bool {
|
||||
if key == "-" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
28
net/goai/goai_callback.go
Normal file
28
net/goai/goai_callback.go
Normal file
@ -0,0 +1,28 @@
|
||||
// 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/internal/json"
|
||||
)
|
||||
|
||||
// Callback is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Callback map[string]*Path
|
||||
|
||||
type Callbacks map[string]*CallbackRef
|
||||
|
||||
type CallbackRef struct {
|
||||
Ref string
|
||||
Value *Callback
|
||||
}
|
||||
|
||||
func (r CallbackRef) MarshalJSON() ([]byte, error) {
|
||||
if r.Ref != "" {
|
||||
return formatRefToBytes(r.Ref), nil
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
24
net/goai/goai_components.go
Normal file
24
net/goai/goai_components.go
Normal file
@ -0,0 +1,24 @@
|
||||
// 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
|
||||
|
||||
// Components is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Components struct {
|
||||
Schemas Schemas `json:"schemas,omitempty"`
|
||||
Parameters ParametersMap `json:"parameters,omitempty"`
|
||||
Headers Headers `json:"headers,omitempty"`
|
||||
RequestBodies RequestBodies `json:"requestBodies,omitempty"`
|
||||
Responses Responses `json:"responses,omitempty"`
|
||||
SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty"`
|
||||
Examples Examples `json:"examples,omitempty"`
|
||||
Links Links `json:"links,omitempty"`
|
||||
Callbacks Callbacks `json:"callbacks,omitempty"`
|
||||
}
|
||||
|
||||
type ParametersMap map[string]*ParameterRef
|
||||
|
||||
type RequestBodies map[string]*RequestBodyRef
|
||||
31
net/goai/goai_config.go
Normal file
31
net/goai/goai_config.go
Normal file
@ -0,0 +1,31 @@
|
||||
// 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
|
||||
|
||||
// Config provides extra configuration feature for OpenApiV3 implements.
|
||||
type Config struct {
|
||||
ReadContentTypes []string // ReadContentTypes specifies the default MIME types for consuming if MIME types are not configured.
|
||||
WriteContentTypes []string // WriteContentTypes specifies the default MIME types for producing if MIME types are not configured.
|
||||
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.`.
|
||||
IgnorePkgPath bool // Ignores package name for schema name.
|
||||
}
|
||||
|
||||
// fillWithDefaultValue fills configuration object of `oai` with default values if these are not configured.
|
||||
func (oai *OpenApiV3) fillWithDefaultValue() {
|
||||
if oai.OpenAPI == "" {
|
||||
oai.OpenAPI = `3.0.0`
|
||||
}
|
||||
if len(oai.Config.ReadContentTypes) == 0 {
|
||||
oai.Config.ReadContentTypes = defaultReadContentTypes
|
||||
}
|
||||
if len(oai.Config.WriteContentTypes) == 0 {
|
||||
oai.Config.WriteContentTypes = defaultWriteContentTypes
|
||||
}
|
||||
}
|
||||
33
net/goai/goai_example.go
Normal file
33
net/goai/goai_example.go
Normal file
@ -0,0 +1,33 @@
|
||||
// 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/internal/json"
|
||||
)
|
||||
|
||||
// Example is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Example struct {
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
ExternalValue string `json:"externalValue,omitempty"`
|
||||
}
|
||||
|
||||
type Examples map[string]*ExampleRef
|
||||
|
||||
type ExampleRef struct {
|
||||
Ref string
|
||||
Value *Example
|
||||
}
|
||||
|
||||
func (r ExampleRef) MarshalJSON() ([]byte, error) {
|
||||
if r.Ref != "" {
|
||||
return formatRefToBytes(r.Ref), nil
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
35
net/goai/goai_external_docs.go
Normal file
35
net/goai/goai_external_docs.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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/internal/json"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// ExternalDocs is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type ExternalDocs struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
func (ed *ExternalDocs) UnmarshalValue(value interface{}) error {
|
||||
var valueBytes = gconv.Bytes(value)
|
||||
if json.Valid(valueBytes) {
|
||||
return json.UnmarshalUseNumber(valueBytes, ed)
|
||||
}
|
||||
var (
|
||||
valueString = string(valueBytes)
|
||||
valueArray = gstr.Split(valueString, "|")
|
||||
)
|
||||
ed.URL = valueArray[0]
|
||||
if len(valueArray) > 1 {
|
||||
ed.Description = valueArray[1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
31
net/goai/goai_header.go
Normal file
31
net/goai/goai_header.go
Normal file
@ -0,0 +1,31 @@
|
||||
// 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/internal/json"
|
||||
)
|
||||
|
||||
// Header is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#headerObject
|
||||
type Header struct {
|
||||
Parameter
|
||||
}
|
||||
|
||||
type Headers map[string]HeaderRef
|
||||
|
||||
type HeaderRef struct {
|
||||
Ref string
|
||||
Value *Header
|
||||
}
|
||||
|
||||
func (r HeaderRef) MarshalJSON() ([]byte, error) {
|
||||
if r.Ref != "" {
|
||||
return formatRefToBytes(r.Ref), nil
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
30
net/goai/goai_info.go
Normal file
30
net/goai/goai_info.go
Normal file
@ -0,0 +1,30 @@
|
||||
// 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
|
||||
|
||||
// Info is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Info struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
TermsOfService string `json:"termsOfService,omitempty"`
|
||||
Contact *Contact `json:"contact,omitempty"`
|
||||
License *License `json:"license,omitempty"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// Contact is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Contact struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// License is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type License struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
35
net/goai/goai_link.go
Normal file
35
net/goai/goai_link.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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/internal/json"
|
||||
)
|
||||
|
||||
// Link is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Link struct {
|
||||
OperationID string `json:"operationId,omitempty"`
|
||||
OperationRef string `json:"operationRef,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Parameters map[string]interface{} `json:"parameters,omitempty"`
|
||||
Server *Server `json:"server,omitempty"`
|
||||
RequestBody interface{} `json:"requestBody,omitempty"`
|
||||
}
|
||||
|
||||
type Links map[string]LinkRef
|
||||
|
||||
type LinkRef struct {
|
||||
Ref string
|
||||
Value *Link
|
||||
}
|
||||
|
||||
func (r LinkRef) MarshalJSON() ([]byte, error) {
|
||||
if r.Ref != "" {
|
||||
return formatRefToBytes(r.Ref), nil
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
27
net/goai/goai_mediatype.go
Normal file
27
net/goai/goai_mediatype.go
Normal file
@ -0,0 +1,27 @@
|
||||
// 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
|
||||
|
||||
// MediaType is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type MediaType struct {
|
||||
Schema *SchemaRef `json:"schema,omitempty"`
|
||||
Example interface{} `json:"example,omitempty"`
|
||||
Examples Examples `json:"examples,omitempty"`
|
||||
Encoding map[string]*Encoding `json:"encoding,omitempty"`
|
||||
}
|
||||
|
||||
// Content is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Content map[string]MediaType
|
||||
|
||||
// Encoding is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Encoding struct {
|
||||
ContentType string `json:"contentType,omitempty"`
|
||||
Headers Headers `json:"headers,omitempty"`
|
||||
Style string `json:"style,omitempty"`
|
||||
Explode *bool `json:"explode,omitempty"`
|
||||
AllowReserved bool `json:"allowReserved,omitempty"`
|
||||
}
|
||||
61
net/goai/goai_operation.go
Normal file
61
net/goai/goai_operation.go
Normal file
@ -0,0 +1,61 @@
|
||||
// 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/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"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
OperationID string `json:"operationId,omitempty"`
|
||||
Parameters Parameters `json:"parameters,omitempty"`
|
||||
RequestBody *RequestBodyRef `json:"requestBody,omitempty"`
|
||||
Responses Responses `json:"responses"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
Callbacks *Callbacks `json:"callbacks,omitempty"`
|
||||
Security *SecurityRequirements `json:"security,omitempty"`
|
||||
Servers *Servers `json:"servers,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
|
||||
XExtensions 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)
|
||||
}
|
||||
63
net/goai/goai_parameter.go
Normal file
63
net/goai/goai_parameter.go
Normal file
@ -0,0 +1,63 @@
|
||||
// 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/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Parameter is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#parameterObject
|
||||
type Parameter struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
In string `json:"in,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Style string `json:"style,omitempty"`
|
||||
Explode *bool `json:"explode,omitempty"`
|
||||
AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
|
||||
AllowReserved bool `json:"allowReserved,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Schema *SchemaRef `json:"schema,omitempty"`
|
||||
Example interface{} `json:"example,omitempty"`
|
||||
Examples *Examples `json:"examples,omitempty"`
|
||||
Content *Content `json:"content,omitempty"`
|
||||
XExtensions XExtensions `json:"-"`
|
||||
}
|
||||
|
||||
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 (p Parameter) MarshalJSON() ([]byte, error) {
|
||||
var (
|
||||
b []byte
|
||||
m map[string]json.RawMessage
|
||||
err error
|
||||
)
|
||||
type tempParameter Parameter // To prevent JSON marshal recursion error.
|
||||
if b, err = json.Marshal(tempParameter(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)
|
||||
}
|
||||
101
net/goai/goai_parameter_ref.go
Normal file
101
net/goai/goai_parameter_ref.go
Normal 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 (
|
||||
"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
|
||||
|
||||
// Ignore parameter.
|
||||
if !isValidParameterName(parameter.Name) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Required check.
|
||||
if parameter.Schema.Value != nil && parameter.Schema.Value.ValidationRules != "" {
|
||||
validationRuleArray := gstr.Split(parameter.Schema.Value.ValidationRules, "|")
|
||||
if gset.NewStrSetFrom(validationRuleArray).Contains(validationRuleKeyForRequired) {
|
||||
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)
|
||||
}
|
||||
323
net/goai/goai_path.go
Normal file
323
net/goai/goai_path.go
Normal file
@ -0,0 +1,323 @@
|
||||
// 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/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"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Connect *Operation `json:"connect,omitempty"`
|
||||
Delete *Operation `json:"delete,omitempty"`
|
||||
Get *Operation `json:"get,omitempty"`
|
||||
Head *Operation `json:"head,omitempty"`
|
||||
Options *Operation `json:"options,omitempty"`
|
||||
Patch *Operation `json:"patch,omitempty"`
|
||||
Post *Operation `json:"post,omitempty"`
|
||||
Put *Operation `json:"put,omitempty"`
|
||||
Trace *Operation `json:"trace,omitempty"`
|
||||
Servers Servers `json:"servers,omitempty"`
|
||||
Parameters Parameters `json:"parameters,omitempty"`
|
||||
XExtensions XExtensions `json:"-"`
|
||||
}
|
||||
|
||||
// Paths are specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Paths map[string]Path
|
||||
|
||||
const (
|
||||
responseOkKey = `200`
|
||||
)
|
||||
|
||||
type addPathInput struct {
|
||||
Path string // Precise route path.
|
||||
Prefix string // Route path prefix.
|
||||
Method string // Route method.
|
||||
Function interface{} // Uniformed function.
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) addPath(in addPathInput) error {
|
||||
if oai.Paths == nil {
|
||||
oai.Paths = map[string]Path{}
|
||||
}
|
||||
|
||||
var (
|
||||
reflectType = reflect.TypeOf(in.Function)
|
||||
)
|
||||
if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported function "%s" for OpenAPI Path register, there should be input & output structures`,
|
||||
reflectType.String(),
|
||||
)
|
||||
}
|
||||
var (
|
||||
inputObject reflect.Value
|
||||
outputObject reflect.Value
|
||||
)
|
||||
// Create instance according input/output types.
|
||||
if reflectType.In(1).Kind() == reflect.Ptr {
|
||||
inputObject = reflect.New(reflectType.In(1).Elem()).Elem()
|
||||
} else {
|
||||
inputObject = reflect.New(reflectType.In(1)).Elem()
|
||||
}
|
||||
if reflectType.Out(0).Kind() == reflect.Ptr {
|
||||
outputObject = reflect.New(reflectType.Out(0).Elem()).Elem()
|
||||
} else {
|
||||
outputObject = reflect.New(reflectType.Out(0)).Elem()
|
||||
}
|
||||
|
||||
var (
|
||||
mime string
|
||||
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{},
|
||||
XExtensions: make(XExtensions),
|
||||
}
|
||||
)
|
||||
// Path check.
|
||||
if in.Path == "" {
|
||||
in.Path = gmeta.Get(inputObject.Interface(), TagNamePath).String()
|
||||
if in.Prefix != "" {
|
||||
in.Path = gstr.TrimRight(in.Prefix, "/") + "/" + gstr.TrimLeft(in.Path, "/")
|
||||
}
|
||||
}
|
||||
if in.Path == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeMissingParameter,
|
||||
`missing necessary path parameter "%s" for input struct "%s", missing tag in attribute Meta?`,
|
||||
TagNamePath, inputStructTypeName,
|
||||
)
|
||||
}
|
||||
|
||||
if v, ok := oai.Paths[in.Path]; ok {
|
||||
path = v
|
||||
}
|
||||
|
||||
// Method check.
|
||||
if in.Method == "" {
|
||||
in.Method = gmeta.Get(inputObject.Interface(), TagNameMethod).String()
|
||||
}
|
||||
if in.Method == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeMissingParameter,
|
||||
`missing necessary method parameter "%s" for input struct "%s", missing tag in attribute Meta?`,
|
||||
TagNameMethod, inputStructTypeName,
|
||||
)
|
||||
}
|
||||
|
||||
if err := oai.addSchema(inputObject.Interface(), outputObject.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(inputMetaMap) > 0 {
|
||||
if err := oai.tagMapToPath(inputMetaMap, &path); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := oai.tagMapToOperation(inputMetaMap, &operation); err != nil {
|
||||
return err
|
||||
}
|
||||
// Allowed request mime.
|
||||
if mime = inputMetaMap[TagNameMime]; mime == "" {
|
||||
mime = inputMetaMap[TagNameConsumes]
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================================================
|
||||
// Request.
|
||||
// =================================================================================================================
|
||||
if operation.RequestBody == nil {
|
||||
operation.RequestBody = &RequestBodyRef{}
|
||||
}
|
||||
if operation.RequestBody.Value == nil {
|
||||
var (
|
||||
requestBody = RequestBody{
|
||||
Required: true,
|
||||
Content: map[string]MediaType{},
|
||||
}
|
||||
)
|
||||
// Supported mime types of request.
|
||||
var (
|
||||
contentTypes = oai.Config.ReadContentTypes
|
||||
tagMimeValue = gmeta.Get(inputObject.Interface(), TagNameMime).String()
|
||||
)
|
||||
if tagMimeValue != "" {
|
||||
contentTypes = gstr.SplitAndTrim(tagMimeValue, ",")
|
||||
}
|
||||
for _, v := range contentTypes {
|
||||
if isInputStructEmpty {
|
||||
requestBody.Content[v] = MediaType{}
|
||||
} else {
|
||||
schemaRef, err := oai.getRequestSchemaRef(getRequestSchemaRefInput{
|
||||
BusinessStructName: inputStructTypeName,
|
||||
RequestObject: oai.Config.CommonRequest,
|
||||
RequestDataField: oai.Config.CommonRequestDataField,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestBody.Content[v] = MediaType{
|
||||
Schema: schemaRef,
|
||||
}
|
||||
}
|
||||
}
|
||||
operation.RequestBody = &RequestBodyRef{
|
||||
Value: &requestBody,
|
||||
}
|
||||
}
|
||||
// It also sets request parameters.
|
||||
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: inputObject.Interface(),
|
||||
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
for _, structField := range structFields {
|
||||
if operation.Parameters == nil {
|
||||
operation.Parameters = []ParameterRef{}
|
||||
}
|
||||
parameterRef, err := oai.newParameterRefWithStructMethod(structField, in.Path, in.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parameterRef != nil {
|
||||
operation.Parameters = append(operation.Parameters, *parameterRef)
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================================================
|
||||
// Response.
|
||||
// =================================================================================================================
|
||||
if _, ok := operation.Responses[responseOkKey]; !ok {
|
||||
var (
|
||||
response = Response{
|
||||
Content: map[string]MediaType{},
|
||||
XExtensions: make(XExtensions),
|
||||
}
|
||||
)
|
||||
if len(outputMetaMap) > 0 {
|
||||
if err := oai.tagMapToResponse(outputMetaMap, &response); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Supported mime types of response.
|
||||
var (
|
||||
contentTypes = oai.Config.ReadContentTypes
|
||||
tagMimeValue = gmeta.Get(outputObject.Interface(), TagNameMime).String()
|
||||
refInput = getResponseSchemaRefInput{
|
||||
BusinessStructName: outputStructTypeName,
|
||||
CommonResponseObject: oai.Config.CommonResponse,
|
||||
CommonResponseDataField: oai.Config.CommonResponseDataField,
|
||||
}
|
||||
)
|
||||
if tagMimeValue != "" {
|
||||
contentTypes = gstr.SplitAndTrim(tagMimeValue, ",")
|
||||
}
|
||||
for _, v := range contentTypes {
|
||||
// If customized response mime type, it then ignores common response feature.
|
||||
if tagMimeValue != "" {
|
||||
refInput.CommonResponseObject = nil
|
||||
refInput.CommonResponseDataField = ""
|
||||
}
|
||||
schemaRef, err := oai.getResponseSchemaRef(refInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response.Content[v] = MediaType{
|
||||
Schema: schemaRef,
|
||||
}
|
||||
}
|
||||
operation.Responses[responseOkKey] = ResponseRef{Value: &response}
|
||||
}
|
||||
|
||||
// Assign to certain operation attribute.
|
||||
switch gstr.ToUpper(in.Method) {
|
||||
case HttpMethodGet:
|
||||
// GET operations cannot have a requestBody.
|
||||
operation.RequestBody = nil
|
||||
path.Get = &operation
|
||||
|
||||
case HttpMethodPut:
|
||||
path.Put = &operation
|
||||
|
||||
case HttpMethodPost:
|
||||
path.Post = &operation
|
||||
|
||||
case HttpMethodDelete:
|
||||
// DELETE operations cannot have a requestBody.
|
||||
operation.RequestBody = nil
|
||||
path.Delete = &operation
|
||||
|
||||
case HttpMethodConnect:
|
||||
// Nothing to do for Connect.
|
||||
|
||||
case HttpMethodHead:
|
||||
path.Head = &operation
|
||||
|
||||
case HttpMethodOptions:
|
||||
path.Options = &operation
|
||||
|
||||
case HttpMethodPatch:
|
||||
path.Patch = &operation
|
||||
|
||||
case HttpMethodTrace:
|
||||
path.Trace = &operation
|
||||
|
||||
default:
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid method "%s"`, in.Method)
|
||||
}
|
||||
oai.Paths[in.Path] = path
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
103
net/goai/goai_requestbody.go
Normal file
103
net/goai/goai_requestbody.go
Normal file
@ -0,0 +1,103 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type RequestBody struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Content Content `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
type RequestBodyRef struct {
|
||||
Ref string
|
||||
Value *RequestBody
|
||||
}
|
||||
|
||||
func (r RequestBodyRef) MarshalJSON() ([]byte, error) {
|
||||
if r.Ref != "" {
|
||||
return formatRefToBytes(r.Ref), nil
|
||||
}
|
||||
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 = oai.Components.Schemas.Get(in.BusinessStructName)
|
||||
schema, err = oai.structToSchema(in.RequestObject)
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.RequestDataField == "" && bizRequestStructSchemaRef != nil {
|
||||
// Normal request.
|
||||
bizRequestStructSchemaRef.Value.Properties.Iterator(func(key string, ref SchemaRef) bool {
|
||||
schema.Properties.Set(key, ref)
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
// Common request.
|
||||
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: in.RequestObject,
|
||||
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(), bizRequestStructSchemaRef.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Properties.Set(fieldName, *bizRequestStructSchemaRef)
|
||||
break
|
||||
}
|
||||
default:
|
||||
if structField.Name() == dataFieldsPartsArray[0] {
|
||||
var structFieldInstance = reflect.New(structField.Type().Type).Elem()
|
||||
schemaRef, err := oai.getRequestSchemaRef(getRequestSchemaRefInput{
|
||||
BusinessStructName: in.BusinessStructName,
|
||||
RequestObject: structFieldInstance,
|
||||
RequestDataField: gstr.Join(dataFieldsPartsArray[1:], "."),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Properties.Set(fieldName, *schemaRef)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &SchemaRef{
|
||||
Value: schema,
|
||||
}, nil
|
||||
}
|
||||
53
net/goai/goai_response.go
Normal file
53
net/goai/goai_response.go
Normal file
@ -0,0 +1,53 @@
|
||||
// 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/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Response is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Response struct {
|
||||
Description string `json:"description"`
|
||||
Headers Headers `json:"headers,omitempty"`
|
||||
Content Content `json:"content,omitempty"`
|
||||
Links Links `json:"links,omitempty"`
|
||||
XExtensions XExtensions `json:"-"`
|
||||
}
|
||||
|
||||
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`)
|
||||
}
|
||||
oai.tagMapToXExtensions(mergedTagMap, response.XExtensions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Response) MarshalJSON() ([]byte, error) {
|
||||
var (
|
||||
b []byte
|
||||
m map[string]json.RawMessage
|
||||
err error
|
||||
)
|
||||
type tempResponse Response // To prevent JSON marshal recursion error.
|
||||
if b, err = json.Marshal(tempResponse(r)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(b, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range r.XExtensions {
|
||||
if b, err = json.Marshal(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[k] = b
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
101
net/goai/goai_response_ref.go
Normal file
101
net/goai/goai_response_ref.go
Normal 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
|
||||
}
|
||||
54
net/goai/goai_security.go
Normal file
54
net/goai/goai_security.go
Normal file
@ -0,0 +1,54 @@
|
||||
// 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/internal/json"
|
||||
)
|
||||
|
||||
type SecurityScheme struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
In string `json:"in,omitempty"`
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
BearerFormat string `json:"bearerFormat,omitempty"`
|
||||
Flows *OAuthFlows `json:"flows,omitempty"`
|
||||
OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty"`
|
||||
}
|
||||
|
||||
type SecuritySchemes map[string]SecuritySchemeRef
|
||||
|
||||
type SecuritySchemeRef struct {
|
||||
Ref string
|
||||
Value *SecurityScheme
|
||||
}
|
||||
|
||||
type SecurityRequirements []SecurityRequirement
|
||||
|
||||
type SecurityRequirement map[string][]string
|
||||
|
||||
type OAuthFlows struct {
|
||||
Implicit *OAuthFlow `json:"implicit,omitempty"`
|
||||
Password *OAuthFlow `json:"password,omitempty"`
|
||||
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty"`
|
||||
AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty"`
|
||||
}
|
||||
|
||||
type OAuthFlow struct {
|
||||
AuthorizationURL string `json:"authorizationUrl,omitempty"`
|
||||
TokenURL string `json:"tokenUrl,omitempty"`
|
||||
RefreshURL string `json:"refreshUrl,omitempty"`
|
||||
Scopes map[string]string `json:"scopes"`
|
||||
}
|
||||
|
||||
func (r SecuritySchemeRef) MarshalJSON() ([]byte, error) {
|
||||
if r.Ref != "" {
|
||||
return formatRefToBytes(r.Ref), nil
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
24
net/goai/goai_server.go
Normal file
24
net/goai/goai_server.go
Normal file
@ -0,0 +1,24 @@
|
||||
// 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
|
||||
|
||||
// Server is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Server struct {
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Variables map[string]*ServerVariable `json:"variables,omitempty"`
|
||||
}
|
||||
|
||||
// ServerVariable is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type ServerVariable struct {
|
||||
Enum []string `json:"enum,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// Servers is specified by OpenAPI/Swagger standard version 3.0.
|
||||
type Servers []Server
|
||||
247
net/goai/goai_shema.go
Normal file
247
net/goai/goai_shema.go
Normal file
@ -0,0 +1,247 @@
|
||||
// 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/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"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"github.com/gogf/gf/v2/util/gvalid"
|
||||
)
|
||||
|
||||
// Schema is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Schema struct {
|
||||
OneOf SchemaRefs `json:"oneOf,omitempty"`
|
||||
AnyOf SchemaRefs `json:"anyOf,omitempty"`
|
||||
AllOf SchemaRefs `json:"allOf,omitempty"`
|
||||
Not *SchemaRef `json:"not,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Enum []interface{} `json:"enum,omitempty"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Example interface{} `json:"example,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
|
||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
||||
ExclusiveMin bool `json:"exclusiveMinimum,omitempty"`
|
||||
ExclusiveMax bool `json:"exclusiveMaximum,omitempty"`
|
||||
Nullable bool `json:"nullable,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
WriteOnly bool `json:"writeOnly,omitempty"`
|
||||
AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
|
||||
XML interface{} `json:"xml,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
Min *float64 `json:"minimum,omitempty"`
|
||||
Max *float64 `json:"maximum,omitempty"`
|
||||
MultipleOf *float64 `json:"multipleOf,omitempty"`
|
||||
MinLength uint64 `json:"minLength,omitempty"`
|
||||
MaxLength *uint64 `json:"maxLength,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
MinItems uint64 `json:"minItems,omitempty"`
|
||||
MaxItems *uint64 `json:"maxItems,omitempty"`
|
||||
Items *SchemaRef `json:"items,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
Properties Schemas `json:"properties,omitempty"`
|
||||
MinProps uint64 `json:"minProperties,omitempty"`
|
||||
MaxProps *uint64 `json:"maxProperties,omitempty"`
|
||||
AdditionalProperties *SchemaRef `json:"additionalProperties,omitempty"`
|
||||
Discriminator *Discriminator `json:"discriminator,omitempty"`
|
||||
XExtensions XExtensions `json:"-"`
|
||||
ValidationRules string `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.
|
||||
type Discriminator struct {
|
||||
PropertyName string `json:"propertyName"`
|
||||
Mapping map[string]string `json:"mapping,omitempty"`
|
||||
}
|
||||
|
||||
// addSchema creates schemas with objects.
|
||||
// Note that the `object` can be array alias like: `type Res []Item`.
|
||||
func (oai *OpenApiV3) addSchema(object ...interface{}) error {
|
||||
for _, v := range object {
|
||||
if err := oai.doAddSchemaSingle(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
|
||||
if oai.Components.Schemas.refs == nil {
|
||||
oai.Components.Schemas.refs = gmap.NewListMap()
|
||||
}
|
||||
|
||||
var (
|
||||
reflectType = reflect.TypeOf(object)
|
||||
structTypeName = oai.golangTypeToSchemaName(reflectType)
|
||||
)
|
||||
|
||||
// Already added.
|
||||
if oai.Components.Schemas.Get(structTypeName) != nil {
|
||||
return nil
|
||||
}
|
||||
// Take the holder first.
|
||||
oai.Components.Schemas.Set(structTypeName, SchemaRef{})
|
||||
|
||||
schema, err := oai.structToSchema(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oai.Components.Schemas.Set(structTypeName, SchemaRef{
|
||||
Ref: "",
|
||||
Value: schema,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// structToSchema converts and returns given struct object as Schema.
|
||||
func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
|
||||
var (
|
||||
tagMap = gmeta.Data(object)
|
||||
schema = &Schema{
|
||||
Properties: createSchemas(),
|
||||
XExtensions: make(XExtensions),
|
||||
}
|
||||
ignoreProperties []interface{}
|
||||
)
|
||||
if len(tagMap) > 0 {
|
||||
if err := oai.tagMapToSchema(tagMap, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if schema.Type != "" && schema.Type != TypeObject {
|
||||
return schema, nil
|
||||
}
|
||||
// []struct.
|
||||
if utils.IsArray(object) {
|
||||
schema.Type = TypeArray
|
||||
subSchemaRef, err := oai.newSchemaRefWithGolangType(reflect.TypeOf(object).Elem(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Items = subSchemaRef
|
||||
if len(schema.Enum) > 0 {
|
||||
schema.Items.Value.Enum = schema.Enum
|
||||
schema.Enum = nil
|
||||
}
|
||||
return schema, nil
|
||||
}
|
||||
// struct.
|
||||
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: object,
|
||||
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
schema.Type = TypeObject
|
||||
for _, structField := range structFields {
|
||||
if !gstr.IsLetterUpper(structField.Name()[0]) {
|
||||
continue
|
||||
}
|
||||
var fieldName = structField.Name()
|
||||
if jsonName := structField.TagJsonName(); jsonName != "" {
|
||||
fieldName = jsonName
|
||||
}
|
||||
schemaRef, err := oai.newSchemaRefWithGolangType(
|
||||
structField.Type().Type,
|
||||
structField.TagMap(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Properties.Set(fieldName, *schemaRef)
|
||||
}
|
||||
|
||||
schema.Properties.Iterator(func(key string, ref SchemaRef) bool {
|
||||
if ref.Value != nil && ref.Value.ValidationRules != "" {
|
||||
validationRuleSet := gset.NewStrSetFrom(gstr.Split(ref.Value.ValidationRules, "|"))
|
||||
if validationRuleSet.Contains(validationRuleKeyForRequired) {
|
||||
schema.Required = append(schema.Required, key)
|
||||
}
|
||||
}
|
||||
if !isValidParameterName(key) {
|
||||
ignoreProperties = append(ignoreProperties, key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if len(ignoreProperties) > 0 {
|
||||
schema.Properties.Removes(ignoreProperties)
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) error {
|
||||
var mergedTagMap = oai.fileMapWithShortTags(tagMap)
|
||||
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 {
|
||||
_, validationRules, _ := gvalid.ParseTagValue(validationTagValue)
|
||||
schema.ValidationRules = validationRules
|
||||
// Enum checks.
|
||||
if len(schema.Enum) == 0 {
|
||||
for _, rule := range gstr.SplitAndTrim(validationRules, "|") {
|
||||
if gstr.HasPrefix(rule, validationRuleKeyForIn) {
|
||||
var (
|
||||
isAllEnumNumber = true
|
||||
enumArray = gstr.SplitAndTrim(rule[len(validationRuleKeyForIn):], ",")
|
||||
)
|
||||
for _, enum := range enumArray {
|
||||
if !gstr.IsNumeric(enum) {
|
||||
isAllEnumNumber = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isAllEnumNumber {
|
||||
schema.Enum = gconv.Interfaces(gconv.Int64s(enumArray))
|
||||
} else {
|
||||
schema.Enum = gconv.Interfaces(enumArray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
107
net/goai/goai_shema_ref.go
Normal file
107
net/goai/goai_shema_ref.go
Normal file
@ -0,0 +1,107 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
type SchemaRefs []SchemaRef
|
||||
|
||||
type SchemaRef struct {
|
||||
Ref string
|
||||
Value *Schema
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap map[string]string) (*SchemaRef, error) {
|
||||
var (
|
||||
oaiType = oai.golangTypeToOAIType(golangType)
|
||||
oaiFormat = oai.golangTypeToOAIFormat(golangType)
|
||||
schemaRef = &SchemaRef{}
|
||||
schema = &Schema{
|
||||
Type: oaiType,
|
||||
Format: oaiFormat,
|
||||
XExtensions: make(XExtensions),
|
||||
}
|
||||
)
|
||||
if len(tagMap) > 0 {
|
||||
if err := oai.tagMapToSchema(tagMap, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
schemaRef.Value = schema
|
||||
switch oaiType {
|
||||
case
|
||||
TypeInteger,
|
||||
TypeNumber,
|
||||
TypeString,
|
||||
TypeBoolean:
|
||||
// Nothing to do.
|
||||
|
||||
case
|
||||
TypeArray:
|
||||
subSchemaRef, err := oai.newSchemaRefWithGolangType(golangType.Elem(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.Items = subSchemaRef
|
||||
if len(schema.Enum) > 0 {
|
||||
schema.Items.Value.Enum = schema.Enum
|
||||
schema.Enum = nil
|
||||
}
|
||||
|
||||
case
|
||||
TypeObject:
|
||||
for golangType.Kind() == reflect.Ptr {
|
||||
golangType = golangType.Elem()
|
||||
}
|
||||
switch golangType.Kind() {
|
||||
case reflect.Map:
|
||||
// Specially for map type.
|
||||
subSchemaRef, err := oai.newSchemaRefWithGolangType(golangType.Elem(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema.AdditionalProperties = subSchemaRef
|
||||
return schemaRef, nil
|
||||
|
||||
case reflect.Interface:
|
||||
// Specially for interface type.
|
||||
var (
|
||||
structTypeName = oai.golangTypeToSchemaName(golangType)
|
||||
)
|
||||
if oai.Components.Schemas.Get(structTypeName) == nil {
|
||||
if err := oai.addSchema(reflect.New(golangType).Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
schemaRef.Ref = structTypeName
|
||||
schemaRef.Value = nil
|
||||
|
||||
default:
|
||||
// Normal struct object.
|
||||
var structTypeName = oai.golangTypeToSchemaName(golangType)
|
||||
if oai.Components.Schemas.Get(structTypeName) == nil {
|
||||
if err := oai.addSchema(reflect.New(golangType).Elem().Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
schemaRef.Ref = structTypeName
|
||||
schemaRef.Value = nil
|
||||
}
|
||||
}
|
||||
return schemaRef, nil
|
||||
}
|
||||
|
||||
func (r SchemaRef) MarshalJSON() ([]byte, error) {
|
||||
if r.Ref != "" {
|
||||
return formatRefToBytes(r.Ref), nil
|
||||
}
|
||||
return json.Marshal(r.Value)
|
||||
}
|
||||
69
net/goai/goai_shemas.go
Normal file
69
net/goai/goai_shemas.go
Normal file
@ -0,0 +1,69 @@
|
||||
// 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/container/gmap"
|
||||
)
|
||||
|
||||
type Schemas struct {
|
||||
refs *gmap.ListMap // map[string]SchemaRef
|
||||
}
|
||||
|
||||
func createSchemas() Schemas {
|
||||
return Schemas{
|
||||
refs: gmap.NewListMap(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Schemas) init() {
|
||||
if s.refs == nil {
|
||||
s.refs = gmap.NewListMap()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Schemas) Get(name string) *SchemaRef {
|
||||
s.init()
|
||||
value := s.refs.Get(name)
|
||||
if value != nil {
|
||||
ref := value.(SchemaRef)
|
||||
return &ref
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Schemas) Set(name string, ref SchemaRef) {
|
||||
s.init()
|
||||
s.refs.Set(name, ref)
|
||||
}
|
||||
|
||||
func (s *Schemas) Removes(names []interface{}) {
|
||||
s.init()
|
||||
s.refs.Removes(names)
|
||||
}
|
||||
|
||||
func (s *Schemas) Map() map[string]SchemaRef {
|
||||
s.init()
|
||||
m := make(map[string]SchemaRef)
|
||||
s.refs.Iterator(func(key, value interface{}) bool {
|
||||
m[key.(string)] = value.(SchemaRef)
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *Schemas) Iterator(f func(key string, ref SchemaRef) bool) {
|
||||
s.init()
|
||||
s.refs.Iterator(func(key, value interface{}) bool {
|
||||
return f(key.(string), value.(SchemaRef))
|
||||
})
|
||||
}
|
||||
|
||||
func (s Schemas) MarshalJSON() ([]byte, error) {
|
||||
s.init()
|
||||
return s.refs.MarshalJSON()
|
||||
}
|
||||
17
net/goai/goai_tags.go
Normal file
17
net/goai/goai_tags.go
Normal file
@ -0,0 +1,17 @@
|
||||
// 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
|
||||
|
||||
// Tags is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Tags []Tag
|
||||
|
||||
// Tag is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Tag struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
|
||||
}
|
||||
22
net/goai/goai_xextensions.go
Normal file
22
net/goai/goai_xextensions.go
Normal 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]string
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
903
net/goai/goai_z_unit_test.go
Normal file
903
net/goai/goai_z_unit_test.go
Normal file
@ -0,0 +1,903 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/goai"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
)
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"cookie" description:"应用Id"`
|
||||
ResourceId string `json:"resourceId" in:"query" description:"资源Id"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" description:"StorageType"`
|
||||
Shards int32 `description:"shards 分片数"`
|
||||
Params []string `description:"默认参数(json 串-ClickHouseParams)"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default"`
|
||||
Name string `description:"实例名称"`
|
||||
Product string `description:"业务类型"`
|
||||
Region string `v:"required" description:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required" description:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" description:"配置Slice"`
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
req = new(CreateResourceReq)
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Object: req,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 2)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Type, goai.TypeObject)
|
||||
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Map()), 7)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`appId`).Value.Type, goai.TypeInteger)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`resourceId`).Value.Type, goai.TypeString)
|
||||
|
||||
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Map()), 3)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Get(`Params`).Value.Type, goai.TypeArray)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_Add(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" description:"应用Id"`
|
||||
ResourceId string `json:"resourceId" in:"query" description:"资源Id"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" description:"StorageType"`
|
||||
Shards int32 `description:"shards 分片数"`
|
||||
Params []string `description:"默认参数(json 串-ClickHouseParams)"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default"`
|
||||
Name string `description:"实例名称"`
|
||||
Product string `description:"业务类型"`
|
||||
Region string `v:"required" description:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required" description:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" description:"配置Slice"`
|
||||
}
|
||||
|
||||
type CreateResourceRes struct {
|
||||
gmeta.Meta `description:"Demo Response Struct"`
|
||||
FlowId int64 `description:"创建实例流程id"`
|
||||
}
|
||||
|
||||
f := func(ctx context.Context, req *CreateResourceReq) (res *CreateResourceRes, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/test1/{appId}",
|
||||
Method: goai.HttpMethodPut,
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/test1/{appId}",
|
||||
Method: goai.HttpMethodPost,
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// fmt.Println(oai.String())
|
||||
// Schema asserts.
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 3)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Type, goai.TypeObject)
|
||||
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Map()), 7)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`appId`).Value.Type, goai.TypeInteger)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`resourceId`).Value.Type, goai.TypeString)
|
||||
|
||||
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Map()), 3)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Get(`Params`).Value.Type, goai.TypeArray)
|
||||
|
||||
// Paths.
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.AssertNE(oai.Paths[`/test1/{appId}`].Put, nil)
|
||||
t.Assert(len(oai.Paths[`/test1/{appId}`].Put.Tags), 1)
|
||||
t.Assert(len(oai.Paths[`/test1/{appId}`].Put.Parameters), 2)
|
||||
t.AssertNE(oai.Paths[`/test1/{appId}`].Post, nil)
|
||||
t.Assert(len(oai.Paths[`/test1/{appId}`].Post.Tags), 1)
|
||||
t.Assert(len(oai.Paths[`/test1/{appId}`].Post.Parameters), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_Add_Recursive(t *testing.T) {
|
||||
type CategoryTreeItem struct {
|
||||
Id uint `json:"id"`
|
||||
ParentId uint `json:"parent_id"`
|
||||
Items []*CategoryTreeItem `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type CategoryGetTreeReq struct {
|
||||
gmeta.Meta `path:"/category-get-tree" method:"GET" tags:"default"`
|
||||
ContentType string `in:"query"`
|
||||
}
|
||||
type CategoryGetTreeRes struct {
|
||||
List []*CategoryTreeItem
|
||||
}
|
||||
|
||||
f := func(ctx context.Context, req *CategoryGetTreeReq) (res *CategoryGetTreeRes, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/tree",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 3)
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`).Value.Type, goai.TypeObject)
|
||||
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`).Value.Properties.Map()), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_Add_EmptyReqAndRes(t *testing.T) {
|
||||
type CaptchaIndexReq struct {
|
||||
gmeta.Meta `method:"PUT" summary:"获取默认的验证码" description:"注意直接返回的是图片二进制内容" tags:"前台-验证码"`
|
||||
}
|
||||
type CaptchaIndexRes struct {
|
||||
gmeta.Meta `mime:"png" description:"验证码二进制内容" `
|
||||
}
|
||||
|
||||
f := func(ctx context.Context, req *CaptchaIndexReq) (res *CaptchaIndexRes, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/tree",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
fmt.Println(oai.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_Add_AutoDetectIn(t *testing.T) {
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"get" tags:"default"`
|
||||
Name string
|
||||
Product string
|
||||
Region string
|
||||
}
|
||||
|
||||
type Res struct {
|
||||
gmeta.Meta `description:"Demo Response 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()
|
||||
path = `/test/{product}/{name}`
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: path,
|
||||
Method: goai.HttpMethodGet,
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
fmt.Println(oai.String())
|
||||
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 2)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.AssertNE(oai.Paths[path].Get, nil)
|
||||
t.Assert(len(oai.Paths[path].Get.Parameters), 3)
|
||||
t.Assert(oai.Paths[path].Get.Parameters[0].Value.Name, `Name`)
|
||||
t.Assert(oai.Paths[path].Get.Parameters[0].Value.In, goai.ParameterInPath)
|
||||
t.Assert(oai.Paths[path].Get.Parameters[1].Value.Name, `Product`)
|
||||
t.Assert(oai.Paths[path].Get.Parameters[1].Value.In, goai.ParameterInPath)
|
||||
t.Assert(oai.Paths[path].Get.Parameters[2].Value.Name, `Region`)
|
||||
t.Assert(oai.Paths[path].Get.Parameters[2].Value.In, goai.ParameterInQuery)
|
||||
})
|
||||
}
|
||||
|
||||
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.Map()), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 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.Map()), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 5)
|
||||
})
|
||||
}
|
||||
|
||||
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.Map()), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 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.Map()), 4)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Get(`Request`).Value.Properties.Map()), 4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonResponse(t *testing.T) {
|
||||
type CommonResponse 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:"GET"`
|
||||
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.CommonResponse = CommonResponse{}
|
||||
oai.Config.CommonResponseDataField = `Data`
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
//g.Dump(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map())
|
||||
// Schema asserts.
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 3)
|
||||
t.Assert(
|
||||
oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Get("data").Value.Description,
|
||||
`Result data for certain request according API definition`,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonResponse_WithoutDataField_Setting(t *testing.T) {
|
||||
type CommonResponse 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:"GET"`
|
||||
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.CommonResponse = CommonResponse{}
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
fmt.Println(oai.String())
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 8)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonResponse_EmptyResponse(t *testing.T) {
|
||||
type CommonResponse 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{}
|
||||
|
||||
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.CommonResponse = CommonResponse{}
|
||||
oai.Config.CommonResponseDataField = `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.Map()), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Ref, `github.com.gogf.gf.v2.protocol.goai_test.Req`)
|
||||
t.Assert(len(oai.Paths["/index"].Put.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_CommonResponse_SubDataField(t *testing.T) {
|
||||
type CommonResError struct {
|
||||
Code string `description:"错误码"`
|
||||
Message string `description:"错误描述"`
|
||||
}
|
||||
|
||||
type CommonResResponse struct {
|
||||
RequestId string `description:"RequestId"`
|
||||
Error *CommonResError `json:",omitempty" description:"执行错误信息"`
|
||||
}
|
||||
|
||||
type CommonRes struct {
|
||||
Response CommonResResponse
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
gmeta.Meta `method:"GET"`
|
||||
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.CommonResponse = CommonRes{}
|
||||
oai.Config.CommonResponseDataField = `Response.`
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/index",
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
// fmt.Println(oai.String())
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 4)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 1)
|
||||
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Get(`Response`).Value.Properties.Map()), 7)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_ShortTags(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string `des:"实例名称"`
|
||||
Product string `des:"业务类型"`
|
||||
Region string `v:"required" des:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
|
||||
}
|
||||
|
||||
type CreateResourceRes struct {
|
||||
gmeta.Meta `des:"Demo Response Struct"`
|
||||
FlowId int64 `des:"创建实例流程id"`
|
||||
}
|
||||
|
||||
f := func(ctx context.Context, req *CreateResourceReq) (res *CreateResourceRes, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/test1/{appId}",
|
||||
Method: goai.HttpMethodPut,
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/test1/{appId}",
|
||||
Method: goai.HttpMethodPost,
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// fmt.Println(oai.String())
|
||||
// Schema asserts.
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 3)
|
||||
t.Assert(oai.Paths[`/test1/{appId}`].Summary, `CreateResourceReq sum`)
|
||||
t.Assert(oai.
|
||||
Components.
|
||||
Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).
|
||||
Value.Properties.Get(`resourceId`).Value.Description, `资源Id`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_HtmlResponse(t *testing.T) {
|
||||
type Req struct {
|
||||
g.Meta `path:"/test" method:"get" summary:"展示内容详情页面" tags:"内容"`
|
||||
Id uint `json:"id" v:"min:1#请选择查看的内容" dc:"内容id"`
|
||||
}
|
||||
type Res struct {
|
||||
g.Meta `mime:"text/html" type:"string" example:"<html/>"`
|
||||
}
|
||||
|
||||
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()
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/test",
|
||||
Method: goai.HttpMethodGet,
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// fmt.Println(oai.String())
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.Res`).Value.Type, goai.TypeString)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_HtmlResponseWithCommonResponse(t *testing.T) {
|
||||
type CommonResError struct {
|
||||
Code string `description:"错误码"`
|
||||
Message string `description:"错误描述"`
|
||||
}
|
||||
|
||||
type CommonResResponse struct {
|
||||
RequestId string `description:"RequestId"`
|
||||
Error *CommonResError `json:",omitempty" description:"执行错误信息"`
|
||||
}
|
||||
|
||||
type CommonRes struct {
|
||||
Response CommonResResponse
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
g.Meta `path:"/test" method:"get" summary:"展示内容详情页面" tags:"内容"`
|
||||
Id uint `json:"id" v:"min:1#请选择查看的内容" dc:"内容id"`
|
||||
}
|
||||
type Res struct {
|
||||
g.Meta `mime:"text/html" type:"string" example:"<html/>"`
|
||||
}
|
||||
|
||||
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.CommonResponse = CommonRes{}
|
||||
oai.Config.CommonResponseDataField = `Response.`
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Path: "/test",
|
||||
Method: goai.HttpMethodGet,
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// fmt.Println(oai.String())
|
||||
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.Res`).Value.Type, goai.TypeString)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Required_In_Schema(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"cookie" description:"应用Id"`
|
||||
ResourceId string `json:"resourceId" in:"query" description:"资源Id"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" description:"StorageType"`
|
||||
Shards int32 `description:"shards 分片数"`
|
||||
Params []string `description:"默认参数(json 串-ClickHouseParams)"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default"`
|
||||
Name string `description:"实例名称"`
|
||||
Product string `description:"业务类型"`
|
||||
Region string `v:"required|min:1" description:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required|min:1" description:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required|min:1" description:"配置Slice"`
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
req = new(CreateResourceReq)
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Object: req,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
var (
|
||||
schemaKey1 = `github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`
|
||||
schemaKey2 = `github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`
|
||||
)
|
||||
t.Assert(oai.Components.Schemas.Map()[schemaKey1].Value.Required, g.Slice{
|
||||
"appId",
|
||||
"Region",
|
||||
"SetMap",
|
||||
"SetSlice",
|
||||
})
|
||||
t.Assert(oai.Components.Schemas.Map()[schemaKey2].Value.Required, g.Slice{
|
||||
"StorageType",
|
||||
})
|
||||
t.Assert(oai.Components.Schemas.Map()[schemaKey2].Value.Properties.Map()["StorageType"].Value.Enum, g.Slice{
|
||||
"CLOUD_PREMIUM",
|
||||
"CLOUD_SSD",
|
||||
"CLOUD_HSSD",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Properties_In_Sequence(t *testing.T) {
|
||||
type ResourceCreateReq struct {
|
||||
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:"创建实例的用户账号"`
|
||||
Product string `v:"required" dc:"业务类型" eg:"tdach"`
|
||||
Region string `v:"required" dc:"地域" eg:"ap-guangzhou"`
|
||||
Zone string `v:"required" dc:"区域" eg:"ap-guangzhou-1"`
|
||||
Tenant string `v:"required" dc:"业务自定义数据,透传到底层"`
|
||||
VpcId string `dc:"业务Vpc Id, TCS场景下非必须"`
|
||||
SubnetId string `dc:"业务Vpc子网Id"`
|
||||
Name string `dc:"自定义实例名称,默认和ResourceId一致"`
|
||||
ClusterPreset string `dc:"业务自定义Cluster定义,透传到底层"`
|
||||
Engine string `dc:"引擎名称,例如:TxLightning"`
|
||||
Version string `dc:"引擎版本,例如:10.3.213 (兼容ClickHouse 21.3.12)"`
|
||||
SkipUpdateStatus bool `dc:"是否跳过状态更新,继续保持creating" ed:"http://goframe.org"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
req = new(ResourceCreateReq)
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Object: req,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
fmt.Println(oai)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenApiV3_Ignore_Parameter(t *testing.T) {
|
||||
type CommonResponse 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 ProductSearchReq struct {
|
||||
gmeta.Meta `path:"/test" method:"get"`
|
||||
Product string `json:"-" in:"query" v:"required|max:5" description:"Unique product key"`
|
||||
Name string `json:"name" in:"query" v:"required" description:"Instance name"`
|
||||
}
|
||||
type ProductSearchRes struct {
|
||||
ID int64 `json:"-" description:"Product ID"`
|
||||
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 *ProductSearchReq) (res *ProductSearchRes, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
)
|
||||
|
||||
oai.Config.CommonResponse = CommonResponse{}
|
||||
|
||||
err = oai.Add(goai.AddInput{
|
||||
Object: f,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Schema asserts.
|
||||
// fmt.Println(oai.String())
|
||||
t.Assert(len(oai.Components.Schemas.Map()), 3)
|
||||
t.Assert(len(oai.Paths), 1)
|
||||
t.Assert(len(oai.Paths["/test"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 8)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_EnumOfSchemaItems(t *testing.T) {
|
||||
type CreateResourceReq struct {
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST"`
|
||||
Members []string `v:"required|in:a,b,c"`
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
err error
|
||||
oai = goai.New()
|
||||
req = new(CreateResourceReq)
|
||||
)
|
||||
err = oai.Add(goai.AddInput{
|
||||
Object: req,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(
|
||||
oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.
|
||||
Properties.Get(`Members`).Value.
|
||||
Items.Value.Enum,
|
||||
g.Slice{"a", "b", "c"},
|
||||
)
|
||||
})
|
||||
}
|
||||
728
net/goai/testdata/example.yaml
vendored
Normal file
728
net/goai/testdata/example.yaml
vendored
Normal file
@ -0,0 +1,728 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: Swagger Petstore
|
||||
description: 'This is a sample server Petstore server. You can find out more about Swagger
|
||||
at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For
|
||||
this sample, you can use the api key `special-key` to test the authorization filters.'
|
||||
termsOfService: http://swagger.io/terms/
|
||||
contact:
|
||||
email: apiteam@swagger.io
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
version: 1.0.0
|
||||
externalDocs:
|
||||
description: Find out more about Swagger
|
||||
url: http://swagger.io
|
||||
servers:
|
||||
- url: https://petstore.swagger.io/v2
|
||||
- url: http://petstore.swagger.io/v2
|
||||
tags:
|
||||
- name: pet
|
||||
description: Everything about your Pets
|
||||
externalDocs:
|
||||
description: Find out more
|
||||
url: http://swagger.io
|
||||
- name: store
|
||||
description: Access to Petstore orders
|
||||
- name: user
|
||||
description: Operations about user
|
||||
externalDocs:
|
||||
description: Find out more about our store
|
||||
url: http://swagger.io
|
||||
paths:
|
||||
/pet:
|
||||
put:
|
||||
tags:
|
||||
- pet
|
||||
summary: Update an existing pet
|
||||
operationId: updatePet
|
||||
requestBody:
|
||||
description: Pet object that needs to be added to the store
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
required: true
|
||||
responses:
|
||||
400:
|
||||
description: Invalid ID supplied
|
||||
content: {}
|
||||
404:
|
||||
description: Pet not found
|
||||
content: {}
|
||||
405:
|
||||
description: Validation exception
|
||||
content: {}
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
x-codegen-request-body-name: body
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: Add a new pet to the store
|
||||
operationId: addPet
|
||||
requestBody:
|
||||
description: Pet object that needs to be added to the store
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
required: true
|
||||
responses:
|
||||
405:
|
||||
description: Invalid input
|
||||
content: {}
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
x-codegen-request-body-name: body
|
||||
/pet/findByStatus:
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Finds Pets by status
|
||||
description: Multiple status values can be provided with comma separated strings
|
||||
operationId: findPetsByStatus
|
||||
parameters:
|
||||
- name: status
|
||||
in: query
|
||||
description: Status values that need to be considered for filter
|
||||
required: true
|
||||
style: form
|
||||
explode: true
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: available
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
400:
|
||||
description: Invalid status value
|
||||
content: {}
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
/pet/findByTags:
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Finds Pets by tags
|
||||
description: Multiple tags can be provided with comma separated strings. Use tag1,
|
||||
tag2, tag3 for testing.
|
||||
operationId: findPetsByTags
|
||||
parameters:
|
||||
- name: tags
|
||||
in: query
|
||||
description: Tags to filter by
|
||||
required: true
|
||||
style: form
|
||||
explode: true
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
400:
|
||||
description: Invalid tag value
|
||||
content: {}
|
||||
deprecated: true
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
/pet/{petId}:
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Find pet by ID
|
||||
description: Returns a single pet
|
||||
operationId: getPetById
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet to return
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
400:
|
||||
description: Invalid ID supplied
|
||||
content: {}
|
||||
404:
|
||||
description: Pet not found
|
||||
content: {}
|
||||
security:
|
||||
- api_key: []
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: Updates a pet in the store with form data
|
||||
operationId: updatePetWithForm
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet that needs to be updated
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
requestBody:
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Updated name of the pet
|
||||
status:
|
||||
type: string
|
||||
description: Updated status of the pet
|
||||
responses:
|
||||
405:
|
||||
description: Invalid input
|
||||
content: {}
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
delete:
|
||||
tags:
|
||||
- pet
|
||||
summary: Deletes a pet
|
||||
operationId: deletePet
|
||||
parameters:
|
||||
- name: api_key
|
||||
in: header
|
||||
schema:
|
||||
type: string
|
||||
- name: petId
|
||||
in: path
|
||||
description: Pet id to delete
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
400:
|
||||
description: Invalid ID supplied
|
||||
content: {}
|
||||
404:
|
||||
description: Pet not found
|
||||
content: {}
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
/pet/{petId}/uploadImage:
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: uploads an image
|
||||
operationId: uploadFile
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet to update
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
properties:
|
||||
additionalMetadata:
|
||||
type: string
|
||||
description: Additional data to pass to server
|
||||
file:
|
||||
type: string
|
||||
description: file to upload
|
||||
format: binary
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
/store/inventory:
|
||||
get:
|
||||
tags:
|
||||
- store
|
||||
summary: Returns pet inventories by status
|
||||
description: Returns a map of status codes to quantities
|
||||
operationId: getInventory
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
format: int32
|
||||
security:
|
||||
- api_key: []
|
||||
/store/order:
|
||||
post:
|
||||
tags:
|
||||
- store
|
||||
summary: Place an order for a pet
|
||||
operationId: placeOrder
|
||||
requestBody:
|
||||
description: order placed for purchasing the pet
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
400:
|
||||
description: Invalid Order
|
||||
content: {}
|
||||
x-codegen-request-body-name: body
|
||||
/store/order/{orderId}:
|
||||
get:
|
||||
tags:
|
||||
- store
|
||||
summary: Find purchase order by ID
|
||||
description: For valid response try integer IDs with value >= 1 and <= 10. Other
|
||||
values will generated exceptions
|
||||
operationId: getOrderById
|
||||
parameters:
|
||||
- name: orderId
|
||||
in: path
|
||||
description: ID of pet that needs to be fetched
|
||||
required: true
|
||||
schema:
|
||||
maximum: 10.0
|
||||
minimum: 1.0
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
400:
|
||||
description: Invalid ID supplied
|
||||
content: {}
|
||||
404:
|
||||
description: Order not found
|
||||
content: {}
|
||||
delete:
|
||||
tags:
|
||||
- store
|
||||
summary: Delete purchase order by ID
|
||||
description: For valid response try integer IDs with positive integer value. Negative
|
||||
or non-integer values will generate API errors
|
||||
operationId: deleteOrder
|
||||
parameters:
|
||||
- name: orderId
|
||||
in: path
|
||||
description: ID of the order that needs to be deleted
|
||||
required: true
|
||||
schema:
|
||||
minimum: 1.0
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
400:
|
||||
description: Invalid ID supplied
|
||||
content: {}
|
||||
404:
|
||||
description: Order not found
|
||||
content: {}
|
||||
/user:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Create user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: createUser
|
||||
requestBody:
|
||||
description: Created user object
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
required: true
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
content: {}
|
||||
x-codegen-request-body-name: body
|
||||
/user/createWithArray:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Creates list of users with given input array
|
||||
operationId: createUsersWithArrayInput
|
||||
requestBody:
|
||||
description: List of user object
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
required: true
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
content: {}
|
||||
x-codegen-request-body-name: body
|
||||
/user/createWithList:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Creates list of users with given input array
|
||||
operationId: createUsersWithListInput
|
||||
requestBody:
|
||||
description: List of user object
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
required: true
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
content: {}
|
||||
x-codegen-request-body-name: body
|
||||
/user/login:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Logs user into the system
|
||||
operationId: loginUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: query
|
||||
description: The user name for login
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: password
|
||||
in: query
|
||||
description: The password for login in clear text
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
headers:
|
||||
X-Rate-Limit:
|
||||
description: calls per hour allowed by the user
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
X-Expires-After:
|
||||
description: date in UTC when token expires
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: string
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
400:
|
||||
description: Invalid username/password supplied
|
||||
content: {}
|
||||
/user/logout:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Logs out current logged in user session
|
||||
operationId: logoutUser
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
content: {}
|
||||
/user/{username}:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Get user by user name
|
||||
operationId: getUserByName
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: 'The name that needs to be fetched. Use user1 for testing. '
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
400:
|
||||
description: Invalid username supplied
|
||||
content: {}
|
||||
404:
|
||||
description: User not found
|
||||
content: {}
|
||||
put:
|
||||
tags:
|
||||
- user
|
||||
summary: Updated user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: updateUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: name that need to be updated
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
description: Updated user object
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
required: true
|
||||
responses:
|
||||
400:
|
||||
description: Invalid user supplied
|
||||
content: {}
|
||||
404:
|
||||
description: User not found
|
||||
content: {}
|
||||
x-codegen-request-body-name: body
|
||||
delete:
|
||||
tags:
|
||||
- user
|
||||
summary: Delete user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: deleteUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: The name that needs to be deleted
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
400:
|
||||
description: Invalid username supplied
|
||||
content: {}
|
||||
404:
|
||||
description: User not found
|
||||
content: {}
|
||||
components:
|
||||
schemas:
|
||||
Order:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
petId:
|
||||
type: integer
|
||||
format: int64
|
||||
quantity:
|
||||
type: integer
|
||||
format: int32
|
||||
shipDate:
|
||||
type: string
|
||||
format: date-time
|
||||
status:
|
||||
type: string
|
||||
description: Order Status
|
||||
enum:
|
||||
- placed
|
||||
- approved
|
||||
- delivered
|
||||
complete:
|
||||
type: boolean
|
||||
default: false
|
||||
xml:
|
||||
name: Order
|
||||
Category:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
xml:
|
||||
name: Category
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
username:
|
||||
type: string
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
userStatus:
|
||||
type: integer
|
||||
description: User Status
|
||||
format: int32
|
||||
xml:
|
||||
name: User
|
||||
Tag:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
xml:
|
||||
name: Tag
|
||||
Pet:
|
||||
required:
|
||||
- name
|
||||
- photoUrls
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
category:
|
||||
$ref: '#/components/schemas/Category'
|
||||
name:
|
||||
type: string
|
||||
example: doggie
|
||||
photoUrls:
|
||||
type: array
|
||||
xml:
|
||||
name: photoUrl
|
||||
wrapped: true
|
||||
items:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
xml:
|
||||
name: tag
|
||||
wrapped: true
|
||||
items:
|
||||
$ref: '#/components/schemas/Tag'
|
||||
status:
|
||||
type: string
|
||||
description: pet status in the store
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
xml:
|
||||
name: Pet
|
||||
ApiResponse:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
format: int32
|
||||
type:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
securitySchemes:
|
||||
petstore_auth:
|
||||
type: oauth2
|
||||
flows:
|
||||
implicit:
|
||||
authorizationUrl: http://petstore.swagger.io/oauth/dialog
|
||||
scopes:
|
||||
write:pets: modify pets in your account
|
||||
read:pets: read your pets
|
||||
api_key:
|
||||
type: apiKey
|
||||
name: api_key
|
||||
in: header
|
||||
Reference in New Issue
Block a user