sebuf provides comprehensive automatic validation for both HTTP headers and request bodies, powered by protovalidate for body validation and custom middleware for header validation, giving you production-ready validation with zero configuration.
Add validation rules to your protobuf messages using buf.validate annotations:
syntax = "proto3";
import "buf/validate/validate.proto";
message CreateUserRequest {
// Name must be between 2 and 100 characters
string name = 1 [(buf.validate.field).string = {
min_len: 2,
max_len: 100
}];
// Email must be valid
string email = 2 [(buf.validate.field).string.email = true];
// Age must be between 18 and 120
int32 age = 3 [(buf.validate.field).int32 = {
gte: 18,
lte: 120
}];
}Add header validation to your services using sebuf annotations:
import "sebuf/http/headers.proto";
service UserService {
option (sebuf.http.service_headers) = {
required_headers: [
{
name: "X-API-Key"
type: "string"
format: "uuid"
required: true
description: "API authentication key"
}
]
};
rpc CreateUser(CreateUserRequest) returns (User);
}That's it! Both header and body validation happen automatically in your HTTP handlers.
- ✅ Zero configuration - Validation works automatically
- ✅ Comprehensive coverage - Both headers and request bodies validated
- ✅ All protovalidate rules - Full compatibility with buf.validate ecosystem for body validation
- ✅ Header type validation - Support for string, integer, number, boolean, array types
- ✅ Header format validation - Built-in validators for UUID, email, datetime formats
- ✅ Performance optimized - Cached validator instances
- ✅ Structured error responses - JSON or protobuf ValidationError with field-level details
- ✅ Content-type aware - Error format matches client's requested content type
- ✅ Fail-fast validation - Headers validated before body for efficiency
message StringValidationExample {
// Length constraints
string name = 1 [(buf.validate.field).string = {
min_len: 1,
max_len: 50
}];
// Email validation
string email = 2 [(buf.validate.field).string.email = true];
// UUID validation
string id = 3 [(buf.validate.field).string.uuid = true];
// Pattern matching (regex)
string phone = 4 [(buf.validate.field).string.pattern = "^\\+?[1-9]\\d{1,14}$"];
// Enum-like validation (allowed values)
string status = 5 [(buf.validate.field).string = {
in: ["active", "inactive", "pending"]
}];
// URL validation
string website = 6 [(buf.validate.field).string.uri = true];
}message NumericValidationExample {
// Integer range
int32 age = 1 [(buf.validate.field).int32 = {
gte: 0,
lte: 150
}];
// Exact value
int32 version = 2 [(buf.validate.field).int32.const = 1];
// List of allowed values
int32 priority = 3 [(buf.validate.field).int32 = {
in: [1, 2, 3, 4, 5]
}];
// Float validation
float score = 4 [(buf.validate.field).float = {
gte: 0.0,
lte: 100.0
}];
}message CollectionValidationExample {
// Repeated field size
repeated string tags = 1 [(buf.validate.field).repeated = {
min_items: 1,
max_items: 10
}];
// Map validation
map<string, string> metadata = 2 [(buf.validate.field).map = {
min_pairs: 1,
max_pairs: 20
}];
// Nested message validation
repeated UserInfo users = 3 [(buf.validate.field).repeated.min_items = 1];
}message MessageValidationExample {
// Required field (non-zero/non-empty)
string required_field = 1 [(buf.validate.field).required = true];
// Skip validation for this field
string internal_field = 2 [(buf.validate.field).ignore = IGNORE_ALWAYS];
}Headers defined at the service level apply to all RPCs in that service:
service APIService {
option (sebuf.http.service_headers) = {
required_headers: [
{
name: "X-API-Key"
description: "API authentication key"
type: "string"
format: "uuid"
required: true
example: "123e4567-e89b-12d3-a456-426614174000"
},
{
name: "X-Tenant-ID"
description: "Tenant identifier"
type: "integer"
required: true
},
{
name: "X-Debug-Mode"
description: "Enable debug mode"
type: "boolean"
required: false
}
]
};
}Headers can be specified per RPC method, overriding service-level headers with the same name:
rpc CreateResource(CreateResourceRequest) returns (Resource) {
option (sebuf.http.method_headers) = {
required_headers: [
{
name: "X-Request-ID"
type: "string"
format: "uuid"
required: true
},
{
name: "X-Idempotency-Key"
description: "Idempotency key for safe retries"
type: "string"
required: true
}
]
};
}| Type | Formats | Description |
|---|---|---|
string |
uuid, email, date-time, date, time |
Text with optional format validation |
integer |
- | Whole numbers |
number |
- | Decimal numbers |
boolean |
- | true or false values |
array |
- | Comma-separated values |
// Comprehensive header validation example
service SecureAPI {
option (sebuf.http.service_headers) = {
required_headers: [
// UUID validation
{
name: "X-Trace-ID"
description: "Trace ID for request tracing"
type: "string"
format: "uuid"
required: true
example: "123e4567-e89b-12d3-a456-426614174000"
},
// Email validation
{
name: "X-Admin-Email"
description: "Admin contact email"
type: "string"
format: "email"
required: false
example: "admin@example.com"
},
// Date-time validation
{
name: "X-Request-Time"
description: "Request timestamp"
type: "string"
format: "date-time"
required: true
example: "2024-01-15T10:30:00Z"
},
// Integer type
{
name: "X-Rate-Limit"
description: "Rate limit override"
type: "integer"
required: false
example: "100"
},
// String type
{
name: "X-API-Version"
description: "API version"
type: "string"
required: false
example: "v2"
},
// Array type
{
name: "X-Features"
type: "array"
required: false
description: "Comma-separated feature flags"
example: "feature1,feature2"
}
]
};
}When validation fails, sebuf returns an HTTP 400 Bad Request with a structured error response. The response format respects the client's Content-Type header, returning either JSON or protobuf. Headers are validated before the request body.
Validation errors are returned as a ValidationError message containing field-level violations:
message ValidationError {
repeated FieldViolation violations = 1;
}
message FieldViolation {
string field = 1; // Field that failed validation
string description = 2; // Description of the violation
}# Missing required header
curl -X POST /api/users \
-H "Content-Type: application/json" \
-d '{"name": "John"}'
# Returns: 400 Bad Request
# Body:
{
"violations": [{
"field": "X-API-Key",
"description": "required header 'X-API-Key' is missing"
}]
}
# Invalid header format (UUID)
curl -X POST /api/users \
-H "Content-Type: application/json" \
-H "X-API-Key: not-a-uuid" \
-d '{"name": "John"}'
# Returns: 400 Bad Request
# Body:
{
"violations": [{
"field": "X-API-Key",
"description": "header 'X-API-Key' validation failed: invalid UUID format"
}]
}
# Invalid header type (expecting integer)
curl -X POST /api/users \
-H "Content-Type: application/json" \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000" \
-H "X-Tenant-ID: abc" \
-d '{"name": "John"}'
# Returns: 400 Bad Request
# Body:
{
"violations": [{
"field": "X-Tenant-ID",
"description": "header 'X-Tenant-ID' validation failed: value is not a valid integer"
}]
}# Invalid email
curl -X POST /api/users \
-H "Content-Type: application/json" \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000" \
-d '{"email": "invalid"}'
# Returns: 400 Bad Request
# Body:
{
"violations": [{
"field": "email",
"description": "value must be a valid email address"
}]
}
# Multiple validation failures
curl -X POST /api/users \
-H "Content-Type: application/json" \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000" \
-d '{"name": "J", "email": "invalid", "age": 200}'
# Returns: 400 Bad Request
# Body:
{
"violations": [
{
"field": "name",
"description": "value length must be at least 2 runes"
},
{
"field": "email",
"description": "value must be a valid email address"
},
{
"field": "age",
"description": "value must be less than or equal to 120"
}
]
}When using protobuf content type, errors are returned as binary protobuf:
curl -X POST /api/users \
-H "Content-Type: application/x-protobuf" \
-H "X-API-Key: invalid" \
--data-binary @request.pb
# Returns: 400 Bad Request with binary ValidationError protobufsebuf error types implement Go's standard error interface, enabling seamless error handling when using sebuf as a client library.
The HTTP generator automatically implements the Go error interface for any protobuf message whose name ends with "Error". This includes the built-in ValidationError and Error types, as well as any custom error types you define.
Built-in Error Types:
ValidationError- Validation failures with field-level detailsError- General service errors with custom messages
Custom Error Types: You can define your own error types in protobuf and they automatically get error interface support:
message AuthenticationError {
string token = 1;
string reason = 2;
}
message RateLimitError {
int32 requests_remaining = 1;
int64 reset_time = 2;
}All error types provide Error() methods that return formatted error messages:
import (
"errors"
sebufhttp "github.com/SebastienMelki/sebuf/http"
)
// After receiving an error response from a sebuf API
func handleAPIError(err error) {
// Check for validation errors specifically
var validationErr *sebufhttp.ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Validation failed: %s\n", validationErr.Error())
// Output: "validation error: email: must be a valid email address"
// Access individual violations
for _, violation := range validationErr.Violations {
fmt.Printf(" - %s: %s\n", violation.Field, violation.Description)
}
return
}
// Check for general sebuf errors
var sebufErr *sebufhttp.Error
if errors.As(err, &sebufErr) {
fmt.Printf("Service error: %s\n", sebufErr.Error())
// Output: "user not found"
return
}
// Handle other error types
fmt.Printf("Unknown error: %s\n", err.Error())
}ValidationError messages:
- Single violation:
"validation error: field: description" - Multiple violations:
"validation error: [field1: desc1, field2: desc2]" - No violations:
"validation error: no violations"
Error messages:
- Returns the
Messagefield directly - Empty message:
"error: empty message"
// Works with all standard Go error patterns
func processUserData(data UserData) error {
resp, err := apiClient.CreateUser(ctx, data)
if err != nil {
// Standard error handling
var validationErr *sebufhttp.ValidationError
if errors.As(err, &validationErr) {
// Handle validation errors specifically
return fmt.Errorf("invalid user data: %w", validationErr)
}
// Can be wrapped with additional context
return fmt.Errorf("failed to create user: %w", err)
}
return nil
}
// Error comparison with errors.Is
func isValidationError(err error) bool {
var validationErr *sebufhttp.ValidationError
return errors.As(err, &validationErr)
}The TypeScript generators automatically include interfaces for proto messages ending with "Error", enabling type-safe custom error handling:
import { ApiError, ValidationError, type NotFoundError } from "./generated/proto/service_client.ts";
try {
await client.getUser({ id: "not-found" });
} catch (e) {
if (e instanceof ValidationError) {
// Built-in: header/body validation failures (400)
for (const v of e.violations) {
console.log(`${v.field}: ${v.description}`);
}
} else if (e instanceof ApiError) {
// Custom proto-defined errors: parse body using generated interface
if (e.statusCode === 404) {
const err = JSON.parse(e.body) as NotFoundError;
console.log(err.resourceType, err.resourceId);
}
}
}Use CEL expressions for custom validation logic:
message AdvancedValidation {
string password = 1 [(buf.validate.field).string = {
min_len: 8,
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$"
}];
// Custom CEL validation
string username = 2 [(buf.validate.field).cel = {
id: "username.unique",
message: "Username must be unique and start with letter",
expression: "this.matches('^[a-zA-Z][a-zA-Z0-9_]*$')"
}];
}message ConditionalValidation {
string type = 1;
// Only validate email if type is "email"
string contact = 2 [(buf.validate.field).cel = {
id: "contact.conditional",
expression: "this.type != 'email' || this.contact.isEmail()"
}];
}Validation is highly optimized:
- Cached validators: Validator instances for body validation are created once and reused
- Efficient header checking: Headers validated in a single pass before body processing
- No reflection overhead: Validation rules are pre-compiled
- Minimal allocations: Only allocates on validation errors
- Sub-microsecond latency: After initial warm-up
- Fail-fast: Headers validated first to avoid unnecessary body parsing
sebuf validation is fully compatible with the protovalidate ecosystem:
- buf CLI: Use buf validate commands for body validation rules
- IDE support: Validation rules show in proto IDE plugins
- Other languages: Same body validation rules work with protovalidate for Python, Java, etc.
- OpenAPI: Header validations automatically appear in generated OpenAPI specs
- Migration: Uses standard buf.validate annotations for body validation
- Validate at the boundary: Add validation to both headers and request messages
- Be specific: Use the most specific validation rule (email vs pattern, UUID format vs string)
- Layer validation: Use headers for auth/metadata, body for business data
- Consider UX: Validation errors are shown to users - make them helpful
- Test edge cases: Test validation with boundary values and missing headers
- Document constraints: Include validation info in API documentation
- Use service-level headers: Define common headers once at service level
- Override when needed: Use method-level headers for specific requirements
Body validation not working?
- Ensure you're importing
"buf/validate/validate.proto" - Check that your message fields have validation annotations
- Regenerate your code after adding validation rules
Header validation not working?
- Ensure you're importing
"sebuf/http/headers.proto" - Check that headers are defined in service or method options
- Verify header names match exactly (case-sensitive)
- Regenerate your code after adding header annotations
Performance concerns?
- Validation overhead is minimal (<1μs per request after warm-up)
- Header validation is done in a single pass
- Body validators are cached automatically
- No code generation required for validation logic
Debugging validation issues?
- Headers are validated first - check header errors before body errors
- Use curl with -v flag to see all headers being sent
- Check generated OpenAPI spec to verify header requirements
Need help?
- Check the protovalidate documentation for body validation
- See the validation-showcase example for comprehensive validation patterns
- Review http-generation.md for header details
- Open an issue on GitHub
- validation-showcase - Comprehensive buf.validate patterns (string, numeric, array, map, nested)
- multi-service-api - Service and method-level header validation