Skip to content

Commit df2333c

Browse files
committed
re-updating guidance for snake / kebab case
rolling out the changed based on aep-dev/aeps#320, where variables are all snake_case while resource types and path constants are kebab-base
1 parent 904e500 commit df2333c

File tree

10 files changed

+76
-27
lines changed

10 files changed

+76
-27
lines changed

examples/resource-definitions/bookstore.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ resources:
7777
properties:
7878
success:
7979
type: boolean
80-
book_edition:
81-
singular: "book_edition"
82-
plural: "book_editions"
80+
book-edition:
81+
singular: "book-edition"
82+
plural: "book-editions"
8383
parents: ["book"]
8484
schema:
8585
type: object

pkg/api/api_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ func TestLoadFromJsonBookstore(t *testing.T) {
603603
assert.NotEmpty(t, apiResult.Resources, "Resources map should be populated")
604604
assert.Contains(t, apiResult.Resources, "publisher", "Resources map should contain 'publisher'")
605605
assert.Contains(t, apiResult.Resources, "book", "Resources map should contain 'book'")
606-
assert.Contains(t, apiResult.Resources, "book_edition", "Resources map should contain 'book-edition'")
606+
assert.Contains(t, apiResult.Resources, "book-edition", "Resources map should contain 'book-edition'")
607607
assert.Contains(t, apiResult.Resources, "isbn", "Resources map should contain 'isbn'")
608608

609609
// Check some details of a resource
@@ -631,4 +631,15 @@ func TestLoadFromJsonBookstore(t *testing.T) {
631631
assert.True(t, bookResource.Methods.Create.SupportsUserSettableCreate)
632632
assert.Len(t, bookResource.CustomMethods, 1, "'book' should have 1 custom method")
633633
assert.Equal(t, "archive", bookResource.CustomMethods[0].Name)
634+
635+
// Check book-edition resource details
636+
bookEditionResource := apiResult.Resources["book-edition"]
637+
assert.NotNil(t, bookEditionResource, "'book-edition' resource should not be nil")
638+
assert.Equal(t, "book-edition", bookEditionResource.Singular)
639+
assert.Equal(t, "book-editions", bookEditionResource.Plural)
640+
assert.NotNil(t, bookEditionResource.Schema, "'book-edition' resource schema should not be nil")
641+
assert.Equal(t, "object", bookEditionResource.Schema.Type)
642+
assert.Contains(t, bookEditionResource.Schema.Properties, "displayname")
643+
assert.Equal(t, "string", bookEditionResource.Schema.Properties["displayname"].Type)
644+
assert.NotNil(t, bookEditionResource.Methods.List, "'book-edition' should have List method")
634645
}

pkg/api/loader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/aep-dev/aep-lib-go/pkg/openapi"
1010
)
1111

12-
var singularPluralRegex = regexp.MustCompile("^[a-z][a-z0-9_]*[a-z0-9]$")
12+
var singularPluralRegex = regexp.MustCompile("^[a-z][a-z0-9_-]*[a-z0-9]$")
1313

