openapi.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package generators
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "path/filepath"
  19. "reflect"
  20. "sort"
  21. "strings"
  22. "k8s.io/gengo/generator"
  23. "k8s.io/gengo/namer"
  24. "k8s.io/gengo/types"
  25. openapi "k8s.io/kube-openapi/pkg/common"
  26. "k8s.io/klog"
  27. )
  28. // This is the comment tag that carries parameters for open API generation.
  29. const tagName = "k8s:openapi-gen"
  30. const tagOptional = "optional"
  31. // Known values for the tag.
  32. const (
  33. tagValueTrue = "true"
  34. tagValueFalse = "false"
  35. )
  36. // Used for temporary validation of patch struct tags.
  37. // TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
  38. var tempPatchTags = [...]string{
  39. "patchMergeKey",
  40. "patchStrategy",
  41. }
  42. func getOpenAPITagValue(comments []string) []string {
  43. return types.ExtractCommentTags("+", comments)[tagName]
  44. }
  45. func getSingleTagsValue(comments []string, tag string) (string, error) {
  46. tags, ok := types.ExtractCommentTags("+", comments)[tag]
  47. if !ok || len(tags) == 0 {
  48. return "", nil
  49. }
  50. if len(tags) > 1 {
  51. return "", fmt.Errorf("multiple values are not allowed for tag %s", tag)
  52. }
  53. return tags[0], nil
  54. }
  55. func hasOpenAPITagValue(comments []string, value string) bool {
  56. tagValues := getOpenAPITagValue(comments)
  57. for _, val := range tagValues {
  58. if val == value {
  59. return true
  60. }
  61. }
  62. return false
  63. }
  64. // hasOptionalTag returns true if the member has +optional in its comments or
  65. // omitempty in its json tags.
  66. func hasOptionalTag(m *types.Member) bool {
  67. hasOptionalCommentTag := types.ExtractCommentTags(
  68. "+", m.CommentLines)[tagOptional] != nil
  69. hasOptionalJsonTag := strings.Contains(
  70. reflect.StructTag(m.Tags).Get("json"), "omitempty")
  71. return hasOptionalCommentTag || hasOptionalJsonTag
  72. }
  73. func apiTypeFilterFunc(c *generator.Context, t *types.Type) bool {
  74. // There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
  75. if strings.HasPrefix(t.Name.Name, "codecSelfer") {
  76. return false
  77. }
  78. pkg := c.Universe.Package(t.Name.Package)
  79. if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
  80. return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
  81. }
  82. if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
  83. return true
  84. }
  85. return false
  86. }
  87. const (
  88. specPackagePath = "github.com/go-openapi/spec"
  89. openAPICommonPackagePath = "k8s.io/kube-openapi/pkg/common"
  90. )
  91. // openApiGen produces a file with auto-generated OpenAPI functions.
  92. type openAPIGen struct {
  93. generator.DefaultGen
  94. // TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
  95. targetPackage string
  96. imports namer.ImportTracker
  97. }
  98. func newOpenAPIGen(sanitizedName string, targetPackage string) generator.Generator {
  99. return &openAPIGen{
  100. DefaultGen: generator.DefaultGen{
  101. OptionalName: sanitizedName,
  102. },
  103. imports: generator.NewImportTracker(),
  104. targetPackage: targetPackage,
  105. }
  106. }
  107. const nameTmpl = "schema_$.type|private$"
  108. func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
  109. // Have the raw namer for this file track what it imports.
  110. return namer.NameSystems{
  111. "raw": namer.NewRawNamer(g.targetPackage, g.imports),
  112. "private": &namer.NameStrategy{
  113. Join: func(pre string, in []string, post string) string {
  114. return strings.Join(in, "_")
  115. },
  116. PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/...
  117. },
  118. }
  119. }
  120. func (g *openAPIGen) isOtherPackage(pkg string) bool {
  121. if pkg == g.targetPackage {
  122. return false
  123. }
  124. if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") {
  125. return false
  126. }
  127. return true
  128. }
  129. func (g *openAPIGen) Imports(c *generator.Context) []string {
  130. importLines := []string{}
  131. for _, singleImport := range g.imports.ImportLines() {
  132. importLines = append(importLines, singleImport)
  133. }
  134. return importLines
  135. }
  136. func argsFromType(t *types.Type) generator.Args {
  137. return generator.Args{
  138. "type": t,
  139. "ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"),
  140. "OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
  141. "SpecSchemaType": types.Ref(specPackagePath, "Schema"),
  142. }
  143. }
  144. func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
  145. sw := generator.NewSnippetWriter(w, c, "$", "$")
  146. sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil))
  147. sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
  148. for _, t := range c.Order {
  149. err := newOpenAPITypeWriter(sw, c).generateCall(t)
  150. if err != nil {
  151. return err
  152. }
  153. }
  154. sw.Do("}\n", nil)
  155. sw.Do("}\n\n", nil)
  156. return sw.Error()
  157. }
  158. func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
  159. klog.V(5).Infof("generating for type %v", t)
  160. sw := generator.NewSnippetWriter(w, c, "$", "$")
  161. err := newOpenAPITypeWriter(sw, c).generate(t)
  162. if err != nil {
  163. return err
  164. }
  165. return sw.Error()
  166. }
  167. func getJsonTags(m *types.Member) []string {
  168. jsonTag := reflect.StructTag(m.Tags).Get("json")
  169. if jsonTag == "" {
  170. return []string{}
  171. }
  172. return strings.Split(jsonTag, ",")
  173. }
  174. func getReferableName(m *types.Member) string {
  175. jsonTags := getJsonTags(m)
  176. if len(jsonTags) > 0 {
  177. if jsonTags[0] == "-" {
  178. return ""
  179. } else {
  180. return jsonTags[0]
  181. }
  182. } else {
  183. return m.Name
  184. }
  185. }
  186. func shouldInlineMembers(m *types.Member) bool {
  187. jsonTags := getJsonTags(m)
  188. return len(jsonTags) > 1 && jsonTags[1] == "inline"
  189. }
  190. type openAPITypeWriter struct {
  191. *generator.SnippetWriter
  192. context *generator.Context
  193. refTypes map[string]*types.Type
  194. GetDefinitionInterface *types.Type
  195. }
  196. func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) openAPITypeWriter {
  197. return openAPITypeWriter{
  198. SnippetWriter: sw,
  199. context: c,
  200. refTypes: map[string]*types.Type{},
  201. }
  202. }
  203. func methodReturnsValue(mt *types.Type, pkg, name string) bool {
  204. if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
  205. return false
  206. }
  207. r := mt.Signature.Results[0]
  208. return r.Name.Name == name && r.Name.Package == pkg
  209. }
  210. func hasOpenAPIV3DefinitionMethod(t *types.Type) bool {
  211. for mn, mt := range t.Methods {
  212. if mn != "OpenAPIV3Definition" {
  213. continue
  214. }
  215. return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
  216. }
  217. return false
  218. }
  219. func hasOpenAPIDefinitionMethod(t *types.Type) bool {
  220. for mn, mt := range t.Methods {
  221. if mn != "OpenAPIDefinition" {
  222. continue
  223. }
  224. return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
  225. }
  226. return false
  227. }
  228. func hasOpenAPIDefinitionMethods(t *types.Type) bool {
  229. var hasSchemaTypeMethod, hasOpenAPISchemaFormat bool
  230. for mn, mt := range t.Methods {
  231. switch mn {
  232. case "OpenAPISchemaType":
  233. hasSchemaTypeMethod = methodReturnsValue(mt, "", "[]string")
  234. case "OpenAPISchemaFormat":
  235. hasOpenAPISchemaFormat = methodReturnsValue(mt, "", "string")
  236. }
  237. }
  238. return hasSchemaTypeMethod && hasOpenAPISchemaFormat
  239. }
  240. // typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name.
  241. func typeShortName(t *types.Type) string {
  242. return filepath.Base(t.Name.Package) + "." + t.Name.Name
  243. }
  244. func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) {
  245. var err error
  246. for _, m := range t.Members {
  247. if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
  248. continue
  249. }
  250. if shouldInlineMembers(&m) {
  251. required, err = g.generateMembers(m.Type, required)
  252. if err != nil {
  253. return required, err
  254. }
  255. continue
  256. }
  257. name := getReferableName(&m)
  258. if name == "" {
  259. continue
  260. }
  261. if !hasOptionalTag(&m) {
  262. required = append(required, name)
  263. }
  264. if err = g.generateProperty(&m, t); err != nil {
  265. klog.Errorf("Error when generating: %v, %v\n", name, m)
  266. return required, err
  267. }
  268. }
  269. return required, nil
  270. }
  271. func (g openAPITypeWriter) generateCall(t *types.Type) error {
  272. // Only generate for struct type and ignore the rest
  273. switch t.Kind {
  274. case types.Struct:
  275. args := argsFromType(t)
  276. g.Do("\"$.$\": ", t.Name)
  277. hasV2Definition := hasOpenAPIDefinitionMethod(t)
  278. hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
  279. hasV3Definition := hasOpenAPIV3DefinitionMethod(t)
  280. switch {
  281. case hasV2DefinitionTypeAndFormat:
  282. g.Do(nameTmpl+"(ref),\n", args)
  283. case hasV2Definition && hasV3Definition:
  284. g.Do("common.EmbedOpenAPIDefinitionIntoV2Extension($.type|raw${}.OpenAPIV3Definition(), $.type|raw${}.OpenAPIDefinition()),\n", args)
  285. case hasV2Definition:
  286. g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
  287. case hasV3Definition:
  288. g.Do("$.type|raw${}.OpenAPIV3Definition(),\n", args)
  289. default:
  290. g.Do(nameTmpl+"(ref),\n", args)
  291. }
  292. }
  293. return g.Error()
  294. }
  295. func (g openAPITypeWriter) generate(t *types.Type) error {
  296. // Only generate for struct type and ignore the rest
  297. switch t.Kind {
  298. case types.Struct:
  299. hasV2Definition := hasOpenAPIDefinitionMethod(t)
  300. hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
  301. hasV3Definition := hasOpenAPIV3DefinitionMethod(t)
  302. if hasV2Definition || (hasV3Definition && !hasV2DefinitionTypeAndFormat) {
  303. // already invoked directly
  304. return nil
  305. }
  306. args := argsFromType(t)
  307. g.Do("func "+nameTmpl+"(ref $.ReferenceCallback|raw$) $.OpenAPIDefinition|raw$ {\n", args)
  308. switch {
  309. case hasV2DefinitionTypeAndFormat && hasV3Definition:
  310. g.Do("return common.EmbedOpenAPIDefinitionIntoV2Extension($.type|raw${}.OpenAPIV3Definition(), $.OpenAPIDefinition|raw${\n"+
  311. "Schema: spec.Schema{\n"+
  312. "SchemaProps: spec.SchemaProps{\n", args)
  313. g.generateDescription(t.CommentLines)
  314. g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
  315. "Format:$.type|raw${}.OpenAPISchemaFormat(),\n"+
  316. "},\n"+
  317. "},\n"+
  318. "})\n}\n\n", args)
  319. return nil
  320. case hasV2DefinitionTypeAndFormat:
  321. g.Do("return $.OpenAPIDefinition|raw${\n"+
  322. "Schema: spec.Schema{\n"+
  323. "SchemaProps: spec.SchemaProps{\n", args)
  324. g.generateDescription(t.CommentLines)
  325. g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
  326. "Format:$.type|raw${}.OpenAPISchemaFormat(),\n"+
  327. "},\n"+
  328. "},\n"+
  329. "}\n}\n\n", args)
  330. return nil
  331. }
  332. g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
  333. g.generateDescription(t.CommentLines)
  334. g.Do("Type: []string{\"object\"},\n", nil)
  335. // write members into a temporary buffer, in order to postpone writing out the Properties field. We only do
  336. // that if it is not empty.
  337. propertiesBuf := bytes.Buffer{}
  338. bsw := g
  339. bsw.SnippetWriter = generator.NewSnippetWriter(&propertiesBuf, g.context, "$", "$")
  340. required, err := bsw.generateMembers(t, []string{})
  341. if err != nil {
  342. return err
  343. }
  344. if propertiesBuf.Len() > 0 {
  345. g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
  346. g.Do(strings.Replace(propertiesBuf.String(), "$", "$\"$\"$", -1), nil) // escape $ (used as delimiter of the templates)
  347. g.Do("},\n", nil)
  348. }
  349. if len(required) > 0 {
  350. g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
  351. }
  352. g.Do("},\n", nil)
  353. if err := g.generateStructExtensions(t); err != nil {
  354. return err
  355. }
  356. g.Do("},\n", nil)
  357. // Map order is undefined, sort them or we may get a different file generated each time.
  358. keys := []string{}
  359. for k := range g.refTypes {
  360. keys = append(keys, k)
  361. }
  362. sort.Strings(keys)
  363. deps := []string{}
  364. for _, k := range keys {
  365. v := g.refTypes[k]
  366. if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" {
  367. // This is a known type, we do not need a reference to it
  368. // Will eliminate special case of time.Time
  369. continue
  370. }
  371. deps = append(deps, k)
  372. }
  373. if len(deps) > 0 {
  374. g.Do("Dependencies: []string{\n", args)
  375. for _, k := range deps {
  376. g.Do("\"$.$\",", k)
  377. }
  378. g.Do("},\n", nil)
  379. }
  380. g.Do("}\n}\n\n", nil)
  381. }
  382. return nil
  383. }
  384. func (g openAPITypeWriter) generateStructExtensions(t *types.Type) error {
  385. extensions, errors := parseExtensions(t.CommentLines)
  386. // Initially, we will only log struct extension errors.
  387. if len(errors) > 0 {
  388. for _, e := range errors {
  389. klog.Errorf("[%s]: %s\n", t.String(), e)
  390. }
  391. }
  392. unions, errors := parseUnions(t)
  393. if len(errors) > 0 {
  394. for _, e := range errors {
  395. klog.Errorf("[%s]: %s\n", t.String(), e)
  396. }
  397. }
  398. // TODO(seans3): Validate struct extensions here.
  399. g.emitExtensions(extensions, unions)
  400. return nil
  401. }
  402. func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type) error {
  403. extensions, parseErrors := parseExtensions(m.CommentLines)
  404. validationErrors := validateMemberExtensions(extensions, m)
  405. errors := append(parseErrors, validationErrors...)
  406. // Initially, we will only log member extension errors.
  407. if len(errors) > 0 {
  408. errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String())
  409. for _, e := range errors {
  410. klog.V(2).Infof("%s %s\n", errorPrefix, e)
  411. }
  412. }
  413. g.emitExtensions(extensions, nil)
  414. return nil
  415. }
  416. func (g openAPITypeWriter) emitExtensions(extensions []extension, unions []union) {
  417. // If any extensions exist, then emit code to create them.
  418. if len(extensions) == 0 && len(unions) == 0 {
  419. return
  420. }
  421. g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
  422. for _, extension := range extensions {
  423. g.Do("\"$.$\": ", extension.xName)
  424. if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
  425. g.Do("[]interface{}{\n", nil)
  426. }
  427. for _, value := range extension.values {
  428. g.Do("\"$.$\",\n", value)
  429. }
  430. if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
  431. g.Do("},\n", nil)
  432. }
  433. }
  434. if len(unions) > 0 {
  435. g.Do("\"x-kubernetes-unions\": []interface{}{\n", nil)
  436. for _, u := range unions {
  437. u.emit(g)
  438. }
  439. g.Do("},\n", nil)
  440. }
  441. g.Do("},\n},\n", nil)
  442. }
  443. // TODO(#44005): Move this validation outside of this generator (probably to policy verifier)
  444. func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
  445. // TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
  446. for _, tagKey := range tempPatchTags {
  447. structTagValue := reflect.StructTag(m.Tags).Get(tagKey)
  448. commentTagValue, err := getSingleTagsValue(m.CommentLines, tagKey)
  449. if err != nil {
  450. return err
  451. }
  452. if structTagValue != commentTagValue {
  453. return fmt.Errorf("Tags in comment and struct should match for member (%s) of (%s)",
  454. m.Name, parent.Name.String())
  455. }
  456. }
  457. return nil
  458. }
  459. func (g openAPITypeWriter) generateDescription(CommentLines []string) {
  460. var buffer bytes.Buffer
  461. delPrevChar := func() {
  462. if buffer.Len() > 0 {
  463. buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
  464. }
  465. }
  466. for _, line := range CommentLines {
  467. // Ignore all lines after ---
  468. if line == "---" {
  469. break
  470. }
  471. line = strings.TrimRight(line, " ")
  472. leading := strings.TrimLeft(line, " ")
  473. switch {
  474. case len(line) == 0: // Keep paragraphs
  475. delPrevChar()
  476. buffer.WriteString("\n\n")
  477. case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
  478. case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
  479. default:
  480. if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
  481. delPrevChar()
  482. line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
  483. } else {
  484. line += " "
  485. }
  486. buffer.WriteString(line)
  487. }
  488. }
  489. postDoc := strings.TrimRight(buffer.String(), "\n")
  490. postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
  491. postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
  492. postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
  493. postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
  494. postDoc = strings.Trim(postDoc, " ")
  495. if postDoc != "" {
  496. g.Do("Description: \"$.$\",\n", postDoc)
  497. }
  498. }
  499. func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error {
  500. name := getReferableName(m)
  501. if name == "" {
  502. return nil
  503. }
  504. if err := g.validatePatchTags(m, parent); err != nil {
  505. return err
  506. }
  507. g.Do("\"$.$\": {\n", name)
  508. if err := g.generateMemberExtensions(m, parent); err != nil {
  509. return err
  510. }
  511. g.Do("SchemaProps: spec.SchemaProps{\n", nil)
  512. g.generateDescription(m.CommentLines)
  513. jsonTags := getJsonTags(m)
  514. if len(jsonTags) > 1 && jsonTags[1] == "string" {
  515. g.generateSimpleProperty("string", "")
  516. g.Do("},\n},\n", nil)
  517. return nil
  518. }
  519. t := resolveAliasAndPtrType(m.Type)
  520. // If we can get a openAPI type and format for this type, we consider it to be simple property
  521. typeString, format := openapi.GetOpenAPITypeFormat(t.String())
  522. if typeString != "" {
  523. g.generateSimpleProperty(typeString, format)
  524. g.Do("},\n},\n", nil)
  525. return nil
  526. }
  527. switch t.Kind {
  528. case types.Builtin:
  529. return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
  530. case types.Map:
  531. if err := g.generateMapProperty(t); err != nil {
  532. return err
  533. }
  534. case types.Slice, types.Array:
  535. if err := g.generateSliceProperty(t); err != nil {
  536. return err
  537. }
  538. case types.Struct, types.Interface:
  539. g.generateReferenceProperty(t)
  540. default:
  541. return fmt.Errorf("cannot generate spec for type %v", t)
  542. }
  543. g.Do("},\n},\n", nil)
  544. return g.Error()
  545. }
  546. func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
  547. g.Do("Type: []string{\"$.$\"},\n", typeString)
  548. g.Do("Format: \"$.$\",\n", format)
  549. }
  550. func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
  551. g.refTypes[t.Name.String()] = t
  552. g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
  553. }
  554. func resolveAliasAndPtrType(t *types.Type) *types.Type {
  555. var prev *types.Type
  556. for prev != t {
  557. prev = t
  558. if t.Kind == types.Alias {
  559. t = t.Underlying
  560. }
  561. if t.Kind == types.Pointer {
  562. t = t.Elem
  563. }
  564. }
  565. return t
  566. }
  567. func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
  568. keyType := resolveAliasAndPtrType(t.Key)
  569. elemType := resolveAliasAndPtrType(t.Elem)
  570. // According to OpenAPI examples, only map from string is supported
  571. if keyType.Name.Name != "string" {
  572. return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
  573. }
  574. g.Do("Type: []string{\"object\"},\n", nil)
  575. g.Do("AdditionalProperties: &spec.SchemaOrBool{\nAllows: true,\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
  576. typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
  577. if typeString != "" {
  578. g.generateSimpleProperty(typeString, format)
  579. g.Do("},\n},\n},\n", nil)
  580. return nil
  581. }
  582. switch elemType.Kind {
  583. case types.Builtin:
  584. return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
  585. case types.Struct:
  586. g.generateReferenceProperty(elemType)
  587. case types.Slice, types.Array:
  588. if err := g.generateSliceProperty(elemType); err != nil {
  589. return err
  590. }
  591. case types.Map:
  592. if err := g.generateMapProperty(elemType); err != nil {
  593. return err
  594. }
  595. default:
  596. return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
  597. }
  598. g.Do("},\n},\n},\n", nil)
  599. return nil
  600. }
  601. func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
  602. elemType := resolveAliasAndPtrType(t.Elem)
  603. g.Do("Type: []string{\"array\"},\n", nil)
  604. g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
  605. typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
  606. if typeString != "" {
  607. g.generateSimpleProperty(typeString, format)
  608. g.Do("},\n},\n},\n", nil)
  609. return nil
  610. }
  611. switch elemType.Kind {
  612. case types.Builtin:
  613. return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
  614. case types.Struct:
  615. g.generateReferenceProperty(elemType)
  616. case types.Slice, types.Array:
  617. if err := g.generateSliceProperty(elemType); err != nil {
  618. return err
  619. }
  620. case types.Map:
  621. if err := g.generateMapProperty(elemType); err != nil {
  622. return err
  623. }
  624. default:
  625. return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
  626. }
  627. g.Do("},\n},\n},\n", nil)
  628. return nil
  629. }