Purpose: This is the foundation for Phase 2. All 4 generators will import this package instead of their duplicated annotation parsing code. The package must expose a convention-based API where each annotation type lives in its own file with GetXxx() / ParseXxx() function signatures.
Output: A fully compiled, tested internal/annotations package ready for generators to import.
<execution_context> @/Users/sebastienmelki/.claude/get-shit-done/workflows/execute-plan.md @/Users/sebastienmelki/.claude/get-shit-done/templates/summary.md </execution_context>
doc.go: Package documentation explaining convention-based extensibility pattern. Each file = one annotation concept, each with GetXxx() functions.
http_config.go:
HTTPConfigstruct:Path string,Method string,PathParams []string(same as existing but exported)ServiceConfigstruct:BasePath string(replaces bothServiceConfigImplandServiceHTTPConfignames)GetMethodHTTPConfig(method *protogen.Method) *HTTPConfig-- extracts HTTP config from method options. UsesHTTPMethodToString()for the Method field (uppercase). Identical logic to existinggetMethodHTTPConfigacross all 4 generators.GetServiceBasePath(service *protogen.Service) string-- extracts base path from service options. Returns empty string if not configured. Simpler API than returning a struct with one field.
headers.go:
GetServiceHeaders(service *protogen.Service) []*http.Header-- identical to existingGetMethodHeaders(method *protogen.Method) []*http.Header-- identical to existingCombineHeaders(serviceHeaders, methodHeaders []*http.Header) []*http.Header-- merges with method precedence, sorted by name. Extracted from openapiv3. Usesort.Stringsinstead of bubble sort.
query.go:
QueryParamstruct: ALL fields from all generators combined:FieldName string(proto field name)FieldGoName string(Go field name)FieldJSONName string(JSON field name / camelCase)ParamName string(query parameter name)Required boolFieldKind string(proto field kind: string, int32, bool, etc.)Field *protogen.Field(raw protogen field reference)
GetQueryParams(message *protogen.Message) []QueryParam-- populates ALL fields for each param. Usefield.GoNamefor FieldGoName,field.Desc.JSONName()for FieldJSONName,field.Desc.Kind().String()for FieldKind, and passfielddirectly for the Field reference.
unwrap.go:
UnwrapFieldInfostruct: same as httpgen's (Field, ElementType, IsRootUnwrap, IsMapField)UnwrapValidationErrorstruct: same as httpgen's (MessageName, FieldName, Reason) with Error() methodHasUnwrapAnnotation(field *protogen.Field) bool-- identical to existingGetUnwrapField(message *protogen.Message) (*UnwrapFieldInfo, error)-- full validation version from httpgen (validates non-repeated/non-map, multiple unwrap fields, map-without-root-unwrap). Returnnil, nilfor no unwrap field (with nolint comment).FindUnwrapField(message *protogen.Message) *protogen.Field-- simple version from tsclientgen/openapiv3: returns the unwrap-annotated repeated field or nil. No validation, no error.IsRootUnwrap(message *protogen.Message) bool-- from tsclientgen: single field with unwrap=true
field_examples.go:
GetFieldExamples(field *protogen.Field) []string-- identical to existing
path.go:
- Unexported
pathParamRegex(package-level compiled regex, same as existing) ExtractPathParams(path string) []string-- identical to existingBuildHTTPPath(servicePath, methodPath string) string-- from openapiv3'sbuildHTTPPathEnsureLeadingSlash(path string) string-- from openapiv3'sensureLeadingSlash(export it since generators may need it)
method.go:
HTTPMethodToString(m http.HttpMethod) string-- returns UPPERCASE ("GET", "POST", etc.). Default POST for unspecified.HTTPMethodToLower(m http.HttpMethod) string-- returns lowercase ("get", "post", etc.) for OpenAPI. Usesstrings.ToLower(HTTPMethodToString(m))for DRY.
helpers.go:
LowerFirst(s string) string-- converts "FooBar" to "fooBar". Identical to existing 3 copies.
IMPORTANT: Do NOT import any generator package (httpgen, clientgen, tsclientgen, openapiv3). Only import: stdlib, protogen, proto, descriptorpb, sebuf/http.
After creating files, run go build ./internal/annotations/ to verify compilation, then run make lint-fix to catch any linting issues.
go build ./internal/annotations/ succeeds with zero errors.
make lint-fix produces no errors.
go vet ./internal/annotations/ produces no warnings.
Tests to write:
TestHTTPMethodToString-- all 5 methods + UNSPECIFIED + unknown values. Verify UPPERCASE output.TestHTTPMethodToLower-- all 5 methods + UNSPECIFIED + unknown values. Verify lowercase output.TestExtractPathParams-- single param, multiple params, no params, edge cases (empty braces, unclosed brace, hyphenated params). Reuse test cases from httpgen.TestBuildHTTPPath-- both paths, service only, method only, empty, slash handling, complex paths. Reuse test cases from openapiv3.TestEnsureLeadingSlash-- with slash, without slash, empty, just slash. Reuse from openapiv3.TestCombineHeaders-- service only, method only, override, sorted merge, empty names skipped. Reuse from openapiv3.TestCombineHeaders_MethodOverridesService-- verifies description and required field override.TestLowerFirst-- empty string, single char, normal cases.TestHTTPConfig_Struct-- struct field verification.TestQueryParam_Struct-- verify all fields including new FieldJSONName, FieldKind, Field.TestServiceConfig_Struct-- struct field verification.- Benchmark tests: BenchmarkExtractPathParams, BenchmarkBuildHTTPPath, BenchmarkCombineHeaders, BenchmarkHTTPMethodToString.
After writing tests, run go test ./internal/annotations/ to verify all pass, then run make lint-fix.
go test -v ./internal/annotations/ shows all tests passing.
go test -bench=. ./internal/annotations/ runs benchmarks successfully.
make lint-fix produces no errors.
<success_criteria>
- internal/annotations package exists with 9 source files
- Package compiles and passes all unit tests
- Exported API covers: HTTPConfig, ServiceConfig, QueryParam, UnwrapFieldInfo, UnwrapValidationError, GetMethodHTTPConfig, GetServiceBasePath, GetServiceHeaders, GetMethodHeaders, CombineHeaders, GetQueryParams, HasUnwrapAnnotation, GetUnwrapField, FindUnwrapField, IsRootUnwrap, GetFieldExamples, ExtractPathParams, BuildHTTPPath, EnsureLeadingSlash, HTTPMethodToString, HTTPMethodToLower, LowerFirst
- Convention-based pattern is clear: one file per annotation concept, GetXxx function signatures </success_criteria>
