diff --git a/common.go b/common.go new file mode 100644 index 0000000..469b561 --- /dev/null +++ b/common.go @@ -0,0 +1,167 @@ +// Package api_doc ... +// +// Description : api_doc ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-12-24 10:52 +package api_doc + +import ( + "fmt" + "git.zhangdeman.cn/gateway/api-doc/define" + "git.zhangdeman.cn/zhangdeman/consts" + "strings" +) + +// GetUriPathParamList 获取uri参数列表 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:52 2024/12/24 +func GetUriPathParamList(uriPath string) []*define.ParamConfig { + var ( + paramList []string + result = make([]*define.ParamConfig, 0) + ) + if paramList = define.UriParamRegexp.FindAllString(uriPath, -1); len(paramList) == 0 { + return result + } + for _, param := range paramList { + result = append(result, &define.ParamConfig{ + Location: consts.RequestDataLocationUriPath.String(), + Path: strings.TrimRight(strings.TrimLeft(param, "{"), "}"), + Type: consts.DataTypeString.String(), + Title: param, + Description: param, + Required: true, + }) + } + return result +} + +// GetDataType 将文档配置的数据类型转换为归一化处理后的数据类型 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:52 2024/12/24 +func GetDataType(docParamType string, formatType string) consts.DataType { + docParamType = strings.ToLower(docParamType) + formatType = strings.ToLower(formatType) + if len(formatType) == 0 { + formatType = docParamType + } + switch docParamType { + case "integer": + if formatType == "int64" { + return consts.DataTypeInt + } + return consts.DataTypeUint + case "string", "apikey": + return consts.DataTypeString + case "object": + return consts.DataTypeMapStrAny + case "boolean": + return consts.DataTypeBool + case "number", "float", "double", "float32", "float64": + return consts.DataTypeFloat + case "array": + if formatType == "integer" { + return consts.DataTypeSliceInt + } else if formatType == "string" { + return consts.DataTypeSliceString + } else { + return consts.DataTypeSliceAny + } + default: + return consts.DataTypeAny + } +} + +// GetParamLocation 获取参数位置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:02 2024/12/24 +func GetParamLocation(docParamLocation string) consts.RequestDataLocation { + docParamLocation = strings.ToLower(docParamLocation) + switch docParamLocation { + case "query": + return consts.RequestDataLocationQuery + case "header": + return consts.RequestDataLocationHeader + case "cookie": + return consts.RequestDataLocationCookie + case "body": + return consts.RequestDataLocationBody + case "path": + return consts.RequestDataLocationUriPath + default: + return consts.RequestDataLocationQuery + } +} + +// GetRealDefinitionsKey 通过schema下的 $ref 获取真实的 definitions key +func GetRealDefinitionsKey(ref string) string { + return strings.TrimPrefix(ref, "#/definitions/") +} + +// GetRealResponseKey 通过schema下的 $ref 获取真实的 response key +func GetRealResponseKey(ref string) string { + return strings.TrimPrefix(ref, "#/responses/") +} + +// GetSuccessResponseConfig 获取成功的响应配置 +func GetSuccessResponseConfig(resultConfig map[string]*define.SwaggerPathConfigResponse) *define.SwaggerPathConfigResponse { + for httpCode := 200; httpCode <= 299; httpCode++ { + if cfg := resultConfig[fmt.Sprintf("%d", httpCode)]; nil != cfg { + return cfg + } + } + return nil +} + +// DataTypeIsArray 判断数据类型是否为数组 +func DataTypeIsArray(docDataType string) bool { + return strings.ToLower(docDataType) == "array" +} + +// ExpandArrayParam 展开详细的数组配置 +func ExpandArrayParam(swaggerDoc *define.Swagger, ref string, rootPath string) []*define.ParamConfig { + pathPrefix := "" + if len(rootPath) > 0 { + pathPrefix = rootPath + "." + } + res := make([]*define.ParamConfig, 0) + for itemKey, itemConfig := range swaggerDoc.Definitions[GetRealDefinitionsKey(ref)].Properties { + res = append(res, &define.ParamConfig{ + Location: consts.RequestDataLocationBody.String(), + Path: pathPrefix + "{{#idx#}}." + itemKey, + Type: GetDataType(itemConfig.Type, "").String(), + Title: pathPrefix + itemKey, + Description: pathPrefix + itemKey, + Required: false, + }) + } + return res +} + +// ExpandArrayResult 展开返回值配置 +func ExpandArrayResult(swaggerDoc *define.Swagger, ref string, rootPath string) []*define.ResultConfig { + pathPrefix := "" + if len(rootPath) > 0 && rootPath != consts.ResponseDataLocationBodyRoot.String() { + pathPrefix = rootPath + "." + } + ref = GetRealDefinitionsKey(ref) + res := make([]*define.ResultConfig, 0) + for itemKey, itemConfig := range swaggerDoc.Definitions[ref].Properties { + res = append(res, &define.ResultConfig{ + Location: consts.ResponseDataLocationBody.String(), + Path: pathPrefix + "{{#idx#}}." + itemKey, + Type: GetDataType(itemConfig.Type, "").String(), + Title: pathPrefix + itemKey, + Description: pathPrefix + itemKey, + }) + } + return res +} diff --git a/define/generate.go b/define/generate.go index aa97da3..7ecfe1b 100644 --- a/define/generate.go +++ b/define/generate.go @@ -7,18 +7,36 @@ // Date : 2024-08-14 15:20 package define +import "regexp" + +// DocParseResult 文档解析结果 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:30 2024/12/24 +type DocParseResult struct { + Domain string `json:"domain"` // 域名 + Title string `json:"title"` // 标题 + Description string `json:"description"` // 描述 + UriList []*UriBaseConfig `json:"uri_list"` // 接口列表 + GlobalSecurityParamList []*ParamConfig `json:"global_security_param_list"` // 全局安全类参数配置 +} + // UriBaseConfig 添加接口时的基础配置 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:21 2024/8/14 type UriBaseConfig struct { - Uri string `json:"uri"` // 接口路由 - Method string `json:"method"` // 接口请求方法 - ContentType string `json:"content_type"` // 接口请求类型 - TagList []string `json:"tag_list"` // 接口标签列表 - Summary string `json:"summary"` // 接口摘要描述 - Description string `json:"description"` // 接口详细描述 + Uri string `json:"uri"` // 接口路由 + Method string `json:"method"` // 接口请求方法 + ContentType string `json:"content_type"` // 接口请求类型 + OutputContentType string `json:"output_content_type"` // 输出数据类型 + TagList []string `json:"tag_list"` // 接口标签列表 + Summary string `json:"summary"` // 接口摘要描述 + Description string `json:"description"` // 接口详细描述 + ParamList []*ParamConfig `json:"param_list"` // 参数列表 + ResultList []*ResultConfig `json:"result_list"` // 返回值列表 } // ParamConfig 参数配置 @@ -32,6 +50,7 @@ type ParamConfig struct { Type string `json:"type"` // 参数类型 Title string `json:"title"` // 参数标题 Description string `json:"description"` // 参数描述 + Required bool `json:"required"` // 是否必传 } // ResultConfig 返回值配置 @@ -46,3 +65,7 @@ type ResultConfig struct { Title string `json:"title"` // 返回值标题 Description string `json:"description"` // 返回值描述 } + +var ( + UriParamRegexp = regexp.MustCompile("({.*?})") // uri路径中参数提取的正则表达式 +) diff --git a/define/types.go b/define/swagger.go similarity index 58% rename from define/types.go rename to define/swagger.go index 86c48eb..096159a 100644 --- a/define/types.go +++ b/define/swagger.go @@ -15,6 +15,8 @@ package define type SwaggerPathConfig struct { Description string `json:"description"` // 接口描述 Consumes []string `json:"consumes"` // 请求方式, application/json等 + Produces []string `json:"produces"` // 请求方式, application/json等 + OperationID string `json:"operationId"` // 操作ID Tags []string `json:"tags"` // 接口标签 Summary string `json:"summary"` // 接口摘要 Parameters []*SwaggerPathConfigParameter `json:"parameters"` // 参数列表 @@ -27,13 +29,18 @@ type SwaggerPathConfig struct { // // Date : 16:53 2024/4/19 type SwaggerPathConfigParameter struct { - Type string `json:"type,omitempty"` // 类型 - Description string `json:"description"` // 描述 - Name string `json:"name"` // 参数名称 - In string `json:"in"` // 参数位置 - Required bool `json:"required"` // 是否必传 - EnumList []interface{} `json:"enum_list,omitempty"` // 枚举值列表 - Schema map[string]string `json:"schema"` // 参数schema + Type string `json:"type,omitempty"` // 类型 + Format string `json:"format"` // 格式化类型 + Description string `json:"description"` // 描述 + Name string `json:"name"` // 参数名称 + In string `json:"in"` // 参数位置 + Required bool `json:"required"` // 是否必传 + EnumList []any `json:"enum_list,omitempty"` // 枚举值列表 + Schema *SwaggerPathConfigParameterSchema `json:"schema"` // 参数schema +} + +type SwaggerPathConfigParameterSchema struct { + Ref string `json:"$ref"` // 引用的数据结构定义 } // SwaggerPathConfigResponse ... @@ -42,8 +49,17 @@ type SwaggerPathConfigParameter struct { // // Date : 16:57 2024/4/19 type SwaggerPathConfigResponse struct { - Description string `json:"description"` // 返回值描述 - Schema map[string]string `json:"schema"` // 返回值结构 + Description string `json:"description"` // 返回值描述 + Schema *SwaggerPathConfigResponseSchema `json:"schema"` // 返回值结构 + Ref string `json:"$ref"` // 引用的数据 +} + +type SwaggerPathConfigResponseSchema struct { + Type string `json:"type"` // 数据类型 + Items struct { + Ref string `json:"$ref"` // type = array 时, 引用的每一项数据结构 + } `json:"items"` + Ref string `json:"$ref"` // 引用的数据结构 } // SwaggerDefinition ... @@ -64,10 +80,19 @@ type SwaggerDefinition struct { // // Date : 17:25 2024/4/19 type SwaggerDefinitionProperty struct { - Description string `json:"description"` // 描述 - Type string `json:"type"` // 类型 - Items map[string]string `json:"items,omitempty"` // 引用类型中的引用(数组) - AllOf []map[string]string `json:"allOf,omitempty"` // 引用类型中的引用(对象) + Description string `json:"description"` // 描述 + Type string `json:"type"` // 类型 + Items *SwaggerDefinitionPropertyItem `json:"items,omitempty"` // 引用类型中的引用(数组) + AllOf []map[string]string `json:"allOf,omitempty"` // 引用类型中的引用(对象) + XGoName string `json:"x-go-name"` // go字段名称 + XGoPackage string `json:"x-go-package"` // go 包路径 +} + +// SwaggerDefinitionPropertyItem 属性item兴义 +type SwaggerDefinitionPropertyItem struct { + Type string `json:"type"` // 类型 + Ref string `json:"$ref"` // 引用 + Enum []any `json:"enum"` // 枚举值 } // Swagger 文档整体结构定义 @@ -76,13 +101,15 @@ type SwaggerDefinitionProperty struct { // // Date : 16:12 2024/4/19 type Swagger struct { - Schemes []string `json:"schemes"` - Swagger string `json:"swagger"` // swagger版本 - 2.0 - Host string `json:"host"` // 服务域名 - BasePath string `json:"basePath"` // 基础path - Info Info `json:"info"` // 文档描述信息 - Paths map[string]map[string]*SwaggerPathConfig `json:"paths"` // 接口列表 : 接口 => 请求方法 => 请求配置 - Definitions map[string]*SwaggerDefinition `json:"definitions"` // 数据定义 + Schemes []string `json:"schemes"` + Swagger string `json:"swagger"` // swagger版本 - 2.0 + Host string `json:"host"` // 服务域名 + BasePath string `json:"basePath"` // 基础path + Info Info `json:"info"` // 文档描述信息 + Paths map[string]map[string]*SwaggerPathConfig `json:"paths"` // 接口列表 : 接口 => 请求方法 => 请求配置 + Definitions map[string]*SwaggerDefinition `json:"definitions"` // 数据定义 + Responses map[string]*SwaggerPathConfigResponse `json:"responses"` // 响应结构列表 + SecurityDefinitions map[string]*SwaggerPathConfigParameter `json:"securityDefinitions"` // 安全选项 } // SwaggerInput ... diff --git a/go.mod b/go.mod index 0cbd41e..396f4ce 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,22 @@ module git.zhangdeman.cn/gateway/api-doc go 1.22.2 require ( - git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240817091513-491f455a23c0 + git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241225030757-c00354fe4630 + git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240422034417-8c922be06d95 + github.com/tidwall/gjson v1.18.0 ) require ( git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211 // indirect - git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240325080031-1f58204e8687 // indirect - git.zhangdeman.cn/zhangdeman/util v0.0.0-20231227095334-7eb5cdbf9253 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect + git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e // indirect + github.com/BurntSushi/toml v1.4.0 // indirect github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mozillazg/go-pinyin v0.20.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5e1b3f0..7301eb6 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,17 @@ -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240425091616-00e0a924efcd h1:RBFDiEMI97fuzpyb5HBN4lu3UXTAGYo6nlGhV2gWq5U= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240425091616-00e0a924efcd/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240722080005-ca68a3ff8bc7 h1:8wJlcuJPhEHKdNIENvcxGiZwwOgjclvKwgKt/MU6WWI= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240722080005-ca68a3ff8bc7/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240726024939-e424db29c5c4 h1:mibnyzYbZullK0aTHVASHl3UeoVr8IgytQZsuyv+yEM= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240726024939-e424db29c5c4/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240817091513-491f455a23c0 h1:U12XDtyRrmsqb/wRvRZG9+SBKMCGFNADpiLogsp5POw= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240817091513-491f455a23c0/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241125100843-b1b286c7a701 h1:G+lGQmjMOBWGspZfijZvenGUAKpjBBrkRXLg3+GZp0U= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241125100843-b1b286c7a701/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241225030757-c00354fe4630 h1:KTp27pHr25X5khTi4B/OdFv/34UWkq6sgDfz5aBtj74= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20241225030757-c00354fe4630/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211 h1:I/wOsRpCSRkU9vo1u703slQsmK0wnNeZzsWQOGtIAG0= git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211/go.mod h1:SrtvrQRdzt+8KfYzvosH++gWxo2ShPTzR1m3VQ6uX7U= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240325080031-1f58204e8687 h1:uQcGqdzi4UdpZlp4f4FUPeBqoygP58pEKJkmN3ROsE0= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240325080031-1f58204e8687/go.mod h1:gf7SW2TXATgux8pfdFedMkXWv2515OtIIM/5c4atkFw= -git.zhangdeman.cn/zhangdeman/util v0.0.0-20231227095334-7eb5cdbf9253 h1:GO3oZa5a2sqwAzGcLDJtQzmshSWRmoP7IDS8bwFqvC4= -git.zhangdeman.cn/zhangdeman/util v0.0.0-20231227095334-7eb5cdbf9253/go.mod h1:VpPjBlwz8U+OxZuxzHQBv1aEEZ3pStH6bZvT21ADEbI= +git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd h1:q7GG14qgXKB4MEXQFOe7/UYebsqMfPaSX80TcPdOosI= +git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd/go.mod h1:+D6uPSljwHywjVY5WSBY4TRVMj26TN5f5cFGEYMldjs= +git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e h1:Q973S6CcWr1ICZhFI1STFOJ+KUImCl2BaIXm6YppBqI= +git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e/go.mod h1:VpPjBlwz8U+OxZuxzHQBv1aEEZ3pStH6bZvT21ADEbI= git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240422034417-8c922be06d95 h1:3lO4ap9p7kEA+4yL5ojG9mAWsz5sY28Nu2tSzAZEehw= git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240422034417-8c922be06d95/go.mod h1:Fo4XOiZPua4E4/Qzy3ZYS5zyd15bS/lsb3t6S6PQFGY= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -30,8 +26,17 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= +github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/parser.go b/parser.go index 409886a..327fe9f 100644 --- a/parser.go +++ b/parser.go @@ -7,17 +7,35 @@ // Date : 2024-07-19 11:45 package api_doc -var ( - Parser = &parser{} +import ( + "git.zhangdeman.cn/gateway/api-doc/define" + "git.zhangdeman.cn/zhangdeman/serialize" + "github.com/tidwall/gjson" + "strings" ) -type parser struct { +// Parse 解析swagger文档 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:54 2024/12/23 +func Parse(docUrl string) (*define.DocParseResult, error) { + var ( + err error + docContent []byte + ) + + if docContent, err = serialize.File.ReadFromRemote(docUrl); nil != err { + return nil, err + } + swaggerVersion := gjson.GetBytes(docContent, "swagger").String() + if "" == swaggerVersion || !strings.HasPrefix(swaggerVersion, "2.") { + // 未指定swagger版本或swagger版本3.x + return ParseOpenapi3(docContent) + } + return ParseSwagger2(docContent) } -func (p *parser) Openapi3() { - -} - -func (p *parser) swagger2() { - +func ParseOpenapi3(docContent []byte) (*define.DocParseResult, error) { + return nil, nil } diff --git a/parser_test.go b/parser_test.go index 25b76b7..1bcfa94 100644 --- a/parser_test.go +++ b/parser_test.go @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" "git.zhangdeman.cn/gateway/api-doc/define" + "git.zhangdeman.cn/zhangdeman/serialize" "os" "os/user" "testing" @@ -32,3 +33,9 @@ func Test_parser_Openapi3(t *testing.T) { fmt.Println("解析成功") } } + +func TestParseForSwagger(t *testing.T) { + docUrl := "https://git.zhangdeman.cn/swagger.v1.json" + res, _ := Parse(docUrl) + serialize.JSON.ConsoleOutput(res) +} diff --git a/swagger.go b/swagger.go new file mode 100644 index 0000000..5df17fe --- /dev/null +++ b/swagger.go @@ -0,0 +1,233 @@ +// Package api_doc ... +// +// Description : api_doc ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-12-24 10:34 +package api_doc + +import ( + "errors" + "git.zhangdeman.cn/gateway/api-doc/define" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/serialize" + "net/http" + "strings" +) + +// ParseSwagger2 解析swagger2.0版本文档 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:33 2024/12/24 +func ParseSwagger2(docContent []byte) (*define.DocParseResult, error) { + var ( + err error + swaggerDoc define.Swagger + ) + + if err = serialize.JSON.UnmarshalWithNumber(docContent, &swaggerDoc); nil != err { + return nil, errors.New("parse swagger json fail : " + err.Error()) + } + docResult := &define.DocParseResult{ + Domain: swaggerDoc.Host, + Title: swaggerDoc.Info.Title, + Description: swaggerDoc.Info.Description, + UriList: make([]*define.UriBaseConfig, 0), + GlobalSecurityParamList: make([]*define.ParamConfig, 0), + } + if docResult.UriList, err = buildUriList(&swaggerDoc); nil != err { + return nil, err + } + docResult.GlobalSecurityParamList = buildSwagger2GlobalSecurityParamList(&swaggerDoc) + return docResult, nil +} + +// buildSwagger2GlobalSecurityParamList 构建全局安全类参数 +func buildSwagger2GlobalSecurityParamList(swaggerDoc *define.Swagger) []*define.ParamConfig { + result := make([]*define.ParamConfig, 0) + for paramName, paramConfig := range swaggerDoc.SecurityDefinitions { + if len(paramConfig.In) == 0 { + continue + } + result = append(result, &define.ParamConfig{ + Location: GetParamLocation(paramConfig.In).String(), + Path: paramName, + Type: GetDataType(paramConfig.Type, "").String(), + Title: paramName, + Description: paramConfig.Description, + Required: false, + }) + } + return result +} + +// 解析uri列表 +func buildUriList(swaggerDoc *define.Swagger) ([]*define.UriBaseConfig, error) { + uriList := make([]*define.UriBaseConfig, 0) + for itemUri, itemUriMethodConfig := range swaggerDoc.Paths { + for requestMethod, methodConfig := range itemUriMethodConfig { + initSwagger2UriMethodConfig(requestMethod, methodConfig) + uriResult := &define.UriBaseConfig{ + Uri: swaggerDoc.BasePath + "/" + strings.TrimLeft(itemUri, "/"), + Method: strings.ToUpper(requestMethod), + ContentType: methodConfig.Consumes[0], + OutputContentType: methodConfig.Produces[0], + TagList: methodConfig.Tags, + Summary: methodConfig.Summary, + Description: methodConfig.Description, + ParamList: buildSwagger2ParamConfig(swaggerDoc, methodConfig.Parameters), + ResultList: buildSwagger2ResultConfig(swaggerDoc, methodConfig.Responses), + } + uriList = append(uriList, uriResult) + } + } + return uriList, nil +} + +// 初始化配置请求方法 返回类型 +func initSwagger2UriMethodConfig(requestMethod string, methodConfig *define.SwaggerPathConfig) { + if len(methodConfig.Consumes) == 0 { + // 未配置请求方法, 写请求 json , 读请求 form + if strings.ToUpper(requestMethod) == http.MethodPost || + strings.ToUpper(requestMethod) == http.MethodPut || + strings.ToUpper(requestMethod) == http.MethodPatch || + strings.ToUpper(requestMethod) == http.MethodDelete { + methodConfig.Consumes = []string{ + consts.MimeTypeJson, + } + } else { + methodConfig.Consumes = []string{ + consts.MimeTypeXWWWFormUrlencoded, + } + } + } + if len(methodConfig.Produces) == 0 { + // 未配置输出数据类型, 默认 json + methodConfig.Produces = []string{ + consts.MimeTypeJson, + } + } +} + +// buildSwagger2ParamConfig 构建请求参数配置 +func buildSwagger2ParamConfig(swaggerDoc *define.Swagger, paramConfigList []*define.SwaggerPathConfigParameter) []*define.ParamConfig { + res := make([]*define.ParamConfig, 0) + // 解析参数 + for _, paramConfig := range paramConfigList { + if paramConfig.Name != "body" { + paramConfigBuildConfig := &define.ParamConfig{ + Location: GetParamLocation(paramConfig.In).String(), + Path: paramConfig.Name, + Type: GetDataType(paramConfig.Type, paramConfig.Format).String(), + Title: paramConfig.Name, + Description: paramConfig.Description, + Required: paramConfig.Required, + } + res = append(res, paramConfigBuildConfig) + } + + if nil == paramConfig.Schema || len(paramConfig.Schema.Ref) == 0 { + continue + } + // 可以继续展开 + requiredTable := make(map[string]bool) + for _, paramName := range swaggerDoc.Definitions[GetRealDefinitionsKey(paramConfig.Schema.Ref)].Required { + requiredTable[paramName] = true + } + for paramName, paramMoreConfig := range swaggerDoc.Definitions[GetRealDefinitionsKey(paramConfig.Schema.Ref)].Properties { + if paramConfig.Name != "body" { + paramName = paramConfig.Name + "." + paramName + } + paramConfigBuildConfig := &define.ParamConfig{ + Location: GetParamLocation(paramConfig.In).String(), + Path: paramName, + Type: GetDataType(paramMoreConfig.Type, "").String(), + Title: paramName, + Description: paramMoreConfig.Description, + Required: requiredTable[paramName], + } + res = append(res, paramConfigBuildConfig) + if DataTypeIsArray(paramMoreConfig.Type) && nil != paramMoreConfig.Items && len(paramMoreConfig.Items.Ref) > 0 { + // 是数组,且每一项是对象, 继续展开 + res = append(res, ExpandArrayParam(swaggerDoc, paramMoreConfig.Items.Ref, paramConfigBuildConfig.Path)...) + } + } + } + + return res +} + +// buildSwagger2ResultConfig 构建响应结果配置 +func buildSwagger2ResultConfig(swaggerDoc *define.Swagger, resultConfig map[string]*define.SwaggerPathConfigResponse) []*define.ResultConfig { + res := make([]*define.ResultConfig, 0) + if nil == resultConfig || len(resultConfig) == 0 { + return res + } + successResponseConfig := GetSuccessResponseConfig(resultConfig) + if nil == successResponseConfig { + return res + } + definitionsKey := "" + if swaggerDoc.Responses == nil { + definitionsKey = successResponseConfig.Schema.Ref + } else { + responseKey := GetRealResponseKey(successResponseConfig.Ref) + if len(responseKey) == 0 { + // 204 场景下,可能没有响应body + res = append(res, &define.ResultConfig{ + Location: consts.ResponseDataLocationBody.String(), + Path: consts.ResponseDataLocationBodyRoot.String(), + Type: consts.DataTypeAny.String(), + Title: "response body no content", + Description: "no content", + }) + return res + } + ref := swaggerDoc.Responses[responseKey].Ref + if len(ref) == 0 { + if nil != swaggerDoc.Responses[responseKey].Schema { + ref = swaggerDoc.Responses[responseKey].Schema.Ref + } + } + definitionsKey = GetRealDefinitionsKey(ref) + } + if len(definitionsKey) == 0 { + // 不是引用类型, 直接定义的具体类型 + responseKey := GetRealResponseKey(successResponseConfig.Ref) + responseTypeDefine := swaggerDoc.Responses[responseKey] + responseType := "" + schemaType := "" + if nil == responseTypeDefine.Schema { + // 204 等场景下,可能没有响应body + responseType = consts.DataTypeAny.String() + } else { + schemaType = responseTypeDefine.Schema.Type + responseType = GetDataType(responseTypeDefine.Schema.Type, "").String() + } + resCfg := &define.ResultConfig{ + Location: consts.ResponseDataLocationBody.String(), + Path: consts.ResponseDataLocationBodyRoot.String(), + Type: responseType, + Title: "response body", + Description: "response body", + } + res = append(res, resCfg) + if DataTypeIsArray(schemaType) && len(responseTypeDefine.Schema.Items.Ref) > 0 { + res = append(res, ExpandArrayResult(swaggerDoc, responseTypeDefine.Schema.Items.Ref, resCfg.Path)...) + } + } else { + responseTypeDefine := swaggerDoc.Definitions[definitionsKey] + for responseKey, responseKeyConfig := range responseTypeDefine.Properties { + res = append(res, &define.ResultConfig{ + Location: consts.ResponseDataLocationBody.String(), + Path: responseKey, + Type: GetDataType(responseKeyConfig.Type, "").String(), + Title: responseKey, + Description: responseKey, + }) + } + } + return res +} diff --git a/swagger/run.go b/swagger/run.go index 6f59a91..c82b620 100644 --- a/swagger/run.go +++ b/swagger/run.go @@ -43,11 +43,13 @@ func Generate(docConfig *define.SwaggerInput) (*define.Swagger, error) { // Date : 10:54 2024/4/22 func formatDocConfig(docConfig *define.SwaggerInput) { if len(docConfig.Schemes) == 0 { - docConfig.Schemes = []string{consts.SchemeHTTP} + docConfig.Schemes = []string{ + consts.SchemeHTTP.String(), + } } docConfig.Host = wrapper.String(docConfig.Host).ReplaceChar(map[string]string{ - consts.SchemeHTTP + "://": "", - consts.SchemeHTTPS + "://": "", + consts.SchemeHTTP.String() + "://": "", + consts.SchemeHTTPS.String() + "://": "", }).Value() for _, itemPath := range docConfig.PathConfigList { // 默认请求类型 application/json @@ -79,7 +81,7 @@ func formatDocConfig(docConfig *define.SwaggerInput) { for _, itemResponse := range itemResponseConfig.List { // 默认返回数据类型 itemResponse.Type = strings.TrimSpace(itemResponse.Type) - itemResponse.Type = wrapper.TernaryOperator.String(len(itemResponse.Type) == 0, consts.DataTypeString, wrapper.String(itemResponse.Type)).Value() + itemResponse.Type = wrapper.TernaryOperator.String(len(itemResponse.Type) == 0, wrapper.String(consts.DataTypeString), wrapper.String(itemResponse.Type)).Value() // 填充默认描述 itemResponse.Description = strings.TrimSpace(itemResponse.Description) itemResponse.Description = wrapper.TernaryOperator.String(len(itemResponse.Description) == 0, wrapper.String(itemResponse.Type+" : "+itemResponse.Field), wrapper.String(itemResponse.Description)).Value() @@ -133,7 +135,7 @@ func generatePathParameterConfig(swaggerInfo *define.Swagger, pathConfig *define namePath := strings.Split(itemParamInput.Name, ".") realParamName := namePath[0] parentPath := "" - if strings.ToUpper(itemParamInput.In) == consts.RequestDataLocationBody && !strings.Contains(realParamName, ".") { + if strings.ToUpper(itemParamInput.In) == consts.RequestDataLocationBody.String() && !strings.Contains(realParamName, ".") { realParamName = "jsonBody" parentPath = strings.ReplaceAll(pathConfig.Uri, ".", "-") + ".jsonBody" } @@ -177,7 +179,7 @@ func generatePathResponseConfig(swaggerInfo *define.Swagger, pathConfig *define. Type: itemResponseInput.Type, Description: itemResponseInput.Description, Name: itemResponseInput.Field, - In: consts.RequestDataLocationBody, + In: consts.RequestDataLocationBody.String(), Required: false, EnumList: nil, }) @@ -186,8 +188,8 @@ func generatePathResponseConfig(swaggerInfo *define.Swagger, pathConfig *define. hasDealResponseTable[namePath[0]] = true swaggerInfo.Paths[pathConfig.Uri][pathConfig.Method].Responses[itemResponseConfig.Code] = &define.SwaggerPathConfigResponse{ Description: "返回数据", - Schema: map[string]string{ - "$ref": getRefValue(outputDefine), + Schema: &define.SwaggerPathConfigResponseSchema{ + Ref: getRefValue(outputDefine), }, } } @@ -298,7 +300,7 @@ func generateParameterDefinitions(swaggerInfo *define.Swagger, uri string, paren // // Date : 16:50 2024/4/25 func handleOneLevelSubPath(swaggerInfo *define.Swagger, uri string, parentPath string, subPath string, paramConfig *define.SwaggerParameterInput) { - if paramConfig.In != strings.ToLower(consts.RequestDataLocationBody) && !isGenerateOutput(parentPath) { + if paramConfig.In != strings.ToLower(consts.RequestDataLocationBody.String()) && !isGenerateOutput(parentPath) { // 长度为1, 还不在 body, 无需生成结构体 return } @@ -368,13 +370,13 @@ func setGlobalMapDefinition(swaggerInfo *define.Swagger, dataType string) { // Date : 15:46 2024/4/25 func isGlobalMapType(dataType string) bool { return wrapper.ArrayType([]string{ - consts.DataTypeMapAnyAny, - consts.DataTypeMapStrUint, - consts.DataTypeMapStrInt, - consts.DataTypeMapStrSlice, - consts.DataTypeMapStrFloat, - consts.DataTypeMapStrBool, - consts.DataTypeMapStrAny, + consts.DataTypeMapAnyAny.String(), + consts.DataTypeMapStrUint.String(), + consts.DataTypeMapStrInt.String(), + consts.DataTypeMapStrSlice.String(), + consts.DataTypeMapStrFloat.String(), + consts.DataTypeMapStrBool.String(), + consts.DataTypeMapStrAny.String(), }).Has(dataType) >= 0 } @@ -395,7 +397,7 @@ func initAnyDefinition(swaggerInfo *define.Swagger, definitionName string) { } swaggerInfo.Definitions[definitionName] = &define.SwaggerDefinition{ Type: consts.SwaggerDataTypeObject, - Format: consts.DataTypeMapStrAny, + Format: consts.DataTypeMapStrAny.String(), Required: make([]string, 0), Properties: make(map[string]*define.SwaggerDefinitionProperty), } diff --git a/swagger/run_test.go b/swagger/run_test.go index a9b3c4e..2cef0bb 100644 --- a/swagger/run_test.go +++ b/swagger/run_test.go @@ -25,12 +25,12 @@ func TestGenerate(t *testing.T) { Info: define.Info{ Description: "测试", Title: "测试", - Contact: define.Contact{ + Contact: &define.Contact{ Name: "白茶", Url: "http://www.baidu.com", Email: "go@email.com", }, - License: define.License{ + License: &define.License{ Name: consts.LicenseApache20, Url: consts.LicenseUrlTable[consts.LicenseApache20], },