http urls monitor.

parser.go 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. package swag
  2. import (
  3. "fmt"
  4. "go/ast"
  5. goparser "go/parser"
  6. "go/token"
  7. "log"
  8. "net/http"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "reflect"
  13. "strconv"
  14. "strings"
  15. "unicode"
  16. "github.com/go-openapi/jsonreference"
  17. "github.com/go-openapi/spec"
  18. )
  19. // Parser implements a parser for Go source files.
  20. type Parser struct {
  21. // swagger represents the root document object for the API specification
  22. swagger *spec.Swagger
  23. //files is a map that stores map[real_go_file_path][astFile]
  24. files map[string]*ast.File
  25. // TypeDefinitions is a map that stores [package name][type name][*ast.TypeSpec]
  26. TypeDefinitions map[string]map[string]*ast.TypeSpec
  27. //registerTypes is a map that stores [refTypeName][*ast.TypeSpec]
  28. registerTypes map[string]*ast.TypeSpec
  29. PropNamingStrategy string
  30. }
  31. // New creates a new Parser with default properties.
  32. func New() *Parser {
  33. parser := &Parser{
  34. swagger: &spec.Swagger{
  35. SwaggerProps: spec.SwaggerProps{
  36. Info: &spec.Info{
  37. InfoProps: spec.InfoProps{
  38. Contact: &spec.ContactInfo{},
  39. License: &spec.License{},
  40. },
  41. },
  42. Paths: &spec.Paths{
  43. Paths: make(map[string]spec.PathItem),
  44. },
  45. Definitions: make(map[string]spec.Schema),
  46. },
  47. },
  48. files: make(map[string]*ast.File),
  49. TypeDefinitions: make(map[string]map[string]*ast.TypeSpec),
  50. registerTypes: make(map[string]*ast.TypeSpec),
  51. }
  52. return parser
  53. }
  54. // ParseAPI parses general api info for gived searchDir and mainAPIFile
  55. func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) {
  56. log.Println("Generate general API Info")
  57. parser.getAllGoFileInfo(searchDir)
  58. parser.ParseGeneralAPIInfo(path.Join(searchDir, mainAPIFile))
  59. for _, astFile := range parser.files {
  60. parser.ParseType(astFile)
  61. }
  62. for _, astFile := range parser.files {
  63. parser.ParseRouterAPIInfo(astFile)
  64. }
  65. parser.ParseDefinitions()
  66. }
  67. // ParseGeneralAPIInfo parses general api info for gived mainAPIFile path
  68. func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) {
  69. fileSet := token.NewFileSet()
  70. fileTree, err := goparser.ParseFile(fileSet, mainAPIFile, nil, goparser.ParseComments)
  71. if err != nil {
  72. log.Panicf("ParseGeneralApiInfo occur error:%+v", err)
  73. }
  74. parser.swagger.Swagger = "2.0"
  75. securityMap := map[string]*spec.SecurityScheme{}
  76. if fileTree.Comments != nil {
  77. for _, comment := range fileTree.Comments {
  78. comments := strings.Split(comment.Text(), "\n")
  79. for _, commentLine := range comments {
  80. attribute := strings.ToLower(strings.Split(commentLine, " ")[0])
  81. switch attribute {
  82. case "@version":
  83. parser.swagger.Info.Version = strings.TrimSpace(commentLine[len(attribute):])
  84. case "@title":
  85. parser.swagger.Info.Title = strings.TrimSpace(commentLine[len(attribute):])
  86. case "@description":
  87. parser.swagger.Info.Description = strings.TrimSpace(commentLine[len(attribute):])
  88. case "@termsofservice":
  89. parser.swagger.Info.TermsOfService = strings.TrimSpace(commentLine[len(attribute):])
  90. case "@contact.name":
  91. parser.swagger.Info.Contact.Name = strings.TrimSpace(commentLine[len(attribute):])
  92. case "@contact.email":
  93. parser.swagger.Info.Contact.Email = strings.TrimSpace(commentLine[len(attribute):])
  94. case "@contact.url":
  95. parser.swagger.Info.Contact.URL = strings.TrimSpace(commentLine[len(attribute):])
  96. case "@license.name":
  97. parser.swagger.Info.License.Name = strings.TrimSpace(commentLine[len(attribute):])
  98. case "@license.url":
  99. parser.swagger.Info.License.URL = strings.TrimSpace(commentLine[len(attribute):])
  100. case "@host":
  101. parser.swagger.Host = strings.TrimSpace(commentLine[len(attribute):])
  102. case "@basepath":
  103. parser.swagger.BasePath = strings.TrimSpace(commentLine[len(attribute):])
  104. case "@schemes":
  105. parser.swagger.Schemes = GetSchemes(commentLine)
  106. }
  107. }
  108. for i := 0; i < len(comments); i++ {
  109. attribute := strings.ToLower(strings.Split(comments[i], " ")[0])
  110. switch attribute {
  111. case "@securitydefinitions.basic":
  112. securityMap[strings.TrimSpace(comments[i][len(attribute):])] = spec.BasicAuth()
  113. case "@securitydefinitions.apikey":
  114. attrMap := map[string]string{}
  115. for _, v := range comments[i+1:] {
  116. securityAttr := strings.ToLower(strings.Split(v, " ")[0])
  117. if securityAttr == "@in" || securityAttr == "@name" {
  118. attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
  119. }
  120. // next securityDefinitions
  121. if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
  122. break
  123. }
  124. }
  125. if len(attrMap) != 2 {
  126. log.Panic("@securitydefinitions.apikey is @name and @in required")
  127. }
  128. securityMap[strings.TrimSpace(comments[i][len(attribute):])] = spec.APIKeyAuth(attrMap["@name"], attrMap["@in"])
  129. case "@securitydefinitions.oauth2.application":
  130. attrMap := map[string]string{}
  131. scopes := map[string]string{}
  132. for _, v := range comments[i+1:] {
  133. securityAttr := strings.ToLower(strings.Split(v, " ")[0])
  134. if securityAttr == "@tokenurl" {
  135. attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
  136. } else if isExistsScope(securityAttr) {
  137. scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
  138. }
  139. // next securityDefinitions
  140. if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
  141. break
  142. }
  143. }
  144. if len(attrMap) != 1 {
  145. log.Panic("@securitydefinitions.oauth2.application is @tokenUrl required")
  146. }
  147. securityScheme := spec.OAuth2Application(attrMap["@tokenurl"])
  148. for scope, description := range scopes {
  149. securityScheme.AddScope(scope, description)
  150. }
  151. securityMap[strings.TrimSpace(comments[i][len(attribute):])] = securityScheme
  152. case "@securitydefinitions.oauth2.implicit":
  153. attrMap := map[string]string{}
  154. scopes := map[string]string{}
  155. for _, v := range comments[i+1:] {
  156. securityAttr := strings.ToLower(strings.Split(v, " ")[0])
  157. if securityAttr == "@authorizationurl" {
  158. attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
  159. } else if isExistsScope(securityAttr) {
  160. scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
  161. }
  162. // next securityDefinitions
  163. if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
  164. break
  165. }
  166. }
  167. if len(attrMap) != 1 {
  168. log.Panic("@securitydefinitions.oauth2.implicit is @authorizationUrl required")
  169. }
  170. securityScheme := spec.OAuth2Implicit(attrMap["@authorizationurl"])
  171. for scope, description := range scopes {
  172. securityScheme.AddScope(scope, description)
  173. }
  174. securityMap[strings.TrimSpace(comments[i][len(attribute):])] = securityScheme
  175. case "@securitydefinitions.oauth2.password":
  176. attrMap := map[string]string{}
  177. scopes := map[string]string{}
  178. for _, v := range comments[i+1:] {
  179. securityAttr := strings.ToLower(strings.Split(v, " ")[0])
  180. if securityAttr == "@tokenurl" {
  181. attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
  182. } else if isExistsScope(securityAttr) {
  183. scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
  184. }
  185. // next securityDefinitions
  186. if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
  187. break
  188. }
  189. }
  190. if len(attrMap) != 1 {
  191. log.Panic("@securitydefinitions.oauth2.password is @tokenUrl required")
  192. }
  193. securityScheme := spec.OAuth2Password(attrMap["@tokenurl"])
  194. for scope, description := range scopes {
  195. securityScheme.AddScope(scope, description)
  196. }
  197. securityMap[strings.TrimSpace(comments[i][len(attribute):])] = securityScheme
  198. case "@securitydefinitions.oauth2.accesscode":
  199. attrMap := map[string]string{}
  200. scopes := map[string]string{}
  201. for _, v := range comments[i+1:] {
  202. securityAttr := strings.ToLower(strings.Split(v, " ")[0])
  203. if securityAttr == "@tokenurl" || securityAttr == "@authorizationurl" {
  204. attrMap[securityAttr] = strings.TrimSpace(v[len(securityAttr):])
  205. } else if isExistsScope(securityAttr) {
  206. scopes[getScopeScheme(securityAttr)] = v[len(securityAttr):]
  207. }
  208. // next securityDefinitions
  209. if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
  210. break
  211. }
  212. }
  213. if len(attrMap) != 2 {
  214. log.Panic("@securitydefinitions.oauth2.accessCode is @tokenUrl and @authorizationUrl required")
  215. }
  216. securityScheme := spec.OAuth2AccessToken(attrMap["@authorizationurl"], attrMap["@tokenurl"])
  217. for scope, description := range scopes {
  218. securityScheme.AddScope(scope, description)
  219. }
  220. securityMap[strings.TrimSpace(comments[i][len(attribute):])] = securityScheme
  221. }
  222. }
  223. }
  224. }
  225. if len(securityMap) > 0 {
  226. parser.swagger.SecurityDefinitions = securityMap
  227. }
  228. }
  229. func getScopeScheme(scope string) string {
  230. scopeValue := scope[strings.Index(scope, "@scope."):]
  231. if scopeValue == "" {
  232. panic("@scope is empty")
  233. }
  234. return scope[len("@scope."):]
  235. }
  236. func isExistsScope(scope string) bool {
  237. s := strings.Fields(scope)
  238. for _, v := range s {
  239. if strings.Index(v, "@scope.") != -1 {
  240. if strings.Index(v, ",") != -1 {
  241. panic("@scope can't use comma(,) get=" + v)
  242. }
  243. }
  244. }
  245. return strings.Index(scope, "@scope.") != -1
  246. }
  247. // GetSchemes parses swagger schemes for gived commentLine
  248. func GetSchemes(commentLine string) []string {
  249. attribute := strings.ToLower(strings.Split(commentLine, " ")[0])
  250. return strings.Split(strings.TrimSpace(commentLine[len(attribute):]), " ")
  251. }
  252. // ParseRouterAPIInfo parses router api info for gived astFile
  253. func (parser *Parser) ParseRouterAPIInfo(astFile *ast.File) {
  254. for _, astDescription := range astFile.Decls {
  255. switch astDeclaration := astDescription.(type) {
  256. case *ast.FuncDecl:
  257. if astDeclaration.Doc != nil && astDeclaration.Doc.List != nil {
  258. operation := NewOperation() //for per 'function' comment, create a new 'Operation' object
  259. operation.parser = parser
  260. for _, comment := range astDeclaration.Doc.List {
  261. if err := operation.ParseComment(comment.Text); err != nil {
  262. log.Panicf("ParseComment panic:%+v", err)
  263. }
  264. }
  265. var pathItem spec.PathItem
  266. var ok bool
  267. if pathItem, ok = parser.swagger.Paths.Paths[operation.Path]; !ok {
  268. pathItem = spec.PathItem{}
  269. }
  270. switch strings.ToUpper(operation.HTTPMethod) {
  271. case http.MethodGet:
  272. pathItem.Get = &operation.Operation
  273. case http.MethodPost:
  274. pathItem.Post = &operation.Operation
  275. case http.MethodDelete:
  276. pathItem.Delete = &operation.Operation
  277. case http.MethodPut:
  278. pathItem.Put = &operation.Operation
  279. case http.MethodPatch:
  280. pathItem.Patch = &operation.Operation
  281. case http.MethodHead:
  282. pathItem.Head = &operation.Operation
  283. case http.MethodOptions:
  284. pathItem.Options = &operation.Operation
  285. }
  286. parser.swagger.Paths.Paths[operation.Path] = pathItem
  287. }
  288. }
  289. }
  290. }
  291. // ParseType parses type info for gived astFile
  292. func (parser *Parser) ParseType(astFile *ast.File) {
  293. if _, ok := parser.TypeDefinitions[astFile.Name.String()]; !ok {
  294. parser.TypeDefinitions[astFile.Name.String()] = make(map[string]*ast.TypeSpec)
  295. }
  296. for _, astDeclaration := range astFile.Decls {
  297. if generalDeclaration, ok := astDeclaration.(*ast.GenDecl); ok && generalDeclaration.Tok == token.TYPE {
  298. for _, astSpec := range generalDeclaration.Specs {
  299. if typeSpec, ok := astSpec.(*ast.TypeSpec); ok {
  300. parser.TypeDefinitions[astFile.Name.String()][typeSpec.Name.String()] = typeSpec
  301. }
  302. }
  303. }
  304. }
  305. }
  306. // ParseDefinitions parses Swagger Api definitions
  307. func (parser *Parser) ParseDefinitions() {
  308. for refTypeName, typeSpec := range parser.registerTypes {
  309. ss := strings.Split(refTypeName, ".")
  310. pkgName := ss[0]
  311. parser.ParseDefinition(pkgName, typeSpec, typeSpec.Name.Name)
  312. }
  313. }
  314. var structStacks []string
  315. // isNotRecurringNestStruct check if a structure that is not a not repeating
  316. func isNotRecurringNestStruct(refTypeName string, structStacks []string) bool {
  317. if len(structStacks) <= 0 {
  318. return true
  319. }
  320. startStruct := structStacks[0]
  321. for _, v := range structStacks[1:] {
  322. if startStruct == v {
  323. return false
  324. }
  325. }
  326. return true
  327. }
  328. // ParseDefinition TODO: NEEDS COMMENT INFO
  329. func (parser *Parser) ParseDefinition(pkgName string, typeSpec *ast.TypeSpec, typeName string) {
  330. var refTypeName string
  331. if len(pkgName) > 0 {
  332. refTypeName = pkgName + "." + typeName
  333. } else {
  334. refTypeName = typeName
  335. }
  336. if _, already := parser.swagger.Definitions[refTypeName]; already {
  337. log.Println("Skipping '" + refTypeName + "', already present.")
  338. return
  339. }
  340. properties := make(map[string]spec.Schema)
  341. // stop repetitive structural parsing
  342. if isNotRecurringNestStruct(refTypeName, structStacks) {
  343. structStacks = append(structStacks, refTypeName)
  344. parser.parseTypeSpec(pkgName, typeSpec, properties)
  345. }
  346. structStacks = []string{}
  347. requiredFields := make([]string, 0)
  348. for k, prop := range properties {
  349. // todo find the pkgName of the property type
  350. tname := prop.SchemaProps.Type[0]
  351. if _, ok := parser.TypeDefinitions[pkgName][tname]; ok {
  352. tspec := parser.TypeDefinitions[pkgName][tname]
  353. parser.ParseDefinition(pkgName, tspec, tname)
  354. }
  355. if tname != "object" {
  356. requiredFields = append(requiredFields, prop.SchemaProps.Required...)
  357. prop.SchemaProps.Required = make([]string, 0)
  358. }
  359. properties[k] = prop
  360. }
  361. log.Println("Generating " + refTypeName)
  362. parser.swagger.Definitions[refTypeName] = spec.Schema{
  363. SchemaProps: spec.SchemaProps{
  364. Type: []string{"object"},
  365. Properties: properties,
  366. Required: requiredFields,
  367. },
  368. }
  369. }
  370. func (parser *Parser) parseTypeSpec(pkgName string, typeSpec *ast.TypeSpec, properties map[string]spec.Schema) {
  371. switch typeSpec.Type.(type) {
  372. case *ast.StructType:
  373. structDecl := typeSpec.Type.(*ast.StructType)
  374. fields := structDecl.Fields.List
  375. for _, field := range fields {
  376. if field.Names == nil { //anonymous field
  377. parser.parseAnonymousField(pkgName, field, properties)
  378. } else {
  379. props := parser.parseStruct(pkgName, field)
  380. for k, v := range props {
  381. properties[k] = v
  382. }
  383. }
  384. }
  385. case *ast.ArrayType:
  386. log.Panic("ParseDefinitions not supported 'Array' yet.")
  387. case *ast.InterfaceType:
  388. log.Panic("ParseDefinitions not supported 'Interface' yet.")
  389. case *ast.MapType:
  390. log.Panic("ParseDefinitions not supported 'Map' yet.")
  391. }
  392. }
  393. type structField struct {
  394. name string
  395. schemaType string
  396. arrayType string
  397. formatType string
  398. isRequired bool
  399. exampleValue interface{}
  400. }
  401. func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties map[string]spec.Schema) {
  402. properties = map[string]spec.Schema{}
  403. // name, schemaType, arrayType, formatType, exampleValue :=
  404. structField := parser.parseField(field)
  405. if structField.name == "" {
  406. return
  407. }
  408. var desc string
  409. if field.Doc != nil {
  410. desc = field.Doc.Text()
  411. }
  412. // TODO: find package of schemaType and/or arrayType
  413. if _, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field
  414. // write definition if not yet present
  415. parser.ParseDefinition(pkgName, parser.TypeDefinitions[pkgName][structField.schemaType], structField.schemaType)
  416. properties[structField.name] = spec.Schema{
  417. SchemaProps: spec.SchemaProps{
  418. Type: []string{"object"}, // to avoid swagger validation error
  419. Description: desc,
  420. Ref: spec.Ref{
  421. Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType),
  422. },
  423. },
  424. }
  425. } else if structField.schemaType == "array" { // array field type
  426. // if defined -- ref it
  427. if _, ok := parser.TypeDefinitions[pkgName][structField.arrayType]; ok { // user type in array
  428. parser.ParseDefinition(pkgName, parser.TypeDefinitions[pkgName][structField.arrayType], structField.arrayType)
  429. properties[structField.name] = spec.Schema{
  430. SchemaProps: spec.SchemaProps{
  431. Type: []string{structField.schemaType},
  432. Description: desc,
  433. Items: &spec.SchemaOrArray{
  434. Schema: &spec.Schema{
  435. SchemaProps: spec.SchemaProps{
  436. Ref: spec.Ref{
  437. Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.arrayType),
  438. },
  439. },
  440. },
  441. },
  442. },
  443. }
  444. } else { // standard type in array
  445. required := make([]string, 0)
  446. if structField.isRequired {
  447. required = append(required, structField.name)
  448. }
  449. properties[structField.name] = spec.Schema{
  450. SchemaProps: spec.SchemaProps{
  451. Type: []string{structField.schemaType},
  452. Description: desc,
  453. Format: structField.formatType,
  454. Required: required,
  455. Items: &spec.SchemaOrArray{
  456. Schema: &spec.Schema{
  457. SchemaProps: spec.SchemaProps{
  458. Type: []string{structField.arrayType},
  459. },
  460. },
  461. },
  462. },
  463. SwaggerSchemaProps: spec.SwaggerSchemaProps{
  464. Example: structField.exampleValue,
  465. },
  466. }
  467. }
  468. } else {
  469. required := make([]string, 0)
  470. if structField.isRequired {
  471. required = append(required, structField.name)
  472. }
  473. properties[structField.name] = spec.Schema{
  474. SchemaProps: spec.SchemaProps{
  475. Type: []string{structField.schemaType},
  476. Description: desc,
  477. Format: structField.formatType,
  478. Required: required,
  479. },
  480. SwaggerSchemaProps: spec.SwaggerSchemaProps{
  481. Example: structField.exampleValue,
  482. },
  483. }
  484. nestStruct, ok := field.Type.(*ast.StructType)
  485. if ok {
  486. props := map[string]spec.Schema{}
  487. nestRequired := make([]string, 0)
  488. for _, v := range nestStruct.Fields.List {
  489. p := parser.parseStruct(pkgName, v)
  490. for k, v := range p {
  491. if v.SchemaProps.Type[0] != "object" {
  492. nestRequired = append(nestRequired, v.SchemaProps.Required...)
  493. v.SchemaProps.Required = make([]string, 0)
  494. }
  495. props[k] = v
  496. }
  497. }
  498. properties[structField.name] = spec.Schema{
  499. SchemaProps: spec.SchemaProps{
  500. Type: []string{structField.schemaType},
  501. Description: desc,
  502. Format: structField.formatType,
  503. Properties: props,
  504. Required: nestRequired,
  505. },
  506. SwaggerSchemaProps: spec.SwaggerSchemaProps{
  507. Example: structField.exampleValue,
  508. },
  509. }
  510. }
  511. }
  512. return
  513. }
  514. func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field, properties map[string]spec.Schema) {
  515. if astTypeIdent, ok := field.Type.(*ast.Ident); ok {
  516. findPgkName := pkgName
  517. findBaseTypeName := astTypeIdent.Name
  518. ss := strings.Split(astTypeIdent.Name, ".")
  519. if len(ss) > 1 {
  520. findPgkName = ss[0]
  521. findBaseTypeName = ss[1]
  522. }
  523. baseTypeSpec := parser.TypeDefinitions[findPgkName][findBaseTypeName]
  524. parser.parseTypeSpec(findPgkName, baseTypeSpec, properties)
  525. }
  526. }
  527. func (parser *Parser) parseField(field *ast.Field) *structField {
  528. prop := getPropertyName(field)
  529. if len(prop.ArrayType) == 0 {
  530. CheckSchemaType(prop.SchemaType)
  531. } else {
  532. CheckSchemaType("array")
  533. }
  534. structField := &structField{
  535. name: field.Names[0].Name,
  536. schemaType: prop.SchemaType,
  537. arrayType: prop.ArrayType,
  538. }
  539. if parser.PropNamingStrategy == "snakecase" {
  540. // snakecase
  541. structField.name = toSnakeCase(structField.name)
  542. } else if parser.PropNamingStrategy != "uppercamelcase" {
  543. // default
  544. structField.name = toLowerCamelCase(structField.name)
  545. }
  546. if field.Tag == nil {
  547. return structField
  548. }
  549. // `json:"tag"` -> json:"tag"
  550. structTag := strings.Replace(field.Tag.Value, "`", "", -1)
  551. jsonTag := reflect.StructTag(structTag).Get("json")
  552. // json:"tag,hoge"
  553. if strings.Contains(jsonTag, ",") {
  554. // json:",hoge"
  555. if strings.HasPrefix(jsonTag, ",") {
  556. jsonTag = ""
  557. } else {
  558. jsonTag = strings.SplitN(jsonTag, ",", 2)[0]
  559. }
  560. }
  561. if jsonTag == "-" {
  562. structField.name = ""
  563. } else if jsonTag != "" {
  564. structField.name = jsonTag
  565. }
  566. exampleTag := reflect.StructTag(structTag).Get("example")
  567. if exampleTag != "" {
  568. structField.exampleValue = defineTypeOfExample(structField.schemaType, exampleTag)
  569. }
  570. formatTag := reflect.StructTag(structTag).Get("format")
  571. if formatTag != "" {
  572. structField.formatType = formatTag
  573. }
  574. bindingTag := reflect.StructTag(structTag).Get("binding")
  575. if bindingTag != "" {
  576. for _, val := range strings.Split(bindingTag, ",") {
  577. if val == "required" {
  578. structField.isRequired = true
  579. break
  580. }
  581. }
  582. }
  583. validateTag := reflect.StructTag(structTag).Get("validate")
  584. if validateTag != "" {
  585. for _, val := range strings.Split(validateTag, ",") {
  586. if val == "required" {
  587. structField.isRequired = true
  588. break
  589. }
  590. }
  591. }
  592. return structField
  593. }
  594. func toSnakeCase(in string) string {
  595. runes := []rune(in)
  596. length := len(runes)
  597. var out []rune
  598. for i := 0; i < length; i++ {
  599. if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
  600. out = append(out, '_')
  601. }
  602. out = append(out, unicode.ToLower(runes[i]))
  603. }
  604. return string(out)
  605. }
  606. func toLowerCamelCase(in string) string {
  607. runes := []rune(in)
  608. var out []rune
  609. flag := false
  610. for i, curr := range runes {
  611. if (i == 0 && unicode.IsUpper(curr)) || (flag && unicode.IsUpper(curr)) {
  612. out = append(out, unicode.ToLower(curr))
  613. flag = true
  614. } else {
  615. out = append(out, curr)
  616. flag = false
  617. }
  618. }
  619. return string(out)
  620. }
  621. // defineTypeOfExample example value define the type (object and array unsupported)
  622. func defineTypeOfExample(schemaType string, exampleValue string) interface{} {
  623. switch schemaType {
  624. case "string":
  625. return exampleValue
  626. case "number":
  627. v, err := strconv.ParseFloat(exampleValue, 64)
  628. if err != nil {
  629. panic(fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err))
  630. }
  631. return v
  632. case "integer":
  633. v, err := strconv.Atoi(exampleValue)
  634. if err != nil {
  635. panic(fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err))
  636. }
  637. return v
  638. case "boolean":
  639. v, err := strconv.ParseBool(exampleValue)
  640. if err != nil {
  641. panic(fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err))
  642. }
  643. return v
  644. case "array":
  645. return strings.Split(exampleValue, ",")
  646. default:
  647. panic(fmt.Errorf("%s is unsupported type in example value", schemaType))
  648. }
  649. }
  650. // GetAllGoFileInfo gets all Go source files information for given searchDir.
  651. func (parser *Parser) getAllGoFileInfo(searchDir string) {
  652. filepath.Walk(searchDir, parser.visit)
  653. }
  654. func (parser *Parser) visit(path string, f os.FileInfo, err error) error {
  655. if err := Skip(f); err != nil {
  656. return err
  657. }
  658. if ext := filepath.Ext(path); ext == ".go" {
  659. fset := token.NewFileSet() // positions are relative to fset
  660. astFile, err := goparser.ParseFile(fset, path, nil, goparser.ParseComments)
  661. if err != nil {
  662. log.Panicf("ParseFile panic:%+v", err)
  663. }
  664. parser.files[path] = astFile
  665. }
  666. return nil
  667. }
  668. // Skip returns filepath.SkipDir error if match vendor and hidden folder
  669. func Skip(f os.FileInfo) error {
  670. // exclude vendor folder
  671. if f.IsDir() && f.Name() == "vendor" {
  672. return filepath.SkipDir
  673. }
  674. // exclude all hidden folder
  675. if f.IsDir() && len(f.Name()) > 1 && f.Name()[0] == '.' {
  676. return filepath.SkipDir
  677. }
  678. return nil
  679. }
  680. // GetSwagger returns *spec.Swagger which is the root document object for the API specification.
  681. func (parser *Parser) GetSwagger() *spec.Swagger {
  682. return parser.swagger
  683. }