forked from grpc-ecosystem/grpc-gateway
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Naming strategy (grpc-ecosystem#2310)
* naming-strategy start * main_test change * main_test make it more readable * bazel build * Regenerate files Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
- Loading branch information
1 parent
9185677
commit e257a35
Showing
9 changed files
with
435 additions
and
146 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package genopenapi | ||
|
||
import ( | ||
"reflect" | ||
"strings" | ||
) | ||
|
||
// LookupNamingStrategy looks up the given naming strategy and returns the naming | ||
// strategy function for it. The naming strategy function takes in the list of all | ||
// fully-qualified proto message names, and returns a mapping from fully-qualified | ||
// name to OpenAPI name. | ||
func LookupNamingStrategy(strategyName string) func([]string) map[string]string { | ||
switch strings.ToLower(strategyName) { | ||
case "fqn": | ||
return resolveNamesFQN | ||
case "legacy": | ||
return resolveNamesLegacy | ||
case "simple": | ||
return resolveNamesSimple | ||
} | ||
return nil | ||
} | ||
|
||
// resolveNamesFQN uses the fully-qualified proto message name as the | ||
// OpenAPI name, stripping the leading dot. | ||
func resolveNamesFQN(messages []string) map[string]string { | ||
uniqueNames := make(map[string]string, len(messages)) | ||
for _, p := range messages { | ||
// strip leading dot from proto fqn | ||
uniqueNames[p] = p[1:] | ||
} | ||
return uniqueNames | ||
} | ||
|
||
// resolveNamesLegacy takes the names of all proto messages and generates unique references by | ||
// applying the legacy heuristics for deriving unique names: starting from the bottom of the name hierarchy, it | ||
// determines the minimum number of components necessary to yield a unique name, adds one | ||
// to that number, and then concatenates those last components with no separator in between | ||
// to form a unique name. | ||
// | ||
// E.g., if the fully qualified name is `.a.b.C.D`, and there are other messages with fully | ||
// qualified names ending in `.D` but not in `.C.D`, it assigns the unique name `bCD`. | ||
func resolveNamesLegacy(messages []string) map[string]string { | ||
return resolveNamesUniqueWithContext(messages, 1, "") | ||
} | ||
|
||
// resolveNamesSimple takes the names of all proto messages and generates unique references by using a simple | ||
// heuristic: starting from the bottom of the name hierarchy, it determines the minimum | ||
// number of components necessary to yield a unique name, and then concatenates those last | ||
// components with a "." separator in between to form a unique name. | ||
// | ||
// E.g., if the fully qualified name is `.a.b.C.D`, and there are other messages with | ||
// fully qualified names ending in `.D` but not in `.C.D`, it assigns the unique name `C.D`. | ||
func resolveNamesSimple(messages []string) map[string]string { | ||
return resolveNamesUniqueWithContext(messages, 0, ".") | ||
} | ||
|
||
// Take the names of every proto message and generates a unique reference by: | ||
// first, separating each message name into its components by splitting at dots. Then, | ||
// take the shortest suffix slice from each components slice that is unique among all | ||
// messages, and convert it into a component name by taking extraContext additional | ||
// components into consideration and joining all components with componentSeparator. | ||
func resolveNamesUniqueWithContext(messages []string, extraContext int, componentSeparator string) map[string]string { | ||
packagesByDepth := make(map[int][][]string) | ||
uniqueNames := make(map[string]string) | ||
|
||
hierarchy := func(pkg string) []string { | ||
return strings.Split(pkg, ".") | ||
} | ||
|
||
for _, p := range messages { | ||
h := hierarchy(p) | ||
for depth := range h { | ||
if _, ok := packagesByDepth[depth]; !ok { | ||
packagesByDepth[depth] = make([][]string, 0) | ||
} | ||
packagesByDepth[depth] = append(packagesByDepth[depth], h[len(h)-depth:]) | ||
} | ||
} | ||
|
||
count := func(list [][]string, item []string) int { | ||
i := 0 | ||
for _, element := range list { | ||
if reflect.DeepEqual(element, item) { | ||
i++ | ||
} | ||
} | ||
return i | ||
} | ||
|
||
for _, p := range messages { | ||
h := hierarchy(p) | ||
depth := 0 | ||
for ; depth < len(h); depth++ { | ||
// depth + extraContext > 0 ensures that we only break for values of depth when the | ||
// resulting slice of name components is non-empty. Otherwise, we would return the | ||
// empty string as the concise unique name is len(messages) == 1 (which is | ||
// technically correct). | ||
if depth+extraContext > 0 && count(packagesByDepth[depth], h[len(h)-depth:]) == 1 { | ||
break | ||
} | ||
} | ||
start := len(h) - depth - extraContext | ||
if start < 0 { | ||
start = 0 | ||
} | ||
uniqueNames[p] = strings.Join(h[start:], componentSeparator) | ||
} | ||
return uniqueNames | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package genopenapi | ||
|
||
import "testing" | ||
|
||
func TestNaming(t *testing.T) { | ||
type expectedNames struct { | ||
fqn, legacy, simple string | ||
} | ||
messageNameToExpected := map[string]expectedNames{ | ||
".A": {"A", "A", "A"}, | ||
".a.B.C": {"a.B.C", "aBC", "B.C"}, | ||
".a.D.C": {"a.D.C", "aDC", "D.C"}, | ||
".a.E.F": {"a.E.F", "aEF", "a.E.F"}, | ||
".b.E.F": {"b.E.F", "bEF", "b.E.F"}, | ||
".c.G.H": {"c.G.H", "GH", "H"}, | ||
} | ||
|
||
allMessageNames := make([]string, 0, len(messageNameToExpected)) | ||
for msgName := range messageNameToExpected { | ||
allMessageNames = append(allMessageNames, msgName) | ||
} | ||
|
||
t.Run("fqn", func(t *testing.T) { | ||
uniqueNames := resolveNamesFQN(allMessageNames) | ||
for _, msgName := range allMessageNames { | ||
expected := messageNameToExpected[msgName].fqn | ||
actual := uniqueNames[msgName] | ||
if expected != actual { | ||
t.Errorf("fqn unique name %q does not match expected name %q", actual, expected) | ||
} | ||
} | ||
}) | ||
t.Run("legacy", func(t *testing.T) { | ||
uniqueNames := resolveNamesLegacy(allMessageNames) | ||
for _, msgName := range allMessageNames { | ||
expected := messageNameToExpected[msgName].legacy | ||
actual := uniqueNames[msgName] | ||
if expected != actual { | ||
t.Errorf("legacy unique name %q does not match expected name %q", actual, expected) | ||
} | ||
} | ||
}) | ||
t.Run("simple", func(t *testing.T) { | ||
uniqueNames := resolveNamesSimple(allMessageNames) | ||
for _, msgName := range allMessageNames { | ||
expected := messageNameToExpected[msgName].simple | ||
actual := uniqueNames[msgName] | ||
if expected != actual { | ||
t.Errorf("simple unique name %q does not match expected name %q", actual, expected) | ||
} | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.