flatten.go 44 KB

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