podman
802 строки · 24.1 Кб
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
15package analysis16
17import (18"fmt"19"log"20"path"21"sort"22"strings"23
24"github.com/go-openapi/analysis/internal/flatten/normalize"25"github.com/go-openapi/analysis/internal/flatten/operations"26"github.com/go-openapi/analysis/internal/flatten/replace"27"github.com/go-openapi/analysis/internal/flatten/schutils"28"github.com/go-openapi/analysis/internal/flatten/sortref"29"github.com/go-openapi/jsonpointer"30"github.com/go-openapi/spec"31)
32
33const definitionsPath = "#/definitions"34
35// newRef stores information about refs created during the flattening process
36type newRef struct {37key string38newName string39path string40isOAIGen bool41resolved bool42schema *spec.Schema43parents []string44}
45
46// context stores intermediary results from flatten
47type context struct {48newRefs map[string]*newRef49warnings []string50resolved map[string]string51}
52
53func newContext() *context {54return &context{55newRefs: make(map[string]*newRef, 150),56warnings: make([]string, 0),57resolved: make(map[string]string, 50),58}59}
60
61// Flatten an analyzed spec and produce a self-contained spec bundle.
62//
63// There is a minimal and a full flattening mode.
64//
65//
66// Minimally flattening a spec means:
67// - Expanding parameters, responses, path items, parameter items and header items (references to schemas are left
68// unscathed)
69// - Importing external (http, file) references so they become internal to the document
70// - Moving every JSON pointer to a $ref to a named definition (i.e. the reworked spec does not contain pointers
71// like "$ref": "#/definitions/myObject/allOfs/1")
72//
73// A minimally flattened spec thus guarantees the following properties:
74// - all $refs point to a local definition (i.e. '#/definitions/...')
75// - definitions are unique
76//
77// NOTE: arbitrary JSON pointers (other than $refs to top level definitions) are rewritten as definitions if they
78// represent a complex schema or express commonality in the spec.
79// Otherwise, they are simply expanded.
80// Self-referencing JSON pointers cannot resolve to a type and trigger an error.
81//
82//
83// Minimal flattening is necessary and sufficient for codegen rendering using go-swagger.
84//
85// Fully flattening a spec means:
86// - Moving every complex inline schema to be a definition with an auto-generated name in a depth-first fashion.
87//
88// By complex, we mean every JSON object with some properties.
89// Arrays, when they do not define a tuple,
90// or empty objects with or without additionalProperties, are not considered complex and remain inline.
91//
92// NOTE: rewritten schemas get a vendor extension x-go-gen-location so we know from which part of the spec definitions
93// have been created.
94//
95// Available flattening options:
96// - Minimal: stops flattening after minimal $ref processing, leaving schema constructs untouched
97// - Expand: expand all $ref's in the document (inoperant if Minimal set to true)
98// - Verbose: croaks about name conflicts detected
99// - RemoveUnused: removes unused parameters, responses and definitions after expansion/flattening
100//
101// NOTE: expansion removes all $ref save circular $ref, which remain in place
102//
103// TODO: additional options
104// - ProgagateNameExtensions: ensure that created entries properly follow naming rules when their parent have set a
105// x-go-name extension
106// - LiftAllOfs:
107// - limit the flattening of allOf members when simple objects
108// - merge allOf with validation only
109// - merge allOf with extensions only
110// - ...
111//
112func Flatten(opts FlattenOpts) error {113debugLog("FlattenOpts: %#v", opts)114
115opts.flattenContext = newContext()116
117// 1. Recursively expand responses, parameters, path items and items in simple schemas.118//119// This simplifies the spec and leaves only the $ref's in schema objects.120if err := expand(&opts); err != nil {121return err122}123
124// 2. Strip the current document from absolute $ref's that actually a in the root,125// so we can recognize them as proper definitions126//127// In particular, this works around issue go-openapi/spec#76: leading absolute file in $ref is stripped128if err := normalizeRef(&opts); err != nil {129return err130}131
132// 3. Optionally remove shared parameters and responses already expanded (now unused).133//134// Operation parameters (i.e. under paths) remain.135if opts.RemoveUnused {136removeUnusedShared(&opts)137}138
139// 4. Import all remote references.140if err := importReferences(&opts); err != nil {141return err142}143
144// 5. full flattening: rewrite inline schemas (schemas that aren't simple types or arrays or maps)145if !opts.Minimal && !opts.Expand {146if err := nameInlinedSchemas(&opts); err != nil {147return err148}149}150
151// 6. Rewrite JSON pointers other than $ref to named definitions152// and attempt to resolve conflicting names whenever possible.153if err := stripPointersAndOAIGen(&opts); err != nil {154return err155}156
157// 7. Strip the spec from unused definitions158if opts.RemoveUnused {159removeUnused(&opts)160}161
162// 8. Issue warning notifications, if any163opts.croak()164
165// TODO: simplify known schema patterns to flat objects with properties166// examples:167// - lift simple allOf object,168// - empty allOf with validation only or extensions only169// - rework allOf arrays170// - rework allOf additionalProperties171
172return nil173}
174
175func expand(opts *FlattenOpts) error {176if err := spec.ExpandSpec(opts.Swagger(), opts.ExpandOpts(!opts.Expand)); err != nil {177return err178}179
180opts.Spec.reload() // re-analyze181
182return nil183}
184
185// normalizeRef strips the current file from any absolute file $ref. This works around issue go-openapi/spec#76:
186// leading absolute file in $ref is stripped
187func normalizeRef(opts *FlattenOpts) error {188debugLog("normalizeRef")189
190altered := false191for k, w := range opts.Spec.references.allRefs {192if !strings.HasPrefix(w.String(), opts.BasePath+definitionsPath) { // may be a mix of / and \, depending on OS193continue194}195
196altered = true197debugLog("stripping absolute path for: %s", w.String())198
199// strip the base path from definition200if err := replace.UpdateRef(opts.Swagger(), k,201spec.MustCreateRef(path.Join(definitionsPath, path.Base(w.String())))); err != nil {202return err203}204}205
206if altered {207opts.Spec.reload() // re-analyze208}209
210return nil211}
212
213func removeUnusedShared(opts *FlattenOpts) {214opts.Swagger().Parameters = nil215opts.Swagger().Responses = nil216
217opts.Spec.reload() // re-analyze218}
219
220func importReferences(opts *FlattenOpts) error {221var (222imported bool223err error224)225
226for !imported && err == nil {227// iteratively import remote references until none left.228// This inlining deals with name conflicts by introducing auto-generated names ("OAIGen")229imported, err = importExternalReferences(opts)230
231opts.Spec.reload() // re-analyze232}233
234return err235}
236
237// nameInlinedSchemas replaces every complex inline construct by a named definition.
238func nameInlinedSchemas(opts *FlattenOpts) error {239debugLog("nameInlinedSchemas")240
241namer := &InlineSchemaNamer{242Spec: opts.Swagger(),243Operations: operations.AllOpRefsByRef(opts.Spec, nil),244flattenContext: opts.flattenContext,245opts: opts,246}247
248depthFirst := sortref.DepthFirst(opts.Spec.allSchemas)249for _, key := range depthFirst {250sch := opts.Spec.allSchemas[key]251if sch.Schema == nil || sch.Schema.Ref.String() != "" || sch.TopLevel {252continue253}254
255asch, err := Schema(SchemaOpts{Schema: sch.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})256if err != nil {257return fmt.Errorf("schema analysis [%s]: %w", key, err)258}259
260if asch.isAnalyzedAsComplex() { // move complex schemas to definitions261if err := namer.Name(key, sch.Schema, asch); err != nil {262return err263}264}265}266
267opts.Spec.reload() // re-analyze268
269return nil270}
271
272func removeUnused(opts *FlattenOpts) {273expected := make(map[string]struct{})274for k := range opts.Swagger().Definitions {275expected[path.Join(definitionsPath, jsonpointer.Escape(k))] = struct{}{}276}277
278for _, k := range opts.Spec.AllDefinitionReferences() {279delete(expected, k)280}281
282for k := range expected {283debugLog("removing unused definition %s", path.Base(k))284if opts.Verbose {285log.Printf("info: removing unused definition: %s", path.Base(k))286}287delete(opts.Swagger().Definitions, path.Base(k))288}289
290opts.Spec.reload() // re-analyze291}
292
293func importKnownRef(entry sortref.RefRevIdx, refStr, newName string, opts *FlattenOpts) error {294// rewrite ref with already resolved external ref (useful for cyclical refs):295// rewrite external refs to local ones296debugLog("resolving known ref [%s] to %s", refStr, newName)297
298for _, key := range entry.Keys {299if err := replace.UpdateRef(opts.Swagger(), key, spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {300return err301}302}303
304return nil305}
306
307func importNewRef(entry sortref.RefRevIdx, refStr string, opts *FlattenOpts) error {308var (309isOAIGen bool310newName string311)312
313debugLog("resolving schema from remote $ref [%s]", refStr)314
315sch, err := spec.ResolveRefWithBase(opts.Swagger(), &entry.Ref, opts.ExpandOpts(false))316if err != nil {317return fmt.Errorf("could not resolve schema: %w", err)318}319
320// at this stage only $ref analysis matters321partialAnalyzer := &Spec{322references: referenceAnalysis{},323patterns: patternAnalysis{},324enums: enumAnalysis{},325}326partialAnalyzer.reset()327partialAnalyzer.analyzeSchema("", sch, "/")328
329// now rewrite those refs with rebase330for key, ref := range partialAnalyzer.references.allRefs {331if err := replace.UpdateRef(sch, key, spec.MustCreateRef(normalize.RebaseRef(entry.Ref.String(), ref.String()))); err != nil {332return fmt.Errorf("failed to rewrite ref for key %q at %s: %w", key, entry.Ref.String(), err)333}334}335
336// generate a unique name - isOAIGen means that a naming conflict was resolved by changing the name337newName, isOAIGen = uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref))338debugLog("new name for [%s]: %s - with name conflict:%t", strings.Join(entry.Keys, ", "), newName, isOAIGen)339
340opts.flattenContext.resolved[refStr] = newName341
342// rewrite the external refs to local ones343for _, key := range entry.Keys {344if err := replace.UpdateRef(opts.Swagger(), key,345spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {346return err347}348
349// keep track of created refs350resolved := false351if _, ok := opts.flattenContext.newRefs[key]; ok {352resolved = opts.flattenContext.newRefs[key].resolved353}354
355debugLog("keeping track of ref: %s (%s), resolved: %t", key, newName, resolved)356opts.flattenContext.newRefs[key] = &newRef{357key: key,358newName: newName,359path: path.Join(definitionsPath, newName),360isOAIGen: isOAIGen,361resolved: resolved,362schema: sch,363}364}365
366// add the resolved schema to the definitions367schutils.Save(opts.Swagger(), newName, sch)368
369return nil370}
371
372// importExternalReferences iteratively digs remote references and imports them into the main schema.
373//
374// At every iteration, new remotes may be found when digging deeper: they are rebased to the current schema before being imported.
375//
376// This returns true when no more remote references can be found.
377func importExternalReferences(opts *FlattenOpts) (bool, error) {378debugLog("importExternalReferences")379
380groupedRefs := sortref.ReverseIndex(opts.Spec.references.schemas, opts.BasePath)381sortedRefStr := make([]string, 0, len(groupedRefs))382if opts.flattenContext == nil {383opts.flattenContext = newContext()384}385
386// sort $ref resolution to ensure deterministic name conflict resolution387for refStr := range groupedRefs {388sortedRefStr = append(sortedRefStr, refStr)389}390sort.Strings(sortedRefStr)391
392complete := true393
394for _, refStr := range sortedRefStr {395entry := groupedRefs[refStr]396if entry.Ref.HasFragmentOnly {397continue398}399
400complete = false401
402newName := opts.flattenContext.resolved[refStr]403if newName != "" {404if err := importKnownRef(entry, refStr, newName, opts); err != nil {405return false, err406}407
408continue409}410
411// resolve schemas412if err := importNewRef(entry, refStr, opts); err != nil {413return false, err414}415}416
417// maintains ref index entries418for k := range opts.flattenContext.newRefs {419r := opts.flattenContext.newRefs[k]420
421// update tracking with resolved schemas422if r.schema.Ref.String() != "" {423ref := spec.MustCreateRef(r.path)424sch, err := spec.ResolveRefWithBase(opts.Swagger(), &ref, opts.ExpandOpts(false))425if err != nil {426return false, fmt.Errorf("could not resolve schema: %w", err)427}428
429r.schema = sch430}431
432if r.path == k {433continue434}435
436// update tracking with renamed keys: got a cascade of refs437renamed := *r438renamed.key = r.path439opts.flattenContext.newRefs[renamed.path] = &renamed440
441// indirect ref442r.newName = path.Base(k)443r.schema = spec.RefSchema(r.path)444r.path = k445r.isOAIGen = strings.Contains(k, "OAIGen")446}447
448return complete, nil449}
450
451// stripPointersAndOAIGen removes anonymous JSON pointers from spec and chain with name conflicts handler.
452// This loops until the spec has no such pointer and all name conflicts have been reduced as much as possible.
453func stripPointersAndOAIGen(opts *FlattenOpts) error {454// name all JSON pointers to anonymous documents455if err := namePointers(opts); err != nil {456return err457}458
459// remove unnecessary OAIGen ref (created when flattening external refs creates name conflicts)460hasIntroducedPointerOrInline, ers := stripOAIGen(opts)461if ers != nil {462return ers463}464
465// iterate as pointer or OAIGen resolution may introduce inline schemas or pointers466for hasIntroducedPointerOrInline {467if !opts.Minimal {468opts.Spec.reload() // re-analyze469if err := nameInlinedSchemas(opts); err != nil {470return err471}472}473
474if err := namePointers(opts); err != nil {475return err476}477
478// restrip and re-analyze479var err error480if hasIntroducedPointerOrInline, err = stripOAIGen(opts); err != nil {481return err482}483}484
485return nil486}
487
488// stripOAIGen strips the spec from unnecessary OAIGen constructs, initially created to dedupe flattened definitions.
489//
490// A dedupe is deemed unnecessary whenever:
491// - the only conflict is with its (single) parent: OAIGen is merged into its parent (reinlining)
492// - there is a conflict with multiple parents: merge OAIGen in first parent, the rewrite other parents to point to
493// the first parent.
494//
495// This function returns true whenever it re-inlined a complex schema, so the caller may chose to iterate
496// pointer and name resolution again.
497func stripOAIGen(opts *FlattenOpts) (bool, error) {498debugLog("stripOAIGen")499replacedWithComplex := false500
501// figure out referers of OAIGen definitions (doing it before the ref start mutating)502for _, r := range opts.flattenContext.newRefs {503updateRefParents(opts.Spec.references.allRefs, r)504}505
506for k := range opts.flattenContext.newRefs {507r := opts.flattenContext.newRefs[k]508debugLog("newRefs[%s]: isOAIGen: %t, resolved: %t, name: %s, path:%s, #parents: %d, parents: %v, ref: %s",509k, r.isOAIGen, r.resolved, r.newName, r.path, len(r.parents), r.parents, r.schema.Ref.String())510
511if !r.isOAIGen || len(r.parents) == 0 {512continue513}514
515hasReplacedWithComplex, err := stripOAIGenForRef(opts, k, r)516if err != nil {517return replacedWithComplex, err518}519
520replacedWithComplex = replacedWithComplex || hasReplacedWithComplex521}522
523debugLog("replacedWithComplex: %t", replacedWithComplex)524opts.Spec.reload() // re-analyze525
526return replacedWithComplex, nil527}
528
529// updateRefParents updates all parents of an updated $ref
530func updateRefParents(allRefs map[string]spec.Ref, r *newRef) {531if !r.isOAIGen || r.resolved { // bail on already resolved entries (avoid looping)532return533}534for k, v := range allRefs {535if r.path != v.String() {536continue537}538
539found := false540for _, p := range r.parents {541if p == k {542found = true543
544break545}546}547if !found {548r.parents = append(r.parents, k)549}550}551}
552
553func stripOAIGenForRef(opts *FlattenOpts, k string, r *newRef) (bool, error) {554replacedWithComplex := false555
556pr := sortref.TopmostFirst(r.parents)557
558// rewrite first parent schema in hierarchical then lexicographical order559debugLog("rewrite first parent %s with schema", pr[0])560if err := replace.UpdateRefWithSchema(opts.Swagger(), pr[0], r.schema); err != nil {561return false, err562}563
564if pa, ok := opts.flattenContext.newRefs[pr[0]]; ok && pa.isOAIGen {565// update parent in ref index entry566debugLog("update parent entry: %s", pr[0])567pa.schema = r.schema568pa.resolved = false569replacedWithComplex = true570}571
572// rewrite other parents to point to first parent573if len(pr) > 1 {574for _, p := range pr[1:] {575replacingRef := spec.MustCreateRef(pr[0])576
577// set complex when replacing ref is an anonymous jsonpointer: further processing may be required578replacedWithComplex = replacedWithComplex || path.Dir(replacingRef.String()) != definitionsPath579debugLog("rewrite parent with ref: %s", replacingRef.String())580
581// NOTE: it is possible at this stage to introduce json pointers (to non-definitions places).582// Those are stripped later on.583if err := replace.UpdateRef(opts.Swagger(), p, replacingRef); err != nil {584return false, err585}586
587if pa, ok := opts.flattenContext.newRefs[p]; ok && pa.isOAIGen {588// update parent in ref index589debugLog("update parent entry: %s", p)590pa.schema = r.schema591pa.resolved = false592replacedWithComplex = true593}594}595}596
597// remove OAIGen definition598debugLog("removing definition %s", path.Base(r.path))599delete(opts.Swagger().Definitions, path.Base(r.path))600
601// propagate changes in ref index for keys which have this one as a parent602for kk, value := range opts.flattenContext.newRefs {603if kk == k || !value.isOAIGen || value.resolved {604continue605}606
607found := false608newParents := make([]string, 0, len(value.parents))609for _, parent := range value.parents {610switch {611case parent == r.path:612found = true613parent = pr[0]614case strings.HasPrefix(parent, r.path+"/"):615found = true616parent = path.Join(pr[0], strings.TrimPrefix(parent, r.path))617}618
619newParents = append(newParents, parent)620}621
622if found {623value.parents = newParents624}625}626
627// mark naming conflict as resolved628debugLog("marking naming conflict resolved for key: %s", r.key)629opts.flattenContext.newRefs[r.key].isOAIGen = false630opts.flattenContext.newRefs[r.key].resolved = true631
632// determine if the previous substitution did inline a complex schema633if r.schema != nil && r.schema.Ref.String() == "" { // inline schema634asch, err := Schema(SchemaOpts{Schema: r.schema, Root: opts.Swagger(), BasePath: opts.BasePath})635if err != nil {636return false, err637}638
639debugLog("re-inlined schema: parent: %s, %t", pr[0], asch.isAnalyzedAsComplex())640replacedWithComplex = replacedWithComplex || !(path.Dir(pr[0]) == definitionsPath) && asch.isAnalyzedAsComplex()641}642
643return replacedWithComplex, nil644}
645
646// namePointers replaces all JSON pointers to anonymous documents by a $ref to a new named definitions.
647//
648// This is carried on depth-first. Pointers to $refs which are top level definitions are replaced by the $ref itself.
649// Pointers to simple types are expanded, unless they express commonality (i.e. several such $ref are used).
650func namePointers(opts *FlattenOpts) error {651debugLog("name pointers")652
653refsToReplace := make(map[string]SchemaRef, len(opts.Spec.references.schemas))654for k, ref := range opts.Spec.references.allRefs {655if path.Dir(ref.String()) == definitionsPath {656// this a ref to a top-level definition: ok657continue658}659
660result, err := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), ref)661if err != nil {662return fmt.Errorf("at %s, %w", k, err)663}664
665replacingRef := result.Ref666sch := result.Schema667if opts.flattenContext != nil {668opts.flattenContext.warnings = append(opts.flattenContext.warnings, result.Warnings...)669}670
671debugLog("planning pointer to replace at %s: %s, resolved to: %s", k, ref.String(), replacingRef.String())672refsToReplace[k] = SchemaRef{673Name: k, // caller674Ref: replacingRef, // called675Schema: sch,676TopLevel: path.Dir(replacingRef.String()) == definitionsPath,677}678}679
680depthFirst := sortref.DepthFirst(refsToReplace)681namer := &InlineSchemaNamer{682Spec: opts.Swagger(),683Operations: operations.AllOpRefsByRef(opts.Spec, nil),684flattenContext: opts.flattenContext,685opts: opts,686}687
688for _, key := range depthFirst {689v := refsToReplace[key]690// update current replacement, which may have been updated by previous changes of deeper elements691result, erd := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), v.Ref)692if erd != nil {693return fmt.Errorf("at %s, %w", key, erd)694}695
696if opts.flattenContext != nil {697opts.flattenContext.warnings = append(opts.flattenContext.warnings, result.Warnings...)698}699
700v.Ref = result.Ref701v.Schema = result.Schema702v.TopLevel = path.Dir(result.Ref.String()) == definitionsPath703debugLog("replacing pointer at %s: resolved to: %s", key, v.Ref.String())704
705if v.TopLevel {706debugLog("replace pointer %s by canonical definition: %s", key, v.Ref.String())707
708// if the schema is a $ref to a top level definition, just rewrite the pointer to this $ref709if err := replace.UpdateRef(opts.Swagger(), key, v.Ref); err != nil {710return err711}712
713continue714}715
716if err := flattenAnonPointer(key, v, refsToReplace, namer, opts); err != nil {717return err718}719}720
721opts.Spec.reload() // re-analyze722
723return nil724}
725
726func flattenAnonPointer(key string, v SchemaRef, refsToReplace map[string]SchemaRef, namer *InlineSchemaNamer, opts *FlattenOpts) error {727// this is a JSON pointer to an anonymous document (internal or external):728// create a definition for this schema when:729// - it is a complex schema730// - or it is pointed by more than one $ref (i.e. expresses commonality)731// otherwise, expand the pointer (single reference to a simple type)732//733// The named definition for this follows the target's key, not the caller's734debugLog("namePointers at %s for %s", key, v.Ref.String())735
736// qualify the expanded schema737asch, ers := Schema(SchemaOpts{Schema: v.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})738if ers != nil {739return fmt.Errorf("schema analysis [%s]: %w", key, ers)740}741callers := make([]string, 0, 64)742
743debugLog("looking for callers")744
745an := New(opts.Swagger())746for k, w := range an.references.allRefs {747r, err := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), w)748if err != nil {749return fmt.Errorf("at %s, %w", key, err)750}751
752if opts.flattenContext != nil {753opts.flattenContext.warnings = append(opts.flattenContext.warnings, r.Warnings...)754}755
756if r.Ref.String() == v.Ref.String() {757callers = append(callers, k)758}759}760
761debugLog("callers for %s: %d", v.Ref.String(), len(callers))762if len(callers) == 0 {763// has already been updated and resolved764return nil765}766
767parts := sortref.KeyParts(v.Ref.String())768debugLog("number of callers for %s: %d", v.Ref.String(), len(callers))769
770// identifying edge case when the namer did nothing because we point to a non-schema object771// no definition is created and we expand the $ref for all callers772if (!asch.IsSimpleSchema || len(callers) > 1) && !parts.IsSharedParam() && !parts.IsSharedResponse() {773debugLog("replace JSON pointer at [%s] by definition: %s", key, v.Ref.String())774if err := namer.Name(v.Ref.String(), v.Schema, asch); err != nil {775return err776}777
778// regular case: we named the $ref as a definition, and we move all callers to this new $ref779for _, caller := range callers {780if caller == key {781continue782}783
784// move $ref for next to resolve785debugLog("identified caller of %s at [%s]", v.Ref.String(), caller)786c := refsToReplace[caller]787c.Ref = v.Ref788refsToReplace[caller] = c789}790
791return nil792}793
794debugLog("expand JSON pointer for key=%s", key)795
796if err := replace.UpdateRefWithSchema(opts.Swagger(), key, v.Schema); err != nil {797return err798}799// NOTE: there is no other caller to update800
801return nil802}
803