1414
func LoadAPIFromJson(data []byte) (*API, error) {
1515
api := &API{}

pkg/api/openapi.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
5858
Ref: schemaRef,
5959
}
6060
singular := r.Singular
61+
// Convert kebab-case singular to snake_case for path variables
62+
singularSnake := cases.KebabToSnakeCase(singular)
6163
// declare some commonly used objects, to be used later.
6264
bodyParam := openapi.RequestBody{
6365
Required: true,
@@ -69,7 +71,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
6971
}
7072
idParam := openapi.Parameter{
7173
In: "path",
72-
Name: singular + "_id",
74+
Name: singularSnake + "_id",
7375
Required: true,
7476
Schema: &openapi.Schema{
7577
Type: "string",
@@ -84,7 +86,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
8486
},
8587
}
8688
for _, pwp := range *parentPWPS {
87-
resourcePath := fmt.Sprintf("%s%s/{%s_id}", pwp.Pattern, collection, singular)
89+
resourcePath := fmt.Sprintf("%s%s/{%s_id}", pwp.Pattern, collection, singularSnake)
8890
patterns = append(patterns, resourcePath[1:])
8991
if r.Methods.List != nil {
9092
listPath := fmt.Sprintf("%s%s", pwp.Pattern, collection)
@@ -145,7 +147,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
145147
})
146148
}
147149
methodInfo := openapi.Operation{
148-
OperationID: fmt.Sprintf("List%s", cases.SnakeToPascalCase(r.Singular)),
150+
OperationID: fmt.Sprintf("List%s", cases.SnakeToPascalCase(singularSnake)),
149151
Description: fmt.Sprintf("List method for %s", r.Singular),
150152
Parameters: params,
151153
Responses: map[string]openapi.Response{
@@ -178,7 +180,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
178180
})
179181
}
180182
methodInfo := openapi.Operation{
181-
OperationID: fmt.Sprintf("Create%s", cases.SnakeToPascalCase(r.Singular)),
183+
OperationID: fmt.Sprintf("Create%s", cases.SnakeToPascalCase(singularSnake)),
182184
Description: fmt.Sprintf("Create method for %s", r.Singular),
183185
Parameters: params,
184186
RequestBody: &bodyParam,
@@ -209,7 +211,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
209211
}
210212
if r.Methods.Get != nil {
211213
methodInfo := openapi.Operation{
212-
OperationID: fmt.Sprintf("Get%s", cases.SnakeToPascalCase(r.Singular)),
214+
OperationID: fmt.Sprintf("Get%s", cases.SnakeToPascalCase(singularSnake)),
213215
Description: fmt.Sprintf("Get method for %s", r.Singular),
214216
Parameters: append(pwp.Params, idParam),
215217
Responses: map[string]openapi.Response{
@@ -220,7 +222,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
220222
}
221223
if r.Methods.Update != nil {
222224
methodInfo := openapi.Operation{
223-
OperationID: fmt.Sprintf("Update%s", cases.SnakeToPascalCase(r.Singular)),
225+
OperationID: fmt.Sprintf("Update%s", cases.SnakeToPascalCase(singularSnake)),
224226
Description: fmt.Sprintf("Update method for %s", r.Singular),
225227
Parameters: append(pwp.Params, idParam),
226228
RequestBody: &openapi.RequestBody{
@@ -281,7 +283,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
281283
})
282284
}
283285
methodInfo := openapi.Operation{
284-
OperationID: fmt.Sprintf("Delete%s", cases.SnakeToPascalCase(r.Singular)),
286+
OperationID: fmt.Sprintf("Delete%s", cases.SnakeToPascalCase(singularSnake)),
285287
Description: fmt.Sprintf("Delete method for %s", r.Singular),
286288
Parameters: params,
287289
Responses: map[string]openapi.Response{
@@ -318,7 +320,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
318320
}
319321
if r.Methods.Apply != nil {
320322
methodInfo := openapi.Operation{
321-
OperationID: fmt.Sprintf("Apply%s", cases.SnakeToPascalCase(r.Singular)),
323+
OperationID: fmt.Sprintf("Apply%s", cases.SnakeToPascalCase(singularSnake)),
322324
Description: fmt.Sprintf("Apply method for %s", r.Singular),
323325
Parameters: append(pwp.Params, idParam),
324326
RequestBody: &bodyParam,
@@ -368,7 +370,7 @@ func ConvertToOpenAPI(api *API) (*openapi.OpenAPI, error) {
368370
}
369371
cmPath := fmt.Sprintf("%s:%s", resourcePath, custom.Name)
370372
methodInfo := openapi.Operation{
371-
OperationID: fmt.Sprintf(":%s%s", cases.SnakeToPascalCase(custom.Name), cases.SnakeToPascalCase(r.Singular)),
373+
OperationID: fmt.Sprintf(":%s%s", cases.SnakeToPascalCase(custom.Name), cases.SnakeToPascalCase(singularSnake)),
372374
Description: fmt.Sprintf("Custom method %s for %s", custom.Name, r.Singular),
373375
Parameters: append(pwp.Params, idParam),
374376
Responses: map[string]openapi.Response{
@@ -515,10 +517,12 @@ func generateParentPatternsWithParams(r *Resource) (string, *[]PathWithParams) {
515517
pwps := []PathWithParams{}
516518
for _, parent := range r.ParentResources() {
517519
singular := parent.Singular
518-
basePattern := fmt.Sprintf("/%s/{%s_id}", CollectionName(parent), singular)
520+
// Convert kebab-case singular to snake_case for path variables
521+
singularSnake := cases.KebabToSnakeCase(singular)
522+
basePattern := fmt.Sprintf("/%s/{%s_id}", CollectionName(parent), singularSnake)
519523
baseParam := openapi.Parameter{
520524
In: "path",
521-
Name: singular,
525+
Name: singularSnake,
522526
Required: true,
523527
Schema: &openapi.Schema{
524528
Type: "string",

pkg/api/openapi_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ func TestLongRunningOperation(t *testing.T) {
543543
assert.NoError(t, err)
544544
assert.NotNil(t, openAPI)
545545

546-
path := "/test_resources/{test_resource_id}:longRunningTest"
546+
path := "/test-resources/{test_resource_id}:longRunningTest"
547547
operation := openAPI.Paths[path].Post
548548
assert.NotNil(t, operation, "Expected POST operation for long-running test")
549549
assert.Equal(t, AEP_OPERATION_REF,
@@ -593,7 +593,7 @@ func TestLongRunningMethods(t *testing.T) {
593593
assert.NotNil(t, openAPI)
594594

595595
// Validate CreateMethod
596-
createPath := "/test_resources"
596+
createPath := "/test-resources"
597597
createOperation := openAPI.Paths[createPath].Post
598598
assert.NotNil(t, createOperation, "Expected POST operation for CreateMethod")
599599
assert.Equal(t, AEP_OPERATION_REF,
@@ -603,7 +603,7 @@ func TestLongRunningMethods(t *testing.T) {
603603
"Expected XAEPLongRunningOperation to be set for CreateMethod")
604604

605605
// Validate ApplyMethod
606-
applyPath := "/test_resources/{test_resource_id}"
606+
applyPath := "/test-resources/{test_resource_id}"
607607
applyOperation := openAPI.Paths[applyPath].Put
608608
assert.NotNil(t, applyOperation, "Expected PUT operation for ApplyMethod")
609609
assert.Equal(t, AEP_OPERATION_REF,

pkg/api/resource.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"strings"
66

7+
"github.com/aep-dev/aep-lib-go/pkg/cases"
78
"github.com/aep-dev/aep-lib-go/pkg/openapi"
89
)
910

@@ -97,18 +98,21 @@ func CollectionName(r *Resource) string {
9798
parent := r.ParentResources()[0].Singular
9899
// if collectionName has a prefix of parent, remove it
99100
if strings.HasPrefix(collectionName, parent) {
100-
collectionName = strings.TrimPrefix(collectionName, parent+"_")
101+
collectionName = strings.TrimPrefix(collectionName, parent+"-")
101102
}
102103
}
103-
return collectionName
104+
// Convert to kebab-case for path elements
105+
return cases.SnakeToKebabCase(collectionName)
104106
}
105107

106108
// GeneratePatternStrings generates the pattern strings for a resource
107109
// TODO(yft): support multiple parents
108110
func (r *Resource) PatternElems() []string {
109111
if len(r.patternElems) == 0 {
112+
// Convert kebab-case singular to snake_case for path variables
113+
singularSnake := cases.KebabToSnakeCase(r.Singular)
110114
// Base pattern without params
111-
patternElems := []string{CollectionName(r), fmt.Sprintf("{%s_id}", r.Singular)}
115+
patternElems := []string{CollectionName(r), fmt.Sprintf("{%s_id}", singularSnake)}
112116
if len(r.Parents) > 0 {
113117
patternElems = append(
114118
r.ParentResources()[0].PatternElems(),

pkg/api/testutils.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,10 @@ func ExampleAPI() *API {
119119
}
120120
publisher.Children = append(publisher.Children, tome)
121121

122-
// Create book_edition resource
122+
// Create book-edition resource
123123
bookEdition := &Resource{
124-
Singular: "book_edition",
125-
Plural: "book_editions",
124+
Singular: "book-edition",
125+
Plural: "book-editions",
126126
Parents: []string{"book"},
127127
parentResources: []*Resource{book},
128128
Schema: &openapi.Schema{
@@ -157,7 +157,7 @@ func ExampleAPI() *API {
157157
},
158158
Resources: map[string]*Resource{
159159
"book": book,
160-
"book_edition": bookEdition,
160+
"book-edition": bookEdition,
161161
"publisher": publisher,
162162
"operation": OperationResourceWithDefaults(),
163163
"tome": tome,

pkg/cases/cases.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ func KebabToSnakeCase(s string) string {
8787
return strings.ReplaceAll(s, "-", "_")
8888
}
8989

90+
func SnakeToKebabCase(s string) string {
91+
return strings.ReplaceAll(s, "_", "-")
92+
}
93+
9094
func UpperFirst(s string) string {
9195
return strings.ToUpper(s[:1]) + s[1:]
9296
}

pkg/cases/cases_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,30 @@ func TestKebabToSnakeCase(t *testing.T) {
7474
}
7575
}
7676

77+
func TestSnakeToKebabCase(t *testing.T) {
78+
tests := []struct {
79+
name string
80+
input string
81+
expected string
82+
}{
83+
{"empty string", "", ""},
84+
{"single word", "user", "user"},
85+
{"multiple words", "user_profile", "user-profile"},
86+
{"consecutive underscores", "api__response", "api--response"},
87+
{"starting with underscore", "_background", "-background"},
88+
{"ending with underscore", "foreground_", "foreground-"},
89+
}
90+
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
got := SnakeToKebabCase(tt.input)
94+
if got != tt.expected {
95+
t.Errorf("SnakeToKebabCase(%q) = %q, want %q", tt.input, got, tt.expected)
96+
}
97+
})
98+
}
99+
}
100+
77101
func TestUpperFirst(t *testing.T) {
78102
tests := []struct {
79103
name string

pkg/proto/proto.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,9 @@ func toProtoServiceName(serviceName string) string {
143143
}
144144

145145
func toMessageName(resource string) string {
146-
return cases.SnakeToCamelCase(resource)
146+
// Convert kebab-case to snake_case first, then to camel case
147+
snakeCase := cases.KebabToSnakeCase(resource)
148+
return cases.SnakeToCamelCase(snakeCase)
147149
}
148150

149151
func getSortedResources(a *api.API) []*api.Resource {

0 commit comments

Comments
 (0)