Skip to content

Commit 3cd9cf1

Browse files
authored
[TFgen] Added a identifier translator to match the go SDK (#3850)
* [TFgen] Added a identifier translator to match the goSDK The translator will transform the OAS snake_case names into the equivalent PascalCase that are used in the go SDK. Removed internal ticket names. * [TFgen] Added .gitignore
1 parent a9e21ec commit 3cd9cf1

7 files changed

Lines changed: 119 additions & 4 deletions

File tree

.generator-v2/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tfgen

.generator-v2/internal/cli/generate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ func newGenerateCmd(flags *globalFlags) *cobra.Command {
1212
Use: "generate",
1313
Short: "Generate Terraform artifacts from the OpenAPI spec",
1414
RunE: func(cmd *cobra.Command, args []string) error {
15-
// TODO: implement in T033
15+
// TODO: implement
1616
return nil
1717
},
1818
}

.generator-v2/internal/cli/verify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func newVerifyCmd(flags *globalFlags) *cobra.Command {
1111
Use: "verify",
1212
Short: "Run post-generation checks without writing files",
1313
RunE: func(cmd *cobra.Command, args []string) error {
14-
// TODO: implement in T065
14+
// TODO: implement
1515
return nil
1616
},
1717
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package model
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
"unicode"
7+
)
8+
9+
// Mirroring the generator's own rule is what makes the names we emit match
10+
// datadog-api-client-go, the SDK uses naive PascalCase with
11+
// no acronym uppercasing ("org_id" → "OrgId","url" → "Url", "uuid" → "Uuid").
12+
var (
13+
patternLeadingAlpha = regexp.MustCompile(`(.)([A-Z][a-z]+)`)
14+
patternFollowingAlpha = regexp.MustCompile(`([a-z0-9])([A-Z])`)
15+
patternWhitespace = regexp.MustCompile(`\W`)
16+
patternDoubleUnderscore = regexp.MustCompile(`__+`)
17+
)
18+
19+
func snakeCase(value string) string {
20+
value = patternLeadingAlpha.ReplaceAllString(value, "${1}_${2}")
21+
value = strings.ToLower(patternFollowingAlpha.ReplaceAllString(value, "${1}_${2}"))
22+
value = patternWhitespace.ReplaceAllString(value, "_")
23+
value = strings.TrimRight(value, "_")
24+
return patternDoubleUnderscore.ReplaceAllString(value, "_")
25+
}
26+
27+
// SdkName translates an OpenAPI identifier into the PascalCase form used by
28+
// datadog-api-client-go.
29+
//
30+
// OperationIds in the Datadog spec are already PascalCase and serve as SDK
31+
// method anchors directly; SdkName is for snake_case property and parameter names.
32+
func SdkName(openapiName string) string {
33+
var b strings.Builder
34+
for _, part := range strings.Split(snakeCase(openapiName), "_") {
35+
if part == "" {
36+
continue
37+
}
38+
runes := []rune(part)
39+
b.WriteRune(unicode.ToUpper(runes[0]))
40+
b.WriteString(string(runes[1:]))
41+
}
42+
return b.String()
43+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package model
2+
3+
import "testing"
4+
5+
// TestSdkName locks in the naive PascalCase translation: split on underscores,
6+
// Title-case each segment (first rune upper, the rest lower), and never
7+
// special-case acronyms. The "no acronym uppercasing" cases (url, uuid, ID)
8+
// are the ones that keep generated code compiling against the SDK.
9+
func TestSdkName(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
in string
13+
want string
14+
}{
15+
// Basic cases
16+
{"two snake segments", "org_id", "OrgId"},
17+
{"api key", "api_key", "ApiKey"},
18+
{"single word", "url", "Url"},
19+
{"compound word", "http_endpoint", "HttpEndpoint"},
20+
{"acronym stays naive", "uuid", "Uuid"},
21+
22+
// Mixed cases: input casing is normalized, not preserved.
23+
{"upper acronym segment lowercased", "HTTP_endpoint", "HttpEndpoint"},
24+
{"mixed segments normalized", "Org_ID", "OrgId"},
25+
{"trailing digits preserved", "o_auth2", "OAuth2"},
26+
{"camelCase split on case boundary", "isURL", "IsUrl"},
27+
28+
// Edge cases.
29+
{"empty input", "", ""},
30+
{"stray underscores skipped", "__org__id__", "OrgId"},
31+
{"leading underscore", "_id", "Id"},
32+
33+
// Generator parity: snake_case folds \W (spaces, hyphens) to
34+
// underscores, and acronyms stay naive
35+
{"whitespace folded", "foo bar", "FooBar"},
36+
{"hyphen folded", "foo-bar", "FooBar"},
37+
{"all-caps stays naive", "DASHBOARD_ID", "DashboardId"},
38+
}
39+
40+
for _, tt := range tests {
41+
t.Run(tt.name, func(t *testing.T) {
42+
if got := SdkName(tt.in); got != tt.want {
43+
t.Errorf("SdkName(%q) = %q, want %q", tt.in, got, tt.want)
44+
}
45+
})
46+
}
47+
}
48+
49+
// TestSdkNameIdempotent asserts SdkName is idempotent: applying it to an
50+
// already-converted name is a no-op, SdkName(SdkName(x)) == SdkName(x). This
51+
// guards the case where a name reaches the translator twice, and covers both
52+
// snake_case inputs and their PascalCase outputs (the latter must be fixed points).
53+
func TestSdkNameIdempotent(t *testing.T) {
54+
inputs := []string{
55+
// snake_case inputs
56+
"org_id", "api_key", "url", "http_endpoint", "uuid", "team_id", "o_auth2",
57+
// already-cased / mixed inputs
58+
"HTTP_endpoint", "Org_ID", "isURL", "v2_api",
59+
// already-PascalCase outputs — these must map to themselves
60+
"OrgId", "ApiKey", "Url", "HttpEndpoint", "Uuid", "OAuth2", "IsUrl", "HttpServer",
61+
"", "_id",
62+
}
63+
64+
for _, in := range inputs {
65+
t.Run(in, func(t *testing.T) {
66+
once := SdkName(in)
67+
twice := SdkName(once)
68+
if once != twice {
69+
t.Errorf("SdkName not idempotent: SdkName(%q) = %q, SdkName(%q) = %q", in, once, once, twice)
70+
}
71+
})
72+
}
73+
}

.generator-v2/internal/model/types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ const (
5151
IdStrategyHeaderLocation IdStrategy = "header.location"
5252
)
5353

54-
5554
// ----------------------------------------------------------------------------
5655
// Parser-facing types
5756
// ----------------------------------------------------------------------------

.generator-v2/internal/parser/parser.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package parser
33
import "github.com/pb33f/libopenapi"
44

55
// LoadSpec loads and parses the OpenAPI specification at path.
6-
// Full implementation in T009.
76
func LoadSpec(path string) (libopenapi.Document, error) {
87
panic("not implemented")
98
}

0 commit comments

Comments
 (0)