Skip to main content

postcrate_core/
error.rs

1//! Crate-wide error type. Every public method on [`crate::Service`] returns
2//! `Result<_, Error>`. Boundary adapters (the built-in Axum routes,
3//! downstream UI shims) convert this into HTTP status codes or whatever
4//! shape the surrounding host expects.
5
6use std::io;
7
8use thiserror::Error;
9
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Every fallible operation in `postcrate-core` returns this.
13#[derive(Debug, Error)]
14pub enum Error {
15    #[error("database error: {0}")]
16    Db(#[from] sqlx::Error),
17
18    #[error("database migration error: {0}")]
19    Migrate(#[from] sqlx::migrate::MigrateError),
20
21    #[error("io error: {0}")]
22    Io(#[from] io::Error),
23
24    #[error("json error: {0}")]
25    Json(#[from] serde_json::Error),
26
27    #[error("parse error: {0}")]
28    Parse(String),
29
30    #[error("mailbox not found: {0}")]
31    MailboxNotFound(String),
32
33    #[error("email not found: {0}")]
34    EmailNotFound(String),
35
36    #[error("attachment not found: {0}")]
37    AttachmentNotFound(String),
38
39    #[error("bounce rule not found: {0}")]
40    BounceRuleNotFound(String),
41
42    #[error("port {0} is already in use")]
43    PortInUse(u16),
44
45    #[error("port {0} not in allowed range")]
46    PortOutOfRange(u16),
47
48    #[error("ephemeral port range exhausted")]
49    PortRangeExhausted,
50
51    #[error("mailbox name '{0}' already exists in project")]
52    DuplicateMailbox(String),
53
54    #[error("smtp protocol error: {0}")]
55    SmtpProto(String),
56
57    #[error("invalid input: {0}")]
58    Invalid(String),
59
60    #[error("not implemented: {0}")]
61    NotImplemented(&'static str),
62
63    #[error("internal: {0}")]
64    Internal(String),
65}
66
67impl Error {
68    /// HTTP status mapping for the Axum layer.
69    pub fn http_status(&self) -> http::StatusCode {
70        use http::StatusCode as S;
71        match self {
72            Error::MailboxNotFound(_)
73            | Error::EmailNotFound(_)
74            | Error::AttachmentNotFound(_)
75            | Error::BounceRuleNotFound(_) => S::NOT_FOUND,
76            Error::DuplicateMailbox(_) | Error::PortInUse(_) => S::CONFLICT,
77            Error::Invalid(_) | Error::Parse(_) | Error::PortOutOfRange(_) => S::BAD_REQUEST,
78            Error::NotImplemented(_) => S::NOT_IMPLEMENTED,
79            Error::PortRangeExhausted => S::SERVICE_UNAVAILABLE,
80            _ => S::INTERNAL_SERVER_ERROR,
81        }
82    }
83
84    /// Short machine-readable code for the `{error, message}` JSON body.
85    pub fn code(&self) -> &'static str {
86        match self {
87            Error::Db(_) | Error::Migrate(_) => "db_error",
88            Error::Io(_) => "io_error",
89            Error::Json(_) => "json_error",
90            Error::Parse(_) => "parse_error",
91            Error::MailboxNotFound(_) => "mailbox_not_found",
92            Error::EmailNotFound(_) => "email_not_found",
93            Error::AttachmentNotFound(_) => "attachment_not_found",
94            Error::BounceRuleNotFound(_) => "bounce_rule_not_found",
95            Error::PortInUse(_) => "port_in_use",
96            Error::PortOutOfRange(_) => "port_out_of_range",
97            Error::PortRangeExhausted => "port_range_exhausted",
98            Error::DuplicateMailbox(_) => "duplicate_mailbox",
99            Error::SmtpProto(_) => "smtp_proto",
100            Error::Invalid(_) => "invalid_input",
101            Error::NotImplemented(_) => "not_implemented",
102            Error::Internal(_) => "internal",
103        }
104    }
105}