Skip to main content

diidi_travel_common_error/
code.rs

1//! Stable identifier for an error. Use `const`-able static codes for the common cases (`const
2//! USER_NOT_FOUND: ErrorCode = ErrorCode::from_static("user.not_found");`) and the [`ErrorCode::new`]
3//! constructor when the code is composed at runtime.
4
5use serde::{Deserialize, Serialize};
6use std::borrow::Cow;
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct ErrorCode(Cow<'static, str>);
12
13impl ErrorCode {
14  /// Construct from a `'static` string slice. Zero-allocation; safe to use in `const` context.
15  pub const fn from_static(code: &'static str) -> Self {
16    Self(Cow::Borrowed(code))
17  }
18
19  /// Construct from a runtime string. Allocates.
20  pub fn new(code: impl Into<String>) -> Self {
21    Self(Cow::Owned(code.into()))
22  }
23
24  pub fn as_str(&self) -> &str {
25    &self.0
26  }
27}
28
29impl fmt::Display for ErrorCode {
30  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31    f.write_str(&self.0)
32  }
33}
34
35impl From<&'static str> for ErrorCode {
36  fn from(s: &'static str) -> Self {
37    Self::from_static(s)
38  }
39}
40
41impl From<String> for ErrorCode {
42  fn from(s: String) -> Self {
43    Self::new(s)
44  }
45}
46
47/// Sentinel for "no specific code" — useful when category alone is enough context.
48pub const UNSPECIFIED: ErrorCode = ErrorCode::from_static("unspecified");
49
50#[cfg(test)]
51mod tests {
52  use super::*;
53
54  #[test]
55  fn const_constructor_is_zero_alloc() {
56    const CODE: ErrorCode = ErrorCode::from_static("user.not_found");
57    assert_eq!(CODE.as_str(), "user.not_found");
58  }
59
60  #[test]
61  fn runtime_constructor_works() {
62    let code = ErrorCode::new(format!("user.{}", "not_found"));
63    assert_eq!(code.as_str(), "user.not_found");
64  }
65
66  #[test]
67  fn serializes_as_string() {
68    let code = ErrorCode::from_static("payment.declined");
69    let json = serde_json::to_string(&code).unwrap();
70    assert_eq!(json, "\"payment.declined\"");
71    let back: ErrorCode = serde_json::from_str(&json).unwrap();
72    assert_eq!(back, code);
73  }
74}