You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1757 lines
52 KiB

  1. // Copyright 2015 go-swagger maintainers
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package analysis
  15. import (
  16. "fmt"
  17. "log"
  18. "net/http"
  19. "net/url"
  20. "os"
  21. slashpath "path"
  22. "path/filepath"
  23. "sort"
  24. "strings"
  25. "strconv"
  26. "github.com/go-openapi/analysis/internal"
  27. "github.com/go-openapi/jsonpointer"
  28. swspec "github.com/go-openapi/spec"
  29. "github.com/go-openapi/swag"
  30. )
  31. // FlattenOpts configuration for flattening a swagger specification.
  32. type FlattenOpts struct {
  33. Spec *Spec // The analyzed spec to work with
  34. flattenContext *context // Internal context to track flattening activity
  35. BasePath string
  36. // Flattening options
  37. Expand bool // If Expand is true, we skip flattening the spec and expand it instead
  38. Minimal bool
  39. Verbose bool
  40. RemoveUnused bool
  41. ContinueOnError bool // Continues when facing some issues
  42. /* Extra keys */
  43. _ struct{} // require keys
  44. }
  45. // ExpandOpts creates a spec.ExpandOptions to configure expanding a specification document.
  46. func (f *FlattenOpts) ExpandOpts(skipSchemas bool) *swspec.ExpandOptions {
  47. return &swspec.ExpandOptions{RelativeBase: f.BasePath, SkipSchemas: skipSchemas}
  48. }
  49. // Swagger gets the swagger specification for this flatten operation
  50. func (f *FlattenOpts) Swagger() *swspec.Swagger {
  51. return f.Spec.spec
  52. }
  53. // newRef stores information about refs created during the flattening process
  54. type newRef struct {
  55. key string
  56. newName string
  57. path string
  58. isOAIGen bool
  59. resolved bool
  60. schema *swspec.Schema
  61. parents []string
  62. }
  63. // context stores intermediary results from flatten
  64. type context struct {
  65. newRefs map[string]*newRef
  66. warnings []string
  67. resolved map[string]string
  68. }
  69. func newContext() *context {
  70. return &context{
  71. newRefs: make(map[string]*newRef, 150),
  72. warnings: make([]string, 0),
  73. resolved: make(map[string]string, 50),
  74. }
  75. }
  76. // Flatten an analyzed spec and produce a self-contained spec bundle.
  77. //
  78. // There is a minimal and a full flattening mode.
  79. //
  80. // Minimally flattening a spec means:
  81. // - Expanding parameters, responses, path items, parameter items and header items (references to schemas are left
  82. // unscathed)
  83. // - Importing external (http, file) references so they become internal to the document
  84. // - Moving every JSON pointer to a $ref to a named definition (i.e. the reworked spec does not contain pointers
  85. // like "$ref": "#/definitions/myObject/allOfs/1")
  86. //
  87. // A minimally flattened spec thus guarantees the following properties:
  88. // - all $refs point to a local definition (i.e. '#/definitions/...')
  89. // - definitions are unique
  90. //
  91. // NOTE: arbitrary JSON pointers (other than $refs to top level definitions) are rewritten as definitions if they
  92. // represent a complex schema or express commonality in the spec.
  93. // Otherwise, they are simply expanded.
  94. //
  95. // Minimal flattening is necessary and sufficient for codegen rendering using go-swagger.
  96. //
  97. // Fully flattening a spec means:
  98. // - Moving every complex inline schema to be a definition with an auto-generated name in a depth-first fashion.
  99. //
  100. // By complex, we mean every JSON object with some properties.
  101. // Arrays, when they do not define a tuple,
  102. // or empty objects with or without additionalProperties, are not considered complex and remain inline.
  103. //
  104. // NOTE: rewritten schemas get a vendor extension x-go-gen-location so we know from which part of the spec definitions
  105. // have been created.
  106. //
  107. // Available flattening options:
  108. // - Minimal: stops flattening after minimal $ref processing, leaving schema constructs untouched
  109. // - Expand: expand all $ref's in the document (inoperant if Minimal set to true)
  110. // - Verbose: croaks about name conflicts detected
  111. // - RemoveUnused: removes unused parameters, responses and definitions after expansion/flattening
  112. //
  113. // NOTE: expansion removes all $ref save circular $ref, which remain in place
  114. //
  115. // TODO: additional options
  116. // - ProgagateNameExtensions: ensure that created entries properly follow naming rules when their parent have set a
  117. // x-go-name extension
  118. // - LiftAllOfs:
  119. // - limit the flattening of allOf members when simple objects
  120. // - merge allOf with validation only
  121. // - merge allOf with extensions only
  122. // - ...
  123. //
  124. func Flatten(opts FlattenOpts) error {
  125. debugLog("FlattenOpts: %#v", opts)
  126. // Make sure opts.BasePath is an absolute path
  127. if !filepath.IsAbs(opts.BasePath) {
  128. cwd, _ := os.Getwd()
  129. opts.BasePath = filepath.Join(cwd, opts.BasePath)
  130. }
  131. // make sure drive letter on windows is normalized to lower case
  132. u, _ := url.Parse(opts.BasePath)
  133. opts.BasePath = u.String()
  134. opts.flattenContext = newContext()
  135. // recursively expand responses, parameters, path items and items in simple schemas.
  136. // This simplifies the spec and leaves $ref only into schema objects.
  137. expandOpts := opts.ExpandOpts(!opts.Expand)
  138. expandOpts.ContinueOnError = opts.ContinueOnError
  139. if err := swspec.ExpandSpec(opts.Swagger(), expandOpts); err != nil {
  140. return err
  141. }
  142. // strip current file from $ref's, so we can recognize them as proper definitions
  143. // In particular, this works around for issue go-openapi/spec#76: leading absolute file in $ref is stripped
  144. if err := normalizeRef(&opts); err != nil {
  145. return err
  146. }
  147. if opts.RemoveUnused {
  148. // optionally removes shared parameters and responses already expanded (now unused)
  149. // default parameters (i.e. under paths) remain.
  150. opts.Swagger().Parameters = nil
  151. opts.Swagger().Responses = nil
  152. }
  153. opts.Spec.reload() // re-analyze
  154. // at this point there are no references left but in schemas
  155. for imported := false; !imported; {
  156. // iteratively import remote references until none left.
  157. // This inlining deals with name conflicts by introducing auto-generated names ("OAIGen")
  158. var err error
  159. if imported, err = importExternalReferences(&opts); err != nil {
  160. return err
  161. }
  162. opts.Spec.reload() // re-analyze
  163. }
  164. if !opts.Minimal && !opts.Expand {
  165. // full flattening: rewrite inline schemas (schemas that aren't simple types or arrays or maps)
  166. if err := nameInlinedSchemas(&opts); err != nil {
  167. return err
  168. }
  169. opts.Spec.reload() // re-analyze
  170. }
  171. // rewrite JSON pointers other than $ref to named definitions
  172. // and attempt to resolve conflicting names whenever possible.
  173. if err := stripPointersAndOAIGen(&opts); err != nil {
  174. return err
  175. }
  176. if opts.RemoveUnused {
  177. // remove unused definitions
  178. expected := make(map[string]struct{})
  179. for k := range opts.Swagger().Definitions {
  180. expected[slashpath.Join(definitionsPath, jsonpointer.Escape(k))] = struct{}{}
  181. }
  182. for _, k := range opts.Spec.AllDefinitionReferences() {
  183. delete(expected, k)
  184. }
  185. for k := range expected {
  186. debugLog("removing unused definition %s", slashpath.Base(k))
  187. if opts.Verbose {
  188. log.Printf("info: removing unused definition: %s", slashpath.Base(k))
  189. }
  190. delete(opts.Swagger().Definitions, slashpath.Base(k))
  191. }
  192. opts.Spec.reload() // re-analyze
  193. }
  194. // TODO: simplify known schema patterns to flat objects with properties
  195. // examples:
  196. // - lift simple allOf object,
  197. // - empty allOf with validation only or extensions only
  198. // - rework allOf arrays
  199. // - rework allOf additionalProperties
  200. if opts.Verbose {
  201. // issue notifications
  202. croak(&opts)
  203. }
  204. return nil
  205. }
  206. // isAnalyzedAsComplex determines if an analyzed schema is eligible to flattening (i.e. it is "complex").
  207. //
  208. // Complex means the schema is any of:
  209. // - a simple type (primitive)
  210. // - an array of something (items are possibly complex ; if this is the case, items will generate a definition)
  211. // - a map of something (additionalProperties are possibly complex ; if this is the case, additionalProperties will
  212. // generate a definition)
  213. func isAnalyzedAsComplex(asch *AnalyzedSchema) bool {
  214. if !asch.IsSimpleSchema && !asch.IsArray && !asch.IsMap {
  215. return true
  216. }
  217. return false
  218. }
  219. // nameInlinedSchemas replaces every complex inline construct by a named definition.
  220. func nameInlinedSchemas(opts *FlattenOpts) error {
  221. debugLog("nameInlinedSchemas")
  222. namer := &inlineSchemaNamer{
  223. Spec: opts.Swagger(),
  224. Operations: opRefsByRef(gatherOperations(opts.Spec, nil)),
  225. flattenContext: opts.flattenContext,
  226. opts: opts,
  227. }
  228. depthFirst := sortDepthFirst(opts.Spec.allSchemas)
  229. for _, key := range depthFirst {
  230. sch := opts.Spec.allSchemas[key]
  231. if sch.Schema != nil && sch.Schema.Ref.String() == "" && !sch.TopLevel { // inline schema
  232. asch, err := Schema(SchemaOpts{Schema: sch.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
  233. if err != nil {
  234. return fmt.Errorf("schema analysis [%s]: %v", key, err)
  235. }
  236. if isAnalyzedAsComplex(asch) { // move complex schemas to definitions
  237. if err := namer.Name(key, sch.Schema, asch); err != nil {
  238. return err
  239. }
  240. }
  241. }
  242. }
  243. return nil
  244. }
  245. var depthGroupOrder = []string{
  246. "sharedParam", "sharedResponse", "sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition",
  247. }
  248. func sortDepthFirst(data map[string]SchemaRef) []string {
  249. // group by category (shared params, op param, statuscode response, default response, definitions)
  250. // sort groups internally by number of parts in the key and lexical names
  251. // flatten groups into a single list of keys
  252. sorted := make([]string, 0, len(data))
  253. grouped := make(map[string]keys, len(data))
  254. for k := range data {
  255. split := keyParts(k)
  256. var pk string
  257. if split.IsSharedOperationParam() {
  258. pk = "sharedOpParam"
  259. }
  260. if split.IsOperationParam() {
  261. pk = "opParam"
  262. }
  263. if split.IsStatusCodeResponse() {
  264. pk = "codeResponse"
  265. }
  266. if split.IsDefaultResponse() {
  267. pk = "defaultResponse"
  268. }
  269. if split.IsDefinition() {
  270. pk = "definition"
  271. }
  272. if split.IsSharedParam() {
  273. pk = "sharedParam"
  274. }
  275. if split.IsSharedResponse() {
  276. pk = "sharedResponse"
  277. }
  278. grouped[pk] = append(grouped[pk], key{Segments: len(split), Key: k})
  279. }
  280. for _, pk := range depthGroupOrder {
  281. res := grouped[pk]
  282. sort.Sort(res)
  283. for _, v := range res {
  284. sorted = append(sorted, v.Key)
  285. }
  286. }
  287. return sorted
  288. }
  289. type key struct {
  290. Segments int
  291. Key string
  292. }
  293. type keys []key
  294. func (k keys) Len() int { return len(k) }
  295. func (k keys) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
  296. func (k keys) Less(i, j int) bool {
  297. return k[i].Segments > k[j].Segments || (k[i].Segments == k[j].Segments && k[i].Key < k[j].Key)
  298. }
  299. type inlineSchemaNamer struct {
  300. Spec *swspec.Swagger
  301. Operations map[string]opRef
  302. flattenContext *context
  303. opts *FlattenOpts
  304. }
  305. func opRefsByRef(oprefs map[string]opRef) map[string]opRef {
  306. result := make(map[string]opRef, len(oprefs))
  307. for _, v := range oprefs {
  308. result[v.Ref.String()] = v
  309. }
  310. return result
  311. }
  312. func (isn *inlineSchemaNamer) Name(key string, schema *swspec.Schema, aschema *AnalyzedSchema) error {
  313. debugLog("naming inlined schema at %s", key)
  314. parts := keyParts(key)
  315. for _, name := range namesFromKey(parts, aschema, isn.Operations) {
  316. if name != "" {
  317. // create unique name
  318. newName, isOAIGen := uniqifyName(isn.Spec.Definitions, swag.ToJSONName(name))
  319. // clone schema
  320. sch, err := cloneSchema(schema)
  321. if err != nil {
  322. return err
  323. }
  324. // replace values on schema
  325. if err := rewriteSchemaToRef(isn.Spec, key,
  326. swspec.MustCreateRef(slashpath.Join(definitionsPath, newName))); err != nil {
  327. return fmt.Errorf("error while creating definition %q from inline schema: %v", newName, err)
  328. }
  329. // rewrite any dependent $ref pointing to this place,
  330. // when not already pointing to a top-level definition.
  331. //
  332. // NOTE: this is important if such referers use arbitrary JSON pointers.
  333. an := New(isn.Spec)
  334. for k, v := range an.references.allRefs {
  335. r, _, erd := deepestRef(isn.opts, v)
  336. if erd != nil {
  337. return fmt.Errorf("at %s, %v", k, erd)
  338. }
  339. if r.String() == key ||
  340. r.String() == slashpath.Join(definitionsPath, newName) &&
  341. slashpath.Dir(v.String()) != definitionsPath {
  342. debugLog("found a $ref to a rewritten schema: %s points to %s", k, v.String())
  343. // rewrite $ref to the new target
  344. if err := updateRef(isn.Spec, k,
  345. swspec.MustCreateRef(slashpath.Join(definitionsPath, newName))); err != nil {
  346. return err
  347. }
  348. }
  349. }
  350. // NOTE: this extension is currently not used by go-swagger (provided for information only)
  351. sch.AddExtension("x-go-gen-location", genLocation(parts))
  352. // save cloned schema to definitions
  353. saveSchema(isn.Spec, newName, sch)
  354. // keep track of created refs
  355. if isn.flattenContext != nil {
  356. debugLog("track created ref: key=%s, newName=%s, isOAIGen=%t", key, newName, isOAIGen)
  357. resolved := false
  358. if _, ok := isn.flattenContext.newRefs[key]; ok {
  359. resolved = isn.flattenContext.newRefs[key].resolved
  360. }
  361. isn.flattenContext.newRefs[key] = &newRef{
  362. key: key,
  363. newName: newName,
  364. path: slashpath.Join(definitionsPath, newName),
  365. isOAIGen: isOAIGen,
  366. resolved: resolved,
  367. schema: sch,
  368. }
  369. }
  370. }
  371. }
  372. return nil
  373. }
  374. // genLocation indicates from which section of the specification (models or operations) a definition has been created.
  375. //
  376. // This is reflected in the output spec with a "x-go-gen-location" extension. At the moment, this is is provided
  377. // for information only.
  378. func genLocation(parts splitKey) string {
  379. if parts.IsOperation() {
  380. return "operations"
  381. }
  382. if parts.IsDefinition() {
  383. return "models"
  384. }
  385. return ""
  386. }
  387. // uniqifyName yields a unique name for a definition
  388. func uniqifyName(definitions swspec.Definitions, name string) (string, bool) {
  389. isOAIGen := false
  390. if name == "" {
  391. name = "oaiGen"
  392. isOAIGen = true
  393. }
  394. if len(definitions) == 0 {
  395. return name, isOAIGen
  396. }
  397. unq := true
  398. for k := range definitions {
  399. if strings.EqualFold(k, name) {
  400. unq = false
  401. break
  402. }
  403. }
  404. if unq {
  405. return name, isOAIGen
  406. }
  407. name += "OAIGen"
  408. isOAIGen = true
  409. var idx int
  410. unique := name
  411. _, known := definitions[unique]
  412. for known {
  413. idx++
  414. unique = fmt.Sprintf("%s%d", name, idx)
  415. _, known = definitions[unique]
  416. }
  417. return unique, isOAIGen
  418. }
  419. func namesFromKey(parts splitKey, aschema *AnalyzedSchema, operations map[string]opRef) []string {
  420. var baseNames [][]string
  421. var startIndex int
  422. if parts.IsOperation() {
  423. // params
  424. if parts.IsOperationParam() || parts.IsSharedOperationParam() {
  425. piref := parts.PathItemRef()
  426. if piref.String() != "" && parts.IsOperationParam() {
  427. if op, ok := operations[piref.String()]; ok {
  428. startIndex = 5
  429. baseNames = append(baseNames, []string{op.ID, "params", "body"})
  430. }
  431. } else if parts.IsSharedOperationParam() {
  432. pref := parts.PathRef()
  433. for k, v := range operations {
  434. if strings.HasPrefix(k, pref.String()) {
  435. startIndex = 4
  436. baseNames = append(baseNames, []string{v.ID, "params", "body"})
  437. }
  438. }
  439. }
  440. }
  441. // responses
  442. if parts.IsOperationResponse() {
  443. piref := parts.PathItemRef()
  444. if piref.String() != "" {
  445. if op, ok := operations[piref.String()]; ok {
  446. startIndex = 6
  447. baseNames = append(baseNames, []string{op.ID, parts.ResponseName(), "body"})
  448. }
  449. }
  450. }
  451. }
  452. // definitions
  453. if parts.IsDefinition() {
  454. nm := parts.DefinitionName()
  455. if nm != "" {
  456. startIndex = 2
  457. baseNames = append(baseNames, []string{parts.DefinitionName()})
  458. }
  459. }
  460. var result []string
  461. for _, segments := range baseNames {
  462. nm := parts.BuildName(segments, startIndex, aschema)
  463. if nm != "" {
  464. result = append(result, nm)
  465. }
  466. }
  467. sort.Strings(result)
  468. return result
  469. }
  470. const (
  471. paths = "paths"
  472. responses = "responses"
  473. parameters = "parameters"
  474. definitions = "definitions"
  475. definitionsPath = "#/definitions"
  476. )
  477. var (
  478. ignoredKeys map[string]struct{}
  479. validMethods map[string]struct{}
  480. )
  481. func init() {
  482. ignoredKeys = map[string]struct{}{
  483. "schema": {},
  484. "properties": {},
  485. "not": {},
  486. "anyOf": {},
  487. "oneOf": {},
  488. }
  489. validMethods = map[string]struct{}{
  490. "GET": {},
  491. "HEAD": {},
  492. "OPTIONS": {},
  493. "PATCH": {},
  494. "POST": {},
  495. "PUT": {},
  496. "DELETE": {},
  497. }
  498. }
  499. type splitKey []string
  500. func (s splitKey) IsDefinition() bool {
  501. return len(s) > 1 && s[0] == definitions
  502. }
  503. func (s splitKey) DefinitionName() string {
  504. if !s.IsDefinition() {
  505. return ""
  506. }
  507. return s[1]
  508. }
  509. func (s splitKey) isKeyName(i int) bool {
  510. if i <= 0 {
  511. return false
  512. }
  513. count := 0
  514. for idx := i - 1; idx > 0; idx-- {
  515. if s[idx] != "properties" {
  516. break
  517. }
  518. count++
  519. }
  520. return count%2 != 0
  521. }
  522. func (s splitKey) BuildName(segments []string, startIndex int, aschema *AnalyzedSchema) string {
  523. for i, part := range s[startIndex:] {
  524. if _, ignored := ignoredKeys[part]; !ignored || s.isKeyName(startIndex+i) {
  525. if part == "items" || part == "additionalItems" {
  526. if aschema.IsTuple || aschema.IsTupleWithExtra {
  527. segments = append(segments, "tuple")
  528. } else {
  529. segments = append(segments, "items")
  530. }
  531. if part == "additionalItems" {
  532. segments = append(segments, part)
  533. }
  534. continue
  535. }
  536. segments = append(segments, part)
  537. }
  538. }
  539. return strings.Join(segments, " ")
  540. }
  541. func (s splitKey) IsOperation() bool {
  542. return len(s) > 1 && s[0] == paths
  543. }
  544. func (s splitKey) IsSharedOperationParam() bool {
  545. return len(s) > 2 && s[0] == paths && s[2] == parameters
  546. }
  547. func (s splitKey) IsSharedParam() bool {
  548. return len(s) > 1 && s[0] == parameters
  549. }
  550. func (s splitKey) IsOperationParam() bool {
  551. return len(s) > 3 && s[0] == paths && s[3] == parameters
  552. }
  553. func (s splitKey) IsOperationResponse() bool {
  554. return len(s) > 3 && s[0] == paths && s[3] == responses
  555. }
  556. func (s splitKey) IsSharedResponse() bool {
  557. return len(s) > 1 && s[0] == responses
  558. }
  559. func (s splitKey) IsDefaultResponse() bool {
  560. return len(s) > 4 && s[0] == paths && s[3] == responses && s[4] == "default"
  561. }
  562. func (s splitKey) IsStatusCodeResponse() bool {
  563. isInt := func() bool {
  564. _, err := strconv.Atoi(s[4])
  565. return err == nil
  566. }
  567. return len(s) > 4 && s[0] == paths && s[3] == responses && isInt()
  568. }
  569. func (s splitKey) ResponseName() string {
  570. if s.IsStatusCodeResponse() {
  571. code, _ := strconv.Atoi(s[4])
  572. return http.StatusText(code)
  573. }
  574. if s.IsDefaultResponse() {
  575. return "Default"
  576. }
  577. return ""
  578. }
  579. func (s splitKey) PathItemRef() swspec.Ref {
  580. if len(s) < 3 {
  581. return swspec.Ref{}
  582. }
  583. pth, method := s[1], s[2]
  584. if _, isValidMethod := validMethods[strings.ToUpper(method)]; !isValidMethod && !strings.HasPrefix(method, "x-") {
  585. return swspec.Ref{}
  586. }
  587. return swspec.MustCreateRef("#" + slashpath.Join("/", paths, jsonpointer.Escape(pth), strings.ToUpper(method)))
  588. }
  589. func (s splitKey) PathRef() swspec.Ref {
  590. if !s.IsOperation() {
  591. return swspec.Ref{}
  592. }
  593. return swspec.MustCreateRef("#" + slashpath.Join("/", paths, jsonpointer.Escape(s[1])))
  594. }
  595. func keyParts(key string) splitKey {
  596. var res []string
  597. for _, part := range strings.Split(key[1:], "/") {
  598. if part != "" {
  599. res = append(res, jsonpointer.Unescape(part))
  600. }
  601. }
  602. return res
  603. }
  604. func rewriteSchemaToRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
  605. debugLog("rewriting schema to ref for %s with %s", key, ref.String())
  606. _, value, err := getPointerFromKey(spec, key)
  607. if err != nil {
  608. return err
  609. }
  610. switch refable := value.(type) {
  611. case *swspec.Schema:
  612. return rewriteParentRef(spec, key, ref)
  613. case swspec.Schema:
  614. return rewriteParentRef(spec, key, ref)
  615. case *swspec.SchemaOrArray:
  616. if refable.Schema != nil {
  617. refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  618. }
  619. case *swspec.SchemaOrBool:
  620. if refable.Schema != nil {
  621. refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  622. }
  623. default:
  624. return fmt.Errorf("no schema with ref found at %s for %T", key, value)
  625. }
  626. return nil
  627. }
  628. func rewriteParentRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
  629. parent, entry, pvalue, err := getParentFromKey(spec, key)
  630. if err != nil {
  631. return err
  632. }
  633. debugLog("rewriting holder for %T", pvalue)
  634. switch container := pvalue.(type) {
  635. case swspec.Response:
  636. if err := rewriteParentRef(spec, "#"+parent, ref); err != nil {
  637. return err
  638. }
  639. case *swspec.Response:
  640. container.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  641. case *swspec.Responses:
  642. statusCode, err := strconv.Atoi(entry)
  643. if err != nil {
  644. return fmt.Errorf("%s not a number: %v", key[1:], err)
  645. }
  646. resp := container.StatusCodeResponses[statusCode]
  647. resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  648. container.StatusCodeResponses[statusCode] = resp
  649. case map[string]swspec.Response:
  650. resp := container[entry]
  651. resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  652. container[entry] = resp
  653. case swspec.Parameter:
  654. if err := rewriteParentRef(spec, "#"+parent, ref); err != nil {
  655. return err
  656. }
  657. case map[string]swspec.Parameter:
  658. param := container[entry]
  659. param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  660. container[entry] = param
  661. case []swspec.Parameter:
  662. idx, err := strconv.Atoi(entry)
  663. if err != nil {
  664. return fmt.Errorf("%s not a number: %v", key[1:], err)
  665. }
  666. param := container[idx]
  667. param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  668. container[idx] = param
  669. case swspec.Definitions:
  670. container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  671. case map[string]swspec.Schema:
  672. container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  673. case []swspec.Schema:
  674. idx, err := strconv.Atoi(entry)
  675. if err != nil {
  676. return fmt.Errorf("%s not a number: %v", key[1:], err)
  677. }
  678. container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  679. case *swspec.SchemaOrArray:
  680. // NOTE: this is necessarily an array - otherwise, the parent would be *Schema
  681. idx, err := strconv.Atoi(entry)
  682. if err != nil {
  683. return fmt.Errorf("%s not a number: %v", key[1:], err)
  684. }
  685. container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  686. // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema
  687. default:
  688. return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue)
  689. }
  690. return nil
  691. }
  692. func cloneSchema(schema *swspec.Schema) (*swspec.Schema, error) {
  693. var sch swspec.Schema
  694. if err := swag.FromDynamicJSON(schema, &sch); err != nil {
  695. return nil, fmt.Errorf("cannot clone schema: %v", err)
  696. }
  697. return &sch, nil
  698. }
  699. // importExternalReferences iteratively digs remote references and imports them into the main schema.
  700. //
  701. // At every iteration, new remotes may be found when digging deeper: they are rebased to the current schema before being imported.
  702. //
  703. // This returns true when no more remote references can be found.
  704. func importExternalReferences(opts *FlattenOpts) (bool, error) {
  705. debugLog("importExternalReferences")
  706. groupedRefs := reverseIndexForSchemaRefs(opts)
  707. sortedRefStr := make([]string, 0, len(groupedRefs))
  708. if opts.flattenContext == nil {
  709. opts.flattenContext = newContext()
  710. }
  711. // sort $ref resolution to ensure deterministic name conflict resolution
  712. for refStr := range groupedRefs {
  713. sortedRefStr = append(sortedRefStr, refStr)
  714. }
  715. sort.Strings(sortedRefStr)
  716. complete := true
  717. for _, refStr := range sortedRefStr {
  718. entry := groupedRefs[refStr]
  719. if entry.Ref.HasFragmentOnly {
  720. continue
  721. }
  722. complete = false
  723. var isOAIGen bool
  724. newName := opts.flattenContext.resolved[refStr]
  725. if newName != "" {
  726. // rewrite ref with already resolved external ref (useful for cyclical refs):
  727. // rewrite external refs to local ones
  728. debugLog("resolving known ref [%s] to %s", refStr, newName)
  729. for _, key := range entry.Keys {
  730. if err := updateRef(opts.Swagger(), key,
  731. swspec.MustCreateRef(slashpath.Join(definitionsPath, newName))); err != nil {
  732. return false, err
  733. }
  734. }
  735. } else {
  736. // resolve schemas
  737. debugLog("resolving schema from remote $ref [%s]", refStr)
  738. sch, err := swspec.ResolveRefWithBase(opts.Swagger(), &entry.Ref, opts.ExpandOpts(false))
  739. if err != nil {
  740. return false, fmt.Errorf("could not resolve schema: %v", err)
  741. }
  742. // at this stage only $ref analysis matters
  743. partialAnalyzer := &Spec{
  744. references: referenceAnalysis{},
  745. patterns: patternAnalysis{},
  746. enums: enumAnalysis{},
  747. }
  748. partialAnalyzer.reset()
  749. partialAnalyzer.analyzeSchema("", sch, "/")
  750. // now rewrite those refs with rebase
  751. for key, ref := range partialAnalyzer.references.allRefs {
  752. if err := updateRef(sch, key, swspec.MustCreateRef(rebaseRef(entry.Ref.String(), ref.String()))); err != nil {
  753. return false, fmt.Errorf("failed to rewrite ref for key %q at %s: %v", key, entry.Ref.String(), err)
  754. }
  755. }
  756. // generate a unique name - isOAIGen means that a naming conflict was resolved by changing the name
  757. newName, isOAIGen = uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref))
  758. debugLog("new name for [%s]: %s - with name conflict:%t",
  759. strings.Join(entry.Keys, ", "), newName, isOAIGen)
  760. opts.flattenContext.resolved[refStr] = newName
  761. // rewrite the external refs to local ones
  762. for _, key := range entry.Keys {
  763. if err := updateRef(opts.Swagger(), key,
  764. swspec.MustCreateRef(slashpath.Join(definitionsPath, newName))); err != nil {
  765. return false, err
  766. }
  767. // keep track of created refs
  768. resolved := false
  769. if _, ok := opts.flattenContext.newRefs[key]; ok {
  770. resolved = opts.flattenContext.newRefs[key].resolved
  771. }
  772. debugLog("keeping track of ref: %s (%s), resolved: %t", key, newName, resolved)
  773. opts.flattenContext.newRefs[key] = &newRef{
  774. key: key,
  775. newName: newName,
  776. path: slashpath.Join(definitionsPath, newName),
  777. isOAIGen: isOAIGen,
  778. resolved: resolved,
  779. schema: sch,
  780. }
  781. }
  782. // add the resolved schema to the definitions
  783. saveSchema(opts.Swagger(), newName, sch)
  784. }
  785. }
  786. // maintains ref index entries
  787. for k := range opts.flattenContext.newRefs {
  788. r := opts.flattenContext.newRefs[k]
  789. // update tracking with resolved schemas
  790. if r.schema.Ref.String() != "" {
  791. ref := swspec.MustCreateRef(r.path)
  792. sch, err := swspec.ResolveRefWithBase(opts.Swagger(), &ref, opts.ExpandOpts(false))
  793. if err != nil {
  794. return false, fmt.Errorf("could not resolve schema: %v", err)
  795. }
  796. r.schema = sch
  797. }
  798. // update tracking with renamed keys: got a cascade of refs
  799. if r.path != k {
  800. renamed := *r
  801. renamed.key = r.path
  802. opts.flattenContext.newRefs[renamed.path] = &renamed
  803. // indirect ref
  804. r.newName = slashpath.Base(k)
  805. r.schema = swspec.RefSchema(r.path)
  806. r.path = k
  807. r.isOAIGen = strings.Contains(k, "OAIGen")
  808. }
  809. }
  810. return complete, nil
  811. }
  812. type refRevIdx struct {
  813. Ref swspec.Ref
  814. Keys []string
  815. }
  816. // rebaseRef rebase a remote ref relative to a base ref.
  817. //
  818. // NOTE: does not support JSONschema ID for $ref (we assume we are working with swagger specs here).
  819. //
  820. // NOTE(windows):
  821. // * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
  822. // * "/ in paths may appear as escape sequences
  823. func rebaseRef(baseRef string, ref string) string {
  824. debugLog("rebasing ref: %s onto %s", ref, baseRef)
  825. baseRef, _ = url.PathUnescape(baseRef)
  826. ref, _ = url.PathUnescape(ref)
  827. if baseRef == "" || baseRef == "." || strings.HasPrefix(baseRef, "#") {
  828. return ref
  829. }
  830. parts := strings.Split(ref, "#")
  831. baseParts := strings.Split(baseRef, "#")
  832. baseURL, _ := url.Parse(baseParts[0])
  833. if strings.HasPrefix(ref, "#") {
  834. if baseURL.Host == "" {
  835. return strings.Join([]string{baseParts[0], parts[1]}, "#")
  836. }
  837. return strings.Join([]string{baseParts[0], parts[1]}, "#")
  838. }
  839. refURL, _ := url.Parse(parts[0])
  840. if refURL.Host != "" || filepath.IsAbs(parts[0]) {
  841. // not rebasing an absolute path
  842. return ref
  843. }
  844. // there is a relative path
  845. var basePath string
  846. if baseURL.Host != "" {
  847. // when there is a host, standard URI rules apply (with "/")
  848. baseURL.Path = slashpath.Dir(baseURL.Path)
  849. baseURL.Path = slashpath.Join(baseURL.Path, "/"+parts[0])
  850. return baseURL.String()
  851. }
  852. // this is a local relative path
  853. // basePart[0] and parts[0] are local filesystem directories/files
  854. basePath = filepath.Dir(baseParts[0])
  855. relPath := filepath.Join(basePath, string(filepath.Separator)+parts[0])
  856. if len(parts) > 1 {
  857. return strings.Join([]string{relPath, parts[1]}, "#")
  858. }
  859. return relPath
  860. }
  861. // normalizePath renders absolute path on remote file refs
  862. //
  863. // NOTE(windows):
  864. // * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
  865. // * "/ in paths may appear as escape sequences
  866. func normalizePath(ref swspec.Ref, opts *FlattenOpts) (normalizedPath string) {
  867. uri, _ := url.PathUnescape(ref.String())
  868. if ref.HasFragmentOnly || filepath.IsAbs(uri) {
  869. normalizedPath = uri
  870. return
  871. }
  872. refURL, _ := url.Parse(uri)
  873. if refURL.Host != "" {
  874. normalizedPath = uri
  875. return
  876. }
  877. parts := strings.Split(uri, "#")
  878. // BasePath, parts[0] are local filesystem directories, guaranteed to be absolute at this stage
  879. parts[0] = filepath.Join(filepath.Dir(opts.BasePath), parts[0])
  880. normalizedPath = strings.Join(parts, "#")
  881. return
  882. }
  883. func reverseIndexForSchemaRefs(opts *FlattenOpts) map[string]refRevIdx {
  884. collected := make(map[string]refRevIdx)
  885. for key, schRef := range opts.Spec.references.schemas {
  886. // normalize paths before sorting,
  887. // so we get together keys in same external file
  888. normalizedPath := normalizePath(schRef, opts)
  889. if entry, ok := collected[normalizedPath]; ok {
  890. entry.Keys = append(entry.Keys, key)
  891. collected[normalizedPath] = entry
  892. } else {
  893. collected[normalizedPath] = refRevIdx{
  894. Ref: schRef,
  895. Keys: []string{key},
  896. }
  897. }
  898. }
  899. return collected
  900. }
  901. func nameFromRef(ref swspec.Ref) string {
  902. u := ref.GetURL()
  903. if u.Fragment != "" {
  904. return swag.ToJSONName(slashpath.Base(u.Fragment))
  905. }
  906. if u.Path != "" {
  907. bn := slashpath.Base(u.Path)
  908. if bn != "" && bn != "/" {
  909. ext := slashpath.Ext(bn)
  910. if ext != "" {
  911. return swag.ToJSONName(bn[:len(bn)-len(ext)])
  912. }
  913. return swag.ToJSONName(bn)
  914. }
  915. }
  916. return swag.ToJSONName(strings.Replace(u.Host, ".", " ", -1))
  917. }
  918. func saveSchema(spec *swspec.Swagger, name string, schema *swspec.Schema) {
  919. if schema == nil {
  920. return
  921. }
  922. if spec.Definitions == nil {
  923. spec.Definitions = make(map[string]swspec.Schema, 150)
  924. }
  925. spec.Definitions[name] = *schema
  926. }
  927. // getPointerFromKey retrieves the content of the JSON pointer "key"
  928. func getPointerFromKey(spec interface{}, key string) (string, interface{}, error) {
  929. switch spec.(type) {
  930. case *swspec.Schema:
  931. case *swspec.Swagger:
  932. default:
  933. panic("unexpected type used in getPointerFromKey")
  934. }
  935. if key == "#/" {
  936. return "", spec, nil
  937. }
  938. // unescape chars in key, e.g. "{}" from path params
  939. pth, _ := internal.PathUnescape(key[1:])
  940. ptr, err := jsonpointer.New(pth)
  941. if err != nil {
  942. return "", nil, err
  943. }
  944. value, _, err := ptr.Get(spec)
  945. if err != nil {
  946. debugLog("error when getting key: %s with path: %s", key, pth)
  947. return "", nil, err
  948. }
  949. return pth, value, nil
  950. }
  951. // getParentFromKey retrieves the container of the JSON pointer "key"
  952. func getParentFromKey(spec interface{}, key string) (string, string, interface{}, error) {
  953. switch spec.(type) {
  954. case *swspec.Schema:
  955. case *swspec.Swagger:
  956. default:
  957. panic("unexpected type used in getPointerFromKey")
  958. }
  959. // unescape chars in key, e.g. "{}" from path params
  960. pth, _ := internal.PathUnescape(key[1:])
  961. parent, entry := slashpath.Dir(pth), slashpath.Base(pth)
  962. debugLog("getting schema holder at: %s, with entry: %s", parent, entry)
  963. pptr, err := jsonpointer.New(parent)
  964. if err != nil {
  965. return "", "", nil, err
  966. }
  967. pvalue, _, err := pptr.Get(spec)
  968. if err != nil {
  969. return "", "", nil, fmt.Errorf("can't get parent for %s: %v", parent, err)
  970. }
  971. return parent, entry, pvalue, nil
  972. }
  973. // updateRef replaces a ref by another one
  974. func updateRef(spec interface{}, key string, ref swspec.Ref) error {
  975. switch spec.(type) {
  976. case *swspec.Schema:
  977. case *swspec.Swagger:
  978. default:
  979. panic("unexpected type used in getPointerFromKey")
  980. }
  981. debugLog("updating ref for %s with %s", key, ref.String())
  982. pth, value, err := getPointerFromKey(spec, key)
  983. if err != nil {
  984. return err
  985. }
  986. switch refable := value.(type) {
  987. case *swspec.Schema:
  988. refable.Ref = ref
  989. case *swspec.SchemaOrArray:
  990. if refable.Schema != nil {
  991. refable.Schema.Ref = ref
  992. }
  993. case *swspec.SchemaOrBool:
  994. if refable.Schema != nil {
  995. refable.Schema.Ref = ref
  996. }
  997. case swspec.Schema:
  998. debugLog("rewriting holder for %T", refable)
  999. _, entry, pvalue, erp := getParentFromKey(spec, key)
  1000. if erp != nil {
  1001. return err
  1002. }
  1003. switch container := pvalue.(type) {
  1004. case swspec.Definitions:
  1005. container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  1006. case map[string]swspec.Schema:
  1007. container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  1008. case []swspec.Schema:
  1009. idx, err := strconv.Atoi(entry)
  1010. if err != nil {
  1011. return fmt.Errorf("%s not a number: %v", pth, err)
  1012. }
  1013. container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  1014. case *swspec.SchemaOrArray:
  1015. // NOTE: this is necessarily an array - otherwise, the parent would be *Schema
  1016. idx, err := strconv.Atoi(entry)
  1017. if err != nil {
  1018. return fmt.Errorf("%s not a number: %v", pth, err)
  1019. }
  1020. container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
  1021. // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema
  1022. default:
  1023. return fmt.Errorf("unhandled container type at %s: %T", key, value)
  1024. }
  1025. default:
  1026. return fmt.Errorf("no schema with ref found at %s for %T", key, value)
  1027. }
  1028. return nil
  1029. }
  1030. // updateRefWithSchema replaces a ref with a schema (i.e. re-inline schema)
  1031. func updateRefWithSchema(spec *swspec.Swagger, key string, sch *swspec.Schema) error {
  1032. debugLog("updating ref for %s with schema", key)
  1033. pth, value, err := getPointerFromKey(spec, key)
  1034. if err != nil {
  1035. return err
  1036. }
  1037. switch refable := value.(type) {
  1038. case *swspec.Schema:
  1039. *refable = *sch
  1040. case swspec.Schema:
  1041. _, entry, pvalue, erp := getParentFromKey(spec, key)
  1042. if erp != nil {
  1043. return err
  1044. }
  1045. switch container := pvalue.(type) {
  1046. case swspec.Definitions:
  1047. container[entry] = *sch
  1048. case map[string]swspec.Schema:
  1049. container[entry] = *sch
  1050. case []swspec.Schema:
  1051. idx, err := strconv.Atoi(entry)
  1052. if err != nil {
  1053. return fmt.Errorf("%s not a number: %v", pth, err)
  1054. }
  1055. container[idx] = *sch
  1056. case *swspec.SchemaOrArray:
  1057. // NOTE: this is necessarily an array - otherwise, the parent would be *Schema
  1058. idx, err := strconv.Atoi(entry)
  1059. if err != nil {
  1060. return fmt.Errorf("%s not a number: %v", pth, err)
  1061. }
  1062. container.Schemas[idx] = *sch
  1063. // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema
  1064. default:
  1065. return fmt.Errorf("unhandled type for parent of [%s]: %T", key, value)
  1066. }
  1067. case *swspec.SchemaOrArray:
  1068. *refable.Schema = *sch
  1069. // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema
  1070. case *swspec.SchemaOrBool:
  1071. *refable.Schema = *sch
  1072. default:
  1073. return fmt.Errorf("no schema with ref found at %s for %T", key, value)
  1074. }
  1075. return nil
  1076. }
  1077. func containsString(names []string, name string) bool {
  1078. for _, nm := range names {
  1079. if nm == name {
  1080. return true
  1081. }
  1082. }
  1083. return false
  1084. }
  1085. type opRef struct {
  1086. Method string
  1087. Path string
  1088. Key string
  1089. ID string
  1090. Op *swspec.Operation
  1091. Ref swspec.Ref
  1092. }
  1093. type opRefs []opRef
  1094. func (o opRefs) Len() int { return len(o) }
  1095. func (o opRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
  1096. func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
  1097. func gatherOperations(specDoc *Spec, operationIDs []string) map[string]opRef {
  1098. var oprefs opRefs
  1099. for method, pathItem := range specDoc.Operations() {
  1100. for pth, operation := range pathItem {
  1101. vv := *operation
  1102. oprefs = append(oprefs, opRef{
  1103. Key: swag.ToGoName(strings.ToLower(method) + " " + pth),
  1104. Method: method,
  1105. Path: pth,
  1106. ID: vv.ID,
  1107. Op: &vv,
  1108. Ref: swspec.MustCreateRef("#" + slashpath.Join("/paths", jsonpointer.Escape(pth), method)),
  1109. })
  1110. }
  1111. }
  1112. sort.Sort(oprefs)
  1113. operations := make(map[string]opRef)
  1114. for _, opr := range oprefs {
  1115. nm := opr.ID
  1116. if nm == "" {
  1117. nm = opr.Key
  1118. }
  1119. oo, found := operations[nm]
  1120. if found && oo.Method != opr.Method && oo.Path != opr.Path {
  1121. nm = opr.Key
  1122. }
  1123. if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) {
  1124. opr.ID = nm
  1125. opr.Op.ID = nm
  1126. operations[nm] = opr
  1127. }
  1128. }
  1129. return operations
  1130. }
  1131. // stripPointersAndOAIGen removes anonymous JSON pointers from spec and chain with name conflicts handler.
  1132. // This loops until the spec has no such pointer and all name conflicts have been reduced as much as possible.
  1133. func stripPointersAndOAIGen(opts *FlattenOpts) error {
  1134. // name all JSON pointers to anonymous documents
  1135. if err := namePointers(opts); err != nil {
  1136. return err
  1137. }
  1138. // remove unnecessary OAIGen ref (created when flattening external refs creates name conflicts)
  1139. hasIntroducedPointerOrInline, ers := stripOAIGen(opts)
  1140. if ers != nil {
  1141. return ers
  1142. }
  1143. // iterate as pointer or OAIGen resolution may introduce inline schemas or pointers
  1144. for hasIntroducedPointerOrInline {
  1145. if !opts.Minimal {
  1146. opts.Spec.reload() // re-analyze
  1147. if err := nameInlinedSchemas(opts); err != nil {
  1148. return err
  1149. }
  1150. }
  1151. if err := namePointers(opts); err != nil {
  1152. return err
  1153. }
  1154. // restrip and re-analyze
  1155. if hasIntroducedPointerOrInline, ers = stripOAIGen(opts); ers != nil {
  1156. return ers
  1157. }
  1158. }
  1159. return nil
  1160. }
  1161. func updateRefParents(opts *FlattenOpts, r *newRef) {
  1162. if !r.isOAIGen || r.resolved { // bail on already resolved entries (avoid looping)
  1163. return
  1164. }
  1165. for k, v := range opts.Spec.references.allRefs {
  1166. if r.path != v.String() {
  1167. continue
  1168. }
  1169. found := false
  1170. for _, p := range r.parents {
  1171. if p == k {
  1172. found = true
  1173. break
  1174. }
  1175. }
  1176. if !found {
  1177. r.parents = append(r.parents, k)
  1178. }
  1179. }
  1180. }
  1181. // topMostRefs is able to sort refs by hierarchical then lexicographic order,
  1182. // yielding refs ordered breadth-first.
  1183. type topmostRefs []string
  1184. func (k topmostRefs) Len() int { return len(k) }
  1185. func (k topmostRefs) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
  1186. func (k topmostRefs) Less(i, j int) bool {
  1187. li, lj := len(strings.Split(k[i], "/")), len(strings.Split(k[j], "/"))
  1188. if li == lj {
  1189. return k[i] < k[j]
  1190. }
  1191. return li < lj
  1192. }
  1193. func topmostFirst(refs []string) []string {
  1194. res := topmostRefs(refs)
  1195. sort.Sort(res)
  1196. return res
  1197. }
  1198. // stripOAIGen strips the spec from unnecessary OAIGen constructs, initially created to dedupe flattened definitions.
  1199. //
  1200. // A dedupe is deemed unnecessary whenever:
  1201. // - the only conflict is with its (single) parent: OAIGen is merged into its parent (reinlining)
  1202. // - there is a conflict with multiple parents: merge OAIGen in first parent, the rewrite other parents to point to
  1203. // the first parent.
  1204. //
  1205. // This function returns true whenever it re-inlined a complex schema, so the caller may chose to iterate
  1206. // pointer and name resolution again.
  1207. func stripOAIGen(opts *FlattenOpts) (bool, error) {
  1208. debugLog("stripOAIGen")
  1209. replacedWithComplex := false
  1210. // figure out referers of OAIGen definitions (doing it before the ref start mutating)
  1211. for _, r := range opts.flattenContext.newRefs {
  1212. updateRefParents(opts, r)
  1213. }
  1214. for k := range opts.flattenContext.newRefs {
  1215. r := opts.flattenContext.newRefs[k]
  1216. debugLog("newRefs[%s]: isOAIGen: %t, resolved: %t, name: %s, path:%s, #parents: %d, parents: %v, ref: %s",
  1217. k, r.isOAIGen, r.resolved, r.newName, r.path, len(r.parents), r.parents, r.schema.Ref.String())
  1218. if r.isOAIGen && len(r.parents) >= 1 {
  1219. pr := topmostFirst(r.parents)
  1220. // rewrite first parent schema in hierarchical then lexicographical order
  1221. debugLog("rewrite first parent %s with schema", pr[0])
  1222. if err := updateRefWithSchema(opts.Swagger(), pr[0], r.schema); err != nil {
  1223. return false, err
  1224. }
  1225. if pa, ok := opts.flattenContext.newRefs[pr[0]]; ok && pa.isOAIGen {
  1226. // update parent in ref index entry
  1227. debugLog("update parent entry: %s", pr[0])
  1228. pa.schema = r.schema
  1229. pa.resolved = false
  1230. replacedWithComplex = true
  1231. }
  1232. // rewrite other parents to point to first parent
  1233. if len(pr) > 1 {
  1234. for _, p := range pr[1:] {
  1235. replacingRef := swspec.MustCreateRef(pr[0])
  1236. // set complex when replacing ref is an anonymous jsonpointer: further processing may be required
  1237. replacedWithComplex = replacedWithComplex ||
  1238. slashpath.Dir(replacingRef.String()) != definitionsPath
  1239. debugLog("rewrite parent with ref: %s", replacingRef.String())
  1240. // NOTE: it is possible at this stage to introduce json pointers (to non-definitions places).
  1241. // Those are stripped later on.
  1242. if err := updateRef(opts.Swagger(), p, replacingRef); err != nil {
  1243. return false, err
  1244. }
  1245. if pa, ok := opts.flattenContext.newRefs[p]; ok && pa.isOAIGen {
  1246. // update parent in ref index
  1247. debugLog("update parent entry: %s", p)
  1248. pa.schema = r.schema
  1249. pa.resolved = false
  1250. replacedWithComplex = true
  1251. }
  1252. }
  1253. }
  1254. // remove OAIGen definition
  1255. debugLog("removing definition %s", slashpath.Base(r.path))
  1256. delete(opts.Swagger().Definitions, slashpath.Base(r.path))
  1257. // propagate changes in ref index for keys which have this one as a parent
  1258. for kk, value := range opts.flattenContext.newRefs {
  1259. if kk == k || !value.isOAIGen || value.resolved {
  1260. continue
  1261. }
  1262. found := false
  1263. newParents := make([]string, 0, len(value.parents))
  1264. for _, parent := range value.parents {
  1265. switch {
  1266. case parent == r.path:
  1267. found = true
  1268. parent = pr[0]
  1269. case strings.HasPrefix(parent, r.path+"/"):
  1270. found = true
  1271. parent = slashpath.Join(pr[0], strings.TrimPrefix(parent, r.path))
  1272. }
  1273. newParents = append(newParents, parent)
  1274. }
  1275. if found {
  1276. value.parents = newParents
  1277. }
  1278. }
  1279. // mark naming conflict as resolved
  1280. debugLog("marking naming conflict resolved for key: %s", r.key)
  1281. opts.flattenContext.newRefs[r.key].isOAIGen = false
  1282. opts.flattenContext.newRefs[r.key].resolved = true
  1283. // determine if the previous substitution did inline a complex schema
  1284. if r.schema != nil && r.schema.Ref.String() == "" { // inline schema
  1285. asch, err := Schema(SchemaOpts{Schema: r.schema, Root: opts.Swagger(), BasePath: opts.BasePath})
  1286. if err != nil {
  1287. return false, err
  1288. }
  1289. debugLog("re-inlined schema: parent: %s, %t", pr[0], isAnalyzedAsComplex(asch))
  1290. replacedWithComplex = replacedWithComplex ||
  1291. !(slashpath.Dir(pr[0]) == definitionsPath) && isAnalyzedAsComplex(asch)
  1292. }
  1293. }
  1294. }
  1295. debugLog("replacedWithComplex: %t", replacedWithComplex)
  1296. opts.Spec.reload() // re-analyze
  1297. return replacedWithComplex, nil
  1298. }
  1299. // croak logs notifications and warnings about valid, but possibly unwanted constructs resulting
  1300. // from flattening a spec
  1301. func croak(opts *FlattenOpts) {
  1302. reported := make(map[string]bool, len(opts.flattenContext.newRefs))
  1303. for _, v := range opts.Spec.references.allRefs {
  1304. // warns about duplicate handling
  1305. for _, r := range opts.flattenContext.newRefs {
  1306. if r.isOAIGen && r.path == v.String() {
  1307. reported[r.newName] = true
  1308. }
  1309. }
  1310. }
  1311. for k := range reported {
  1312. log.Printf("warning: duplicate flattened definition name resolved as %s", k)
  1313. }
  1314. // warns about possible type mismatches
  1315. uniqueMsg := make(map[string]bool)
  1316. for _, msg := range opts.flattenContext.warnings {
  1317. if _, ok := uniqueMsg[msg]; ok {
  1318. continue
  1319. }
  1320. log.Printf("warning: %s", msg)
  1321. uniqueMsg[msg] = true
  1322. }
  1323. }
  1324. // namePointers replaces all JSON pointers to anonymous documents by a $ref to a new named definitions.
  1325. //
  1326. // This is carried on depth-first. Pointers to $refs which are top level definitions are replaced by the $ref itself.
  1327. // Pointers to simple types are expanded, unless they express commonality (i.e. several such $ref are used).
  1328. func namePointers(opts *FlattenOpts) error {
  1329. debugLog("name pointers")
  1330. refsToReplace := make(map[string]SchemaRef, len(opts.Spec.references.schemas))
  1331. for k, ref := range opts.Spec.references.allRefs {
  1332. if slashpath.Dir(ref.String()) == definitionsPath {
  1333. // this a ref to a top-level definition: ok
  1334. continue
  1335. }
  1336. replacingRef, sch, erd := deepestRef(opts, ref)
  1337. if erd != nil {
  1338. return fmt.Errorf("at %s, %v", k, erd)
  1339. }
  1340. debugLog("planning pointer to replace at %s: %s, resolved to: %s", k, ref.String(), replacingRef.String())
  1341. refsToReplace[k] = SchemaRef{
  1342. Name: k, // caller
  1343. Ref: replacingRef, // callee
  1344. Schema: sch,
  1345. TopLevel: slashpath.Dir(replacingRef.String()) == definitionsPath,
  1346. }
  1347. }
  1348. depthFirst := sortDepthFirst(refsToReplace)
  1349. namer := &inlineSchemaNamer{
  1350. Spec: opts.Swagger(),
  1351. Operations: opRefsByRef(gatherOperations(opts.Spec, nil)),
  1352. flattenContext: opts.flattenContext,
  1353. opts: opts,
  1354. }
  1355. for _, key := range depthFirst {
  1356. v := refsToReplace[key]
  1357. // update current replacement, which may have been updated by previous changes of deeper elements
  1358. replacingRef, sch, erd := deepestRef(opts, v.Ref)
  1359. if erd != nil {
  1360. return fmt.Errorf("at %s, %v", key, erd)
  1361. }
  1362. v.Ref = replacingRef
  1363. v.Schema = sch
  1364. v.TopLevel = slashpath.Dir(replacingRef.String()) == definitionsPath
  1365. debugLog("replacing pointer at %s: resolved to: %s", key, v.Ref.String())
  1366. if v.TopLevel {
  1367. debugLog("replace pointer %s by canonical definition: %s", key, v.Ref.String())
  1368. // if the schema is a $ref to a top level definition, just rewrite the pointer to this $ref
  1369. if err := updateRef(opts.Swagger(), key, v.Ref); err != nil {
  1370. return err
  1371. }
  1372. } else {
  1373. // this is a JSON pointer to an anonymous document (internal or external):
  1374. // create a definition for this schema when:
  1375. // - it is a complex schema
  1376. // - or it is pointed by more than one $ref (i.e. expresses commonality)
  1377. // otherwise, expand the pointer (single reference to a simple type)
  1378. //
  1379. // The named definition for this follows the target's key, not the caller's
  1380. debugLog("namePointers at %s for %s", key, v.Ref.String())
  1381. // qualify the expanded schema
  1382. /*
  1383. if key == "#/paths/~1some~1where~1{id}/get/parameters/1/items" {
  1384. // DEBUG
  1385. //func getPointerFromKey(spec interface{}, key string) (string, interface{}, error) {
  1386. k, res, err := getPointerFromKey(namer.Spec, key)
  1387. debugLog("k = %s, res=%#v, err=%v", k, res, err)
  1388. }
  1389. */
  1390. asch, ers := Schema(SchemaOpts{Schema: v.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
  1391. if ers != nil {
  1392. return fmt.Errorf("schema analysis [%s]: %v", key, ers)
  1393. }
  1394. callers := make([]string, 0, 64)
  1395. debugLog("looking for callers")
  1396. an := New(opts.Swagger())
  1397. for k, w := range an.references.allRefs {
  1398. r, _, erd := deepestRef(opts, w)
  1399. if erd != nil {
  1400. return fmt.Errorf("at %s, %v", key, erd)
  1401. }
  1402. if r.String() == v.Ref.String() {
  1403. callers = append(callers, k)
  1404. }
  1405. }
  1406. debugLog("callers for %s: %d", v.Ref.String(), len(callers))
  1407. if len(callers) == 0 {
  1408. // has already been updated and resolved
  1409. continue
  1410. }
  1411. parts := keyParts(v.Ref.String())
  1412. debugLog("number of callers for %s: %d", v.Ref.String(), len(callers))
  1413. // identifying edge case when the namer did nothing because we point to a non-schema object
  1414. // no definition is created and we expand the $ref for all callers
  1415. if (!asch.IsSimpleSchema || len(callers) > 1) && !parts.IsSharedParam() && !parts.IsSharedResponse() {
  1416. debugLog("replace JSON pointer at [%s] by definition: %s", key, v.Ref.String())
  1417. if err := namer.Name(v.Ref.String(), v.Schema, asch); err != nil {
  1418. return err
  1419. }
  1420. // regular case: we named the $ref as a definition, and we move all callers to this new $ref
  1421. for _, caller := range callers {
  1422. if caller != key {
  1423. // move $ref for next to resolve
  1424. debugLog("identified caller of %s at [%s]", v.Ref.String(), caller)
  1425. c := refsToReplace[caller]
  1426. c.Ref = v.Ref
  1427. refsToReplace[caller] = c
  1428. }
  1429. }
  1430. } else {
  1431. debugLog("expand JSON pointer for key=%s", key)
  1432. if err := updateRefWithSchema(opts.Swagger(), key, v.Schema); err != nil {
  1433. return err
  1434. }
  1435. // NOTE: there is no other caller to update
  1436. }
  1437. }
  1438. }
  1439. opts.Spec.reload() // re-analyze
  1440. return nil
  1441. }
  1442. // deepestRef finds the first definition ref, from a cascade of nested refs which are not definitions.
  1443. // - if no definition is found, returns the deepest ref.
  1444. // - pointers to external files are expanded
  1445. //
  1446. // NOTE: all external $ref's are assumed to be already expanded at this stage.
  1447. func deepestRef(opts *FlattenOpts, ref swspec.Ref) (swspec.Ref, *swspec.Schema, error) {
  1448. if !ref.HasFragmentOnly {
  1449. // we found an external $ref, which is odd
  1450. // does nothing on external $refs
  1451. return ref, nil, nil
  1452. }
  1453. currentRef := ref
  1454. visited := make(map[string]bool, 64)
  1455. DOWNREF:
  1456. for currentRef.String() != "" {
  1457. if slashpath.Dir(currentRef.String()) == definitionsPath {
  1458. // this is a top-level definition: stop here and return this ref
  1459. return currentRef, nil, nil
  1460. }
  1461. if _, beenThere := visited[currentRef.String()]; beenThere {
  1462. return swspec.Ref{}, nil,
  1463. fmt.Errorf("cannot resolve cyclic chain of pointers under %s", currentRef.String())
  1464. }
  1465. visited[currentRef.String()] = true
  1466. value, _, err := currentRef.GetPointer().Get(opts.Swagger())
  1467. if err != nil {
  1468. return swspec.Ref{}, nil, err
  1469. }
  1470. switch refable := value.(type) {
  1471. case *swspec.Schema:
  1472. if refable.Ref.String() == "" {
  1473. break DOWNREF
  1474. }
  1475. currentRef = refable.Ref
  1476. case swspec.Schema:
  1477. if refable.Ref.String() == "" {
  1478. break DOWNREF
  1479. }
  1480. currentRef = refable.Ref
  1481. case *swspec.SchemaOrArray:
  1482. if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
  1483. break DOWNREF
  1484. }
  1485. currentRef = refable.Schema.Ref
  1486. case *swspec.SchemaOrBool:
  1487. if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
  1488. break DOWNREF
  1489. }
  1490. currentRef = refable.Schema.Ref
  1491. case swspec.Response:
  1492. // a pointer points to a schema initially marshalled in responses section...
  1493. // Attempt to convert this to a schema. If this fails, the spec is invalid
  1494. asJSON, _ := refable.MarshalJSON()
  1495. var asSchema swspec.Schema
  1496. err := asSchema.UnmarshalJSON(asJSON)
  1497. if err != nil {
  1498. return swspec.Ref{}, nil,
  1499. fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T",
  1500. currentRef.String(), value)
  1501. }
  1502. opts.flattenContext.warnings = append(opts.flattenContext.warnings,
  1503. fmt.Sprintf("found $ref %q (response) interpreted as schema", currentRef.String()))
  1504. if asSchema.Ref.String() == "" {
  1505. break DOWNREF
  1506. }
  1507. currentRef = asSchema.Ref
  1508. case swspec.Parameter:
  1509. // a pointer points to a schema initially marshalled in parameters section...
  1510. // Attempt to convert this to a schema. If this fails, the spec is invalid
  1511. asJSON, _ := refable.MarshalJSON()
  1512. var asSchema swspec.Schema
  1513. err := asSchema.UnmarshalJSON(asJSON)
  1514. if err != nil {
  1515. return swspec.Ref{}, nil,
  1516. fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T",
  1517. currentRef.String(), value)
  1518. }
  1519. opts.flattenContext.warnings = append(opts.flattenContext.warnings,
  1520. fmt.Sprintf("found $ref %q (parameter) interpreted as schema", currentRef.String()))
  1521. if asSchema.Ref.String() == "" {
  1522. break DOWNREF
  1523. }
  1524. currentRef = asSchema.Ref
  1525. default:
  1526. return swspec.Ref{}, nil,
  1527. fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T",
  1528. currentRef.String(), value)
  1529. }
  1530. }
  1531. // assess what schema we're ending with
  1532. sch, erv := swspec.ResolveRefWithBase(opts.Swagger(), &currentRef, opts.ExpandOpts(false))
  1533. if erv != nil {
  1534. return swspec.Ref{}, nil, erv
  1535. }
  1536. if sch == nil {
  1537. return swspec.Ref{}, nil, fmt.Errorf("no schema found at %s", currentRef.String())
  1538. }
  1539. return currentRef, sch, nil
  1540. }
  1541. // normalizeRef strips the current file from any $ref. This works around issue go-openapi/spec#76:
  1542. // leading absolute file in $ref is stripped
  1543. func normalizeRef(opts *FlattenOpts) error {
  1544. debugLog("normalizeRef")
  1545. opts.Spec.reload() // re-analyze
  1546. for k, w := range opts.Spec.references.allRefs {
  1547. if strings.HasPrefix(w.String(), opts.BasePath+definitionsPath) { // may be a mix of / and \, depending on OS
  1548. // strip base path from definition
  1549. debugLog("stripping absolute path for: %s", w.String())
  1550. if err := updateRef(opts.Swagger(), k,
  1551. swspec.MustCreateRef(slashpath.Join(definitionsPath, slashpath.Base(w.String())))); err != nil {
  1552. return err
  1553. }
  1554. }
  1555. }
  1556. opts.Spec.reload() // re-analyze
  1557. return nil
  1558. }