Implement int64/uint64 encoding support in go-http and go-client generators. Both generators must produce identical JSON marshaling for interoperability.

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>

@.planning/PROJECT.md @.planning/phases/04-json-primitive-encoding/04-CONTEXT.md @.planning/phases/04-json-primitive-encoding/04-RESEARCH.md @.planning/phases/04-json-primitive-encoding/04-01-SUMMARY.md

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

Task 1: Implement int64 encoding in go-http generator internal/httpgen/encoding.go internal/httpgen/generator.go Create internal/httpgen/encoding.go with helper functions for detecting and generating int64 encoding code:
  1. Create hasInt64NumberFields(message *protogen.Message) bool - returns true if any int64/uint64 field has NUMBER encoding
  2. Create generateInt64MarshalCode(gf *protogen.GeneratedFile, message *protogen.Message) - generates custom MarshalJSON if needed
  3. 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,
    })
}
```bash go build ./internal/httpgen/... go test -v ./internal/httpgen/... ``` Run the generator on a test proto with int64_encoding annotations and verify: 1. NUMBER encoded fields appear as numbers in JSON 2. STRING/UNSPECIFIED fields appear as strings 3. Warning printed during generation for NUMBER fields - internal/httpgen/encoding.go exists with int64 encoding helpers - Generator detects int64_encoding annotations on fields - NUMBER encoding generates MarshalJSON with direct int64 (JSON number) - STRING/UNSPECIFIED uses protojson default (JSON string) - Generation-time warning printed for NUMBER encoding - Inline comment added to generated marshal function - All existing golden tests pass (backward compatible)
Task 2: Implement int64 encoding in go-client generator internal/clientgen/encoding.go internal/clientgen/generator.go Create internal/clientgen/encoding.go mirroring the httpgen implementation: - Copy the same helper functions from httpgen/encoding.go - Client must produce IDENTICAL marshaling code to server

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:

  1. Generate both server and client code from same proto
  2. Marshal same message instance through both
  3. Compare JSON output (should be byte-identical)
- internal/clientgen/encoding.go exists with int64 encoding helpers - Client generator detects int64_encoding annotations - NUMBER encoding generates identical MarshalJSON to server - STRING/UNSPECIFIED uses protojson default - Generation-time warning printed for NUMBER encoding - All existing golden tests pass - Server and client produce identical JSON for same message
Task 3: Add golden file tests for int64 encoding internal/httpgen/testdata/encoding.proto internal/httpgen/testdata/golden/encoding_http_binding.pb.go internal/clientgen/testdata/encoding.proto internal/clientgen/testdata/golden/encoding_client.pb.go Create test proto files that exercise int64 encoding:
  1. 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
    };
  }
}
  1. Create similar proto for clientgen testdata

  2. 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/...
  1. 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-fix

All 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
1. Build passes: `go build ./...` 2. All tests pass: `go test ./...` 3. Lint clean: `make lint-fix` 4. Golden files updated and committed 5. Generation-time warning printed for NUMBER encoding 6. Server and client marshal identical JSON for same message

<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>
After completion, create `.planning/phases/04-json-primitive-encoding/04-02-SUMMARY.md`