Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

35
app/tool/gdoc/BUILD Normal file
View File

@@ -0,0 +1,35 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_binary",
)
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"gdoc.go",
"swagger.go",
],
importpath = "go-common/app/tool/gdoc",
tags = ["automanaged"],
)
go_binary(
name = "gdoc",
embed = [":go_default_library"],
)

View File

@@ -0,0 +1,8 @@
## parse
### Version 1.0.0
> 1. 初始化自动文档生成
### Version 1.1.0
> 1.支持返回值为数组结构体
> 2.支持返回值为map结构体

View File

@@ -0,0 +1,9 @@
# Owner
lintanghui
# Author
lintanghui
# Reviewer
maojian
haoguanwei

12
app/tool/gdoc/OWNERS Normal file
View File

@@ -0,0 +1,12 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- lintanghui
labels:
- tool
options:
no_parent_owners: true
reviewers:
- haoguanwei
- lintanghui
- maojian

10
app/tool/gdoc/README.MD Normal file
View File

@@ -0,0 +1,10 @@
## parse
### 项目简介
> 1.自动化doc
### 编译环境
> 请只用golang v1.7.x以上版本编译执行。
### 特别说明
> 1.具体使用详看 http://info.bilibili.co/pages/viewpage.action?pageId=4547707

547
app/tool/gdoc/gdoc.go Normal file
View File

