Add core logic
This commit is contained in:
parent
306847994a
commit
c1dc8982c8
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,3 +10,6 @@
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
.DS_Store
|
||||
tmp
|
129
converter.go
129
converter.go
@ -1,8 +1,137 @@
|
||||
package echoswagger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// toSwaggerType returns type、format for a reflect.Type in swagger format
|
||||
func toSwaggerType(t reflect.Type) (string, string) {
|
||||
if t == reflect.TypeOf(time.Time{}) {
|
||||
return "string", "date-time"
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
|
||||
return "integer", "int32"
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
return "integer", "int64"
|
||||
case reflect.Float32:
|
||||
return "number", "float"
|
||||
case reflect.Float64:
|
||||
return "number", "double"
|
||||
case reflect.String:
|
||||
return "string", "string"
|
||||
case reflect.Bool:
|
||||
return "boolean", "boolean"
|
||||
case reflect.Struct:
|
||||
return "object", "object"
|
||||
case reflect.Map:
|
||||
return "object", "map"
|
||||
case reflect.Array, reflect.Slice:
|
||||
return "array", "array"
|
||||
case reflect.Ptr:
|
||||
return toSwaggerType(t.Elem())
|
||||
default:
|
||||
return "string", "string"
|
||||
}
|
||||
}
|
||||
|
||||
// toSwaggerPath returns path in swagger format
|
||||
func toSwaggerPath(path string) string {
|
||||
var params []string
|
||||
for i := 0; i < len(path); i++ {
|
||||
if path[i] == ':' {
|
||||
j := i + 1
|
||||
for ; i < len(path) && path[i] != '/'; i++ {
|
||||
}
|
||||
params = append(params, path[j:i])
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range params {
|
||||
path = strings.Replace(path, ":"+name, "{"+name+"}", 1)
|
||||
}
|
||||
return proccessPath(path)
|
||||
}
|
||||
|
||||
func proccessPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// converter returns string to target type converter for a reflect.StructField
|
||||
func converter(f reflect.StructField) func(s string) (interface{}, error) {
|
||||
switch f.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
return func(s string) (interface{}, error) {
|
||||
v, err := strconv.ParseBool(s)
|
||||
return v, err
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
|
||||
return func(s string) (interface{}, error) {
|
||||
v, err := strconv.Atoi(s)
|
||||
return v, err
|
||||
}
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
return func(s string) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 64)
|
||||
return v, err
|
||||
}
|
||||
case reflect.Float32:
|
||||
return func(s string) (interface{}, error) {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
return float32(v), err
|
||||
}
|
||||
case reflect.Float64:
|
||||
return func(s string) (interface{}, error) {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
return v, err
|
||||
}
|
||||
default:
|
||||
return func(s string) (interface{}, error) {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func asString(rv reflect.Value) (string, bool) {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
av := rv.Int()
|
||||
if av != 0 {
|
||||
return strconv.FormatInt(av, 10), true
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
av := rv.Uint()
|
||||
if av != 0 {
|
||||
return strconv.FormatUint(av, 10), true
|
||||
}
|
||||
case reflect.Float64:
|
||||
av := rv.Float()
|
||||
if av != 0 {
|
||||
return strconv.FormatFloat(av, 'g', -1, 64), true
|
||||
}
|
||||
case reflect.Float32:
|
||||
av := rv.Float()
|
||||
if av != 0 {
|
||||
return strconv.FormatFloat(av, 'g', -1, 32), true
|
||||
}
|
||||
case reflect.Bool:
|
||||
av := rv.Bool()
|
||||
if av {
|
||||
return strconv.FormatBool(av), true
|
||||
}
|
||||
case reflect.String:
|
||||
av := rv.String()
|
||||
if av != "" {
|
||||
return av, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
112
generator.go
Normal file
112
generator.go
Normal file
@ -0,0 +1,112 @@
|
||||
package echoswagger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func (Items) generate(t reflect.Type) *Items {
|
||||
st, sf := toSwaggerType(t)
|
||||
item := &Items{
|
||||
Type: st,
|
||||
}
|
||||
if st == "array" {
|
||||
item.Items = Items{}.generate(t.Elem())
|
||||
item.CollectionFormat = "multi"
|
||||
} else {
|
||||
item.Format = sf
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func (Parameter) generate(f reflect.StructField, in ParamInType) *Parameter {
|
||||
name := getFieldName(f, in)
|
||||
if name == "-" {
|
||||
return nil
|
||||
}
|
||||
st, sf := toSwaggerType(f.Type)
|
||||
pm := &Parameter{
|
||||
Name: name,
|
||||
In: string(in),
|
||||
Type: st,
|
||||
}
|
||||
if st == "array" {
|
||||
pm.Items = Items{}.generate(f.Type.Elem())
|
||||
pm.CollectionFormat = "multi"
|
||||
} else {
|
||||
pm.Format = sf
|
||||
}
|
||||
|
||||
pm.handleSwaggerTags(f, name, in)
|
||||
return pm
|
||||
}
|
||||
|
||||
func (Header) generate(f reflect.StructField) *Header {
|
||||
name := getFieldName(f, ParamInHeader)
|
||||
if name == "-" {
|
||||
return nil
|
||||
}
|
||||
st, sf := toSwaggerType(f.Type)
|
||||
h := &Header{
|
||||
Type: st,
|
||||
}
|
||||
if st == "array" {
|
||||
h.Items = Items{}.generate(f.Type.Elem())
|
||||
h.CollectionFormat = "multi"
|
||||
} else {
|
||||
h.Format = sf
|
||||
}
|
||||
|
||||
h.handleSwaggerTags(f, name)
|
||||
return h
|
||||
}
|
||||
|
||||
func (r *RawDefineDic) genSchema(v reflect.Value) *JSONSchema {
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
v = indirect(v)
|
||||
st, sf := toSwaggerType(v.Type())
|
||||
schema := &JSONSchema{}
|
||||
if st == "array" {
|
||||
schema.Type = JSONType(st)
|
||||
if v.Len() == 0 {
|
||||
v = reflect.MakeSlice(v.Type(), 1, 1)
|
||||
}
|
||||
schema.Items = r.genSchema(v.Index(0))
|
||||
} else if st == "object" && sf == "map" {
|
||||
schema.Type = JSONType(st)
|
||||
if v.Len() == 0 {
|
||||
v = reflect.New(v.Type().Elem())
|
||||
} else {
|
||||
v = v.MapIndex(v.MapKeys()[0])
|
||||
}
|
||||
schema.AdditionalProperties = r.genSchema(v)
|
||||
} else if st == "object" {
|
||||
key := r.addDefinition(v)
|
||||
schema.Ref = DefPrefix + key
|
||||
} else {
|
||||
schema.Type = JSONType(st)
|
||||
schema.Format = sf
|
||||
if ex, ok := asString(v); ok {
|
||||
schema.Example = ex
|
||||
}
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
func (api) genHeader(v reflect.Value) map[string]*Header {
|
||||
rt := indirect(v).Type()
|
||||
if rt.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
mh := make(map[string]*Header)
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
h := Header{}.generate(f)
|
||||
if h != nil {
|
||||
name := getFieldName(f, ParamInHeader)
|
||||
mh[name] = h
|
||||
}
|
||||
}
|
||||
return mh
|
||||
}
|
142
internal.go
Normal file
142
internal.go
Normal file
@ -0,0 +1,142 @@
|
||||
package echoswagger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type ParamInType string
|
||||
|
||||
const (
|
||||
ParamInQuery ParamInType = "query"
|
||||
ParamInHeader ParamInType = "header"
|
||||
ParamInPath ParamInType = "path"
|
||||
ParamInFormData ParamInType = "formData"
|
||||
ParamInBody ParamInType = "body"
|
||||
)
|
||||
|
||||
type UISetting struct {
|
||||
HideTop bool
|
||||
CDN string
|
||||
}
|
||||
|
||||
type RawDefineDic map[string]RawDefine
|
||||
|
||||
type RawDefine struct {
|
||||
Value reflect.Value
|
||||
Schema *JSONSchema
|
||||
}
|
||||
|
||||
func (r *Root) docHandler(swaggerPath string) echo.HandlerFunc {
|
||||
t, err := template.New("swagger").Parse(SwaggerUIContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
buf := new(bytes.Buffer)
|
||||
t.Execute(buf, map[string]interface{}{
|
||||
"title": r.spec.Info.Title,
|
||||
"url": c.Scheme() + "://" + c.Request().Host + swaggerPath,
|
||||
})
|
||||
return c.HTMLBlob(http.StatusOK, buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RawDefineDic) getKey(v reflect.Value) (bool, string) {
|
||||
for k, d := range *r {
|
||||
if reflect.DeepEqual(d.Value.Interface(), v.Interface()) {
|
||||
return true, k
|
||||
}
|
||||
}
|
||||
name := v.Type().Name()
|
||||
for k := range *r {
|
||||
if name == k {
|
||||
name += "_"
|
||||
}
|
||||
}
|
||||
return false, name
|
||||
}
|
||||
|
||||
func (r *routers) appendRoute(method string, route *echo.Route) *api {
|
||||
opr := Operation{
|
||||
Responses: make(map[string]*Response),
|
||||
}
|
||||
a := api{
|
||||
route: route,
|
||||
defs: r.defs,
|
||||
method: method,
|
||||
operation: opr,
|
||||
}
|
||||
r.apis = append(r.apis, a)
|
||||
return &r.apis[len(r.apis)-1]
|
||||
}
|
||||
|
||||
func (g *api) addParams(p interface{}, in ParamInType, name, desc string, required, nest bool) Api {
|
||||
if !isValidParam(reflect.TypeOf(p), nest, false) {
|
||||
panic("echoswagger: invalid " + string(in) + " param")
|
||||
}
|
||||
rt := indirectType(p)
|
||||
st, sf := toSwaggerType(rt)
|
||||
if st == "object" && sf == "object" {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
pm := Parameter{}.generate(rt.Field(i), in)
|
||||
if pm != nil {
|
||||
pm.Name = g.operation.rename(pm.Name)
|
||||
g.operation.Parameters = append(g.operation.Parameters, pm)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
name = g.operation.rename(name)
|
||||
pm := &Parameter{
|
||||
Name: name,
|
||||
In: string(in),
|
||||
Description: desc,
|
||||
Required: required,
|
||||
Type: st,
|
||||
}
|
||||
if st == "array" {
|
||||
pm.Items = Items{}.generate(rt.Elem())
|
||||
pm.CollectionFormat = "multi"
|
||||
} else {
|
||||
pm.Format = sf
|
||||
}
|
||||
g.operation.Parameters = append(g.operation.Parameters, pm)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *api) addBodyParams(p interface{}, name, desc string, required bool) Api {
|
||||
if !isValidSchema(reflect.TypeOf(p), false) {
|
||||
panic("echoswagger: invalid body parameter")
|
||||
}
|
||||
for _, param := range g.operation.Parameters {
|
||||
if param.In == string(ParamInBody) {
|
||||
panic("echoswagger: multiple body parameters are not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
rv := indirectValue(p)
|
||||
pm := &Parameter{
|
||||
Name: name,
|
||||
In: string(ParamInBody),
|
||||
Description: desc,
|
||||
Required: required,
|
||||
Schema: g.defs.genSchema(rv),
|
||||
}
|
||||
g.operation.Parameters = append(g.operation.Parameters, pm)
|
||||
return g
|
||||
}
|
||||
|
||||
func (o Operation) rename(s string) string {
|
||||
for _, p := range o.Parameters {
|
||||
if p.Name == s {
|
||||
return o.rename(s + "_")
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
75
security.go
Normal file
75
security.go
Normal file
@ -0,0 +1,75 @@
|
||||
package echoswagger
|
||||
|
||||
import "errors"
|
||||
|
||||
type SecurityType string
|
||||
|
||||
const (
|
||||
SecurityBasic SecurityType = "basic"
|
||||
SecurityOAuth2 SecurityType = "oauth2"
|
||||
SecurityAPIKey SecurityType = "apiKey"
|
||||
)
|
||||
|
||||
type SecurityInType string
|
||||
|
||||
const (
|
||||
SecurityInQuery SecurityInType = "query"
|
||||
SecurityInHeader SecurityInType = "header"
|
||||
)
|
||||
|
||||
type OAuth2FlowType string
|
||||
|
||||
const (
|
||||
OAuth2FlowImplicit OAuth2FlowType = "implicit"
|
||||
OAuth2FlowPassword OAuth2FlowType = "password"
|
||||
OAuth2FlowApplication OAuth2FlowType = "application"
|
||||
OAuth2FlowAccessCode OAuth2FlowType = "accessCode"
|
||||
)
|
||||
|
||||
func (r *Root) checkSecurity(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
if _, ok := r.spec.SecurityDefinitions[name]; ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func setSecurity(security []map[string][]string, names ...string) []map[string][]string {
|
||||
m := make(map[string][]string)
|
||||
for _, name := range names {
|
||||
m[name] = make([]string, 0)
|
||||
}
|
||||
return append(security, m)
|
||||
}
|
||||
|
||||
func setSecurityWithScope(security []map[string][]string, s ...map[string][]string) []map[string][]string {
|
||||
for _, t := range s {
|
||||
if len(t) == 0 {
|
||||
continue
|
||||
}
|
||||
for k, v := range t {
|
||||
if len(v) == 0 {
|
||||
t[k] = make([]string, 0)
|
||||
}
|
||||
}
|
||||
security = append(security, t)
|
||||
}
|
||||
return security
|
||||
}
|
||||
|
||||
func (o *Operation) addSecurity(defs map[string]*SecurityDefinition, security []map[string][]string) error {
|
||||
for _, scy := range security {
|
||||
for k := range scy {
|
||||
if _, ok := defs[k]; !ok {
|
||||
return errors.New("echoswagger: not found SecurityDefinition with name: " + k)
|
||||
}
|
||||
}
|
||||
if containsMap(o.Security, scy) {
|
||||
continue
|
||||
}
|
||||
o.Security = append(o.Security, scy)
|
||||
}
|
||||
return nil
|
||||
}
|
133
spec.go
133
spec.go
@ -1,20 +1,25 @@
|
||||
package echoswagger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
const SwaggerVersion = "2.0"
|
||||
const (
|
||||
DefPrefix = "#/definitions/"
|
||||
SwaggerVersion = "2.0"
|
||||
)
|
||||
|
||||
func (r *Root) Spec(c echo.Context) error {
|
||||
err := r.genSpec(c)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, err.Error())
|
||||
r.once.Do(func() {
|
||||
r.err = r.genSpec(c)
|
||||
r.cleanUp()
|
||||
})
|
||||
if r.err != nil {
|
||||
return c.String(http.StatusInternalServerError, r.err.Error())
|
||||
}
|
||||
return c.JSON(http.StatusOK, r.spec)
|
||||
}
|
||||
@ -30,26 +35,114 @@ func (r *Root) genSpec(c echo.Context) error {
|
||||
r.spec.Tags = append(r.spec.Tags, &group.tag)
|
||||
for j := range group.apis {
|
||||
a := &group.apis[j]
|
||||
fmt.Println("Group:", a)
|
||||
// TODO
|
||||
if err := a.operation.addSecurity(r.spec.SecurityDefinitions, group.security); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.transfer(a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range r.apis {
|
||||
if err := r.transfer(&r.apis[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range *r.defs {
|
||||
r.spec.Definitions[k] = v.Schema
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Root) docHandler(swaggerPath string) echo.HandlerFunc {
|
||||
t, err := template.New("swagger").Parse(SwaggerUIContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func (r *Root) transfer(a *api) error {
|
||||
if err := a.operation.addSecurity(r.spec.SecurityDefinitions, a.security); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
buf := new(bytes.Buffer)
|
||||
t.Execute(buf, map[string]interface{}{
|
||||
"title": r.spec.Info.Title,
|
||||
"url": c.Scheme() + "://" + c.Request().Host + swaggerPath,
|
||||
})
|
||||
return c.HTMLBlob(http.StatusOK, buf.Bytes())
|
||||
path := toSwaggerPath(a.route.Path)
|
||||
if len(a.operation.Responses) == 0 {
|
||||
a.operation.Responses["default"] = &Response{
|
||||
Description: "successful operation",
|
||||
}
|
||||
}
|
||||
|
||||
if p, ok := r.spec.Paths[path]; ok {
|
||||
p.(*Path).oprationAssign(a.method, &a.operation)
|
||||
} else {
|
||||
p := &Path{}
|
||||
p.oprationAssign(a.method, &a.operation)
|
||||
r.spec.Paths[path] = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Path) oprationAssign(method string, operation *Operation) {
|
||||
switch method {
|
||||
case echo.GET:
|
||||
p.Get = operation
|
||||
case echo.POST:
|
||||
p.Post = operation
|
||||
case echo.PUT:
|
||||
p.Put = operation
|
||||
case echo.PATCH:
|
||||
p.Patch = operation
|
||||
case echo.DELETE:
|
||||
p.Delete = operation
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Root) cleanUp() {
|
||||
r.echo = nil
|
||||
r.groups = nil
|
||||
r.apis = nil
|
||||
r.defs = nil
|
||||
}
|
||||
|
||||
// addDefinition adds definition specification and returns
|
||||
// key of RawDefineDic
|
||||
func (r *RawDefineDic) addDefinition(v reflect.Value) string {
|
||||
exist, key := r.getKey(v)
|
||||
if exist {
|
||||
return key
|
||||
}
|
||||
|
||||
schema := &JSONSchema{
|
||||
Type: "object",
|
||||
Properties: make(map[string]*JSONSchema),
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := v.Type().Field(i)
|
||||
name := getFieldName(f, ParamInBody)
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
if f.Type == reflect.TypeOf(xml.Name{}) {
|
||||
schema.handleXMLTags(f)
|
||||
continue
|
||||
}
|
||||
sp := r.genSchema(v.Field(i))
|
||||
sp.handleXMLTags(f)
|
||||
if sp.XML != nil {
|
||||
sp.handleChildXMLTags(sp.XML.Name, r)
|
||||
}
|
||||
schema.Properties[name] = sp
|
||||
|
||||
schema.handleSwaggerTags(f, name)
|
||||
}
|
||||
|
||||
(*r)[key] = RawDefine{
|
||||
Value: v,
|
||||
Schema: schema,
|
||||
}
|
||||
|
||||
if schema.XML == nil {
|
||||
schema.XML = &XMLSchema{}
|
||||
}
|
||||
if schema.XML.Name == "" {
|
||||
schema.XML.Name = v.Type().Name()
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
350
tag.go
Normal file
350
tag.go
Normal file
@ -0,0 +1,350 @@
|
||||
package echoswagger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// getTag reports is a tag exists and it's content
|
||||
// search tagName in all tags when index = -1
|
||||
func getTag(field reflect.StructField, tagName string, index int) (bool, string) {
|
||||
t := field.Tag.Get(tagName)
|
||||
s := strings.Split(t, ",")
|
||||
|
||||
if len(s) < index+1 {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, strings.TrimSpace(s[index])
|
||||
}
|
||||
|
||||
func getSwaggerTags(field reflect.StructField) map[string]string {
|
||||
t := field.Tag.Get("swagger")
|
||||
r := make(map[string]string)
|
||||
for _, v := range strings.Split(t, ",") {
|
||||
leftIndex := strings.Index(v, "(")
|
||||
rightIndex := strings.LastIndex(v, ")")
|
||||
if leftIndex > 0 && rightIndex > leftIndex {
|
||||
r[v[:leftIndex]] = v[leftIndex+1 : rightIndex]
|
||||
} else {
|
||||
r[v] = ""
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func getFieldName(f reflect.StructField, in ParamInType) string {
|
||||
var name string
|
||||
switch in {
|
||||
case ParamInQuery:
|
||||
name = f.Tag.Get("query")
|
||||
case ParamInFormData:
|
||||
name = f.Tag.Get("form")
|
||||
case ParamInBody, ParamInHeader, ParamInPath:
|
||||
_, name = getTag(f, "json", 0)
|
||||
}
|
||||
if name != "" {
|
||||
return name
|
||||
} else {
|
||||
return f.Name
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parameter) handleSwaggerTags(field reflect.StructField, name string, in ParamInType) {
|
||||
tags := getSwaggerTags(field)
|
||||
|
||||
var collect string
|
||||
if t, ok := tags["collect"]; ok && contains([]string{"csv", "ssv", "tsv", "pipes"}, t) {
|
||||
collect = t
|
||||
}
|
||||
if t, ok := tags["desc"]; ok {
|
||||
p.Description = t
|
||||
}
|
||||
if t, ok := tags["min"]; ok {
|
||||
if m, err := strconv.ParseFloat(t, 64); err == nil {
|
||||
p.Minimum = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["max"]; ok {
|
||||
if m, err := strconv.ParseFloat(t, 64); err == nil {
|
||||
p.Maximum = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["minLen"]; ok {
|
||||
if m, err := strconv.Atoi(t); err == nil {
|
||||
p.MinLength = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["maxLen"]; ok {
|
||||
if m, err := strconv.Atoi(t); err == nil {
|
||||
p.MaxLength = &m
|
||||
}
|
||||
}
|
||||
if _, ok := tags["allowEmpty"]; ok {
|
||||
p.AllowEmptyValue = true
|
||||
}
|
||||
if _, ok := tags["required"]; ok || in == ParamInPath {
|
||||
p.Required = true
|
||||
}
|
||||
|
||||
convert := converter(field)
|
||||
if t, ok := tags["enum"]; ok {
|
||||
enums := strings.Split(t, "|")
|
||||
var es []interface{}
|
||||
for _, s := range enums {
|
||||
v, err := convert(s)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
es = append(es, v)
|
||||
}
|
||||
p.Enum = es
|
||||
}
|
||||
if t, ok := tags["default"]; ok {
|
||||
v, err := convert(t)
|
||||
if err == nil {
|
||||
p.Default = v
|
||||
}
|
||||
}
|
||||
|
||||
// Move part of tags in Parameter to Items
|
||||
if p.Type == "array" {
|
||||
items := p.Items.latest()
|
||||
items.CollectionFormat = collect
|
||||
items.Minimum = p.Minimum
|
||||
items.Maximum = p.Maximum
|
||||
items.MinLength = p.MinLength
|
||||
items.MaxLength = p.MaxLength
|
||||
items.Enum = p.Enum
|
||||
items.Default = p.Default
|
||||
p.Minimum = nil
|
||||
p.Maximum = nil
|
||||
p.MinLength = nil
|
||||
p.MaxLength = nil
|
||||
p.Enum = nil
|
||||
p.Default = nil
|
||||
} else {
|
||||
p.CollectionFormat = collect
|
||||
}
|
||||
}
|
||||
|
||||
func (s *JSONSchema) handleSwaggerTags(field reflect.StructField, name string) {
|
||||
propSchema := s.Properties[name]
|
||||
tags := getSwaggerTags(field)
|
||||
|
||||
if t, ok := tags["desc"]; ok {
|
||||
propSchema.Description = t
|
||||
}
|
||||
if t, ok := tags["min"]; ok {
|
||||
if m, err := strconv.ParseFloat(t, 64); err == nil {
|
||||
propSchema.Minimum = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["max"]; ok {
|
||||
if m, err := strconv.ParseFloat(t, 64); err == nil {
|
||||
propSchema.Maximum = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["minLen"]; ok {
|
||||
if m, err := strconv.Atoi(t); err == nil {
|
||||
propSchema.MinLength = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["maxLen"]; ok {
|
||||
if m, err := strconv.Atoi(t); err == nil {
|
||||
propSchema.MaxLength = &m
|
||||
}
|
||||
}
|
||||
if _, ok := tags["required"]; ok {
|
||||
s.Required = append(s.Required, name)
|
||||
}
|
||||
if _, ok := tags["readOnly"]; ok {
|
||||
propSchema.ReadOnly = true
|
||||
}
|
||||
|
||||
convert := converter(field)
|
||||
if t, ok := tags["enum"]; ok {
|
||||
enums := strings.Split(t, "|")
|
||||
var es []interface{}
|
||||
for _, s := range enums {
|
||||
v, err := convert(s)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
es = append(es, v)
|
||||
}
|
||||
propSchema.Enum = es
|
||||
}
|
||||
if t, ok := tags["default"]; ok {
|
||||
v, err := convert(t)
|
||||
if err == nil {
|
||||
propSchema.DefaultValue = v
|
||||
}
|
||||
}
|
||||
|
||||
// Move part of tags in Schema to Items
|
||||
if propSchema.Type == "array" {
|
||||
items := propSchema.Items.latest()
|
||||
items.Minimum = propSchema.Minimum
|
||||
items.Maximum = propSchema.Maximum
|
||||
items.MinLength = propSchema.MinLength
|
||||
items.MaxLength = propSchema.MaxLength
|
||||
items.Enum = propSchema.Enum
|
||||
items.DefaultValue = propSchema.DefaultValue
|
||||
propSchema.Minimum = nil
|
||||
propSchema.Maximum = nil
|
||||
propSchema.MinLength = nil
|
||||
propSchema.MaxLength = nil
|
||||
propSchema.Enum = nil
|
||||
propSchema.DefaultValue = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Header) handleSwaggerTags(field reflect.StructField, name string) {
|
||||
tags := getSwaggerTags(field)
|
||||
|
||||
var collect string
|
||||
if t, ok := tags["collect"]; ok && contains([]string{"csv", "ssv", "tsv", "pipes"}, t) {
|
||||
collect = t
|
||||
}
|
||||
if t, ok := tags["desc"]; ok {
|
||||
h.Description = t
|
||||
}
|
||||
if t, ok := tags["min"]; ok {
|
||||
if m, err := strconv.ParseFloat(t, 64); err == nil {
|
||||
h.Minimum = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["max"]; ok {
|
||||
if m, err := strconv.ParseFloat(t, 64); err == nil {
|
||||
h.Maximum = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["minLen"]; ok {
|
||||
if m, err := strconv.Atoi(t); err == nil {
|
||||
h.MinLength = &m
|
||||
}
|
||||
}
|
||||
if t, ok := tags["maxLen"]; ok {
|
||||
if m, err := strconv.Atoi(t); err == nil {
|
||||
h.MaxLength = &m
|
||||
}
|
||||
}
|
||||
|
||||
convert := converter(field)
|
||||
if t, ok := tags["enum"]; ok {
|
||||
enums := strings.Split(t, "|")
|
||||
var es []interface{}
|
||||
for _, s := range enums {
|
||||
v, err := convert(s)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
es = append(es, v)
|
||||
}
|
||||
h.Enum = es
|
||||
}
|
||||
if t, ok := tags["default"]; ok {
|
||||
v, err := convert(t)
|
||||
if err == nil {
|
||||
h.Default = v
|
||||
}
|
||||
}
|
||||
|
||||
// Move part of tags in Header to Items
|
||||
if h.Type == "array" {
|
||||
items := h.Items.latest()
|
||||
items.CollectionFormat = collect
|
||||
items.Minimum = h.Minimum
|
||||
items.Maximum = h.Maximum
|
||||
items.MinLength = h.MinLength
|
||||
items.MaxLength = h.MaxLength
|
||||
items.Enum = h.Enum
|
||||
items.Default = h.Default
|
||||
h.Minimum = nil
|
||||
h.Maximum = nil
|
||||
h.MinLength = nil
|
||||
h.MaxLength = nil
|
||||
h.Enum = nil
|
||||
h.Default = nil
|
||||
} else {
|
||||
h.CollectionFormat = collect
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Items) latest() *Items {
|
||||
if t.Items != nil {
|
||||
return t.Items.latest()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (s *JSONSchema) latest() *JSONSchema {
|
||||
if s.Items != nil {
|
||||
return s.Items.latest()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Not support nested elements tag eg:"a>b>c"
|
||||
// Not support tags: ",chardata", ",cdata", ",comment"
|
||||
// Not support embedded structure with tag ",innerxml"
|
||||
// Only support nested elements tag in array type eg:"Name []string `xml:"names>name"`"
|
||||
func (s *JSONSchema) handleXMLTags(f reflect.StructField) {
|
||||
b, a := getTag(f, "xml", 1)
|
||||
if b && contains([]string{"chardata", "cdata", "comment"}, a) {
|
||||
return
|
||||
}
|
||||
|
||||
if b, t := getTag(f, "xml", 0); b {
|
||||
if t == "-" || s.Ref != "" {
|
||||
return
|
||||
} else if t == "" {
|
||||
t = f.Name
|
||||
}
|
||||
|
||||
if s.XML == nil {
|
||||
s.XML = &XMLSchema{}
|
||||
}
|
||||
if a == "attr" {
|
||||
s.XML.Attribute = t
|
||||
} else {
|
||||
s.XML.Name = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *JSONSchema) handleChildXMLTags(rest string, r *RawDefineDic) {
|
||||
if rest == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if s.Items == nil && s.Ref == "" {
|
||||
if s.XML == nil {
|
||||
s.XML = &XMLSchema{}
|
||||
}
|
||||
s.XML.Name = rest
|
||||
} else if s.Ref != "" {
|
||||
key := s.Ref[len(DefPrefix):]
|
||||
if sc, ok := (*r)[key]; ok && sc.Schema != nil {
|
||||
if sc.Schema.XML == nil {
|
||||
sc.Schema.XML = &XMLSchema{}
|
||||
}
|
||||
sc.Schema.XML.Name = rest
|
||||
}
|
||||
} else {
|
||||
if s.XML == nil {
|
||||
s.XML = &XMLSchema{}
|
||||
}
|
||||
s.XML.Wrapped = true
|
||||
i := strings.Index(rest, ">")
|
||||
if i <= 0 {
|
||||
s.XML.Name = rest
|
||||
} else {
|
||||
s.XML.Name = rest[:i]
|
||||
rest = rest[i+1:]
|
||||
s.Items.handleChildXMLTags(rest, r)
|
||||
}
|
||||
}
|
||||
}
|
76
utils.go
Normal file
76
utils.go
Normal file
@ -0,0 +1,76 @@
|
||||
package echoswagger
|
||||
|
||||
import "reflect"
|
||||
|
||||
func contains(list []string, s string) bool {
|
||||
for _, t := range list {
|
||||
if t == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsMap(list []map[string][]string, m map[string][]string) bool {
|
||||
LoopMaps:
|
||||
for _, t := range list {
|
||||
if len(t) != len(m) {
|
||||
continue
|
||||
}
|
||||
for k, v := range t {
|
||||
if mv, ok := m[k]; !ok || !equals(mv, v) {
|
||||
continue LoopMaps
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func equals(a []string, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for _, t := range a {
|
||||
if !contains(b, t) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
t := v.Type()
|
||||
v = reflect.Indirect(v)
|
||||
if !v.IsValid() {
|
||||
v = reflect.New(t)
|
||||
}
|
||||
if v.Kind() == reflect.Ptr {
|
||||
return indirect(v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func indirectValue(p interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(p)
|
||||
LoopValue:
|
||||
v = reflect.Indirect(v)
|
||||
if !v.IsValid() {
|
||||
v = reflect.New(reflect.TypeOf(p))
|
||||
}
|
||||
if v.Kind() == reflect.Ptr {
|
||||
goto LoopValue
|
||||
}
|
||||
// TODO 遍历所有子项,为Invalid初始化Value
|
||||
return v
|
||||
}
|
||||
|
||||
func indirectType(p interface{}) reflect.Type {
|
||||
t := reflect.TypeOf(p)
|
||||
LoopType:
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
goto LoopType
|
||||
}
|
||||
return t
|
||||
}
|
114
validator.go
Normal file
114
validator.go
Normal file
@ -0,0 +1,114 @@
|
||||
package echoswagger
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
func isValidEmail(str string) bool {
|
||||
return emailRegexp.MatchString(str)
|
||||
}
|
||||
|
||||
// See: https://github.com/swagger-api/swagger-js/blob/7414ad062ba9b6d9cc397c72e7561ec775b35a9f/lib/shred/parseUri.js#L28
|
||||
func isValidURL(str string) bool {
|
||||
if _, err := url.ParseRequestURI(str); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isValidParam(t reflect.Type, nest, inner bool) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.String:
|
||||
if !nest || (nest && inner) {
|
||||
return true
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
return isValidParam(t.Elem(), nest, true)
|
||||
case reflect.Ptr:
|
||||
return isValidParam(t.Elem(), nest, inner)
|
||||
case reflect.Struct:
|
||||
if t == reflect.TypeOf(time.Time{}) {
|
||||
return true
|
||||
} else if !inner {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if !isValidParam(t.Field(i).Type, nest, true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isValidSchema reports a type is valid for body param.
|
||||
// valid case:
|
||||
// 1. Struct
|
||||
// 2. Struct{ A int64 }
|
||||
// 3. *[]Struct
|
||||
// 4. [][]Struct
|
||||
// 5. []Struct{ A []Struct }
|
||||
// 6. []Struct{ A Map[string]string }
|
||||
// 7. *Struct{ A []Map[int64]Struct }
|
||||
// 8. Map[string]string
|
||||
// 9. []int64
|
||||
// invalid case:
|
||||
// 1. interface{}
|
||||
// 2. Map[Struct]string
|
||||
func isValidSchema(t reflect.Type, inner bool) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.String:
|
||||
return true
|
||||
case reflect.Array, reflect.Slice:
|
||||
return isValidSchema(t.Elem(), inner)
|
||||
case reflect.Map:
|
||||
return isBasicType(t.Key()) && isValidSchema(t.Elem(), true)
|
||||
case reflect.Ptr:
|
||||
return isValidSchema(t.Elem(), inner)
|
||||
case reflect.Struct:
|
||||
if t == reflect.TypeOf(time.Time{}) {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if !isValidSchema(t.Field(i).Type, true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isBasicType(t reflect.Type) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.String:
|
||||
return true
|
||||
case reflect.Ptr:
|
||||
return isBasicType(t.Elem())
|
||||
case reflect.Struct:
|
||||
if t == reflect.TypeOf(time.Time{}) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
453
wrapper.go
453
wrapper.go
@ -2,24 +2,172 @@ package echoswagger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type RawDefine struct {
|
||||
Value reflect.Value
|
||||
Schema *JSONSchema
|
||||
/*
|
||||
TODO:
|
||||
1.pattern
|
||||
2.opreationId 重复判断
|
||||
|
||||
Notice:
|
||||
1.不会对Email和URL进行验证,因为不影响页面的正常显示
|
||||
2.只支持对应于SwaggerUI页面的Schema,不支持sw、sww等协议
|
||||
3.SetSecurity/SetSecurityWithScope 传多个参数表示Security之间是AND关系;多次调用SetSecurity/SetSecurityWithScope Security之间是OR关系
|
||||
4.只支持基本类型的Map Key
|
||||
*/
|
||||
|
||||
type ApiRoot interface {
|
||||
// ApiGroup creates ApiGroup. Use this instead of Echo#ApiGroup.
|
||||
Group(name, prefix string, m ...echo.MiddlewareFunc) ApiGroup
|
||||
|
||||
// SetRequestContentType sets request content types.
|
||||
SetRequestContentType(types ...string) ApiRoot
|
||||
|
||||
// SetResponseContentType sets response content types.
|
||||
SetResponseContentType(types ...string) ApiRoot
|
||||
|
||||
// SetExternalDocs sets external docs.
|
||||
SetExternalDocs(desc, url string) ApiRoot
|
||||
|
||||
// AddSecurityBasic adds `SecurityDefinition` with type basic.
|
||||
AddSecurityBasic(name, desc string) ApiRoot
|
||||
|
||||
// AddSecurityAPIKey adds `SecurityDefinition` with type apikey.
|
||||
AddSecurityAPIKey(name, desc string, in SecurityInType) ApiRoot
|
||||
|
||||
// AddSecurityOAuth2 adds `SecurityDefinition` with type oauth2.
|
||||
AddSecurityOAuth2(name, desc string, flow OAuth2FlowType, authorizationUrl, tokenUrl string, scopes map[string]string) ApiRoot
|
||||
|
||||
// GetRaw returns raw `Swagger`. Only special case should use.
|
||||
GetRaw() *Swagger
|
||||
|
||||
// SetRaw sets raw `Swagger` to ApiRoot. Only special case should use.
|
||||
SetRaw(s *Swagger) ApiRoot
|
||||
|
||||
// Echo returns the embeded echo instance
|
||||
Echo() *echo.Echo
|
||||
}
|
||||
|
||||
type ApiGroup interface {
|
||||
// GET overrides `Echo#GET()` for sub-routes within the ApiGroup.
|
||||
GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
||||
|
||||
// POST overrides `Echo#POST()` for sub-routes within the ApiGroup.
|
||||
POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
||||
|
||||
// PUT overrides `Echo#PUT()` for sub-routes within the ApiGroup.
|
||||
PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
||||
|
||||
// DELETE overrides `Echo#DELETE()` for sub-routes within the ApiGroup.
|
||||
DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api
|
||||
|
||||
// SetDescription sets description for ApiGroup.
|
||||
SetDescription(desc string) ApiGroup
|
||||
|
||||
// SetExternalDocs sets external docs for ApiGroup.
|
||||
SetExternalDocs(desc, url string) ApiGroup
|
||||
|
||||
// SetSecurity sets Security for all operations within the ApiGroup
|
||||
// which names are reigisters by AddSecurity... functions.
|
||||
SetSecurity(names ...string) ApiGroup
|
||||
|
||||
// SetSecurity sets Security with scopes for all operations
|
||||
// within the ApiGroup which names are reigisters
|
||||
// by AddSecurity... functions.
|
||||
// Should only use when Security type is oauth2.
|
||||
SetSecurityWithScope(s map[string][]string) ApiGroup
|
||||
|
||||
// EchoGroup returns `*echo.Group` within the ApiGroup.
|
||||
EchoGroup() *echo.Group
|
||||
}
|
||||
|
||||
type Api interface {
|
||||
// AddParamPath adds path parameter.
|
||||
AddParamPath(p interface{}, name, desc string) Api
|
||||
|
||||
// AddParamPathNested adds path parameters nested in p.
|
||||
AddParamPathNested(p interface{}) Api
|
||||
|
||||
// AddParamQuery adds query parameter.
|
||||
AddParamQuery(p interface{}, name, desc string, required bool) Api
|
||||
|
||||
// AddParamQueryNested adds query parameters nested in p.
|
||||
AddParamQueryNested(p interface{}) Api
|
||||
|
||||
// AddParamForm adds formData parameter.
|
||||
AddParamForm(p interface{}, name, desc string, required bool) Api
|
||||
|
||||
// AddParamFormNested adds formData parameters nested in p.
|
||||
AddParamFormNested(p interface{}) Api
|
||||
|
||||
// AddParamHeader adds header parameter.
|
||||
AddParamHeader(p interface{}, name, desc string, required bool) Api
|
||||
|
||||
// AddParamHeaderNested adds header parameters nested in p.
|
||||
AddParamHeaderNested(p interface{}) Api
|
||||
|
||||
// AddParamBody adds body parameter.
|
||||
AddParamBody(p interface{}, name, desc string, required bool) Api
|
||||
|
||||
// AddParamBody adds file parameter.
|
||||
AddParamFile(name, desc string, required bool) Api
|
||||
|
||||
// AddResponse adds response for Api.
|
||||
AddResponse(code int, desc string, schema interface{}, header interface{}) Api
|
||||
|
||||
// SetResponseContentType sets request content types.
|
||||
SetRequestContentType(types ...string) Api
|
||||
|
||||
// SetResponseContentType sets response content types.
|
||||
SetResponseContentType(types ...string) Api
|
||||
|
||||
// SetOperationId sets operationId
|
||||
SetOperationId(id string) Api
|
||||
|
||||
// SetDescription marks Api as deprecated.
|
||||
SetDeprecated() Api
|
||||
|
||||
// SetDescription sets description.
|
||||
SetDescription(desc string) Api
|
||||
|
||||
// SetExternalDocs sets external docs.
|
||||
SetExternalDocs(desc, url string) Api
|
||||
|
||||
// SetExternalDocs sets summary.
|
||||
SetSummary(summary string) Api
|
||||
|
||||
// SetSecurity sets Security which names are reigisters
|
||||
// by AddSecurity... functions.
|
||||
SetSecurity(names ...string) Api
|
||||
|
||||
// SetSecurity sets Security for Api which names are
|
||||
// reigisters by AddSecurity... functions.
|
||||
// Should only use when Security type is oauth2.
|
||||
SetSecurityWithScope(s map[string][]string) Api
|
||||
|
||||
// TODO return echo.Router
|
||||
}
|
||||
|
||||
type routers struct {
|
||||
apis []api
|
||||
defs *RawDefineDic
|
||||
}
|
||||
|
||||
type Root struct {
|
||||
apis []api
|
||||
routers
|
||||
spec *Swagger
|
||||
echo *echo.Echo
|
||||
groups []group
|
||||
once sync.Once
|
||||
err error
|
||||
}
|
||||
|
||||
type group struct {
|
||||
apis []api
|
||||
routers
|
||||
echoGroup *echo.Group
|
||||
security []map[string][]string
|
||||
tag Tag
|
||||
@ -27,12 +175,15 @@ type group struct {
|
||||
|
||||
type api struct {
|
||||
route *echo.Route
|
||||
defs *RawDefineDic
|
||||
security []map[string][]string
|
||||
method string
|
||||
operation Operation
|
||||
}
|
||||
|
||||
func New(e *echo.Echo, basePath, docPath string, i *Info) *Root {
|
||||
// New creates ApiRoot instance.
|
||||
// Multiple ApiRoot are allowed in one project.
|
||||
func New(e *echo.Echo, basePath, docPath string, i *Info) ApiRoot {
|
||||
if e == nil {
|
||||
panic("echoswagger: invalid Echo instance")
|
||||
}
|
||||
@ -45,6 +196,12 @@ func New(e *echo.Echo, basePath, docPath string, i *Info) *Root {
|
||||
}
|
||||
specPath := docPath + connector + "swagger.json"
|
||||
|
||||
if i == nil {
|
||||
i = &Info{
|
||||
Title: "Project APIs",
|
||||
}
|
||||
}
|
||||
defs := make(RawDefineDic)
|
||||
r := &Root{
|
||||
echo: e,
|
||||
spec: &Swagger{
|
||||
@ -53,9 +210,293 @@ func New(e *echo.Echo, basePath, docPath string, i *Info) *Root {
|
||||
BasePath: basePath,
|
||||
Definitions: make(map[string]*JSONSchema),
|
||||
},
|
||||
routers: routers{
|
||||
defs: &defs,
|
||||
},
|
||||
}
|
||||
|
||||
e.GET(docPath, r.docHandler(specPath))
|
||||
e.GET(specPath, r.Spec)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) Group(name, prefix string, m ...echo.MiddlewareFunc) ApiGroup {
|
||||
if name == "" {
|
||||
panic("echowagger: invalid name of ApiGroup")
|
||||
}
|
||||
echoGroup := r.echo.Group(prefix, m...)
|
||||
group := group{
|
||||
echoGroup: echoGroup,
|
||||
routers: routers{
|
||||
defs: r.defs,
|
||||
},
|
||||
}
|
||||
var counter int
|
||||
LoopTags:
|
||||
for _, t := range r.groups {
|
||||
if t.tag.Name == name {
|
||||
if counter > 0 {
|
||||
name = name[:len(name)-2]
|
||||
}
|
||||
counter++
|
||||
name += "_" + strconv.Itoa(counter)
|
||||
goto LoopTags
|
||||
}
|
||||
}
|
||||
group.tag = Tag{Name: name}
|
||||
r.groups = append(r.groups, group)
|
||||
return &r.groups[len(r.groups)-1]
|
||||
}
|
||||
|
||||
func (r *Root) SetRequestContentType(types ...string) ApiRoot {
|
||||
r.spec.Consumes = types
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) SetResponseContentType(types ...string) ApiRoot {
|
||||
r.spec.Produces = types
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) SetExternalDocs(desc, url string) ApiRoot {
|
||||
r.spec.ExternalDocs = &ExternalDocs{
|
||||
Description: desc,
|
||||
URL: url,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) AddSecurityBasic(name, desc string) ApiRoot {
|
||||
if !r.checkSecurity(name) {
|
||||
return r
|
||||
}
|
||||
sd := &SecurityDefinition{
|
||||
Type: string(SecurityBasic),
|
||||
Description: desc,
|
||||
}
|
||||
r.spec.SecurityDefinitions[name] = sd
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) AddSecurityAPIKey(name, desc string, in SecurityInType) ApiRoot {
|
||||
if !r.checkSecurity(name) {
|
||||
return r
|
||||
}
|
||||
sd := &SecurityDefinition{
|
||||
Type: string(SecurityAPIKey),
|
||||
Description: desc,
|
||||
Name: name,
|
||||
In: string(in),
|
||||
}
|
||||
r.spec.SecurityDefinitions[name] = sd
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) AddSecurityOAuth2(name, desc string, flow OAuth2FlowType, authorizationUrl, tokenUrl string, scopes map[string]string) ApiRoot {
|
||||
if !r.checkSecurity(name) {
|
||||
return r
|
||||
}
|
||||
sd := &SecurityDefinition{
|
||||
Type: string(SecurityOAuth2),
|
||||
Description: desc,
|
||||
Flow: string(flow),
|
||||
AuthorizationURL: authorizationUrl,
|
||||
TokenURL: tokenUrl,
|
||||
Scopes: scopes,
|
||||
}
|
||||
r.spec.SecurityDefinitions[name] = sd
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) GetRaw() *Swagger {
|
||||
return r.spec
|
||||
}
|
||||
|
||||
func (r *Root) SetRaw(s *Swagger) ApiRoot {
|
||||
r.spec = s
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Root) Echo() *echo.Echo {
|
||||
return r.echo
|
||||
}
|
||||
|
||||
func (g *group) GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
||||
a := g.appendRoute(echo.GET, g.echoGroup.GET(path, h, m...))
|
||||
a.operation.Tags = []string{g.tag.Name}
|
||||
return a
|
||||
}
|
||||
|
||||
func (g *group) POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
||||
a := g.appendRoute(echo.POST, g.echoGroup.POST(path, h, m...))
|
||||
a.operation.Tags = []string{g.tag.Name}
|
||||
return a
|
||||
}
|
||||
|
||||
func (g *group) PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
||||
a := g.appendRoute(echo.PUT, g.echoGroup.PUT(path, h, m...))
|
||||
a.operation.Tags = []string{g.tag.Name}
|
||||
return a
|
||||
}
|
||||
|
||||
func (g *group) DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) Api {
|
||||
a := g.appendRoute(echo.DELETE, g.echoGroup.DELETE(path, h, m...))
|
||||
a.operation.Tags = []string{g.tag.Name}
|
||||
return a
|
||||
}
|
||||
|
||||
func (g *group) SetDescription(desc string) ApiGroup {
|
||||
g.tag.Description = desc
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *group) SetExternalDocs(desc, url string) ApiGroup {
|
||||
g.tag.ExternalDocs = &ExternalDocs{
|
||||
Description: desc,
|
||||
URL: url,
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *group) SetSecurity(names ...string) ApiGroup {
|
||||
if len(names) == 0 {
|
||||
return g
|
||||
}
|
||||
g.security = setSecurity(g.security, names...)
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *group) SetSecurityWithScope(s map[string][]string) ApiGroup {
|
||||
g.security = setSecurityWithScope(g.security, s)
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *group) EchoGroup() *echo.Group {
|
||||
return g.echoGroup
|
||||
}
|
||||
|
||||
func (a *api) AddParamPath(p interface{}, name, desc string) Api {
|
||||
return a.addParams(p, ParamInPath, name, desc, true, false)
|
||||
}
|
||||
|
||||
func (a *api) AddParamPathNested(p interface{}) Api {
|
||||
return a.addParams(p, ParamInPath, "", "", true, true)
|
||||
}
|
||||
|
||||
func (a *api) AddParamQuery(p interface{}, name, desc string, required bool) Api {
|
||||
return a.addParams(p, ParamInQuery, name, desc, required, false)
|
||||
}
|
||||
|
||||
func (a *api) AddParamQueryNested(p interface{}) Api {
|
||||
return a.addParams(p, ParamInQuery, "", "", false, true)
|
||||
}
|
||||
|
||||
func (a *api) AddParamForm(p interface{}, name, desc string, required bool) Api {
|
||||
return a.addParams(p, ParamInFormData, name, desc, required, false)
|
||||
}
|
||||
|
||||
func (a *api) AddParamFormNested(p interface{}) Api {
|
||||
return a.addParams(p, ParamInFormData, "", "", false, true)
|
||||
}
|
||||
|
||||
func (a *api) AddParamHeader(p interface{}, name, desc string, required bool) Api {
|
||||
return a.addParams(p, ParamInHeader, name, desc, required, false)
|
||||
}
|
||||
|
||||
func (a *api) AddParamHeaderNested(p interface{}) Api {
|
||||
return a.addParams(p, ParamInHeader, "", "", false, true)
|
||||
}
|
||||
|
||||
func (a *api) AddParamBody(p interface{}, name, desc string, required bool) Api {
|
||||
return a.addBodyParams(p, name, desc, required)
|
||||
}
|
||||
|
||||
func (a *api) AddParamFile(name, desc string, required bool) Api {
|
||||
name = a.operation.rename(name)
|
||||
a.operation.Parameters = append(a.operation.Parameters, &Parameter{
|
||||
Name: name,
|
||||
In: string(ParamInFormData),
|
||||
Description: desc,
|
||||
Required: required,
|
||||
Type: "file",
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// Notice: header must be nested in a struct.
|
||||
func (a *api) AddResponse(code int, desc string, schema interface{}, header interface{}) Api {
|
||||
r := &Response{
|
||||
Description: desc,
|
||||
}
|
||||
|
||||
st := reflect.TypeOf(schema)
|
||||
if st != nil {
|
||||
if !isValidSchema(st, false) {
|
||||
panic("echoswagger: invalid response schema")
|
||||
}
|
||||
r.Schema = a.defs.genSchema(reflect.ValueOf(schema))
|
||||
}
|
||||
|
||||
ht := reflect.TypeOf(header)
|
||||
if ht != nil {
|
||||
if !isValidParam(reflect.TypeOf(header), true, false) {
|
||||
panic("echoswagger: invalid response header")
|
||||
}
|
||||
r.Headers = a.genHeader(reflect.ValueOf(header))
|
||||
}
|
||||
|
||||
cstr := strconv.Itoa(code)
|
||||
a.operation.Responses[cstr] = r
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetRequestContentType(types ...string) Api {
|
||||
a.operation.Consumes = types
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetResponseContentType(types ...string) Api {
|
||||
a.operation.Produces = types
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetOperationId(id string) Api {
|
||||
a.operation.OperationID = id
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetDeprecated() Api {
|
||||
a.operation.Deprecated = true
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetDescription(desc string) Api {
|
||||
a.operation.Description = desc
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetExternalDocs(desc, url string) Api {
|
||||
a.operation.ExternalDocs = &ExternalDocs{
|
||||
Description: desc,
|
||||
URL: url,
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetSummary(summary string) Api {
|
||||
a.operation.Summary = summary
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetSecurity(names ...string) Api {
|
||||
if len(names) == 0 {
|
||||
return a
|
||||
}
|
||||
a.security = setSecurity(a.security, names...)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *api) SetSecurityWithScope(s map[string][]string) Api {
|
||||
a.security = setSecurityWithScope(a.security, s)
|
||||
return a
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user