...

Source file src/github.com/go-openapi/analysis/flatten_name.go

Documentation: github.com/go-openapi/analysis

     1  package analysis
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/go-openapi/analysis/internal/flatten/operations"
    10  	"github.com/go-openapi/analysis/internal/flatten/replace"
    11  	"github.com/go-openapi/analysis/internal/flatten/schutils"
    12  	"github.com/go-openapi/analysis/internal/flatten/sortref"
    13  	"github.com/go-openapi/spec"
    14  	"github.com/go-openapi/swag"
    15  )
    16  
    17  // InlineSchemaNamer finds a new name for an inlined type
    18  type InlineSchemaNamer struct {
    19  	Spec           *spec.Swagger
    20  	Operations     map[string]operations.OpRef
    21  	flattenContext *context
    22  	opts           *FlattenOpts
    23  }
    24  
    25  // Name yields a new name for the inline schema
    26  func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *AnalyzedSchema) error {
    27  	debugLog("naming inlined schema at %s", key)
    28  
    29  	parts := sortref.KeyParts(key)
    30  	for _, name := range namesFromKey(parts, aschema, isn.Operations) {
    31  		if name == "" {
    32  			continue
    33  		}
    34  
    35  		// create unique name
    36  		mangle := mangler(isn.opts)
    37  		newName, isOAIGen := uniqifyName(isn.Spec.Definitions, mangle(name))
    38  
    39  		// clone schema
    40  		sch := schutils.Clone(schema)
    41  
    42  		// replace values on schema
    43  		debugLog("rewriting schema to ref: key=%s with new name: %s", key, newName)
    44  		if err := replace.RewriteSchemaToRef(isn.Spec, key,
    45  			spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
    46  			return fmt.Errorf("error while creating definition %q from inline schema: %w", newName, err)
    47  		}
    48  
    49  		// rewrite any dependent $ref pointing to this place,
    50  		// when not already pointing to a top-level definition.
    51  		//
    52  		// NOTE: this is important if such referers use arbitrary JSON pointers.
    53  		an := New(isn.Spec)
    54  		for k, v := range an.references.allRefs {
    55  			r, erd := replace.DeepestRef(isn.opts.Swagger(), isn.opts.ExpandOpts(false), v)
    56  			if erd != nil {
    57  				return fmt.Errorf("at %s, %w", k, erd)
    58  			}
    59  
    60  			if isn.opts.flattenContext != nil {
    61  				isn.opts.flattenContext.warnings = append(isn.opts.flattenContext.warnings, r.Warnings...)
    62  			}
    63  
    64  			if r.Ref.String() != key && (r.Ref.String() != path.Join(definitionsPath, newName) || path.Dir(v.String()) == definitionsPath) {
    65  				continue
    66  			}
    67  
    68  			debugLog("found a $ref to a rewritten schema: %s points to %s", k, v.String())
    69  
    70  			// rewrite $ref to the new target
    71  			if err := replace.UpdateRef(isn.Spec, k,
    72  				spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
    73  				return err
    74  			}
    75  		}
    76  
    77  		// NOTE: this extension is currently not used by go-swagger (provided for information only)
    78  		sch.AddExtension("x-go-gen-location", GenLocation(parts))
    79  
    80  		// save cloned schema to definitions
    81  		schutils.Save(isn.Spec, newName, sch)
    82  
    83  		// keep track of created refs
    84  		if isn.flattenContext == nil {
    85  			continue
    86  		}
    87  
    88  		debugLog("track created ref: key=%s, newName=%s, isOAIGen=%t", key, newName, isOAIGen)
    89  		resolved := false
    90  
    91  		if _, ok := isn.flattenContext.newRefs[key]; ok {
    92  			resolved = isn.flattenContext.newRefs[key].resolved
    93  		}
    94  
    95  		isn.flattenContext.newRefs[key] = &newRef{
    96  			key:      key,
    97  			newName:  newName,
    98  			path:     path.Join(definitionsPath, newName),
    99  			isOAIGen: isOAIGen,
   100  			resolved: resolved,
   101  			schema:   sch,
   102  		}
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // uniqifyName yields a unique name for a definition
   109  func uniqifyName(definitions spec.Definitions, name string) (string, bool) {
   110  	isOAIGen := false
   111  	if name == "" {
   112  		name = "oaiGen"
   113  		isOAIGen = true
   114  	}
   115  
   116  	if len(definitions) == 0 {
   117  		return name, isOAIGen
   118  	}
   119  
   120  	unq := true
   121  	for k := range definitions {
   122  		if strings.EqualFold(k, name) {
   123  			unq = false
   124  
   125  			break
   126  		}
   127  	}
   128  
   129  	if unq {
   130  		return name, isOAIGen
   131  	}
   132  
   133  	name += "OAIGen"
   134  	isOAIGen = true
   135  	var idx int
   136  	unique := name
   137  	_, known := definitions[unique]
   138  
   139  	for known {
   140  		idx++
   141  		unique = fmt.Sprintf("%s%d", name, idx)
   142  		_, known = definitions[unique]
   143  	}
   144  
   145  	return unique, isOAIGen
   146  }
   147  
   148  func namesFromKey(parts sortref.SplitKey, aschema *AnalyzedSchema, operations map[string]operations.OpRef) []string {
   149  	var (
   150  		baseNames  [][]string
   151  		startIndex int
   152  	)
   153  
   154  	switch {
   155  	case parts.IsOperation():
   156  		baseNames, startIndex = namesForOperation(parts, operations)
   157  	case parts.IsDefinition():
   158  		baseNames, startIndex = namesForDefinition(parts)
   159  	default:
   160  		// this a non-standard pointer: build a name by concatenating its parts
   161  		baseNames = [][]string{parts}
   162  		startIndex = len(baseNames) + 1
   163  	}
   164  
   165  	result := make([]string, 0, len(baseNames))
   166  	for _, segments := range baseNames {
   167  		nm := parts.BuildName(segments, startIndex, partAdder(aschema))
   168  		if nm == "" {
   169  			continue
   170  		}
   171  
   172  		result = append(result, nm)
   173  	}
   174  	sort.Strings(result)
   175  
   176  	debugLog("names from parts: %v => %v", parts, result)
   177  	return result
   178  }
   179  
   180  func namesForParam(parts sortref.SplitKey, operations map[string]operations.OpRef) ([][]string, int) {
   181  	var (
   182  		baseNames  [][]string
   183  		startIndex int
   184  	)
   185  
   186  	piref := parts.PathItemRef()
   187  	if piref.String() != "" && parts.IsOperationParam() {
   188  		if op, ok := operations[piref.String()]; ok {
   189  			startIndex = 5
   190  			baseNames = append(baseNames, []string{op.ID, "params", "body"})
   191  		}
   192  	} else if parts.IsSharedOperationParam() {
   193  		pref := parts.PathRef()
   194  		for k, v := range operations {
   195  			if strings.HasPrefix(k, pref.String()) {
   196  				startIndex = 4
   197  				baseNames = append(baseNames, []string{v.ID, "params", "body"})
   198  			}
   199  		}
   200  	}
   201  
   202  	return baseNames, startIndex
   203  }
   204  
   205  func namesForOperation(parts sortref.SplitKey, operations map[string]operations.OpRef) ([][]string, int) {
   206  	var (
   207  		baseNames  [][]string
   208  		startIndex int
   209  	)
   210  
   211  	// params
   212  	if parts.IsOperationParam() || parts.IsSharedOperationParam() {
   213  		baseNames, startIndex = namesForParam(parts, operations)
   214  	}
   215  
   216  	// responses
   217  	if parts.IsOperationResponse() {
   218  		piref := parts.PathItemRef()
   219  		if piref.String() != "" {
   220  			if op, ok := operations[piref.String()]; ok {
   221  				startIndex = 6
   222  				baseNames = append(baseNames, []string{op.ID, parts.ResponseName(), "body"})
   223  			}
   224  		}
   225  	}
   226  
   227  	return baseNames, startIndex
   228  }
   229  
   230  func namesForDefinition(parts sortref.SplitKey) ([][]string, int) {
   231  	nm := parts.DefinitionName()
   232  	if nm != "" {
   233  		return [][]string{{parts.DefinitionName()}}, 2
   234  	}
   235  
   236  	return [][]string{}, 0
   237  }
   238  
   239  // partAdder knows how to interpret a schema when it comes to build a name from parts
   240  func partAdder(aschema *AnalyzedSchema) sortref.PartAdder {
   241  	return func(part string) []string {
   242  		segments := make([]string, 0, 2)
   243  
   244  		if part == "items" || part == "additionalItems" {
   245  			if aschema.IsTuple || aschema.IsTupleWithExtra {
   246  				segments = append(segments, "tuple")
   247  			} else {
   248  				segments = append(segments, "items")
   249  			}
   250  
   251  			if part == "additionalItems" {
   252  				segments = append(segments, part)
   253  			}
   254  
   255  			return segments
   256  		}
   257  
   258  		segments = append(segments, part)
   259  
   260  		return segments
   261  	}
   262  }
   263  
   264  func mangler(o *FlattenOpts) func(string) string {
   265  	if o.KeepNames {
   266  		return func(in string) string { return in }
   267  	}
   268  
   269  	return swag.ToJSONName
   270  }
   271  
   272  func nameFromRef(ref spec.Ref, o *FlattenOpts) string {
   273  	mangle := mangler(o)
   274  
   275  	u := ref.GetURL()
   276  	if u.Fragment != "" {
   277  		return mangle(path.Base(u.Fragment))
   278  	}
   279  
   280  	if u.Path != "" {
   281  		bn := path.Base(u.Path)
   282  		if bn != "" && bn != "/" {
   283  			ext := path.Ext(bn)
   284  			if ext != "" {
   285  				return mangle(bn[:len(bn)-len(ext)])
   286  			}
   287  
   288  			return mangle(bn)
   289  		}
   290  	}
   291  
   292  	return mangle(strings.ReplaceAll(u.Host, ".", " "))
   293  }
   294  
   295  // GenLocation indicates from which section of the specification (models or operations) a definition has been created.
   296  //
   297  // This is reflected in the output spec with a "x-go-gen-location" extension. At the moment, this is provided
   298  // for information only.
   299  func GenLocation(parts sortref.SplitKey) string {
   300  	switch {
   301  	case parts.IsOperation():
   302  		return "operations"
   303  	case parts.IsDefinition():
   304  		return "models"
   305  	default:
   306  		return ""
   307  	}
   308  }
   309  

View as plain text