@@ -0,0 +1,547 @@
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path"
"path/filepath"
"reflect"
"runtime"
"strings"
)
// gloabl var.
var (
ErrParams = errors.New("err params")
_gopath = filepath.SplitList(os.Getenv("GOPATH"))
)
var (
dir string
pkgs = make(map[string]*ast.Package)
rlpkgs = make(map[string]*ast.Package)
definitions = make(map[string]*Schema)
swagger = Swagger{
Definitions: make(map[string]*Schema),
Paths: make(map[string]*Item),
SwaggerVersion: "2.0",
Infos: Information{
Title: "go-common api",
Description: "api",
Version: "1.0",
Contact: Contact{
EMail: "lintanghui@bilibili.com",
},
License: &License{
Name: "Apache 2.0",
URL: "http://www.apache.org/licenses/LICENSE-2.0.html",
},
},
}
stdlibObject = map[string]string{
"&{time Time}": "time.Time",
}
)
// refer to builtin.go
var basicTypes = map[string]string{
"bool": "boolean:",
"uint": "integer:int32",
"uint8": "integer:int32",
"uint16": "integer:int32",
"uint32": "integer:int32",
"uint64": "integer:int64",
"int": "integer:int64",
"int8": "integer:int32",
"int16": "integer:int32",
"int32": "integer:int32",
"int64": "integer:int64",
"uintptr": "integer:int64",
"float32": "number:float",
"float64": "number:double",
"string": "string:",
"complex64": "number:float",
"complex128": "number:double",
"byte": "string:byte",
"rune": "string:byte",
// builtin golang objects
"time.Time": "string:string",
}
func main() {
flag.StringVar(&dir, "d", "./", "specific project dir")
flag.Parse()
err := ParseFromDir(dir)
if err != nil {
panic(err)
}
parseModel(pkgs)
parseModel(rlpkgs)
parseRouter()
fd, err := os.Create(path.Join(dir, "swagger.json"))
if err != nil {
panic(err)
}
b, _ := json.MarshalIndent(swagger, "", " ")
fd.Write(b)
}
// ParseFromDir parse ast pkg from dir.
func ParseFromDir(dir string) (err error) {
filepath.Walk(dir, func(fpath string, fileInfo os.FileInfo, err error) error {
if err != nil {
return nil
}
if !fileInfo.IsDir() {
return nil
}
err = parseFromDir(fpath)
return err
})
return
}
func parseFromDir(dir string) (err error) {
fset := token.NewFileSet()
pkgFolder, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
name := info.Name()
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}, parser.ParseComments)
if err != nil {
return
}
for k, p := range pkgFolder {
pkgs[k] = p
}
return
}
func parseImport(dir string) (err error) {
fset := token.NewFileSet()
pkgFolder, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
name := info.Name()
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}, parser.ParseComments)
if err != nil {
return
}
for k, p := range pkgFolder {
rlpkgs[k] = p
}
return
}
func parseModel(pkgs map[string]*ast.Package) {
for _, p := range pkgs {
for _, f := range p.Files {
for _, im := range f.Imports {
if !isSystemPackage(im.Path.Value) {
for _, gp := range _gopath {
path := gp + "/src/" + strings.Trim(im.Path.Value, "\"")
if isExist(path) {
parseImport(path)
}
}
}
}
scom := parseStructComment(f)
for _, obj := range f.Scope.Objects {
if obj.Kind == ast.Typ {
objName := obj.Name
schema := &Schema{
Title: objName,
Type: "object",
}
ts, ok := obj.Decl.(*ast.TypeSpec)
if !ok {
fmt.Printf("obj type error %v ", obj.Kind)
}
st, ok := ts.Type.(*ast.StructType)
if !ok {
continue
}
properites := make(map[string]*Propertie)
for _, fd := range st.Fields.List {
if len(fd.Names) == 0 {
continue
}
name, required, omit, desc := parseFieldTag(fd)
if omit {
continue
}
isSlice, realType, sType := typeAnalyser(fd)
if (isSlice && isBasicType(realType)) || sType == "object" {
if len(strings.Split(realType, " ")) > 1 {
realType = strings.Replace(realType, " ", ".", -1)
realType = strings.Replace(realType, "&", "", -1)
realType = strings.Replace(realType, "{", "", -1)
realType = strings.Replace(realType, "}", "", -1)
}
}
mp := &Propertie{}
if isSlice {
mp.Type = "array"
if isBasicType(strings.Replace(realType, "[]", "", -1)) {
typeFormat := strings.Split(sType, ":")
mp.Items = &Propertie{
Type: typeFormat[0],
Format: typeFormat[1],
}
} else {
ss := strings.Split(realType, ".")
mp.RefImport = ss[len(ss)-1]
mp.Type = "array"
mp.Items = &Propertie{
Ref: "#/definitions/" + mp.RefImport,
Type: sType,
}
}
} else {
if sType == "object" {
ss := strings.Split(realType, ".")
mp.RefImport = ss[len(ss)-1]
mp.Type = sType
mp.Ref = "#/definitions/" + mp.RefImport
} else if isBasicType(realType) {
typeFormat := strings.Split(sType, ":")
mp.Type = typeFormat[0]
mp.Format = typeFormat[1]
} else if realType == "map" {
typeFormat := strings.Split(sType, ":")
mp.AdditionalProperties = &Propertie{
Type: typeFormat[0],
Format: typeFormat[1],
}
}
}
if name == "" {
name = fd.Names[0].Name
}
if required {
schema.Required = append(schema.Required, name)
}
mp.Description = desc
if scm, ok := scom[obj.Name]; ok {
if cm, ok := scm.field[fd.Names[0].Name]; ok {
mp.Description = cm + desc
}
}
properites[name] = mp
}
if scm, ok := scom[obj.Name]; ok {
schema.Description = scm.comment
}
schema.Properties = properites
definitions[schema.Title] = schema
}
}
}
}
}
func parseFieldTag(field *ast.Field) (name string, required, omit bool, tagDes string) {
if field.Tag == nil {
return
}
tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
param := tag.Get("form")
if param != "" {
params := strings.Split(param, ",")
if len(params) > 0 {
name = params[0]
}
if len(params) == 2 && params[1] == "split" {
tagDes = "数组,按逗号分隔"
}
}
if def := tag.Get("default"); def != "" {
tagDes = fmt.Sprintf("%s 默认值 %s", tagDes, def)
}
validate := tag.Get("validate")
if validate != "" {
params := strings.Split(validate, ",")
for _, param := range params {
switch {
case param == "required":
required = true
case strings.HasPrefix(param, "min"):
tagDes = fmt.Sprintf("%s 最小值 %s", tagDes, strings.Split(param, "=")[1])
case strings.HasPrefix(param, "max"):
tagDes = fmt.Sprintf("%s 最大值 %s", tagDes, strings.Split(param, "=")[1])
}
}
}
// parse json response.
json := tag.Get("json")
if json != "" {
jsons := strings.Split(json, ",")
if len(jsons) > 0 {
if jsons[0] == "-" {
omit = true
return
}
}
}
return
}
func parseRouter() {
for _, p := range pkgs {
if p.Name != "http" {
continue
}
fmt.Printf("开始解析生成swagger文档\n")
for _, f := range p.Files {
for _, decl := range f.Decls {
if fdecl, ok := decl.(*ast.FuncDecl); ok {
if fdecl.Doc != nil {
path, req, resp, item, err := parseFuncDoc(fdecl.Doc)
if err != nil {
fmt.Printf("解析失败 注解错误 %v\n", err)
continue
}
if path != "" && err == nil {
fmt.Printf("解析 %s 完成 请求参数为 %s 返回结构为 %s\n", path, req, resp)
swagger.Paths[path] = item
}
}
}
}
}
}
}
func parseFuncDoc(f *ast.CommentGroup) (path, reqObj, respObj string, item *Item, err error) {
item = new(Item)
op := new(Operation)
params := make([]*Parameter, 0)
response := make(map[string]*Response)
for _, d := range f.List {
t := strings.TrimSpace(strings.TrimPrefix(d.Text, "//"))
content := strings.Split(t, " ")
switch content[0] {
case "@params":
if len(content) < 2 {
err = fmt.Errorf("err params %s", content)
return
}
reqObj = content[1]
if model, ok := definitions[content[1]]; ok {
for n, p := range model.Properties {
param := &Parameter{
In: "query",
Name: n,
Description: p.Description,
Type: p.Type,
Format: p.Format,
}
for _, p := range model.Required {
if p == n {
param.Required = true
}
}
params = append(params, param)
}
} else {
err = ErrParams
return
}
case "@router":
if len(content) != 3 {
err = ErrParams
return
}
switch content[1] {
case "get":
item.Get = op
case "post":
item.Post = op
}
path = content[2]
op.OperationID = path
case "@response":
if len(content) < 2 {
err = fmt.Errorf("err response %s", content)
return
}
var (
isarray bool
ismap bool
)
if strings.HasPrefix(content[1], "[]") {
isarray = true
respObj = content[1][2:]
} else if strings.HasPrefix(content[1], "map[]") {
ismap = true
respObj = content[1][5:]
} else {
respObj = content[1]
}
defini, ok := definitions[respObj]
if !ok {
err = ErrParams
return
}
var resp *Propertie
if isarray {
resp = &Propertie{
Type: "array",
Items: &Propertie{
Type: "object",
Ref: "#/definitions/" + respObj,
},
}
} else if ismap {
resp = &Propertie{
Type: "object",
AdditionalProperties: &Propertie{
Ref: "#/definitions/" + respObj,
},
}
} else {
resp = &Propertie{
Type: "object",
Ref: "#/definitions/" + respObj,
}
}
response["200"] = &Response{
Schema: &Schema{
Type: "object",
Properties: map[string]*Propertie{
"code": &Propertie{
Type: "integer",
Description: "错误码描述",
},
"data": resp,
"message": &Propertie{
Type: "string",
Description: "错误码文本描述",
},
"ttl": &Propertie{
Type: "integer",
Format: "int64",
Description: "客户端限速时间",
},
},
},
Description: "服务成功响应内容",
}
op.Responses = response
for _, rl := range defini.Properties {
if rl.RefImport != "" {
swagger.Definitions[rl.RefImport] = definitions[rl.RefImport]
}
}
swagger.Definitions[respObj] = defini
case "@description":
op.Description = content[1]
}
}
op.Parameters = params
return
}
type structComment struct {
comment string
field map[string]string
}
func parseStructComment(f *ast.File) (scom map[string]structComment) {
scom = make(map[string]structComment)
for _, d := range f.Decls {
switch specDecl := d.(type) {
case *ast.GenDecl:
if specDecl.Tok == token.TYPE {
for _, s := range specDecl.Specs {
switch tp := s.(*ast.TypeSpec).Type.(type) {
case *ast.StructType:
fcom := make(map[string]string)
for _, fd := range tp.Fields.List {
if len(fd.Names) == 0 {
continue
}
if len(fd.Comment.Text()) > 0 {
fcom[fd.Names[0].Name] = strings.TrimSuffix(fd.Comment.Text(), "\n")
}
}
sspec := s.(*ast.TypeSpec)
scom[sspec.Name.String()] = structComment{comment: strings.TrimSuffix(specDecl.Doc.Text(), "\n"), field: fcom}
}
}
}
}
}
return
}
func isBasicType(Type string) bool {
if _, ok := basicTypes[Type]; ok {
return true
}
return false
}
func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
if arr, ok := f.Type.(*ast.ArrayType); ok {
if isBasicType(fmt.Sprint(arr.Elt)) {
return true, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
}
if mp, ok := arr.Elt.(*ast.MapType); ok {
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object"
}
if star, ok := arr.Elt.(*ast.StarExpr); ok {
return true, fmt.Sprint(star.X), "object"
}
basicType := fmt.Sprint(arr.Elt)
if object, isStdLibObject := stdlibObject[basicType]; isStdLibObject {
basicType = object
}
if k, ok := basicTypes[basicType]; ok {
return true, basicType, k
}
return true, fmt.Sprint(arr.Elt), "object"
}
switch t := f.Type.(type) {
case *ast.StarExpr:
basicType := fmt.Sprint(t.X)
if k, ok := basicTypes[basicType]; ok {
return false, basicType, k
}
return false, basicType, "object"
case *ast.MapType:
val := fmt.Sprintf("%v", t.Value)
if isBasicType(val) {
return false, "map", basicTypes[val]
}
return false, val, "object"
}
basicType := fmt.Sprint(f.Type)
if object, isStdLibObject := stdlibObject[basicType]; isStdLibObject {
basicType = object
}
if k, ok := basicTypes[basicType]; ok {
return false, basicType, k
}
return false, basicType, "object"
}
func isSystemPackage(pkgpath string) bool {
goroot := os.Getenv("GOROOT")
if goroot == "" {
goroot = runtime.GOROOT()
}
wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath))
if isExist(wg) {
return true
}
wg, _ = filepath.EvalSymlinks(filepath.Join(goroot, "src", pkgpath))
return isExist(wg)
}
func isExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}

