Skip to content

Commit de9c118

Browse files
authored
refactor(api): apply type conversion naming pattern (#4135)
1 parent 5542629 commit de9c118

47 files changed

Lines changed: 479 additions & 383 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
name: go-types-conversion
3+
description: Naming convention for type-translation files and functions. Use when creating or editing files that convert between domain, API, and DB types.
4+
user-invocable: true
5+
allowed-tools: Read, Edit, Write, Grep, Glob
6+
---
7+
8+
# Type Translation Naming
9+
10+
Apply to new and touched code. Do not rename legacy symbols unsolicited.
11+
12+
## File naming
13+
14+
| Path contains | File name | Purpose |
15+
| ------------------------------------------------- | ------------ | ------------ |
16+
| `httpdriver/`, `httphandler/`, `api/v3/handlers/` | `convert.go` | API ↔ domain |
17+
| `adapter/`, `repo/` | `mapping.go` | DB ↔ domain |
18+
19+
Split large files by entity: `convert_plan.go`, `mapping_subscription.go`.
20+
21+
`mapper.go` is forbidden. Rename it to `convert.go` or `mapping.go` (based on layer) when the file is touched.
22+
23+
## Function naming
24+
25+
### Shape: `From<Qualifier><Thing>` / `To<Qualifier><Thing>`
26+
27+
The qualifier is `API` or `DB` — no other qualifiers (`Domain`, `Model`, package-name infixes).
28+
29+
The suffix `<Thing>` is the **non-domain type's unqualified name** — the API type or DB type, not the domain type. This keeps it stable: a matched pair (`FromAPI<Thing>` / `ToAPI<Thing>`) always refers to the same non-domain type, regardless of direction.
30+
31+
- `FromAPI<Thing>` — takes the API type `<Thing>` as input, returns the domain representation.
32+
- `ToAPI<Thing>` — takes the domain type as input, returns the API type `<Thing>`.
33+
- Same for `FromDB<Thing>` / `ToDB<Thing>`.
34+
35+
### Examples
36+
37+
```go
38+
// API ↔ domain
39+
FromAPIPlan(a api.Plan) (plan.Plan, error)
40+
ToAPIPlan(p plan.Plan) api.Plan
41+
42+
// Suffix is the API type name, even when domain type differs
43+
FromAPIPlanCreate(a api.PlanCreate) (plan.CreateInput, error)
44+
ToAPIPlanCreate(p plan.CreateInput) api.PlanCreate
45+
46+
FromAPIProRatingConfig(a api.ProRatingConfig) (productcatalog.ProRatingConfig, error)
47+
ToAPIProRatingConfig(p productcatalog.ProRatingConfig) *api.ProRatingConfig
48+
49+
// DB ↔ domain — suffix is the DB type name
50+
FromDBSubscription(row *db.Subscription) (subscription.Subscription, error)
51+
ToDBSubscription(s subscription.Subscription) *db.Subscription
52+
53+
FromDBChargeFlatFee(row *entdb.ChargeFlatFee) (flatfee.Charge, error)
54+
ToDBChargeFlatFee(c flatfee.Charge) *entdb.ChargeFlatFee
55+
```
56+
57+
### Additional rules
58+
59+
- **Exported** functions always include the type suffix (`FromAPIPlanCreate`, not bare `FromAPI`).
60+
- **Unexported** helpers in a single-type file may drop the suffix (`fromDB`, `toAPI`).
61+
- **Fallible** (parse/validate) → `(T, error)`. **Infallible** (projection) → `T`. Typically `FromAPI…` / `FromDB…` is fallible; the reverse is not.
62+
- **Batch helpers** use the plural: `FromAPIPlans`, `ToDBSubscriptions`. Same suffix rule — the plural of the non-domain type name.
63+
64+
### Forbidden patterns
65+
66+
- `Map…`, `Convert…To…`, primary `As…`
67+
- `<Source>To<Target>` shape (e.g. `APIToPlan`)
68+
- Bare `FromAPI` / `ToDB` without a type suffix
69+
- goverter or other codegen type mappers
70+
71+
## Decision tree
72+
73+
### Naming a function
74+
75+
1. **Pick the qualifier:** API/HTTP/wire on one side → `API`. DB/persistence on one side → `DB`.
76+
2. **Pick the suffix:** the non-domain type's unqualified name (`Plan`, `PlanCreate`, `ChargeFlatFee`).
77+
3. **Pick the return style:** fallible → `(T, error)`, infallible → `T`.
78+
4. **Exported?** Must include the type suffix. Unexported single-type helper may drop it.
79+
80+
### Interacting with the user
81+
82+
- **File is `mapper.go`?** Flag it — should be `convert.go` or `mapping.go` based on layer. Offer to rename as part of the edit. Don't rename silently.
83+
- **Adding new functions to a legacy file?** Use the new convention for new functions. Don't rename old ones unless asked.
84+
- **Task is "clean up this file"?** Rename, update call sites, `Grep` for the old name to catch misses, keep the rename in its own commit.
85+
- **`// Code generated` header?** Off-limits regardless.
86+
87+
## Suggestion phrasing
88+
89+
Lead with the specific rename and the reason. Keep it short.
90+
91+
> `MapChargeFlatFeeFromDB` — use `FromDBChargeFlatFee`. Want me to rename and update callers?
92+
93+
> Direction looks inverted — `FromAPI…` returns a domain type, so this should be `ToAPIPlan`. Drop the error return if it can't actually fail.
94+
95+
> This file is `mapper.go` — should be `convert.go` since it lives in `httphandler/`. Want me to rename it?

api/v3/handlers/apps/convert.gen.go

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v3/handlers/apps/convert.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,27 @@ import (
2323
// goverter:useUnderlyingTypeMethods
2424
// goverter:matchIgnoreCase
2525
// goverter:extend IntToFloat32
26-
// goverter:extend MapAppToAPI
26+
// goverter:extend ToAPIBillingApp
2727
// goverter:enum:unknown @error
2828
var (
29-
ConvertToListAppResponse func(source response.PagePaginationResponse[api.BillingApp]) api.AppPagePaginatedResponse
29+
ToAPIAppPagePaginatedResponse func(source response.PagePaginationResponse[api.BillingApp]) api.AppPagePaginatedResponse
3030

31-
ConvertMarketplaceListingToV3Api func(source app.MarketplaceListing) (api.BillingAppCatalogItem, error)
31+
ToAPIBillingAppCatalogItem func(source app.MarketplaceListing) (api.BillingAppCatalogItem, error)
3232

3333
// goverter:enum:map AppTypeStripe BillingAppTypeStripe
3434
// goverter:enum:map AppTypeSandbox BillingAppTypeSandbox
3535
// goverter:enum:map AppTypeCustomInvoicing BillingAppTypeExternalInvoicing
36-
ConvertAppTypeToV3Api func(source app.AppType) (api.BillingAppType, error)
36+
ToAPIBillingAppTypeFromDomain func(source app.AppType) (api.BillingAppType, error)
3737

38-
ConvertAppsToBillingApps func(source []app.App) ([]api.BillingApp, error)
38+
ToAPIBillingApps func(source []app.App) ([]api.BillingApp, error)
3939
)
4040

4141
func IntToFloat32(i int) float32 {
4242
return float32(i)
4343
}
4444

45-
// MapAppToAPI maps an app to an v3 API app
46-
func MapAppToAPI(item app.App) (api.BillingApp, error) {
45+
// ToAPIBillingApp maps an app to a v3 API app
46+
func ToAPIBillingApp(item app.App) (api.BillingApp, error) {
4747
if item == nil {
4848
return api.BillingApp{}, errors.New("invalid app: nil")
4949
}
@@ -55,7 +55,7 @@ func MapAppToAPI(item app.App) (api.BillingApp, error) {
5555
return api.BillingApp{}, fmt.Errorf("expected stripe app, got %T", item)
5656
}
5757

58-
billingAppStripe, err := mapStripeAppToAPI(stripeApp.Meta)
58+
billingAppStripe, err := toAPIBillingAppStripe(stripeApp.Meta)
5959
if err != nil {
6060
return api.BillingApp{}, fmt.Errorf("failed to map stripe app to API: %w", err)
6161
}
@@ -72,7 +72,7 @@ func MapAppToAPI(item app.App) (api.BillingApp, error) {
7272
return api.BillingApp{}, fmt.Errorf("expected sandbox app, got %T", item)
7373
}
7474

75-
billingAppSandbox, err := mapSandboxAppToAPI(sandboxApp.Meta)
75+
billingAppSandbox, err := toAPIBillingAppSandbox(sandboxApp.Meta)
7676
if err != nil {
7777
return api.BillingApp{}, fmt.Errorf("failed to map sandbox app to API: %w", err)
7878
}
@@ -89,7 +89,7 @@ func MapAppToAPI(item app.App) (api.BillingApp, error) {
8989
return api.BillingApp{}, fmt.Errorf("expected custom invoicing app, got %T", item)
9090
}
9191

92-
billingAppExternalInvoicing, err := mapCustomInvoicingAppToAPI(customInvoicingApp.Meta)
92+
billingAppExternalInvoicing, err := toAPIBillingAppExternalInvoicing(customInvoicingApp.Meta)
9393
if err != nil {
9494
return api.BillingApp{}, fmt.Errorf("failed to map custom invoicing app to API: %w", err)
9595
}
@@ -105,8 +105,8 @@ func MapAppToAPI(item app.App) (api.BillingApp, error) {
105105
}
106106
}
107107

108-
func mapSandboxAppToAPI(sandboxApp appsandbox.Meta) (api.BillingAppSandbox, error) {
109-
definition, err := ConvertMarketplaceListingToV3Api(sandboxApp.GetListing())
108+
func toAPIBillingAppSandbox(sandboxApp appsandbox.Meta) (api.BillingAppSandbox, error) {
109+
definition, err := ToAPIBillingAppCatalogItem(sandboxApp.GetListing())
110110
if err != nil {
111111
return api.BillingAppSandbox{}, err
112112
}
@@ -125,10 +125,10 @@ func mapSandboxAppToAPI(sandboxApp appsandbox.Meta) (api.BillingAppSandbox, erro
125125
}, nil
126126
}
127127

128-
func mapStripeAppToAPI(
128+
func toAPIBillingAppStripe(
129129
stripeApp appstripeentityapp.Meta,
130130
) (api.BillingAppStripe, error) {
131-
definition, err := ConvertMarketplaceListingToV3Api(stripeApp.GetListing())
131+
definition, err := ToAPIBillingAppCatalogItem(stripeApp.GetListing())
132132
if err != nil {
133133
return api.BillingAppStripe{}, err
134134
}
@@ -153,8 +153,8 @@ func mapStripeAppToAPI(
153153
return apiStripeApp, nil
154154
}
155155

156-
func mapCustomInvoicingAppToAPI(customInvoicingApp appcustominvoicing.Meta) (api.BillingAppExternalInvoicing, error) {
157-
definition, err := ConvertMarketplaceListingToV3Api(customInvoicingApp.GetListing())
156+
func toAPIBillingAppExternalInvoicing(customInvoicingApp appcustominvoicing.Meta) (api.BillingAppExternalInvoicing, error) {
157+
definition, err := ToAPIBillingAppCatalogItem(customInvoicingApp.GetListing())
158158
if err != nil {
159159
return api.BillingAppExternalInvoicing{}, err
160160
}

api/v3/handlers/apps/get_app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (h *handler) GetApp() GetAppHandler {
4040
return GetAppResponse{}, fmt.Errorf("failed to get app: %w", err)
4141
}
4242

43-
return MapAppToAPI(app)
43+
return ToAPIBillingApp(app)
4444
},
4545
commonhttp.JSONResponseEncoder[GetAppResponse],
4646
httptransport.AppendOptions(

api/v3/handlers/apps/list_app.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (h *handler) ListApps() ListAppsHandler {
6363
return ListAppsResponse{}, fmt.Errorf("failed to list apps: %w", err)
6464
}
6565

66-
items, err := ConvertAppsToBillingApps(result.Items)
66+
items, err := ToAPIBillingApps(result.Items)
6767
if err != nil {
6868
return ListAppsResponse{}, fmt.Errorf("failed to convert Apps to BillingApps: %w", err)
6969
}
@@ -74,7 +74,7 @@ func (h *handler) ListApps() ListAppsHandler {
7474
Total: lo.ToPtr(result.TotalCount),
7575
})
7676

77-
response := ConvertToListAppResponse(r)
77+
response := ToAPIAppPagePaginatedResponse(r)
7878

7979
return response, nil
8080
},

0 commit comments

Comments
 (0)