Error Handling
Meridian uses typed, explicit error handling throughout. No panics, no silent failures.
Error Types
CodecError (HTTP parsing)
#![allow(unused)]
fn main() {
pub enum CodecError {
Io(std::io::Error), // I/O layer failure
Parse(String), // Malformed HTTP
HeadersTooLarge, // >64KB headers
InvalidHeader(String), // Invalid header name/value
RequestSmuggling, // Both CL and TE present
InvalidContentLength(String), // Non-numeric or padded CL
UnsupportedVersion, // Not HTTP/1.0 or 1.1
ConnectionClosed, // Peer closed mid-parse
ChunkedEncoding(String), // Invalid chunked framing
BodyTooLarge(usize), // Body exceeds limit
}
}
FilterError (filter chain)
#![allow(unused)]
fn main() {
pub enum FilterError {
Internal { filter: &'static str, source: Box<dyn Error> },
Abort { filter: &'static str, reason: String },
Timeout { filter: &'static str, elapsed: Duration },
}
}
TlsError (TLS configuration)
#![allow(unused)]
fn main() {
pub enum TlsError {
Io { path: String, source: std::io::Error },
NoCertificates(String),
NoPrivateKey(String),
Config(String),
}
}
MeridianError (top-level)
#![allow(unused)]
fn main() {
pub enum MeridianError {
Io(std::io::Error),
Config(String),
FilterRejection { reason: String, status: u16 },
UpstreamUnavailable(String),
CircuitBreakerOpen { cluster: String },
RateLimited,
Timeout { elapsed_ms: u64 },
Protocol(String),
Parse(String),
}
}
Error-to-HTTP Mapping
Errors are mapped to generic HTTP status codes. Internal details are never sent to clients:
| Internal Error | Client Sees | Why Generic |
|---|---|---|
CodecError::Parse | 400 Bad Request | Don’t reveal parser internals |
| No route match | 404 Not Found | Safe to expose |
| Cluster not found | 502 Bad Gateway | Don’t reveal cluster names |
| CB open | 503 Service Unavailable | Don’t reveal CB state |
| No healthy endpoints | 502 Bad Gateway | Don’t reveal endpoint topology |
| Upstream connect fail | 502 Bad Gateway | Don’t reveal upstream addresses |
| Upstream timeout | 504 Gateway Timeout | Don’t reveal timeout config |
| Filter error | 500 Internal Server Error | Don’t reveal filter internals |
All internal details are logged via tracing for operator debugging.
Conventions
- Core library: every public function returns
Result<T, ModuleError> - Proxy crate: uses
anyhow::Resultfor application-level errors - No
.unwrap()in core library code (enforced by pre-commit hook) - One error enum per module with
thiserror#[derive(Error)]