Purpose: Establish the annotation infrastructure that all 4 generators will use to control JSON encoding of int64/uint64 and enum fields. This is the foundation for the rest of Phase 4.
Output: Proto extensions for int64_encoding, enum_encoding, and enum_value annotations. Shared Go functions GetInt64Encoding, GetEnumEncoding, GetEnumValueMapping, and HasConflictingEnumAnnotations in internal/annotations package.
<execution_context> @/Users/sebastienmelki/.claude/get-shit-done/workflows/execute-plan.md @/Users/sebastienmelki/.claude/get-shit-done/templates/summary.md </execution_context>
Existing annotation patterns to follow
@internal/annotations/unwrap.go @internal/annotations/http_config.go @proto/sebuf/http/annotations.proto
- Int64Encoding enum:
// Int64Encoding controls how int64/uint64 fields serialize to JSON
enum Int64Encoding {
// Follow protojson default (serialize as string)
INT64_ENCODING_UNSPECIFIED = 0;
// Explicit string encoding: "12345"
INT64_ENCODING_STRING = 1;
// Numeric encoding: 12345 (precision warning for values > 2^53)
INT64_ENCODING_NUMBER = 2;
}- EnumEncoding enum:
// EnumEncoding controls how enum fields serialize to JSON
enum EnumEncoding {
// Follow protojson default (serialize as proto name string)
ENUM_ENCODING_UNSPECIFIED = 0;
// Explicit string encoding: "STATUS_ACTIVE"
ENUM_ENCODING_STRING = 1;
// Numeric encoding: 1
ENUM_ENCODING_NUMBER = 2;
}- Add to existing FieldOptions extension block:
// Controls int64/uint64 JSON encoding for this field
optional Int64Encoding int64_encoding = 50010;
// Controls enum JSON encoding for this field (only valid on enum fields)
optional EnumEncoding enum_encoding = 50011;- Add EnumValueOptions extension:
// Extension for enum value options
extend google.protobuf.EnumValueOptions {
// Custom JSON string for this enum value (e.g., "active" instead of "STATUS_ACTIVE")
optional string enum_value = 50012;
}Use extension numbers 50010, 50011, 50012 (continuing from existing 50009 for unwrap).
buf build or protoc to verify the proto compiles without errors:
cd proto && buf buildRegenerate Go code:
cd proto && buf generateVerify the generated Go code compiles:
go build ./http/...package annotations
import (
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
"github.com/SebastienMelki/sebuf/http"
)
// GetInt64Encoding returns the int64 encoding for a field.
// Returns INT64_ENCODING_UNSPECIFIED if not set (callers should use protojson default: STRING).
// This annotation is valid on int64, sint64, sfixed64, uint64, and fixed64 fields.
func GetInt64Encoding(field *protogen.Field) http.Int64Encoding {
options := field.Desc.Options()
if options == nil {
return http.Int64Encoding_INT64_ENCODING_UNSPECIFIED
}
fieldOptions, ok := options.(*descriptorpb.FieldOptions)
if !ok {
return http.Int64Encoding_INT64_ENCODING_UNSPECIFIED
}
ext := proto.GetExtension(fieldOptions, http.E_Int64Encoding)
if ext == nil {
return http.Int64Encoding_INT64_ENCODING_UNSPECIFIED
}
encoding, ok := ext.(http.Int64Encoding)
if !ok {
return http.Int64Encoding_INT64_ENCODING_UNSPECIFIED
}
return encoding
}
// IsInt64NumberEncoding returns true if the field should encode int64/uint64 as JSON number.
// Returns false for UNSPECIFIED or STRING (both use protojson default string encoding).
func IsInt64NumberEncoding(field *protogen.Field) bool {
return GetInt64Encoding(field) == http.Int64Encoding_INT64_ENCODING_NUMBER
}Create enum_encoding.go:
package annotations
import (
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
"github.com/SebastienMelki/sebuf/http"
)
// GetEnumEncoding returns the enum encoding for a field.
// Returns ENUM_ENCODING_UNSPECIFIED if not set (callers should use protojson default: STRING names).
// This annotation is only valid on enum fields.
func GetEnumEncoding(field *protogen.Field) http.EnumEncoding {
options := field.Desc.Options()
if options == nil {
return http.EnumEncoding_ENUM_ENCODING_UNSPECIFIED
}
fieldOptions, ok := options.(*descriptorpb.FieldOptions)
if !ok {
return http.EnumEncoding_ENUM_ENCODING_UNSPECIFIED
}
ext := proto.GetExtension(fieldOptions, http.E_EnumEncoding)
if ext == nil {
return http.EnumEncoding_ENUM_ENCODING_UNSPECIFIED
}
encoding, ok := ext.(http.EnumEncoding)
if !ok {
return http.EnumEncoding_ENUM_ENCODING_UNSPECIFIED
}
return encoding
}
// GetEnumValueMapping returns the custom JSON value for an enum value, or empty string if not set.
// When set, this value should be used instead of the proto name for JSON serialization.
func GetEnumValueMapping(value *protogen.EnumValue) string {
options := value.Desc.Options()
if options == nil {
return ""
}
enumValueOptions, ok := options.(*descriptorpb.EnumValueOptions)
if !ok {
return ""
}
ext := proto.GetExtension(enumValueOptions, http.E_EnumValue)
if ext == nil {
return ""
}
customValue, ok := ext.(string)
if !ok {
return ""
}
return customValue
}
// HasAnyEnumValueMapping returns true if any value in the enum has a custom JSON mapping.
func HasAnyEnumValueMapping(enum *protogen.Enum) bool {
for _, value := range enum.Values {
if GetEnumValueMapping(value) != "" {
return true
}
}
return false
}
// HasConflictingEnumAnnotations checks if a field has both enum_encoding=NUMBER and
// enum_value annotations on its enum values, which is an error per CONTEXT.md.
func HasConflictingEnumAnnotations(field *protogen.Field) bool {
if field.Enum == nil {
return false
}
encoding := GetEnumEncoding(field)
if encoding != http.EnumEncoding_ENUM_ENCODING_NUMBER {
return false
}
return HasAnyEnumValueMapping(field.Enum)
}Add unit tests to annotations_test.go for the new functions (test with mock protogen types where possible, or pure function tests for helper functions).
go build ./internal/annotations/...
go test -v ./internal/annotations/...
make lint-fixAll tests pass, lint clean.
- GetInt64Encoding function exists and returns correct encoding enum
- IsInt64NumberEncoding helper exists
- GetEnumEncoding function exists and returns correct encoding enum
- GetEnumValueMapping function exists and returns custom JSON values
- HasAnyEnumValueMapping helper exists
- HasConflictingEnumAnnotations validation function exists
- All functions follow existing annotation package patterns (protogen types, nil safety)
- Unit tests cover all functions
- Lint passes
<success_criteria>
- Proto files can import and use int64_encoding, enum_encoding, and enum_value annotations
- GetInt64Encoding(field) returns the configured Int64Encoding enum value
- GetEnumEncoding(field) returns the configured EnumEncoding enum value
- GetEnumValueMapping(value) returns custom JSON string for enum values
- HasConflictingEnumAnnotations detects invalid annotation combinations
- All existing tests continue to pass (zero regression) </success_criteria>
