Skip to main content

sl_glw/
error.rs

1//! Error types for the `sl-glw` crate
2//!
3//! Three layered error enums plus an umbrella [`Error`] that wraps all of
4//! them. Layout mirrors `sl_map_apis::region` so the crates compose well.
5
6/// Errors that can occur while fetching a GLW event over HTTP and
7/// decoding its JSON body.
8#[expect(
9    clippy::module_name_repetitions,
10    reason = "the error types are the primary public surface of this module"
11)]
12#[derive(Debug, thiserror::Error)]
13pub enum FetchError {
14    /// reqwest-level transport error
15    #[error("HTTP error: {0}")]
16    Http(#[from] reqwest::Error),
17    /// the cache layer was unable to clone the request for revalidation
18    #[error("failed to clone request for creation of cache policy")]
19    FailedToCloneRequest,
20    /// the GLW server returned a non-success, non-404 status
21    #[error("HTTP {status} from GLW server at {url}: {body}")]
22    BadStatus {
23        /// HTTP status code returned by the server
24        status: reqwest::StatusCode,
25        /// the URL that produced the error
26        url: String,
27        /// the response body (truncated by the caller if needed)
28        body: String,
29    },
30    /// the response body exceeded the maximum we are willing to buffer
31    #[error("GLW response body exceeded the {limit}-byte limit")]
32    ResponseTooLarge {
33        /// the byte limit that was exceeded
34        limit: usize,
35    },
36    /// the configured base URL was not parseable as a [`url::Url`]
37    #[error("invalid GLW base URL: {0}")]
38    InvalidBaseUrl(#[from] url::ParseError),
39    /// the response body could not be parsed as JSON
40    #[error("JSON decode error: {0}")]
41    Json(#[from] serde_json::Error),
42    /// the parsed JSON did not satisfy the GLW schema
43    #[error("invalid event data: {0}")]
44    Parse(#[from] ParseError),
45}
46
47/// Errors that arise from validating GLW JSON values against their
48/// documented domains (e.g. wind direction must be 0..=359).
49#[expect(
50    clippy::module_name_repetitions,
51    reason = "the error types are the primary public surface of this module"
52)]
53#[derive(Debug, thiserror::Error)]
54pub enum ParseError {
55    /// a numeric field fell outside its allowed range
56    #[error("field {field} out of range: got {value}, expected {allowed}")]
57    OutOfRange {
58        /// the JSON path of the offending field (e.g. `"base.wind.dir"`)
59        field: &'static str,
60        /// the value rendered as a string (works for ints and floats)
61        value: String,
62        /// human-readable description of the allowed domain
63        allowed: &'static str,
64    },
65}
66
67/// Errors that arise from the three-tier cache around GLW events.
68#[expect(
69    clippy::module_name_repetitions,
70    reason = "the error types are the primary public surface of this module"
71)]
72#[derive(Debug, thiserror::Error)]
73pub enum GlwEventCacheError {
74    /// error decoding the JSON serialised cache policy
75    #[error("error decoding the JSON serialized CachePolicy: {0}")]
76    CachePolicyJsonDecodeError(#[from] serde_json::Error),
77    /// the underlying redb database returned an error opening the file
78    #[error("redb database error: {0}")]
79    DatabaseError(#[from] redb::DatabaseError),
80    /// the underlying redb transaction returned an error
81    #[error("redb transaction error: {0}")]
82    TransactionError(#[from] redb::TransactionError),
83    /// the underlying redb table returned an error
84    #[error("redb table error: {0}")]
85    TableError(#[from] redb::TableError),
86    /// the underlying redb storage returned an error
87    #[error("redb storage error: {0}")]
88    StorageError(#[from] redb::StorageError),
89    /// the underlying redb commit returned an error
90    #[error("redb commit error: {0}")]
91    CommitError(#[from] redb::CommitError),
92    /// the upstream HTTP fetch failed
93    #[error("error looking up GLW event via HTTP: {0}")]
94    FetchError(#[from] FetchError),
95    /// system time error while computing cache freshness
96    #[error("error handling system time for cache age calculations: {0}")]
97    SystemTimeError(#[from] std::time::SystemTimeError),
98}
99
100/// Umbrella error type that wraps every error this crate can produce.
101///
102/// Useful when a caller needs a single `Result` type across fetch, parse
103/// and cache boundaries. Rendering is infallible, so it has no variant
104/// here.
105#[derive(Debug, thiserror::Error)]
106pub enum Error {
107    /// an HTTP fetch or response-decoding error
108    #[error(transparent)]
109    Fetch(#[from] FetchError),
110    /// a JSON validation error
111    #[error(transparent)]
112    Parse(#[from] ParseError),
113    /// a cache-layer error
114    #[error(transparent)]
115    Cache(#[from] GlwEventCacheError),
116}