mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 05:52:24 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			308 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			308 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package analysis
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"path"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/go-openapi/analysis/internal/flatten/operations"
 | 
						|
	"github.com/go-openapi/analysis/internal/flatten/replace"
 | 
						|
	"github.com/go-openapi/analysis/internal/flatten/schutils"
 | 
						|
	"github.com/go-openapi/analysis/internal/flatten/sortref"
 | 
						|
	"github.com/go-openapi/spec"
 | 
						|
	"github.com/go-openapi/swag"
 | 
						|
)
 | 
						|
 | 
						|
// InlineSchemaNamer finds a new name for an inlined type
 | 
						|
type InlineSchemaNamer struct {
 | 
						|
	Spec           *spec.Swagger
 | 
						|
	Operations     map[string]operations.OpRef
 | 
						|
	flattenContext *context
 | 
						|
	opts           *FlattenOpts
 | 
						|
}
 | 
						|
 | 
						|
// Name yields a new name for the inline schema
 | 
						|
func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *AnalyzedSchema) error {
 | 
						|
	debugLog("naming inlined schema at %s", key)
 | 
						|
 | 
						|
	parts := sortref.KeyParts(key)
 | 
						|
	for _, name := range namesFromKey(parts, aschema, isn.Operations) {
 | 
						|
		if name == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// create unique name
 | 
						|
		mangle := mangler(isn.opts)
 | 
						|
		newName, isOAIGen := uniqifyName(isn.Spec.Definitions, mangle(name))
 | 
						|
 | 
						|
		// clone schema
 | 
						|
		sch := schutils.Clone(schema)
 | 
						|
 | 
						|
		// replace values on schema
 | 
						|
		debugLog("rewriting schema to ref: key=%s with new name: %s", key, newName)
 | 
						|
		if err := replace.RewriteSchemaToRef(isn.Spec, key,
 | 
						|
			spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
 | 
						|
			return fmt.Errorf("error while creating definition %q from inline schema: %w", newName, err)
 | 
						|
		}
 | 
						|
 | 
						|
		// rewrite any dependent $ref pointing to this place,
 | 
						|
		// when not already pointing to a top-level definition.
 | 
						|
		//
 | 
						|
		// NOTE: this is important if such referers use arbitrary JSON pointers.
 | 
						|
		an := New(isn.Spec)
 | 
						|
		for k, v := range an.references.allRefs {
 | 
						|
			r, erd := replace.DeepestRef(isn.opts.Swagger(), isn.opts.ExpandOpts(false), v)
 | 
						|
			if erd != nil {
 | 
						|
				return fmt.Errorf("at %s, %w", k, erd)
 | 
						|
			}
 | 
						|
 | 
						|
			if isn.opts.flattenContext != nil {
 | 
						|
				isn.opts.flattenContext.warnings = append(isn.opts.flattenContext.warnings, r.Warnings...)
 | 
						|
			}
 | 
						|
 | 
						|
			if r.Ref.String() != key && (r.Ref.String() != path.Join(definitionsPath, newName) || path.Dir(v.String()) == definitionsPath) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			debugLog("found a $ref to a rewritten schema: %s points to %s", k, v.String())
 | 
						|
 | 
						|
			// rewrite $ref to the new target
 | 
						|
			if err := replace.UpdateRef(isn.Spec, k,
 | 
						|
				spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// NOTE: this extension is currently not used by go-swagger (provided for information only)
 | 
						|
		sch.AddExtension("x-go-gen-location", GenLocation(parts))
 | 
						|
 | 
						|
		// save cloned schema to definitions
 | 
						|
		schutils.Save(isn.Spec, newName, sch)
 | 
						|
 | 
						|
		// keep track of created refs
 | 
						|
		if isn.flattenContext == nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		debugLog("track created ref: key=%s, newName=%s, isOAIGen=%t", key, newName, isOAIGen)
 | 
						|
		resolved := false
 | 
						|
 | 
						|
		if _, ok := isn.flattenContext.newRefs[key]; ok {
 | 
						|
			resolved = isn.flattenContext.newRefs[key].resolved
 | 
						|
		}
 | 
						|
 | 
						|
		isn.flattenContext.newRefs[key] = &newRef{
 | 
						|
			key:      key,
 | 
						|
			newName:  newName,
 | 
						|
			path:     path.Join(definitionsPath, newName),
 | 
						|
			isOAIGen: isOAIGen,
 | 
						|
			resolved: resolved,
 | 
						|
			schema:   sch,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// uniqifyName yields a unique name for a definition
 | 
						|
func uniqifyName(definitions spec.Definitions, name string) (string, bool) {
 | 
						|
	isOAIGen := false
 | 
						|
	if name == "" {
 | 
						|
		name = "oaiGen"
 | 
						|
		isOAIGen = true
 | 
						|
	}
 | 
						|
 | 
						|
	if len(definitions) == 0 {
 | 
						|
		return name, isOAIGen
 | 
						|
	}
 | 
						|
 | 
						|
	unq := true
 | 
						|
	for k := range definitions {
 | 
						|
		if strings.EqualFold(k, name) {
 | 
						|
			unq = false
 | 
						|
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if unq {
 | 
						|
		return name, isOAIGen
 | 
						|
	}
 | 
						|
 | 
						|
	name += "OAIGen"
 | 
						|
	isOAIGen = true
 | 
						|
	var idx int
 | 
						|
	unique := name
 | 
						|
	_, known := definitions[unique]
 | 
						|
 | 
						|
	for known {
 | 
						|
		idx++
 | 
						|
		unique = fmt.Sprintf("%s%d", name, idx)
 | 
						|
		_, known = definitions[unique]
 | 
						|
	}
 | 
						|
 | 
						|
	return unique, isOAIGen
 | 
						|
}
 | 
						|
 | 
						|
func namesFromKey(parts sortref.SplitKey, aschema *AnalyzedSchema, operations map[string]operations.OpRef) []string {
 | 
						|
	var (
 | 
						|
		baseNames  [][]string
 | 
						|
		startIndex int
 | 
						|
	)
 | 
						|
 | 
						|
	switch {
 | 
						|
	case parts.IsOperation():
 | 
						|
		baseNames, startIndex = namesForOperation(parts, operations)
 | 
						|
	case parts.IsDefinition():
 | 
						|
		baseNames, startIndex = namesForDefinition(parts)
 | 
						|
	default:
 | 
						|
		// this a non-standard pointer: build a name by concatenating its parts
 | 
						|
		baseNames = [][]string{parts}
 | 
						|
		startIndex = len(baseNames) + 1
 | 
						|
	}
 | 
						|
 | 
						|
	result := make([]string, 0, len(baseNames))
 | 
						|
	for _, segments := range baseNames {
 | 
						|
		nm := parts.BuildName(segments, startIndex, partAdder(aschema))
 | 
						|
		if nm == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		result = append(result, nm)
 | 
						|
	}
 | 
						|
	sort.Strings(result)
 | 
						|
 | 
						|
	debugLog("names from parts: %v => %v", parts, result)
 | 
						|
	return result
 | 
						|
}
 | 
						|
 | 
						|
func namesForParam(parts sortref.SplitKey, operations map[string]operations.OpRef) ([][]string, int) {
 | 
						|
	var (
 | 
						|
		baseNames  [][]string
 | 
						|
		startIndex int
 | 
						|
	)
 | 
						|
 | 
						|
	piref := parts.PathItemRef()
 | 
						|
	if piref.String() != "" && parts.IsOperationParam() {
 | 
						|
		if op, ok := operations[piref.String()]; ok {
 | 
						|
			startIndex = 5
 | 
						|
			baseNames = append(baseNames, []string{op.ID, "params", "body"})
 | 
						|
		}
 | 
						|
	} else if parts.IsSharedOperationParam() {
 | 
						|
		pref := parts.PathRef()
 | 
						|
		for k, v := range operations {
 | 
						|
			if strings.HasPrefix(k, pref.String()) {
 | 
						|
				startIndex = 4
 | 
						|
				baseNames = append(baseNames, []string{v.ID, "params", "body"})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return baseNames, startIndex
 | 
						|
}
 | 
						|
 | 
						|
func namesForOperation(parts sortref.SplitKey, operations map[string]operations.OpRef) ([][]string, int) {
 | 
						|
	var (
 | 
						|
		baseNames  [][]string
 | 
						|
		startIndex int
 | 
						|
	)
 | 
						|
 | 
						|
	// params
 | 
						|
	if parts.IsOperationParam() || parts.IsSharedOperationParam() {
 | 
						|
		baseNames, startIndex = namesForParam(parts, operations)
 | 
						|
	}
 | 
						|
 | 
						|
	// responses
 | 
						|
	if parts.IsOperationResponse() {
 | 
						|
		piref := parts.PathItemRef()
 | 
						|
		if piref.String() != "" {
 | 
						|
			if op, ok := operations[piref.String()]; ok {
 | 
						|
				startIndex = 6
 | 
						|
				baseNames = append(baseNames, []string{op.ID, parts.ResponseName(), "body"})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return baseNames, startIndex
 | 
						|
}
 | 
						|
 | 
						|
func namesForDefinition(parts sortref.SplitKey) ([][]string, int) {
 | 
						|
	nm := parts.DefinitionName()
 | 
						|
	if nm != "" {
 | 
						|
		return [][]string{{parts.DefinitionName()}}, 2
 | 
						|
	}
 | 
						|
 | 
						|
	return [][]string{}, 0
 | 
						|
}
 | 
						|
 | 
						|
// partAdder knows how to interpret a schema when it comes to build a name from parts
 | 
						|
func partAdder(aschema *AnalyzedSchema) sortref.PartAdder {
 | 
						|
	return func(part string) []string {
 | 
						|
		segments := make([]string, 0, 2)
 | 
						|
 | 
						|
		if part == "items" || part == "additionalItems" {
 | 
						|
			if aschema.IsTuple || aschema.IsTupleWithExtra {
 | 
						|
				segments = append(segments, "tuple")
 | 
						|
			} else {
 | 
						|
				segments = append(segments, "items")
 | 
						|
			}
 | 
						|
 | 
						|
			if part == "additionalItems" {
 | 
						|
				segments = append(segments, part)
 | 
						|
			}
 | 
						|
 | 
						|
			return segments
 | 
						|
		}
 | 
						|
 | 
						|
		segments = append(segments, part)
 | 
						|
 | 
						|
		return segments
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func mangler(o *FlattenOpts) func(string) string {
 | 
						|
	if o.KeepNames {
 | 
						|
		return func(in string) string { return in }
 | 
						|
	}
 | 
						|
 | 
						|
	return swag.ToJSONName
 | 
						|
}
 | 
						|
 | 
						|
func nameFromRef(ref spec.Ref, o *FlattenOpts) string {
 | 
						|
	mangle := mangler(o)
 | 
						|
 | 
						|
	u := ref.GetURL()
 | 
						|
	if u.Fragment != "" {
 | 
						|
		return mangle(path.Base(u.Fragment))
 | 
						|
	}
 | 
						|
 | 
						|
	if u.Path != "" {
 | 
						|
		bn := path.Base(u.Path)
 | 
						|
		if bn != "" && bn != "/" {
 | 
						|
			ext := path.Ext(bn)
 | 
						|
			if ext != "" {
 | 
						|
				return mangle(bn[:len(bn)-len(ext)])
 | 
						|
			}
 | 
						|
 | 
						|
			return mangle(bn)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return mangle(strings.ReplaceAll(u.Host, ".", " "))
 | 
						|
}
 | 
						|
 | 
						|
// GenLocation indicates from which section of the specification (models or operations) a definition has been created.
 | 
						|
//
 | 
						|
// This is reflected in the output spec with a "x-go-gen-location" extension. At the moment, this is provided
 | 
						|
// for information only.
 | 
						|
func GenLocation(parts sortref.SplitKey) string {
 | 
						|
	switch {
 | 
						|
	case parts.IsOperation():
 | 
						|
		return "operations"
 | 
						|
	case parts.IsDefinition():
 | 
						|
		return "models"
 | 
						|
	default:
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
}
 |