Skip to main content

bowline_client/
error.rs

1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
6#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
7pub enum Code {
8    Canceled,
9    InvalidArgument,
10    DeadlineExceeded,
11    NotFound,
12    AlreadyExists,
13    PermissionDenied,
14    ResourceExhausted,
15    FailedPrecondition,
16    Aborted,
17    OutOfRange,
18    Unimplemented,
19    Internal,
20    Unavailable,
21    DataLoss,
22    Unauthenticated,
23    #[serde(other)]
24    Unknown,
25}
26
27impl Code {
28    pub fn as_str(&self) -> &'static str {
29        match self {
30            Code::Canceled => "CANCELED",
31            Code::Unknown => "UNKNOWN",
32            Code::InvalidArgument => "INVALID_ARGUMENT",
33            Code::DeadlineExceeded => "DEADLINE_EXCEEDED",
34            Code::NotFound => "NOT_FOUND",
35            Code::AlreadyExists => "ALREADY_EXISTS",
36            Code::PermissionDenied => "PERMISSION_DENIED",
37            Code::ResourceExhausted => "RESOURCE_EXHAUSTED",
38            Code::FailedPrecondition => "FAILED_PRECONDITION",
39            Code::Aborted => "ABORTED",
40            Code::OutOfRange => "OUT_OF_RANGE",
41            Code::Unimplemented => "UNIMPLEMENTED",
42            Code::Internal => "INTERNAL",
43            Code::Unavailable => "UNAVAILABLE",
44            Code::DataLoss => "DATA_LOSS",
45            Code::Unauthenticated => "UNAUTHENTICATED",
46        }
47    }
48
49    pub fn http_status(&self) -> u16 {
50        match self {
51            Code::Canceled | Code::DeadlineExceeded => 408,
52            Code::Unknown | Code::Internal | Code::DataLoss => 500,
53            Code::InvalidArgument | Code::OutOfRange => 400,
54            Code::NotFound | Code::Unimplemented => 404,
55            Code::AlreadyExists | Code::Aborted => 409,
56            Code::PermissionDenied => 403,
57            Code::ResourceExhausted => 429,
58            Code::FailedPrecondition => 412,
59            Code::Unavailable => 503,
60            Code::Unauthenticated => 401,
61        }
62    }
63}
64
65impl fmt::Display for Code {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        f.write_str(self.as_str())
68    }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
72pub struct Issue {
73    pub path: Vec<String>,
74    pub rule: String,
75    pub message: String,
76}
77
78impl Issue {
79    pub fn new(path: Vec<String>, rule: &str, message: impl Into<String>) -> Self {
80        Issue {
81            path,
82            rule: rule.to_string(),
83            message: message.into(),
84        }
85    }
86}
87
88impl fmt::Display for Issue {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "{}: {}", self.path.join("."), self.message)
91    }
92}
93
94#[derive(Clone, Debug, PartialEq)]
95pub struct RemoteError {
96    pub code: Code,
97    pub message: String,
98    pub status: u16,
99    pub variant: Option<String>,
100    pub details: Option<serde_json::Value>,
101    pub issues: Vec<Issue>,
102}
103
104impl RemoteError {
105    pub fn new(code: Code, message: impl Into<String>, status: u16) -> Self {
106        RemoteError {
107            code,
108            message: message.into(),
109            status,
110            variant: None,
111            details: None,
112            issues: Vec::new(),
113        }
114    }
115
116    pub fn variant(&self) -> Option<&str> {
117        self.variant.as_deref()
118    }
119
120    pub fn details_as<T: DeserializeOwned>(&self) -> Option<T> {
121        let details = self.details.clone()?;
122        serde_json::from_value(details).ok()
123    }
124}
125
126impl fmt::Display for RemoteError {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(f, "{}: {}", self.code, self.message)?;
129        for issue in &self.issues {
130            write!(f, "; {issue}")?;
131        }
132        Ok(())
133    }
134}
135
136#[derive(Debug, thiserror::Error)]
137pub enum Error {
138    #[error("{0}")]
139    Remote(RemoteError),
140    #[error("transport error: {0}")]
141    Transport(#[from] reqwest::Error),
142    #[error("decode error: {0}")]
143    Decode(#[from] serde_json::Error),
144    #[error("invalid input: {}", format_issues(.0))]
145    Invalid(Vec<Issue>),
146    #[error("request canceled")]
147    Canceled,
148}
149
150fn format_issues(issues: &[Issue]) -> String {
151    issues
152        .iter()
153        .map(|i| i.to_string())
154        .collect::<Vec<_>>()
155        .join("; ")
156}
157
158impl Error {
159    pub fn code(&self) -> Option<Code> {
160        match self {
161            Error::Remote(e) => Some(e.code),
162            Error::Canceled => Some(Code::Canceled),
163            Error::Invalid(_) => Some(Code::InvalidArgument),
164            _ => None,
165        }
166    }
167
168    pub fn remote(&self) -> Option<&RemoteError> {
169        match self {
170            Error::Remote(e) => Some(e),
171            _ => None,
172        }
173    }
174
175    pub fn variant(&self) -> Option<&str> {
176        self.remote().and_then(|e| e.variant())
177    }
178
179    pub fn details_as<T: DeserializeOwned>(&self) -> Option<T> {
180        self.remote().and_then(|e| e.details_as())
181    }
182}
183
184#[derive(Deserialize)]
185struct WireError {
186    code: Code,
187    message: String,
188    #[serde(rename = "type")]
189    variant: Option<String>,
190    details: Option<serde_json::Value>,
191    #[serde(default)]
192    issues: Vec<Issue>,
193}
194
195#[derive(Deserialize)]
196struct WireEnvelope {
197    error: WireError,
198}
199
200pub(crate) fn decode_remote(status: u16, body: &[u8]) -> RemoteError {
201    match serde_json::from_slice::<WireEnvelope>(body) {
202        Ok(env) => RemoteError {
203            code: env.error.code,
204            message: env.error.message,
205            status,
206            variant: env.error.variant,
207            details: env.error.details,
208            issues: env.error.issues,
209        },
210        Err(_) => RemoteError::new(Code::Unknown, format!("HTTP {status}"), status),
211    }
212}