reliar_core/settings.rs
1//! The shared error every `*Settings::from_env` returns (ADR 0019, ADR 0032).
2
3use core::fmt;
4
5/// Why a `*Settings::from_env` call failed. `OutboxSettings::from_env` (`reliar-outbox`) was the
6/// first caller; every provider's own `from_env` returns this same type, so a host wiring
7/// several `from_env` calls handles one error type for the whole family.
8///
9/// ```
10/// use reliar_core::SettingsError;
11///
12/// let err = SettingsError::parse("RELIAR_OUTBOX_BATCH_SIZE", "u32");
13/// assert_eq!(err.key(), "RELIAR_OUTBOX_BATCH_SIZE");
14/// assert!(err.to_string().contains("could not be parsed"));
15/// ```
16#[derive(Clone, Debug, PartialEq)]
17#[non_exhaustive]
18pub enum SettingsError {
19 /// A present variable could not be parsed as its declared type. The value is **never
20 /// echoed** — it may carry an operator's typo of something sensitive.
21 Parse {
22 /// The full environment variable name, including the prefix.
23 key: String,
24 /// The type or shape that was expected, e.g. `"u32"`, `"milliseconds"`.
25 value_kind: &'static str,
26 },
27
28 /// A present variable parsed but violated a documented bound.
29 OutOfRange {
30 /// The full environment variable name, including the prefix.
31 key: String,
32 /// The bound that was violated.
33 message: &'static str,
34 },
35}
36
37/// **Public constructors, because every provider's `from_env` returns this type.**
38/// `SettingsError` is `#[non_exhaustive]`, so a crate other than the one defining a given
39/// `Settings` type — e.g. `reliar-store-postgres` — cannot build a variant with struct-literal
40/// syntax; without these a provider is forced into a parallel, unrelated error type, and a host
41/// wiring two `from_env` calls ends up handling two different errors for the same class of
42/// failure (ADR 0019).
43impl SettingsError {
44 /// The variable was present but did not parse. `value_kind` names the expected shape
45 /// (`"u32"`, `"milliseconds"`); the offending **value is never carried**.
46 ///
47 /// ```
48 /// use reliar_core::SettingsError;
49 ///
50 /// let err = SettingsError::parse("RELIAR_OUTBOX_BATCH_SIZE", "u32");
51 /// assert_eq!(err.key(), "RELIAR_OUTBOX_BATCH_SIZE");
52 /// ```
53 #[must_use]
54 pub fn parse(key: impl Into<String>, value_kind: &'static str) -> Self {
55 Self::Parse {
56 key: key.into(),
57 value_kind,
58 }
59 }
60
61 /// The variable parsed but is outside the range the setting accepts.
62 ///
63 /// ```
64 /// use reliar_core::SettingsError;
65 ///
66 /// let err = SettingsError::out_of_range("RELIAR_OUTBOX_LEASE_SECS", "must be at least 1");
67 /// assert!(err.to_string().contains("out of range"));
68 /// ```
69 #[must_use]
70 pub fn out_of_range(key: impl Into<String>, message: &'static str) -> Self {
71 Self::OutOfRange {
72 key: key.into(),
73 message,
74 }
75 }
76
77 /// The full environment-variable name, prefix included — what an operator has to go fix.
78 ///
79 /// ```
80 /// use reliar_core::SettingsError;
81 ///
82 /// let err = SettingsError::out_of_range("RELIAR_OUTBOX_BATCH_SIZE", "must be at least 1");
83 /// assert_eq!(err.key(), "RELIAR_OUTBOX_BATCH_SIZE");
84 /// ```
85 #[must_use]
86 pub fn key(&self) -> &str {
87 match self {
88 Self::Parse { key, .. } | Self::OutOfRange { key, .. } => key,
89 }
90 }
91}
92
93impl fmt::Display for SettingsError {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 Self::Parse { key, value_kind } => {
97 write!(f, "{key} could not be parsed as {value_kind}")
98 }
99 Self::OutOfRange { key, message } => {
100 write!(f, "{key} is out of range: {message}")
101 }
102 }
103 }
104}
105
106impl std::error::Error for SettingsError {}