本站源代码
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

1733 linhas
51KB

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