1use cufflink_types::ext::ExtFailure;
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ExtError {
12 pub code: String,
13 pub message: String,
14}
15
16pub type Result<T> = std::result::Result<T, ExtError>;
18
19impl ExtError {
20 pub const FAILED: &'static str = "failed";
22
23 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
24 Self {
25 code: code.into(),
26 message: message.into(),
27 }
28 }
29}
30
31impl<E: Into<Box<dyn std::error::Error + Send + Sync>>> From<E> for ExtError {
32 fn from(error: E) -> Self {
33 Self::new(Self::FAILED, error.into().to_string())
34 }
35}
36
37impl fmt::Display for ExtError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "{}: {}", self.code, self.message)
40 }
41}
42
43impl From<ExtError> for ExtFailure {
44 fn from(error: ExtError) -> Self {
45 Self {
46 code: error.code,
47 message: error.message,
48 }
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 fn converted<E: Into<Box<dyn std::error::Error + Send + Sync>>>(error: E) -> ExtError {
57 ExtError::from(error)
58 }
59
60 #[test]
61 fn any_error_converts_as_failed() {
62 let parsed = "x".parse::<u32>().map_err(ExtError::from).unwrap_err();
63 assert_eq!(
64 parsed,
65 ExtError::new("failed", "invalid digit found in string")
66 );
67 assert_eq!(
68 converted("scope is required"),
69 ExtError::new("failed", "scope is required")
70 );
71 assert_eq!(
72 converted(String::from("no quota")),
73 ExtError::new("failed", "no quota")
74 );
75 assert_eq!(
76 converted(std::io::Error::other("disk gone")),
77 ExtError::new("failed", "disk gone")
78 );
79 }
80
81 #[test]
82 fn question_mark_converts() {
83 fn scope(payload: &serde_json::Value) -> Result<String> {
84 let scope = payload["scope"].as_str().ok_or("scope is required")?;
85 let length: u8 = scope.len().try_into()?;
86 Ok(format!("{scope}:{length}"))
87 }
88 assert_eq!(
89 scope(&serde_json::json!({})),
90 Err(ExtError::new("failed", "scope is required"))
91 );
92 assert_eq!(
93 scope(&serde_json::json!({"scope": "a".repeat(300)})).map_err(|e| e.code),
94 Err("failed".into())
95 );
96 assert_eq!(
97 scope(&serde_json::json!({"scope": "drive"})),
98 Ok("drive:5".into())
99 );
100 }
101
102 #[test]
103 fn an_error_reads_and_travels_as_its_code_and_message() {
104 let error = ExtError::new("quota", "daily quota exhausted");
105 assert_eq!(error.to_string(), "quota: daily quota exhausted");
106 assert_eq!(
107 ExtFailure::from(error),
108 ExtFailure {
109 code: "quota".into(),
110 message: "daily quota exhausted".into()
111 }
112 );
113 }
114}