Skip to content

Commit cadce9b

Browse files
feat(quick-1): add enum/repeated query param test cases and helper functions
- Add Region enum, SearchAdvancedRequest, EmptyRequest to query_params.proto - Add SearchAdvanced and GetDefaults RPCs to QueryParamService - Add TSEnumUnspecifiedValue helper for enum zero-value detection - Update TSZeroCheckForField to handle EnumKind and repeated fields - Add "enum" case to TSZeroCheck for fallback path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 871b387 commit cadce9b

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

internal/httpgen/testdata/proto/query_params.proto

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ service QueryParamService {
4444
method: HTTP_METHOD_GET
4545
};
4646
}
47+
48+
// Advanced search with enum + repeated params
49+
rpc SearchAdvanced(SearchAdvancedRequest) returns (SearchResponse) {
50+
option (sebuf.http.config) = {
51+
path: "/search/advanced"
52+
method: HTTP_METHOD_GET
53+
};
54+
}
55+
56+
// RPC with empty request message
57+
rpc GetDefaults(EmptyRequest) returns (SearchResponse) {
58+
option (sebuf.http.config) = {
59+
path: "/defaults"
60+
method: HTTP_METHOD_GET
61+
};
62+
}
4763
}
4864

4965
message SearchWithTypesRequest {
@@ -83,6 +99,25 @@ message GetWithFiltersRequest {
8399
int32 limit = 3 [(sebuf.http.query) = { name: "limit" }];
84100
}
85101

102+
enum Region {
103+
REGION_UNSPECIFIED = 0;
104+
REGION_AMERICAS = 1;
105+
REGION_EUROPE = 2;
106+
REGION_ASIA = 3;
107+
}
108+
109+
message SearchAdvancedRequest {
110+
// Enum query param (bugs #1 and #2)
111+
Region region = 1 [(sebuf.http.query) = { name: "region" }];
112+
// Repeated string query param (bugs #3 and #4)
113+
repeated string countries = 2 [(sebuf.http.query) = { name: "countries" }];
114+
// Normal string for baseline
115+
string keyword = 3 [(sebuf.http.query) = { name: "keyword" }];
116+
}
117+
118+
// Empty request message (bug #6)
119+
message EmptyRequest {}
120+
86121
message SearchResponse {
87122
repeated string results = 1;
88123
int32 total = 2;

internal/tscommon/types.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@ func TSScalarTypeForField(field *protogen.Field) string {
7171
}
7272
}
7373

74+
// TSEnumUnspecifiedValue returns the first enum value (UNSPECIFIED variant) as a quoted string.
75+
// For enum fields, this is the zero-value equivalent used in TS zero checks.
76+
func TSEnumUnspecifiedValue(field *protogen.Field) string {
77+
if field.Desc.Kind() != protoreflect.EnumKind || field.Enum == nil {
78+
return `""`
79+
}
80+
values := field.Enum.Values
81+
if len(values) == 0 {
82+
return `""`
83+
}
84+
// Check for custom enum_value annotation on the first value
85+
customValue := annotations.GetEnumValueMapping(values[0])
86+
if customValue != "" {
87+
return fmt.Sprintf(`"%s"`, customValue)
88+
}
89+
return fmt.Sprintf(`"%s"`, string(values[0].Desc.Name()))
90+
}
91+
7492
// TSZeroCheck returns the TypeScript zero-value check expression for a query param.
7593
// Uses the proto field kind (not TS type) to determine the appropriate check.
7694
// For int64/uint64 fields, this returns the STRING encoding check; use TSZeroCheckForField
@@ -89,6 +107,10 @@ func TSZeroCheck(fieldKind string) string {
89107
"uint64", "fixed64":
90108
// Default: 64-bit integers are encoded as strings in proto3 JSON
91109
return ` !== "0"`
110+
case "enum":
111+
// Without field context, we cannot determine the UNSPECIFIED value;
112+
// fall back to bool-style truthy check
113+
return ""
92114
default:
93115
return ` !== ""`
94116
}
@@ -97,17 +119,24 @@ func TSZeroCheck(fieldKind string) string {
97119
// TSZeroCheckForField returns the TypeScript zero-value check expression for a field,
98120
// checking encoding annotations for int64/uint64 fields.
99121
func TSZeroCheckForField(field *protogen.Field) string {
122+
// Repeated fields use length check (truthy check pattern)
123+
if field.Desc.IsList() {
124+
return ""
125+
}
126+
100127
kind := field.Desc.Kind()
101128

102129
// Check for int64/uint64 encoding annotation
103-
//exhaustive:ignore - only int64 kinds need special handling, default covers all others
130+
//exhaustive:ignore - only int64/enum kinds need special handling, default covers all others
104131
switch kind {
105132
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind,
106133
protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
107134
if annotations.IsInt64NumberEncoding(field) {
108135
return " !== 0" // NUMBER encoding: numeric zero check
109136
}
110137
return ` !== "0"` // Default (STRING/UNSPECIFIED): string zero check
138+
case protoreflect.EnumKind:
139+
return " !== " + TSEnumUnspecifiedValue(field)
111140
default:
112141
// All other types use the base helper
113142
return TSZeroCheck(kind.String())

0 commit comments

Comments
 (0)