first commit
This commit is contained in:
245
generate/entities.go
Normal file
245
generate/entities.go
Normal file
@ -0,0 +1,245 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type (
|
||||
Plugin struct {
|
||||
Api *spec.ApiSpec
|
||||
Style string
|
||||
Dir string
|
||||
ParentPackage string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
swaggerMapTypes = map[string]reflect.Kind{
|
||||
"string": reflect.String,
|
||||
"int": reflect.Int,
|
||||
"[]string": reflect.Slice,
|
||||
"bool": reflect.Bool,
|
||||
"struct": reflect.Struct,
|
||||
}
|
||||
)
|
||||
|
||||
// http://swagger.io/specification/#infoObject
|
||||
type swaggerInfoObject struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
TermsOfService string `json:"termsOfService,omitempty"`
|
||||
Version string `json:"version"`
|
||||
|
||||
Contact *swaggerContactObject `json:"contact,omitempty"`
|
||||
License *swaggerLicenseObject `json:"license,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#contactObject
|
||||
type swaggerContactObject struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#licenseObject
|
||||
type swaggerLicenseObject struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#externalDocumentationObject
|
||||
type swaggerExternalDocumentationObject struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#swaggerObject
|
||||
type swaggerObject struct {
|
||||
Swagger string `json:"swagger"`
|
||||
Info swaggerInfoObject `json:"info"`
|
||||
Host string `json:"host,omitempty"`
|
||||
BasePath string `json:"basePath,omitempty"`
|
||||
Schemes []string `json:"schemes"`
|
||||
Consumes []string `json:"consumes"`
|
||||
Produces []string `json:"produces"`
|
||||
Paths swaggerPathsObject `json:"paths"`
|
||||
Definitions swaggerDefinitionsObject `json:"definitions"`
|
||||
StreamDefinitions swaggerDefinitionsObject `json:"x-stream-definitions,omitempty"`
|
||||
SecurityDefinitions swaggerSecurityDefinitionsObject `json:"securityDefinitions,omitempty"`
|
||||
Security []swaggerSecurityRequirementObject `json:"security,omitempty"`
|
||||
ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#securityDefinitionsObject
|
||||
type swaggerSecurityDefinitionsObject map[string]swaggerSecuritySchemeObject
|
||||
|
||||
// http://swagger.io/specification/#securitySchemeObject
|
||||
type swaggerSecuritySchemeObject struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
In string `json:"in,omitempty"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
AuthorizationURL string `json:"authorizationUrl,omitempty"`
|
||||
TokenURL string `json:"tokenUrl,omitempty"`
|
||||
Scopes swaggerScopesObject `json:"scopes,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#scopesObject
|
||||
type swaggerScopesObject map[string]string
|
||||
|
||||
// http://swagger.io/specification/#securityRequirementObject
|
||||
type swaggerSecurityRequirementObject map[string][]string
|
||||
|
||||
// http://swagger.io/specification/#pathsObject
|
||||
type swaggerPathsObject map[string]swaggerPathItemObject
|
||||
|
||||
// http://swagger.io/specification/#pathItemObject
|
||||
type swaggerPathItemObject struct {
|
||||
Get *swaggerOperationObject `json:"get,omitempty"`
|
||||
Delete *swaggerOperationObject `json:"delete,omitempty"`
|
||||
Post *swaggerOperationObject `json:"post,omitempty"`
|
||||
Put *swaggerOperationObject `json:"put,omitempty"`
|
||||
Patch *swaggerOperationObject `json:"patch,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#operationObject
|
||||
type swaggerOperationObject struct {
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
OperationID string `json:"operationId"`
|
||||
Responses swaggerResponsesObject `json:"responses"`
|
||||
Parameters swaggerParametersObject `json:"parameters,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
|
||||
Security *[]swaggerSecurityRequirementObject `json:"security,omitempty"`
|
||||
ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
type swaggerParametersObject []swaggerParameterObject
|
||||
|
||||
// http://swagger.io/specification/#parameterObject
|
||||
type swaggerParameterObject struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
In string `json:"in,omitempty"`
|
||||
Required bool `json:"required"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Items *swaggerItemsObject `json:"items,omitempty"`
|
||||
Enum []string `json:"enum,omitempty"`
|
||||
CollectionFormat string `json:"collectionFormat,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
MinItems *int `json:"minItems,omitempty"`
|
||||
|
||||
// Or you can explicitly refer to another type. If this is defined all
|
||||
// other fields should be empty
|
||||
Schema *swaggerSchemaObject `json:"schema,omitempty"`
|
||||
}
|
||||
|
||||
// core part of schema, which is common to itemsObject and schemaObject.
|
||||
// http://swagger.io/specification/#itemsObject
|
||||
type schemaCore struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Example json.RawMessage `json:"example,omitempty"`
|
||||
|
||||
Items *swaggerItemsObject `json:"items,omitempty"`
|
||||
|
||||
// If the item is an enumeration include a list of all the *NAMES* of the
|
||||
// enum values. I'm not sure how well this will work but assuming all enums
|
||||
// start from 0 index it will be great. I don't think that is a good assumption.
|
||||
Enum []string `json:"enum,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
type swaggerItemsObject schemaCore
|
||||
|
||||
// http://swagger.io/specification/#responsesObject
|
||||
type swaggerResponsesObject map[string]swaggerResponseObject
|
||||
|
||||
// http://swagger.io/specification/#responseObject
|
||||
type swaggerResponseObject struct {
|
||||
Description string `json:"description"`
|
||||
Schema swaggerSchemaObject `json:"schema"`
|
||||
}
|
||||
|
||||
type keyVal struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type swaggerSchemaObjectProperties []keyVal
|
||||
|
||||
func (op swaggerSchemaObjectProperties) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("{")
|
||||
for i, kv := range op {
|
||||
if i != 0 {
|
||||
buf.WriteString(",")
|
||||
}
|
||||
key, err := json.Marshal(kv.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(key)
|
||||
buf.WriteString(":")
|
||||
val, err := json.Marshal(kv.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(val)
|
||||
}
|
||||
|
||||
buf.WriteString("}")
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#schemaObject
|
||||
type swaggerSchemaObject struct {
|
||||
schemaCore
|
||||
// Properties can be recursively defined
|
||||
Properties *swaggerSchemaObjectProperties `json:"properties,omitempty"`
|
||||
AdditionalProperties *swaggerSchemaObject `json:"additionalProperties,omitempty"`
|
||||
|
||||
Description string `json:"description,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
|
||||
ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"`
|
||||
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
MultipleOf float64 `json:"multipleOf,omitempty"`
|
||||
Maximum float64 `json:"maximum,omitempty"`
|
||||
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
|
||||
Minimum float64 `json:"minimum,omitempty"`
|
||||
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
|
||||
MaxLength uint64 `json:"maxLength,omitempty"`
|
||||
MinLength uint64 `json:"minLength,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
MaxItems uint64 `json:"maxItems,omitempty"`
|
||||
MinItems uint64 `json:"minItems,omitempty"`
|
||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
||||
MaxProperties uint64 `json:"maxProperties,omitempty"`
|
||||
MinProperties uint64 `json:"minProperties,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
}
|
||||
|
||||
// http://swagger.io/specification/#definitionsObject
|
||||
type swaggerDefinitionsObject map[string]swaggerSchemaObject
|
||||
|
||||
// Internal type mapping from FQMN to descriptor.Message. Used as a set by the
|
||||
// findServiceMessages function.
|
||||
type messageMap map[string]*descriptor.Message
|
||||
|
||||
// Internal type mapping from FQEN to descriptor.Enum. Used as a set by the
|
||||
// findServiceMessages function.
|
||||
type enumMap map[string]*descriptor.Enum
|
||||
|
||||
// Internal type to store used references.
|
||||
type refMap map[string]struct{}
|
29
generate/generate.go
Normal file
29
generate/generate.go
Normal file
@ -0,0 +1,29 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func Do(in Plugin) error {
|
||||
|
||||
swagger, err := applyGenerate(in)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
var formatted bytes.Buffer
|
||||
enc := json.NewEncoder(&formatted)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
if err := enc.Encode(swagger); err != nil {
|
||||
|
||||
}
|
||||
|
||||
output := in.Dir + "/swagger.json"
|
||||
|
||||
err = ioutil.WriteFile(output, formatted.Bytes(), 0666)
|
||||
|
||||
return err
|
||||
}
|
202
generate/parser.go
Normal file
202
generate/parser.go
Normal file
@ -0,0 +1,202 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
requestResponseRefs := refMap{}
|
||||
renderServiceRoutes(p.Api.Service, p.Api.Service.Groups, s.Paths, requestResponseRefs)
|
||||
m := messageMap{}
|
||||
renderReplyAsDefinition(s.Definitions, m, p.Api.Types, requestResponseRefs)
|
||||
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
|
||||
if strings.Contains(path, "/:") {
|
||||
|
||||
}
|
||||
parameters := swaggerParametersObject{}
|
||||
|
||||
reqRef := fmt.Sprintf("#/definitions/%s", route.RequestType.Name)
|
||||
if len(route.ResponseType.Name) < 1 {
|
||||
reqRef = ""
|
||||
}
|
||||
var schema = swaggerSchemaObject{
|
||||
schemaCore: schemaCore{
|
||||
Ref: reqRef,
|
||||
},
|
||||
}
|
||||
|
||||
parameters = append(parameters, swaggerParameterObject{
|
||||
Name: "body",
|
||||
In: "body",
|
||||
Required: true,
|
||||
Schema: &schema,
|
||||
})
|
||||
pathItemObject, ok := paths[path]
|
||||
if !ok {
|
||||
pathItemObject = swaggerPathItemObject{}
|
||||
}
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
operationObject.OperationID = fmt.Sprintf("%s", route.Path)
|
||||
|
||||
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
|
||||
if schema.Properties == nil {
|
||||
schema.Properties = &swaggerSchemaObjectProperties{}
|
||||
}
|
||||
*schema.Properties = append(*schema.Properties, kv)
|
||||
}
|
||||
d[i2.Name] = schema
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func schemaOfField(member spec.Member) swaggerSchemaObject {
|
||||
ret := swaggerSchemaObject{}
|
||||
|
||||
var core schemaCore
|
||||
|
||||
kind := swaggerMapTypes[member.Type]
|
||||
var props *swaggerSchemaObjectProperties
|
||||
|
||||
switch ft := kind; ft {
|
||||
case reflect.Invalid:
|
||||
// []Struct
|
||||
refTypeName := strings.Replace(member.Type, "[", "", 1)
|
||||
refTypeName = strings.Replace(refTypeName, "]", "", 1)
|
||||
core = schemaCore{
|
||||
Ref: "#/definitions/" + refTypeName,
|
||||
}
|
||||
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 {
|
||||
case reflect.Slice, reflect.Invalid:
|
||||
ret = swaggerSchemaObject{
|
||||
schemaCore: schemaCore{
|
||||
Type: "array",
|
||||
Items: (*swaggerItemsObject)(&core),
|
||||
},
|
||||
}
|
||||
default:
|
||||
ret = swaggerSchemaObject{
|
||||
schemaCore: core,
|
||||
Properties: props,
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func primitiveSchema(kind reflect.Kind, t string) (ftype, format string, ok bool) {
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
return "integer", "int32", true
|
||||
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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user