http urls monitor.

operation.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. package swag
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "github.com/go-openapi/jsonreference"
  10. "github.com/go-openapi/spec"
  11. )
  12. // Operation describes a single API operation on a path.
  13. // For more information: https://github.com/swaggo/swag#api-operation
  14. type Operation struct {
  15. HTTPMethod string
  16. Path string
  17. spec.Operation
  18. parser *Parser // TODO: we don't need it
  19. }
  20. // NewOperation creates a new Operation with default properties.
  21. // map[int]Response
  22. func NewOperation() *Operation {
  23. return &Operation{
  24. HTTPMethod: "get",
  25. Operation: spec.Operation{
  26. OperationProps: spec.OperationProps{},
  27. },
  28. }
  29. }
  30. // ParseComment parses comment for gived comment string and returns error if error occurs.
  31. func (operation *Operation) ParseComment(comment string) error {
  32. commentLine := strings.TrimSpace(strings.TrimLeft(comment, "//"))
  33. if len(commentLine) == 0 {
  34. return nil
  35. }
  36. attribute := strings.Fields(commentLine)[0]
  37. switch strings.ToLower(attribute) {
  38. case "@description":
  39. operation.Description = strings.TrimSpace(commentLine[len(attribute):])
  40. case "@summary":
  41. operation.Summary = strings.TrimSpace(commentLine[len(attribute):])
  42. case "@id":
  43. operation.ID = strings.TrimSpace(commentLine[len(attribute):])
  44. case "@tags":
  45. operation.ParseTagsComment(strings.TrimSpace(commentLine[len(attribute):]))
  46. case "@accept":
  47. if err := operation.ParseAcceptComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
  48. return err
  49. }
  50. case "@produce":
  51. if err := operation.ParseProduceComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
  52. return err
  53. }
  54. case "@param":
  55. if err := operation.ParseParamComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
  56. return err
  57. }
  58. case "@success", "@failure":
  59. if err := operation.ParseResponseComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
  60. if errWhenEmpty := operation.ParseEmptyResponseComment(strings.TrimSpace(commentLine[len(attribute):])); errWhenEmpty != nil {
  61. var errs []string
  62. errs = append(errs, err.Error())
  63. errs = append(errs, errWhenEmpty.Error())
  64. return fmt.Errorf(strings.Join(errs, "\n"))
  65. }
  66. }
  67. case "@router":
  68. if err := operation.ParseRouterComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
  69. return err
  70. }
  71. case "@security":
  72. if err := operation.ParseSecurityComment(strings.TrimSpace(commentLine[len(attribute):])); err != nil {
  73. return err
  74. }
  75. }
  76. return nil
  77. }
  78. // ParseParamComment Parse params return []string of param properties
  79. // @Param queryText form string true "The email for login"
  80. // [param name] [paramType] [data type] [is mandatory?] [Comment]
  81. // @Param some_id path int true "Some ID"
  82. func (operation *Operation) ParseParamComment(commentLine string) error {
  83. re := regexp.MustCompile(`([-\w]+)[\s]+([\w]+)[\s]+([\S.]+)[\s]+([\w]+)[\s]+"([^"]+)"`)
  84. matches := re.FindStringSubmatch(commentLine)
  85. if len(matches) != 6 {
  86. return fmt.Errorf("can not parse param comment \"%s\"", commentLine)
  87. }
  88. name := matches[1]
  89. paramType := matches[2]
  90. schemaType := matches[3]
  91. requiredText := strings.ToLower(matches[4])
  92. required := requiredText == "true" || requiredText == "required"
  93. description := matches[5]
  94. var param spec.Parameter
  95. //five possible parameter types.
  96. switch paramType {
  97. case "query", "path", "header":
  98. param = createParameter(paramType, description, name, TransToValidSchemeType(schemaType), required)
  99. case "body":
  100. param = createParameter(paramType, description, name, "object", required) // TODO: if Parameter types can be objects, but also primitives and arrays
  101. // TODO: this snippets have to extract out
  102. refSplit := strings.Split(schemaType, ".")
  103. if len(refSplit) == 2 {
  104. pkgName := refSplit[0]
  105. typeName := refSplit[1]
  106. if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok {
  107. operation.parser.registerTypes[schemaType] = typeSpec
  108. } else {
  109. return fmt.Errorf("can not find ref type:\"%s\"", schemaType)
  110. }
  111. param.Schema.Ref = spec.Ref{
  112. Ref: jsonreference.MustCreateRef("#/definitions/" + schemaType),
  113. }
  114. }
  115. case "formData":
  116. param = createParameter(paramType, description, name, TransToValidSchemeType(schemaType), required)
  117. }
  118. param = operation.parseAndExtractionParamAttribute(commentLine, schemaType, param)
  119. operation.Operation.Parameters = append(operation.Operation.Parameters, param)
  120. return nil
  121. }
  122. var regexAttributes = map[string]*regexp.Regexp{
  123. // for Enums(A, B)
  124. "enums": regexp.MustCompile(`(?i)enums\(.*\)`),
  125. // for Minimum(0)
  126. "maxinum": regexp.MustCompile(`(?i)maxinum\(.*\)`),
  127. // for Maximum(0)
  128. "mininum": regexp.MustCompile(`(?i)mininum\(.*\)`),
  129. // for Maximum(0)
  130. "default": regexp.MustCompile(`(?i)default\(.*\)`),
  131. // for minlength(0)
  132. "minlength": regexp.MustCompile(`(?i)minlength\(.*\)`),
  133. // for maxlength(0)
  134. "maxlength": regexp.MustCompile(`(?i)maxlength\(.*\)`),
  135. // for format(email)
  136. "format": regexp.MustCompile(`(?i)format\(.*\)`),
  137. }
  138. func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schemaType string, param spec.Parameter) spec.Parameter {
  139. schemaType = TransToValidSchemeType(schemaType)
  140. for attrKey, re := range regexAttributes {
  141. switch attrKey {
  142. case "enums":
  143. attr := re.FindString(commentLine)
  144. l := strings.Index(attr, "(")
  145. r := strings.Index(attr, ")")
  146. if !(l == -1 && r == -1) {
  147. enums := strings.Split(attr[l+1:r], ",")
  148. for _, e := range enums {
  149. e = strings.TrimSpace(e)
  150. param.Enum = append(param.Enum, defineType(schemaType, e))
  151. }
  152. }
  153. case "maxinum":
  154. attr := re.FindString(commentLine)
  155. l := strings.Index(attr, "(")
  156. r := strings.Index(attr, ")")
  157. if !(l == -1 && r == -1) {
  158. if schemaType != "integer" && schemaType != "number" {
  159. log.Panicf("maxinum is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
  160. }
  161. attr = strings.TrimSpace(attr[l+1 : r])
  162. n, err := strconv.ParseFloat(attr, 64)
  163. if err != nil {
  164. log.Panicf("maximum is allow only a number. comment=%s got=%s", commentLine, attr)
  165. }
  166. param.Maximum = &n
  167. }
  168. case "mininum":
  169. attr := re.FindString(commentLine)
  170. l := strings.Index(attr, "(")
  171. r := strings.Index(attr, ")")
  172. if !(l == -1 && r == -1) {
  173. if schemaType != "integer" && schemaType != "number" {
  174. log.Panicf("mininum is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
  175. }
  176. attr = strings.TrimSpace(attr[l+1 : r])
  177. n, err := strconv.ParseFloat(attr, 64)
  178. if err != nil {
  179. log.Panicf("mininum is allow only a number got=%s", attr)
  180. }
  181. param.Minimum = &n
  182. }
  183. case "default":
  184. attr := re.FindString(commentLine)
  185. l := strings.Index(attr, "(")
  186. r := strings.Index(attr, ")")
  187. if !(l == -1 && r == -1) {
  188. attr = strings.TrimSpace(attr[l+1 : r])
  189. param.Default = defineType(schemaType, attr)
  190. }
  191. case "maxlength":
  192. attr := re.FindString(commentLine)
  193. l := strings.Index(attr, "(")
  194. r := strings.Index(attr, ")")
  195. if !(l == -1 && r == -1) {
  196. if schemaType != "string" {
  197. log.Panicf("maxlength is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
  198. }
  199. attr = strings.TrimSpace(attr[l+1 : r])
  200. n, err := strconv.ParseInt(attr, 10, 64)
  201. if err != nil {
  202. log.Panicf("maxlength is allow only a number got=%s", attr)
  203. }
  204. param.MaxLength = &n
  205. }
  206. case "minlength":
  207. attr := re.FindString(commentLine)
  208. l := strings.Index(attr, "(")
  209. r := strings.Index(attr, ")")
  210. if !(l == -1 && r == -1) {
  211. if schemaType != "string" {
  212. log.Panicf("maxlength is attribute to set to a number. comment=%s got=%s", commentLine, schemaType)
  213. }
  214. attr = strings.TrimSpace(attr[l+1 : r])
  215. n, err := strconv.ParseInt(attr, 10, 64)
  216. if err != nil {
  217. log.Panicf("minlength is allow only a number got=%s", attr)
  218. }
  219. param.MinLength = &n
  220. }
  221. case "format":
  222. attr := re.FindString(commentLine)
  223. l := strings.Index(attr, "(")
  224. r := strings.Index(attr, ")")
  225. if !(l == -1 && r == -1) {
  226. param.Format = strings.TrimSpace(attr[l+1 : r])
  227. }
  228. }
  229. }
  230. return param
  231. }
  232. // defineType enum value define the type (object and array unsupported)
  233. func defineType(schemaType string, value string) interface{} {
  234. schemaType = TransToValidSchemeType(schemaType)
  235. switch schemaType {
  236. case "string":
  237. return value
  238. case "number":
  239. v, err := strconv.ParseFloat(value, 64)
  240. if err != nil {
  241. panic(fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err))
  242. }
  243. return v
  244. case "integer":
  245. v, err := strconv.Atoi(value)
  246. if err != nil {
  247. panic(fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err))
  248. }
  249. return v
  250. case "boolean":
  251. v, err := strconv.ParseBool(value)
  252. if err != nil {
  253. panic(fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err))
  254. }
  255. return v
  256. default:
  257. panic(fmt.Errorf("%s is unsupported type in enum value", schemaType))
  258. }
  259. }
  260. // ParseTagsComment parses comment for gived `tag` comment string.
  261. func (operation *Operation) ParseTagsComment(commentLine string) {
  262. tags := strings.Split(commentLine, ",")
  263. for _, tag := range tags {
  264. operation.Tags = append(operation.Tags, strings.TrimSpace(tag))
  265. }
  266. }
  267. // ParseAcceptComment parses comment for gived `accept` comment string.
  268. func (operation *Operation) ParseAcceptComment(commentLine string) error {
  269. accepts := strings.Split(commentLine, ",")
  270. for _, a := range accepts {
  271. switch a {
  272. case "json", "application/json":
  273. operation.Consumes = append(operation.Consumes, "application/json")
  274. case "xml", "text/xml":
  275. operation.Consumes = append(operation.Consumes, "text/xml")
  276. case "plain", "text/plain":
  277. operation.Consumes = append(operation.Consumes, "text/plain")
  278. case "html", "text/html":
  279. operation.Consumes = append(operation.Consumes, "text/html")
  280. case "mpfd", "multipart/form-data":
  281. operation.Consumes = append(operation.Consumes, "multipart/form-data")
  282. case "x-www-form-urlencoded", "application/x-www-form-urlencoded":
  283. operation.Consumes = append(operation.Consumes, "application/x-www-form-urlencoded")
  284. case "json-api", "application/vnd.api+json":
  285. operation.Consumes = append(operation.Consumes, "application/vnd.api+json")
  286. case "json-stream", "application/x-json-stream":
  287. operation.Consumes = append(operation.Consumes, "application/x-json-stream")
  288. case "octet-stream", "application/octet-stream":
  289. operation.Consumes = append(operation.Consumes, "application/octet-stream")
  290. case "png", "image/png":
  291. operation.Consumes = append(operation.Consumes, "image/png")
  292. case "jpeg", "image/jpeg":
  293. operation.Consumes = append(operation.Consumes, "image/jpeg")
  294. case "gif", "image/gif":
  295. operation.Consumes = append(operation.Consumes, "image/gif")
  296. default:
  297. return fmt.Errorf("%v accept type can't accepted", a)
  298. }
  299. }
  300. return nil
  301. }
  302. // ParseProduceComment parses comment for gived `produce` comment string.
  303. func (operation *Operation) ParseProduceComment(commentLine string) error {
  304. produces := strings.Split(commentLine, ",")
  305. for _, a := range produces {
  306. switch a {
  307. case "json", "application/json":
  308. operation.Produces = append(operation.Produces, "application/json")
  309. case "xml", "text/xml":
  310. operation.Produces = append(operation.Produces, "text/xml")
  311. case "plain", "text/plain":
  312. operation.Produces = append(operation.Produces, "text/plain")
  313. case "html", "text/html":
  314. operation.Produces = append(operation.Produces, "text/html")
  315. case "mpfd", "multipart/form-data":
  316. operation.Produces = append(operation.Produces, "multipart/form-data")
  317. case "x-www-form-urlencoded", "application/x-www-form-urlencoded":
  318. operation.Produces = append(operation.Produces, "application/x-www-form-urlencoded")
  319. case "json-api", "application/vnd.api+json":
  320. operation.Produces = append(operation.Produces, "application/vnd.api+json")
  321. case "json-stream", "application/x-json-stream":
  322. operation.Produces = append(operation.Produces, "application/x-json-stream")
  323. case "octet-stream", "application/octet-stream":
  324. operation.Produces = append(operation.Produces, "application/octet-stream")
  325. case "png", "image/png":
  326. operation.Produces = append(operation.Produces, "image/png")
  327. case "jpeg", "image/jpeg":
  328. operation.Produces = append(operation.Produces, "image/jpeg")
  329. case "gif", "image/gif":
  330. operation.Produces = append(operation.Produces, "image/gif")
  331. default:
  332. return fmt.Errorf("%v produce type can't accepted", a)
  333. }
  334. }
  335. return nil
  336. }
  337. // ParseRouterComment parses comment for gived `router` comment string.
  338. func (operation *Operation) ParseRouterComment(commentLine string) error {
  339. re := regexp.MustCompile(`([\w\.\/\-{}]+)[^\[]+\[([^\]]+)`)
  340. var matches []string
  341. if matches = re.FindStringSubmatch(commentLine); len(matches) != 3 {
  342. return fmt.Errorf("can not parse router comment \"%s\"", commentLine)
  343. }
  344. path := matches[1]
  345. httpMethod := matches[2]
  346. operation.Path = path
  347. operation.HTTPMethod = strings.ToUpper(httpMethod)
  348. return nil
  349. }
  350. // ParseSecurityComment parses comment for gived `security` comment string.
  351. func (operation *Operation) ParseSecurityComment(commentLine string) error {
  352. securitySource := commentLine[strings.Index(commentLine, "@Security")+1:]
  353. l := strings.Index(securitySource, "[")
  354. r := strings.Index(securitySource, "]")
  355. // exists scope
  356. if !(l == -1 && r == -1) {
  357. scopes := securitySource[l+1 : r]
  358. s := []string{}
  359. for _, scope := range strings.Split(scopes, ",") {
  360. scope = strings.TrimSpace(scope)
  361. s = append(s, scope)
  362. }
  363. securityKey := securitySource[0:l]
  364. securityMap := map[string][]string{}
  365. securityMap[securityKey] = append(securityMap[securityKey], s...)
  366. operation.Security = append(operation.Security, securityMap)
  367. } else {
  368. securityKey := strings.TrimSpace(securitySource)
  369. securityMap := map[string][]string{}
  370. securityMap[securityKey] = []string{}
  371. operation.Security = append(operation.Security, securityMap)
  372. }
  373. return nil
  374. }
  375. // ParseResponseComment parses comment for gived `response` comment string.
  376. func (operation *Operation) ParseResponseComment(commentLine string) error {
  377. re := regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/]+)[^"]*(.*)?`)
  378. var matches []string
  379. if matches = re.FindStringSubmatch(commentLine); len(matches) != 5 {
  380. return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
  381. }
  382. response := spec.Response{}
  383. code, _ := strconv.Atoi(matches[1])
  384. responseDescription := strings.Trim(matches[4], "\"")
  385. if responseDescription == "" {
  386. responseDescription = http.StatusText(code)
  387. }
  388. response.Description = responseDescription
  389. schemaType := strings.Trim(matches[2], "{}")
  390. refType := matches[3]
  391. if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
  392. refSplit := strings.Split(refType, ".")
  393. if len(refSplit) == 2 {
  394. pkgName := refSplit[0]
  395. typeName := refSplit[1]
  396. if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok {
  397. operation.parser.registerTypes[refType] = typeSpec
  398. } else {
  399. return fmt.Errorf("can not find ref type:\"%s\"", refType)
  400. }
  401. }
  402. }
  403. // so we have to know all type in app
  404. //TODO: we might omitted schema.type if schemaType equals 'object'
  405. response.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{schemaType}}}
  406. if schemaType == "object" {
  407. response.Schema.Ref = spec.Ref{
  408. Ref: jsonreference.MustCreateRef("#/definitions/" + refType),
  409. }
  410. }
  411. if schemaType == "array" {
  412. response.Schema.Items = &spec.SchemaOrArray{
  413. Schema: &spec.Schema{
  414. SchemaProps: spec.SchemaProps{
  415. Ref: spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)},
  416. },
  417. },
  418. }
  419. }
  420. if operation.Responses == nil {
  421. operation.Responses = &spec.Responses{
  422. ResponsesProps: spec.ResponsesProps{
  423. StatusCodeResponses: make(map[int]spec.Response),
  424. },
  425. }
  426. }
  427. operation.Responses.StatusCodeResponses[code] = response
  428. return nil
  429. }
  430. // ParseEmptyResponseComment TODO: NEEDS COMMENT INFO
  431. func (operation *Operation) ParseEmptyResponseComment(commentLine string) error {
  432. re := regexp.MustCompile(`([\d]+)[\s]+"(.*)"`)
  433. var matches []string
  434. if matches = re.FindStringSubmatch(commentLine); len(matches) != 3 {
  435. return fmt.Errorf("can not parse empty response comment \"%s\"", commentLine)
  436. }
  437. response := spec.Response{}
  438. code, _ := strconv.Atoi(matches[1])
  439. response.Description = strings.Trim(matches[2], "")
  440. if operation.Responses == nil {
  441. operation.Responses = &spec.Responses{
  442. ResponsesProps: spec.ResponsesProps{
  443. StatusCodeResponses: make(map[int]spec.Response),
  444. },
  445. }
  446. }
  447. operation.Responses.StatusCodeResponses[code] = response
  448. return nil
  449. }
  450. // createParameter returns swagger spec.Parameter for gived paramType, description, paramName, schemaType, required
  451. func createParameter(paramType, description, paramName, schemaType string, required bool) spec.Parameter {
  452. // //five possible parameter types. query, path, body, header, form
  453. paramProps := spec.ParamProps{
  454. Name: paramName,
  455. Description: description,
  456. Required: required,
  457. In: paramType,
  458. }
  459. if paramType == "body" {
  460. paramProps.Schema = &spec.Schema{
  461. SchemaProps: spec.SchemaProps{
  462. Type: []string{schemaType},
  463. },
  464. }
  465. parameter := spec.Parameter{
  466. ParamProps: paramProps,
  467. }
  468. return parameter
  469. }
  470. parameter := spec.Parameter{
  471. ParamProps: paramProps,
  472. SimpleSchema: spec.SimpleSchema{
  473. Type: schemaType,
  474. },
  475. }
  476. return parameter
  477. }