2021-01-01 20:30:52 +08:00
|
|
|
package generate
|
|
|
|
|
|
|
|
import (
|
2021-01-08 21:48:23 +08:00
|
|
|
"bytes"
|
2021-01-01 20:30:52 +08:00
|
|
|
"fmt"
|
|
|
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
|
|
|
"net/http"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2021-01-08 21:48:23 +08:00
|
|
|
"unsafe"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
strColon = []byte(":")
|
2021-01-01 20:30:52 +08:00
|
|
|
)
|
|
|
|
|
2021-01-16 11:53:17 +08:00
|
|
|
const (
|
|
|
|
defaultOption = "default"
|
|
|
|
stringOption = "string"
|
|
|
|
optionalOption = "optional"
|
|
|
|
optionsOption = "options"
|
|
|
|
rangeOption = "range"
|
|
|
|
optionSeparator = "|"
|
|
|
|
equalToken = "="
|
|
|
|
)
|
|
|
|
|
2021-01-01 20:30:52 +08:00
|
|
|
func applyGenerate(p Plugin) (*swaggerObject, error) {
|
|
|
|
|
|
|
|
s := swaggerObject{
|
|
|
|
Swagger: "2.0",
|
|
|
|
Schemes: []string{"http", "https"},
|
|
|
|
Consumes: []string{"application/json"},
|
|
|
|
Produces: []string{"application/json"},
|
|
|
|
Paths: make(swaggerPathsObject),
|
|
|
|
Definitions: make(swaggerDefinitionsObject),
|
|
|
|
StreamDefinitions: make(swaggerDefinitionsObject),
|
|
|
|
Info: swaggerInfoObject{
|
|
|
|
Title: p.Api.Info.Title,
|
|
|
|
Version: p.Api.Info.Version,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-01-08 23:35:08 +08:00
|
|
|
s.SecurityDefinitions = swaggerSecurityDefinitionsObject{}
|
|
|
|
newSecDefValue := swaggerSecuritySchemeObject{}
|
|
|
|
newSecDefValue.Name = "Authorization"
|
|
|
|
newSecDefValue.Description = "Enter JWT Bearer token **_only_**"
|
|
|
|
newSecDefValue.Type = "apiKey"
|
|
|
|
newSecDefValue.In = "header"
|
|
|
|
s.SecurityDefinitions["apiKey"] = newSecDefValue
|
|
|
|
|
2021-01-01 20:30:52 +08:00
|
|
|
requestResponseRefs := refMap{}
|
|
|
|
renderServiceRoutes(p.Api.Service, p.Api.Service.Groups, s.Paths, requestResponseRefs)
|
|
|
|
m := messageMap{}
|
|
|
|
renderReplyAsDefinition(s.Definitions, m, p.Api.Types, requestResponseRefs)
|
2021-01-16 11:53:17 +08:00
|
|
|
|
2021-01-01 20:30:52 +08:00
|
|
|
return &s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swaggerPathsObject, requestResponseRefs refMap) {
|
|
|
|
|
|
|
|
for _, group := range groups {
|
|
|
|
|
|
|
|
for _, route := range group.Routes {
|
|
|
|
path := route.Path
|
|
|
|
parameters := swaggerParametersObject{}
|
2021-01-08 21:48:23 +08:00
|
|
|
if countParams(path) > 0 {
|
|
|
|
p := strings.Split(path, "/")
|
|
|
|
for i := range p {
|
|
|
|
part := p[i]
|
|
|
|
if strings.Contains(part, ":") {
|
|
|
|
key := strings.TrimPrefix(p[i], ":")
|
|
|
|
path = strings.Replace(path, fmt.Sprintf(":%s", key), fmt.Sprintf("{%s}", key), 1)
|
|
|
|
parameters = append(parameters, swaggerParameterObject{
|
|
|
|
Name: key,
|
|
|
|
In: "path",
|
|
|
|
Required: true,
|
|
|
|
Type: "string",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
|
2021-01-16 11:53:17 +08:00
|
|
|
if strings.ToUpper(route.Method) == http.MethodGet {
|
|
|
|
for _, member := range route.RequestType.Members {
|
|
|
|
if strings.Contains(member.Tag, "path") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tempKind := swaggerMapTypes[strings.Replace(member.Type, "[]", "", -1)]
|
|
|
|
ftype, format, ok := primitiveSchema(tempKind, member.Type)
|
|
|
|
if !ok {
|
|
|
|
ftype = tempKind.String()
|
|
|
|
format = "UNKNOWN"
|
|
|
|
}
|
|
|
|
sp := swaggerParameterObject{In: "query", Type: ftype, Format: format}
|
|
|
|
|
|
|
|
for _, tag := range member.Tags() {
|
|
|
|
sp.Name = tag.Name
|
|
|
|
if len(tag.Options) == 0 {
|
|
|
|
sp.Required = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, option := range tag.Options {
|
|
|
|
if strings.HasPrefix(option, defaultOption) {
|
|
|
|
segs := strings.Split(option, equalToken)
|
|
|
|
if len(segs) == 2 {
|
|
|
|
sp.Default = segs[1]
|
|
|
|
}
|
|
|
|
} else if !strings.HasPrefix(option, optionalOption) {
|
|
|
|
sp.Required = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
parameters = append(parameters, sp)
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
reqRef := fmt.Sprintf("#/definitions/%s", route.RequestType.Name)
|
|
|
|
if len(route.RequestType.Name) > 0 {
|
|
|
|
var schema = swaggerSchemaObject{
|
|
|
|
schemaCore: schemaCore{
|
|
|
|
Ref: reqRef,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
parameters = append(parameters, swaggerParameterObject{
|
|
|
|
Name: "body",
|
|
|
|
In: "body",
|
|
|
|
Required: true,
|
|
|
|
Schema: &schema,
|
|
|
|
})
|
2021-01-03 16:10:42 +08:00
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pathItemObject, ok := paths[path]
|
|
|
|
if !ok {
|
|
|
|
pathItemObject = swaggerPathItemObject{}
|
|
|
|
}
|
2021-01-08 21:48:23 +08:00
|
|
|
|
2021-01-01 20:30:52 +08:00
|
|
|
desc := "A successful response."
|
|
|
|
respRef := fmt.Sprintf("#/definitions/%s", route.ResponseType.Name)
|
|
|
|
if len(route.ResponseType.Name) < 1 {
|
|
|
|
respRef = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
tags := service.Name
|
|
|
|
if group.Annotations != nil && len(group.Annotations) > 0 {
|
|
|
|
if groupName, ok := group.Annotations[0].Properties["group"]; ok {
|
|
|
|
tags = groupName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
operationObject := &swaggerOperationObject{
|
|
|
|
Tags: []string{tags},
|
|
|
|
Parameters: parameters,
|
|
|
|
Responses: swaggerResponsesObject{
|
|
|
|
"200": swaggerResponseObject{
|
|
|
|
Description: desc,
|
|
|
|
Schema: swaggerSchemaObject{
|
|
|
|
schemaCore: schemaCore{
|
|
|
|
Ref: respRef,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2021-01-08 22:52:15 +08:00
|
|
|
|
2021-01-08 23:35:08 +08:00
|
|
|
// set OperationID
|
2021-01-08 22:52:15 +08:00
|
|
|
for _, annotation := range route.Annotations {
|
|
|
|
if annotation.Name == "handler" {
|
|
|
|
operationObject.OperationID = annotation.Value
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
|
|
|
|
for _, param := range operationObject.Parameters {
|
|
|
|
if param.Schema != nil && param.Schema.Ref != "" {
|
|
|
|
requestResponseRefs[param.Schema.Ref] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(route.Annotations) > 0 {
|
|
|
|
operationObject.Summary, _ = strconv.Unquote(route.Annotations[0].Properties["summary"])
|
|
|
|
operationObject.Description, _ = strconv.Unquote(route.Annotations[0].Properties["description"])
|
|
|
|
}
|
|
|
|
|
|
|
|
switch strings.ToUpper(route.Method) {
|
|
|
|
case http.MethodGet:
|
|
|
|
pathItemObject.Get = operationObject
|
|
|
|
case http.MethodPost:
|
|
|
|
pathItemObject.Post = operationObject
|
|
|
|
case http.MethodDelete:
|
|
|
|
pathItemObject.Delete = operationObject
|
|
|
|
case http.MethodPut:
|
|
|
|
pathItemObject.Put = operationObject
|
|
|
|
}
|
|
|
|
|
|
|
|
paths[path] = pathItemObject
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderReplyAsDefinition(d swaggerDefinitionsObject, m messageMap, p []spec.Type, refs refMap) {
|
|
|
|
for _, i2 := range p {
|
|
|
|
schema := swaggerSchemaObject{
|
|
|
|
schemaCore: schemaCore{
|
|
|
|
Type: "object",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
schema.Title = i2.Name
|
|
|
|
|
|
|
|
for _, member := range i2.Members {
|
|
|
|
kv := keyVal{Value: schemaOfField(member)}
|
|
|
|
kv.Key = member.Name
|
2021-01-08 21:48:23 +08:00
|
|
|
if tag, err := member.GetPropertyName(); err == nil {
|
|
|
|
kv.Key = tag
|
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
if schema.Properties == nil {
|
|
|
|
schema.Properties = &swaggerSchemaObjectProperties{}
|
|
|
|
}
|
|
|
|
*schema.Properties = append(*schema.Properties, kv)
|
2021-01-16 11:53:17 +08:00
|
|
|
|
|
|
|
for _, tag := range member.Tags() {
|
|
|
|
if len(tag.Options) == 0 {
|
|
|
|
schema.Required = append(schema.Required, tag.Name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, option := range tag.Options {
|
|
|
|
switch {
|
|
|
|
case !strings.HasPrefix(option, optionalOption):
|
|
|
|
if !contains(schema.Required, tag.Name) {
|
|
|
|
schema.Required = append(schema.Required, tag.Name)
|
|
|
|
}
|
|
|
|
case strings.HasPrefix(option, defaultOption):
|
|
|
|
case strings.HasPrefix(option, optionsOption):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
}
|
2021-01-16 11:53:17 +08:00
|
|
|
|
2021-01-01 20:30:52 +08:00
|
|
|
d[i2.Name] = schema
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func schemaOfField(member spec.Member) swaggerSchemaObject {
|
|
|
|
ret := swaggerSchemaObject{}
|
|
|
|
|
|
|
|
var core schemaCore
|
2021-01-16 11:53:17 +08:00
|
|
|
//spew.Dump(member)
|
2021-01-01 20:30:52 +08:00
|
|
|
kind := swaggerMapTypes[member.Type]
|
|
|
|
var props *swaggerSchemaObjectProperties
|
|
|
|
|
2021-01-16 11:53:17 +08:00
|
|
|
comment := member.GetComment()
|
|
|
|
comment = strings.Replace(comment, "//", "", -1)
|
|
|
|
|
2021-01-01 20:30:52 +08:00
|
|
|
switch ft := kind; ft {
|
2021-01-08 21:48:23 +08:00
|
|
|
case reflect.Invalid: //[]Struct 也有可能是 Struct
|
2021-01-01 20:30:52 +08:00
|
|
|
// []Struct
|
2021-01-16 11:53:17 +08:00
|
|
|
//map[ArrayType:map[Star:map[StringExpr:UserSearchReq] StringExpr:*UserSearchReq] StringExpr:[]*UserSearchReq]
|
2021-01-01 20:30:52 +08:00
|
|
|
refTypeName := strings.Replace(member.Type, "[", "", 1)
|
|
|
|
refTypeName = strings.Replace(refTypeName, "]", "", 1)
|
2021-01-16 11:53:17 +08:00
|
|
|
refTypeName = strings.Replace(refTypeName, "*", "", 1)
|
2021-01-01 20:30:52 +08:00
|
|
|
core = schemaCore{
|
|
|
|
Ref: "#/definitions/" + refTypeName,
|
|
|
|
}
|
2021-01-16 11:53:17 +08:00
|
|
|
case reflect.Slice:
|
|
|
|
tempKind := swaggerMapTypes[strings.Replace(member.Type, "[]", "", -1)]
|
|
|
|
ftype, format, ok := primitiveSchema(tempKind, member.Type)
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
core = schemaCore{Type: ftype, Format: format}
|
|
|
|
} else {
|
|
|
|
core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
|
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
default:
|
|
|
|
ftype, format, ok := primitiveSchema(ft, member.Type)
|
|
|
|
if ok {
|
|
|
|
core = schemaCore{Type: ftype, Format: format}
|
|
|
|
} else {
|
|
|
|
core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ft := kind; ft {
|
2021-01-08 21:48:23 +08:00
|
|
|
case reflect.Slice:
|
2021-01-01 20:30:52 +08:00
|
|
|
ret = swaggerSchemaObject{
|
|
|
|
schemaCore: schemaCore{
|
|
|
|
Type: "array",
|
|
|
|
Items: (*swaggerItemsObject)(&core),
|
|
|
|
},
|
|
|
|
}
|
2021-01-08 21:48:23 +08:00
|
|
|
case reflect.Invalid:
|
|
|
|
// 判断是否数组
|
|
|
|
if strings.HasPrefix(member.Type, "[]") {
|
|
|
|
ret = swaggerSchemaObject{
|
|
|
|
schemaCore: schemaCore{
|
|
|
|
Type: "array",
|
|
|
|
Items: (*swaggerItemsObject)(&core),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ret = swaggerSchemaObject{
|
|
|
|
schemaCore: core,
|
|
|
|
Properties: props,
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
default:
|
|
|
|
ret = swaggerSchemaObject{
|
|
|
|
schemaCore: core,
|
|
|
|
Properties: props,
|
|
|
|
}
|
|
|
|
}
|
2021-01-16 11:53:17 +08:00
|
|
|
ret.Description = comment
|
|
|
|
|
|
|
|
for _, tag := range member.Tags() {
|
|
|
|
if len(tag.Options) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, option := range tag.Options {
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(option, defaultOption):
|
|
|
|
segs := strings.Split(option, equalToken)
|
|
|
|
if len(segs) == 2 {
|
|
|
|
ret.Default = segs[1]
|
|
|
|
}
|
|
|
|
case strings.HasPrefix(option, optionsOption):
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 20:30:52 +08:00
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2021-01-03 16:10:42 +08:00
|
|
|
// https://swagger.io/specification/ Data Types
|
2021-01-01 20:30:52 +08:00
|
|
|
func primitiveSchema(kind reflect.Kind, t string) (ftype, format string, ok bool) {
|
|
|
|
switch kind {
|
|
|
|
case reflect.Int:
|
|
|
|
return "integer", "int32", true
|
2021-01-03 16:10:42 +08:00
|
|
|
case reflect.Int64:
|
|
|
|
return "integer", "int64", true
|
2021-01-01 20:30:52 +08:00
|
|
|
case reflect.Bool:
|
|
|
|
return "boolean", "boolean", true
|
|
|
|
case reflect.String:
|
|
|
|
return "string", "", true
|
|
|
|
case reflect.Slice:
|
|
|
|
return strings.Replace(t, "[]", "", -1), "", true
|
|
|
|
default:
|
|
|
|
return "", "", false
|
|
|
|
}
|
|
|
|
}
|
2021-01-08 21:48:23 +08:00
|
|
|
|
|
|
|
// StringToBytes converts string to byte slice without a memory allocation.
|
|
|
|
func stringToBytes(s string) (b []byte) {
|
|
|
|
return *(*[]byte)(unsafe.Pointer(
|
|
|
|
&struct {
|
|
|
|
string
|
|
|
|
Cap int
|
|
|
|
}{s, len(s)},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
func countParams(path string) uint16 {
|
|
|
|
var n uint16
|
|
|
|
s := stringToBytes(path)
|
|
|
|
n += uint16(bytes.Count(s, strColon))
|
|
|
|
return n
|
|
|
|
}
|
2021-01-16 11:53:17 +08:00
|
|
|
func contains(s []string, str string) bool {
|
|
|
|
for _, v := range s {
|
|
|
|
if v == str {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|