Architecture
Analysis Date: 2026-02-05
Pattern Overview#
Overall: Plugin-based code generation using protoc plugin architecture with separate concerns for four complementary generators.
Key Characteristics:
- Multi-plugin design: Each generator is a standalone protoc plugin with isolated responsibility
- Protocol Buffer driven: Input is protobuf service definitions with custom HTTP annotations
- Code generation as primary function: All plugins consume protobuf definitions and emit generated code/documentation
- Type-safe output across languages: Go, TypeScript, and OpenAPI specification generation
- Custom annotation system: Uses protobuf field/method/service extensions for HTTP configuration
Layers#
Plugin Entry Points:
- Purpose: Accept protoc requests and coordinate generation
- Location:
cmd/protoc-gen-go-http/main.go,cmd/protoc-gen-go-client/main.go,cmd/protoc-gen-ts-client/main.go,cmd/protoc-gen-openapiv3/main.go - Contains: Main function, parameter parsing, plugin initialization
- Depends on: Standard protogen library, internal generators
- Used by: protoc compiler during code generation
Generator Layer (HTTP):
- Purpose: Generate HTTP server handlers, bindings, and configuration from protobuf services
- Location:
internal/httpgen/generator.go(1550 lines) - Contains: Service interface generation, handler registration, binding logic generation, validation code generation, mock server generation
- Depends on:
internal/httpgen/annotations.go,internal/httpgen/unwrap.go,internal/httpgen/validation.go,internal/httpgen/mock_generator.go - Used by:
cmd/protoc-gen-go-http/main.go
Generator Layer (Go Client):
- Purpose: Generate type-safe HTTP client code for Go with functional options pattern
- Location:
internal/clientgen/generator.go - Contains: Client interface generation, call option definitions, method implementation, header helpers
- Depends on:
internal/clientgen/annotations.go - Used by:
cmd/protoc-gen-go-client/main.go
Generator Layer (TypeScript Client):
- Purpose: Generate type-safe HTTP client code for TypeScript with typed interfaces
- Location:
internal/tsclientgen/generator.go - Contains: Service class generation, method implementation, error type definitions, interface definitions, header helpers
- Depends on:
internal/tsclientgen/helpers.go,internal/tsclientgen/types.go,internal/tsclientgen/annotations.go - Used by:
cmd/protoc-gen-ts-client/main.go
Generator Layer (OpenAPI):
- Purpose: Generate comprehensive OpenAPI v3.1 specifications from protobuf services
- Location:
internal/openapiv3/generator.go(53 lines in main, but complex generation) - Contains: Service to OpenAPI document conversion, schema generation, header parameter documentation
- Depends on:
internal/openapiv3/types.go,internal/openapiv3/http_annotations.go,internal/openapiv3/validation.go - Used by:
cmd/protoc-gen-openapiv3/main.go
Annotation System:
- Purpose: Define custom protobuf extensions for HTTP configuration
- Location:
proto/sebuf/http/annotations.proto(74 lines) - Contains: HttpConfig, HttpMethod, ServiceConfig, QueryConfig, FieldExamples, unwrap extensions
- Depends on: Google protobuf descriptor extensions
- Used by: All generators for reading configuration from proto definitions
HTTP Package (Runtime):
- Purpose: Provide runtime error types and interfaces for generated code
- Location:
http/errors.pb.go,http/annotations.pb.go,http/headers.pb.go,http/errors_impl.go - Contains: ValidationError, Error protobuf messages, error interface implementations, header validation definitions
- Used by: Generated HTTP handlers and client code
Validation System:
- Purpose: Validate HTTP configurations at generation time and request bodies at runtime
- Location:
internal/httpgen/validation.go(172 lines) - Contains: ValidateMethodConfig, field validation, path parameter type checking, query parameter conflict detection
- Depends on: protogen reflection
- Used by:
internal/httpgen/generator.goduring service generation
Unwrap System:
- Purpose: Handle JSON serialization of wrapped repeated fields in map values
- Location:
internal/httpgen/unwrap.go(902 lines) - Contains: UnwrapContext, RootUnwrapMessage, map value unwrapping logic, JSON method generation
- Depends on: protogen reflection
- Used by:
internal/httpgen/generator.gofor special JSON serialization
Data Flow#
Protoc Plugin Invocation (HTTP Handler Generation):
- protoc passes CodeGeneratorRequest to stdin →
cmd/protoc-gen-go-http/main.go - Main.go creates protogen.Plugin from request → calls
httpgen.NewWithOptions() - httpgen.Generator.Generate() iterates all files
- For each file:
- Calls ValidateService() from
internal/httpgen/validation.go(fail-fast) - Calls generateErrorImplFile() to create Error interface implementations
- Calls generateUnwrapFile() to create JSON unwrap methods
- Calls generateHTTPFile() → generates
*_http.pb.gowith service interfaces and registration functions - Calls generateBindingFile() → generates
*_http_binding.pb.gowith BindingMiddleware, request/response binding logic - Calls generateConfigFile() → generates
*_http_config.pb.gowith path/query parameter configs and header validation - Optionally calls generateMockFile() → generates mock server implementation
- Calls ValidateService() from
- Generated files written to protogen.GeneratedFile
- Response marshaled to stdout as protobuf
Protoc Plugin Invocation (Go Client Generation):
- protoc passes CodeGeneratorRequest to stdin →
cmd/protoc-gen-go-client/main.go - Main.go creates protogen.Plugin → calls
clientgen.New() - clientgen.Generator.Generate() iterates all files
- For each file with services:
- Calls generateClientFile() → generates
*_client.pb.go - For each service: generates ClientInterface, NewClient() constructor, functional option types
- For each method: generates call wrapper with option application, HTTP verb detection, request body serialization
- Calls generateClientFile() → generates
- Generated files written to protogen.GeneratedFile
- Response marshaled to stdout
Protoc Plugin Invocation (TypeScript Client Generation):
- protoc passes CodeGeneratorRequest to stdin →
cmd/protoc-gen-ts-client/main.go - Main.go creates protogen.Plugin with proto3 optional support → calls
tsclientgen.New() - tsclientgen.Generator.Generate() iterates all files
- For each file with services:
- Calls generateClientFile() → generates
*_client.ts - Collects all message interfaces and enums used by services
- Generates TypeScript interfaces for each message
- Generates enum type definitions
- Generates error types (FieldViolation, ValidationError, ApiError)
- For each service: generates service client class
- For each method: generates method implementation with path substitution, query parameter encoding, header helpers
- Calls generateClientFile() → generates
- Generated TypeScript files written to protogen.GeneratedFile
- Response marshaled to stdout
Protoc Plugin Invocation (OpenAPI Generation):
- protoc passes CodeGeneratorRequest to stdin →
cmd/protoc-gen-openapiv3/main.go - Main.go parses format parameter (yaml/json), creates protogen.Plugin
- For each service in each file:
- Creates openapiv3.Generator → calls CollectReferencedMessages() to gather all types
- Calls ProcessService() to convert service to OpenAPI paths
- Calls Render() to serialize to YAML or JSON
- Writes one file per service:
{ServiceName}.openapi.{yaml|json}
- Response marshaled to stdout
Request Handling (Runtime - HTTP Handler):
- HTTP request arrives at mux → routed to generated handler
- BindingMiddleware wrapper intercepts
- Path parameters extracted from URL, unmarshaled to request message fields
- Query parameters parsed from URL, unmarshaled to request message fields
- Request body read from HTTP request, unmarshaled (JSON or protobuf) to request message
- Header validation middleware checks required headers (service-level + method-level)
- Request body validation via protovalidate (buf.validate rules)
- If validation passes: handler function called with context and request
- Handler returns response message or error
- Response serialized to JSON or protobuf based on Accept header
- HTTP response written with status code and serialized body
State Management:
- No persistent state: Each plugin invocation is isolated
- Configuration read from proto annotations at generation time
- Generated code is pure and stateless
- Runtime state managed by application code using generated handlers/clients
Key Abstractions#
HTTPConfig:
- Purpose: Represents HTTP method configuration (path, verb, extracted path parameters)
- Examples:
internal/httpgen/annotations.go:26-30 - Pattern: Extracted from method options via proto.GetExtension()
QueryParam:
- Purpose: Represents query parameter binding (field name, parameter name, required flag)
- Examples:
internal/httpgen/annotations.go:32-38 - Pattern: Extracted from field options via proto.GetExtension()
ValidationError:
- Purpose: Represents generation-time validation errors (service, method, message)
- Examples:
internal/httpgen/validation.go:11-15 - Pattern: Collected during ValidateService() and reported to user
UnwrapContext:
- Purpose: Tracks messages requiring JSON unwrapping for map value collapse
- Examples:
internal/httpgen/unwrap.go:28-43 - Pattern: Analyzed during file processing, used to generate MarshalJSON/UnmarshalJSON
Generator:
- Purpose: Base abstraction for code generation from protobuf definitions
- Pattern: Implements Generate() method iterating files, creates protogen.GeneratedFile for output
Entry Points#
HTTP Handler Plugin:
- Location:
cmd/protoc-gen-go-http/main.go - Triggers:
protoc --go-http_out=. filename.proto - Responsibilities: Register plugins with protoc, delegate to httpgen.Generator
Go Client Plugin:
- Location:
cmd/protoc-gen-go-client/main.go - Triggers:
protoc --go-client_out=. filename.proto - Responsibilities: Register plugins with protoc, delegate to clientgen.Generator
TypeScript Client Plugin:
- Location:
cmd/protoc-gen-ts-client/main.go - Triggers:
protoc --ts-client_out=. filename.proto - Responsibilities: Register plugins with protoc, delegate to tsclientgen.Generator, enable proto3 optional support
OpenAPI Plugin:
- Location:
cmd/protoc-gen-openapiv3/main.go - Triggers:
protoc --openapiv3_out=format=yaml:. filename.proto - Responsibilities: Parse format parameter, register plugins with protoc, delegate to openapiv3.Generator per-service
Error Handling#
Strategy: Fail-fast validation at generation time, with comprehensive error messages. Runtime validation via ValidationError messages.
Patterns:
-
Generation-Time Validation (
internal/httpgen/validation.go):- ValidateService() checks all methods before generating any code
- Returns ValidationError structs with service, method, and detailed message
- Checks path parameter compatibility, query parameter conflicts, field existence
- Prevents invalid configurations from producing broken code
-
Runtime Request Validation (
internal/httpgen/generator.gobinding generation):- Body validation via protovalidate (buf.validate rules)
- Header validation via HeaderValidationMiddleware
- Both return HTTP 400 with ValidationError message if validation fails
- ValidationError includes field-level violation details
-
Client-Side Error Handling (TypeScript and Go clients):
- ApiError and ValidationError types for distinction
- ApiError contains statusCode and message for HTTP errors
- ValidationError contains violations array for field-level errors
- Can be used with errors.As() / errors.Is() in Go, instanceof in TypeScript
Cross-Cutting Concerns#
Logging: Not generated. Application layer responsible for request/response logging via middleware.
Validation:
- Automatic via buf.validate annotations in request messages
- Header validation via sebuf.http.service_headers and method_headers annotations
- Generation-time validation of HTTP configurations via ValidateService()
Authentication:
- Service-level required headers defined in service_headers annotation
- Method-level required headers defined in method_headers annotation
- Generated code enforces presence; application layer handles verification
- Example: X-API-Key and X-Request-ID headers in example proto
Architecture analysis: 2026-02-05
