Purpose: Enable developers to control whether int64/uint64 fields serialize as JSON strings (default) or numbers. Go server and client must match exactly to prevent serialization mismatches.
Output: Generated Go code respects int64_encoding annotation. Server and client produce identical JSON for the same proto message.
<execution_context> @/Users/sebastienmelki/.claude/get-shit-done/workflows/execute-plan.md @/Users/sebastienmelki/.claude/get-shit-done/templates/summary.md </execution_context>
Generator code to modify
@internal/httpgen/generator.go @internal/httpgen/unwrap.go @internal/clientgen/generator.go
Annotation package from Plan 01#
@internal/annotations/int64_encoding.go
- Create
hasInt64NumberFields(message *protogen.Message) bool- returns true if any int64/uint64 field has NUMBER encoding - Create
generateInt64MarshalCode(gf *protogen.GeneratedFile, message *protogen.Message)- generates custom MarshalJSON if needed - Create
generateInt64UnmarshalCode(gf *protogen.GeneratedFile, message *protogen.Message)- generates custom UnmarshalJSON if needed
Key implementation considerations from RESEARCH.md:
- Use type alias pattern to prevent infinite recursion:
type alias Message - For NUMBER encoding, use direct assignment (int64 -> JSON number)
- For STRING encoding (default), use strconv.FormatInt
- Support repeated int64 fields: annotation applies to element encoding
- Support map with int64 values: annotation applies to value encoding
In generator.go, call the encoding helpers during message generation:
- After generating the message struct, check if custom marshal/unmarshal needed
- Print generation-time warning for NUMBER encoding: "Warning: Field X uses int64_encoding=NUMBER. Values > 2^53 may lose precision in JavaScript. Consider STRING encoding."
- Add inline comment in generated code near the marshal function
Handle interaction with existing unwrap annotation:
- If message has both unwrap and int64 NUMBER encoding, generate unified MarshalJSON/UnmarshalJSON that handles both
- The encoding logic applies to fields within the (possibly unwrapped) structure
Example generated code pattern for NUMBER encoding:
// MarshalJSON implements json.Marshaler.
// Warning: int64 fields with NUMBER encoding may lose precision for values > 2^53 in JavaScript.
func (m *Tweet) MarshalJSON() ([]byte, error) {
type Alias Tweet
return json.Marshal(&struct {
*Alias
Id int64 `json:"id"` // int64_encoding=NUMBER
}{
Alias: (*Alias)(m),
Id: m.Id,
})
}In clientgen/generator.go:
- Add the same int64 encoding detection and code generation
- Print the same precision warning during generation
- Add the same inline comment in generated code
The go-client generated code should be byte-for-byte identical to go-http for the MarshalJSON/UnmarshalJSON methods on messages with int64 NUMBER encoding.
Consider extracting shared code if significant duplication:
- Option A: Duplicate code (simpler, generators stay independent)
- Option B: Create internal/encoding package for shared logic
- Recommendation: Start with duplication (Option A), refactor if needed later
Verify client/server consistency:
- Same proto message marshaled by server and client produces identical JSON
- Same JSON unmarshaled by server and client produces identical proto message
go build ./internal/clientgen/...
go test -v ./internal/clientgen/...Verify server/client produce identical JSON:
- Generate both server and client code from same proto
- Marshal same message instance through both
- Compare JSON output (should be byte-identical)
- Create internal/httpgen/testdata/encoding.proto:
syntax = "proto3";
package encoding.test;
import "sebuf/http/annotations.proto";
option go_package = "github.com/SebastienMelki/sebuf/internal/httpgen/testdata/encoding";
message Int64EncodingTest {
// Default (STRING) encoding
int64 default_int64 = 1;
// Explicit STRING encoding
int64 string_int64 = 2 [(sebuf.http.int64_encoding) = INT64_ENCODING_STRING];
// NUMBER encoding (with precision warning)
int64 number_int64 = 3 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
// uint64 variants
uint64 default_uint64 = 4;
uint64 number_uint64 = 5 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
// Repeated fields
repeated int64 repeated_number_int64 = 6 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
}
service EncodingTestService {
option (sebuf.http.service_config) = {
base_path: "/api/v1"
};
rpc TestInt64Encoding(Int64EncodingTest) returns (Int64EncodingTest) {
option (sebuf.http.config) = {
path: "/encoding/int64"
method: HTTP_METHOD_POST
};
}
}-
Create similar proto for clientgen testdata
-
Run golden file tests and update expected output:
UPDATE_GOLDEN=1 go test -run TestExhaustiveGoldenFiles ./internal/httpgen/...
UPDATE_GOLDEN=1 go test -run TestGoldenFiles ./internal/clientgen/...- Verify golden files contain expected patterns:
- NUMBER fields have MarshalJSON/UnmarshalJSON generated
- STRING fields use protojson default
- Warning comment present in generated code
go test -v -run Golden ./internal/httpgen/...
go test -v -run Golden ./internal/clientgen/...
make lint-fixAll golden tests pass without UPDATE_GOLDEN flag.
- Test proto files with int64_encoding annotations exist
- Golden files capture expected generated code
- NUMBER encoding produces custom MarshalJSON in golden output
- STRING/UNSPECIFIED produces no custom marshal (uses protojson)
- Golden tests pass
<success_criteria>
- int64_encoding=NUMBER produces JSON numbers:
{"id": 12345}not{"id": "12345"} - int64_encoding=STRING or UNSPECIFIED produces JSON strings:
{"id": "12345"} - Same message marshaled by go-http and go-client produces identical JSON
- Precision warning printed during generation for NUMBER fields
- Backward compatible: protos without annotations produce identical output to before </success_criteria>
