use std::path::PathBuf; use thiserror::Error; pub type Result = std::result::Result; #[derive(Debug, Error)] pub enum ServerError { /// A filesystem read that knows WHICH path failed — config / topology / /// schema file loads carry the path so the operator sees the offending file. /// Constructed via [`ServerError::io`]; never `?`-converted (that path lacks /// a path to attach). #[error("failed to read {path}: {source}")] Io { path: PathBuf, source: std::io::Error, }, #[error("invalid schema config: {0}")] SchemaConfig(String), #[error("schema build failed: {0}")] SchemaBuild(#[from] tidaldb::schema::SchemaError), #[error("tidalDB error: {0}")] Tidal(#[from] tidaldb::TidalError), /// A pathless `std::io::Error` from socket setup / serving (TCP bind, /// `local_addr`, `axum::serve`). This is the `?`-conversion target for /// `std::io::Error`; the path-tagged file-read case uses [`ServerError::Io`] /// instead. Named for its source (the network/serve stack) rather than the /// old misleading "Http", which read as an HTTP-protocol error. #[error("network/serve I/O error: {0}")] Network(#[from] std::io::Error), #[error("bad request: {0}")] BadRequest(String), #[error("cluster mode requires an explicit opt-in: {0}")] ExperimentalDisabled(String), #[error("cluster error: {0}")] Cluster(String), /// The server is shutting down and the cluster fabric has been taken; map to /// 503 so a post-shutdown access degrades cleanly instead of panicking. #[error("service unavailable: {0}")] Unavailable(String), /// A write reached a non-leader region node in multi-process cluster mode. /// Maps to 503 with a JSON body naming the leader (and its HTTP address when /// known) so the node is honest standalone; task 03 upgrades this to /// transparent leader forwarding. #[error("not the leader; current leader is '{leader}' (term {term})")] NotLeader { leader: String, http_addr: Option, /// The responder's election term (m11p4): lets a forwarder tell a /// stale answer from a fresh one during election churn. term: u64, /// The shard group this not-leader answer is for (m11p6). `None` for the /// legacy single group (S=1, byte-for-byte). Names the group in the body /// so a client retrying a per-shard write learns which group rejected it /// and which `?shard=` to re-target. shard: Option, }, /// A region-pinned read named a region this process does not own (multi- /// process cluster mode). Maps to 400 naming the region; task 03 upgrades /// this to forwarding to the owning process. #[error("region '{region}' is not served by this node")] NotLocal { region: String }, /// The current leader could not be reached while forwarding a write /// (multi-process cluster mode). Maps to 503 with a body naming the leader, /// its HTTP address, and the transport cause, so the client can retry or /// re-target the leader directly. #[error("leader '{leader}' unreachable while forwarding: {cause}")] LeaderUnreachable { leader: String, http_addr: String, cause: String, }, /// A sibling region could not be reached (region-pinned read forward, /// sharded write to its owner, or the reconcile snapshot exchange). Maps to /// 503 naming the region and the transport cause. #[error("region '{region}' unreachable: {cause}")] RegionUnreachable { region: String, cause: String }, /// An `ack=quorum` write was durable on the leader but a majority of the /// replica set did not confirm durability within the quorum budget /// (m11p3). Maps to a retryable 503 naming the laggards. The write is in /// the leader's log and MAY still commit — retries are at-least-once /// (see runbook §8 for the dedup guidance). #[error( "quorum not reached for seqno {seq} within budget: {confirmed} of {needed} required \ follower confirmations (commit index {committed}); laggards: {laggards:?}" )] QuorumTimeout { seq: u64, needed: usize, confirmed: usize, committed: u64, laggards: Vec, }, } impl ServerError { pub fn io(path: impl Into, source: std::io::Error) -> Self { Self::Io { path: path.into(), source, } } }