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}