github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/protoc-gen-openapiv2/internal/genopenapi/naming.go (about)

     1  package genopenapi
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  )
     7  
     8  // LookupNamingStrategy looks up the given naming strategy and returns the naming
     9  // strategy function for it. The naming strategy function takes in the list of all
    10  // fully-qualified proto message names, and returns a mapping from fully-qualified
    11  // name to OpenAPI name.
    12  func LookupNamingStrategy(strategyName string) func([]string) map[string]string {
    13  	switch strings.ToLower(strategyName) {
    14  	case "fqn":
    15  		return resolveNamesFQN
    16  	case "legacy":
    17  		return resolveNamesLegacy
    18  	case "simple":
    19  		return resolveNamesSimple
    20  	}
    21  	return nil
    22  }
    23  
    24  // resolveNamesFQN uses the fully-qualified proto message name as the
    25  // OpenAPI name, stripping the leading dot.
    26  func resolveNamesFQN(messages []string) map[string]string {
    27  	uniqueNames := make(map[string]string, len(messages))
    28  	for _, p := range messages {
    29  		// strip leading dot from proto fqn
    30  		uniqueNames[p] = p[1:]
    31  	}
    32  	return uniqueNames
    33  }
    34  
    35  // resolveNamesLegacy takes the names of all proto messages and generates unique references by
    36  // applying the legacy heuristics for deriving unique names: starting from the bottom of the name hierarchy, it
    37  // determines the minimum number of components necessary to yield a unique name, adds one
    38  // to that number, and then concatenates those last components with no separator in between
    39  // to form a unique name.
    40  //
    41  // E.g., if the fully qualified name is `.a.b.C.D`, and there are other messages with fully
    42  // qualified names ending in `.D` but not in `.C.D`, it assigns the unique name `bCD`.
    43  func resolveNamesLegacy(messages []string) map[string]string {
    44  	return resolveNamesUniqueWithContext(messages, 1, "")
    45  }
    46  
    47  // resolveNamesSimple takes the names of all proto messages and generates unique references by using a simple
    48  // heuristic: starting from the bottom of the name hierarchy, it determines the minimum
    49  // number of components necessary to yield a unique name, and then concatenates those last
    50  // components with a "." separator in between to form a unique name.
    51  //
    52  // E.g., if the fully qualified name is `.a.b.C.D`, and there are other messages with
    53  // fully qualified names ending in `.D` but not in `.C.D`, it assigns the unique name `C.D`.
    54  func resolveNamesSimple(messages []string) map[string]string {
    55  	return resolveNamesUniqueWithContext(messages, 0, ".")
    56  }
    57  
    58  // Take the names of every proto message and generates a unique reference by:
    59  // first, separating each message name into its components by splitting at dots. Then,
    60  // take the shortest suffix slice from each components slice that is unique among all
    61  // messages, and convert it into a component name by taking extraContext additional
    62  // components into consideration and joining all components with componentSeparator.
    63  func resolveNamesUniqueWithContext(messages []string, extraContext int, componentSeparator string) map[string]string {
    64  	packagesByDepth := make(map[int][][]string)
    65  	uniqueNames := make(map[string]string)
    66  
    67  	hierarchy := func(pkg string) []string {
    68  		return strings.Split(pkg, ".")
    69  	}
    70  
    71  	for _, p := range messages {
    72  		h := hierarchy(p)
    73  		for depth := range h {
    74  			if _, ok := packagesByDepth[depth]; !ok {
    75  				packagesByDepth[depth] = make([][]string, 0)
    76  			}
    77  			packagesByDepth[depth] = append(packagesByDepth[depth], h[len(h)-depth:])
    78  		}
    79  	}
    80  
    81  	count := func(list [][]string, item []string) int {
    82  		i := 0
    83  		for _, element := range list {
    84  			if reflect.DeepEqual(element, item) {
    85  				i++
    86  			}
    87  		}
    88  		return i
    89  	}
    90  
    91  	for _, p := range messages {
    92  		h := hierarchy(p)
    93  		depth := 0
    94  		for ; depth < len(h); depth++ {
    95  			// depth + extraContext > 0 ensures that we only break for values of depth when the
    96  			// resulting slice of name components is non-empty. Otherwise, we would return the
    97  			// empty string as the concise unique name is len(messages) == 1 (which is
    98  			// technically correct).
    99  			if depth+extraContext > 0 && count(packagesByDepth[depth], h[len(h)-depth:]) == 1 {
   100  				break
   101  			}
   102  		}
   103  		start := len(h) - depth - extraContext
   104  		if start < 0 {
   105  			start = 0
   106  		}
   107  		uniqueNames[p] = strings.Join(h[start:], componentSeparator)
   108  	}
   109  	return uniqueNames
   110  }