manta_server/
wire_conv.rs

1//! Conversions between wire types (`manta-shared`) and backend types
2//! (`manta-backend-dispatcher`).
3//!
4//! Lives server-side because manta-shared has no knowledge of the
5//! backend crates. Orphan rules prevent us from writing
6//! `impl From<MantaError> for BackendError` (both types are foreign
7//! to this crate), so we expose a free function used at call sites
8//! via `.map_err(wire_conv::to_backend)?`.
9//!
10//! A NodeDetails conversion isn't needed in-process: the type
11//! boundary is HTTP, and the JSON wire shape is identical between
12//! `csm_rs::node::types::NodeDetails` and
13//! `manta_shared::shared::dto::NodeDetails`.
14
15use manta_backend_dispatcher::error::Error as BackendError;
16use manta_shared::common::error::MantaError;
17
18/// Map a `MantaError` (returned by manta-shared's pure helpers) onto
19/// the structured `BackendError` that the server's service layer uses.
20pub fn to_backend(e: MantaError) -> BackendError {
21  match e {
22    MantaError::IoError(e) => BackendError::IoError(e),
23    MantaError::ConfigError(e) => BackendError::ConfigError(e),
24    MantaError::TomlEditError(e) => BackendError::TomlEditError(e),
25    MantaError::SerdeError(e) => BackendError::SerdeError(e),
26    MantaError::NetError(e) => BackendError::NetError(e),
27    MantaError::YamlError(e) => BackendError::YamlError(e),
28    MantaError::NotFound(s) => BackendError::NotFound(s),
29    MantaError::MissingField(s) => BackendError::MissingField(s),
30    MantaError::JwtMalformed(s) => BackendError::JwtMalformed(s),
31    MantaError::KafkaError(s) => BackendError::KafkaError(s),
32    MantaError::InvalidPattern(s) => BackendError::InvalidPattern(s),
33    MantaError::TemplateError(s) => BackendError::TemplateError(s),
34    MantaError::Other(s) => BackendError::Message(s),
35  }
36}
37
38#[cfg(test)]
39mod tests {
40  use super::*;
41
42  // The string-bearing variants are 1:1 renames. A test per variant
43  // pins the variant name AND the payload preservation, so a mistyped
44  // arm (NotFound → BadRequest, say) would surface immediately.
45  #[test]
46  fn string_variants_preserve_payload_and_variant() {
47    let cases: &[(MantaError, fn(&BackendError) -> bool)] = &[
48      (
49        MantaError::NotFound("a".into()),
50        |e| matches!(e, BackendError::NotFound(s) if s == "a"),
51      ),
52      (
53        MantaError::MissingField("b".into()),
54        |e| matches!(e, BackendError::MissingField(s) if s == "b"),
55      ),
56      (
57        MantaError::JwtMalformed("c".into()),
58        |e| matches!(e, BackendError::JwtMalformed(s) if s == "c"),
59      ),
60      (
61        MantaError::KafkaError("d".into()),
62        |e| matches!(e, BackendError::KafkaError(s) if s == "d"),
63      ),
64      (
65        MantaError::InvalidPattern("e".into()),
66        |e| matches!(e, BackendError::InvalidPattern(s) if s == "e"),
67      ),
68      (
69        MantaError::TemplateError("f".into()),
70        |e| matches!(e, BackendError::TemplateError(s) if s == "f"),
71      ),
72    ];
73    for (input, predicate) in cases {
74      let label = format!("{input:?}");
75      let mapped = to_backend(match input {
76        MantaError::NotFound(s) => MantaError::NotFound(s.clone()),
77        MantaError::MissingField(s) => MantaError::MissingField(s.clone()),
78        MantaError::JwtMalformed(s) => MantaError::JwtMalformed(s.clone()),
79        MantaError::KafkaError(s) => MantaError::KafkaError(s.clone()),
80        MantaError::InvalidPattern(s) => MantaError::InvalidPattern(s.clone()),
81        MantaError::TemplateError(s) => MantaError::TemplateError(s.clone()),
82        _ => unreachable!(),
83      });
84      assert!(
85        predicate(&mapped),
86        "wrong mapping for {label}: got {mapped:?}"
87      );
88    }
89  }
90
91  // `Other` is the only RENAMED arm: MantaError::Other → BackendError::Message.
92  // Easy to silently change to `BackendError::Other` if someone "fixes" it
93  // and breaks every caller that depends on the catch-all being 500.
94  #[test]
95  fn other_maps_to_message() {
96    let mapped = to_backend(MantaError::Other("oops".into()));
97    assert!(
98      matches!(&mapped, BackendError::Message(s) if s == "oops"),
99      "Other must map to Message (became {mapped:?})"
100    );
101  }
102
103  // The `#[from]`-bearing variants forward their inner error. Pin the
104  // variant name; the inner type is checked by the compiler at compile
105  // time so we don't need to reconstruct an exact payload.
106  #[test]
107  fn io_error_maps_to_backend_io_error() {
108    let inner = std::io::Error::other("disk on fire");
109    let mapped = to_backend(MantaError::IoError(inner));
110    assert!(
111      matches!(mapped, BackendError::IoError(_)),
112      "IoError must round-trip to BackendError::IoError"
113    );
114  }
115
116  #[test]
117  fn serde_error_maps_to_backend_serde_error() {
118    let inner =
119      serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
120    let mapped = to_backend(MantaError::SerdeError(inner));
121    assert!(matches!(mapped, BackendError::SerdeError(_)));
122  }
123
124  #[test]
125  fn yaml_error_maps_to_backend_yaml_error() {
126    let inner =
127      serde_yaml::from_str::<serde_yaml::Value>("\t:bad").unwrap_err();
128    let mapped = to_backend(MantaError::YamlError(inner));
129    assert!(matches!(mapped, BackendError::YamlError(_)));
130  }
131}