173
app/tool/gdoc/swagger.go Normal file
View File

@@ -0,0 +1,173 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Swagger is a project used to describe and document RESTful APIs.
//
// The Swagger specification defines a set of files required to describe such an API. These files can then be used by the Swagger-UI project to display the API and Swagger-Codegen to generate clients in various languages. Additional utilities can also take advantage of the resulting files, such as testing tools.
// Now in version 2.0, Swagger is more enabling than ever. And it's 100% open source software.
// Package swagger struct definition
package main
// Swagger list the resource
type Swagger struct {
SwaggerVersion string `json:"swagger,omitempty" yaml:"swagger,omitempty"`
Infos Information `json:"info" yaml:"info"`
Host string `json:"host,omitempty" yaml:"host,omitempty"`
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Paths map[string]*Item `json:"paths" yaml:"paths"`
Definitions map[string]*Schema `json:"definitions,omitempty" yaml:"definitions,omitempty"`
SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
Security []map[string][]string `json:"security,omitempty" yaml:"security,omitempty"`
Tags []Tag `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
// Information Provides metadata about the API. The metadata can be used by the clients if needed.
type Information struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Version string `json:"version,omitempty" yaml:"version,omitempty"`
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
Contact Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
License *License `json:"license,omitempty" yaml:"license,omitempty"`
}
// Contact information for the exposed API.
type Contact struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
EMail string `json:"email,omitempty" yaml:"email,omitempty"`
}
// License information for the exposed API.
type License struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
// Item Describes the operations available on a single path.
type Item struct {
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
}
// Operation Describes a single API operation on a path.
type Operation struct {
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
Security []map[string][]string `json:"security,omitempty" yaml:"security,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
}
// Parameter Describes a single operation parameter.
type Parameter struct {
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Items *ParameterItems `json:"items,omitempty" yaml:"items,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
}
// ParameterItems A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body".
// http://swagger.io/specification/#itemsObject
type ParameterItems struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Items []*ParameterItems `json:"items,omitempty" yaml:"items,omitempty"` //Required if type is "array". Describes the type of items in the array.
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
}
// Schema Object allows the definition of input and output data types.
type Schema struct {
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`
Properties map[string]*Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
}
// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification
type Propertie struct {
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Example string `json:"example,omitempty" yaml:"example,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
Items *Propertie `json:"items,omitempty" yaml:"items,omitempty"`
AdditionalProperties *Propertie `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
RefImport string `json:"-"`
}
// Response as they are returned from executing this operation.
type Response struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
}
// Security Allows the definition of a security scheme that can be used by the operations
type Security struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2".
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"` // Valid values are "query" or "header".
Flow string `json:"flow,omitempty" yaml:"flow,omitempty"` // Valid values are "implicit", "password", "application" or "accessCode".
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"` // The available scopes for the OAuth2 security scheme.
}
// Tag Allows adding meta data to a single tag that is used by the Operation Object
type Tag struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
// ExternalDocs include Additional external documentation
type ExternalDocs struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}