naming.go 4.0 KB

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