1use crate::{AuthenticatorError, FromErrorDetail};
2use reqwest::header::InvalidHeaderValue;
3use reqwest::StatusCode;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt;
7use thiserror::Error;
8
9#[derive(Serialize, Deserialize, Debug)]
10#[serde(rename_all = "camelCase")]
11pub struct ApiErrorWrapper {
13 pub error: ApiErrorMessage,
15}
16
17#[derive(Serialize, Deserialize, Debug)]
18#[serde(untagged)]
19pub enum IntegerStringOrObject {
21 Integer(i64),
23 String(String),
25 Object(HashMap<String, Box<IntegerStringOrObject>>),
27}
28
29impl IntegerStringOrObject {
30 pub fn integer(&self) -> Option<i64> {
32 match self {
33 Self::Integer(i) => Some(*i),
34 Self::String(s) => s.parse().ok(),
35 _ => None,
36 }
37 }
38 pub fn string(&self) -> Option<&String> {
40 match self {
41 Self::Integer(_) => None,
42 Self::String(s) => Some(s),
43 _ => None,
44 }
45 }
46 pub fn object(&self) -> Option<&HashMap<String, Box<Self>>> {
48 match self {
49 Self::Object(o) => Some(o),
50 _ => None,
51 }
52 }
53}
54
55#[derive(Serialize, Deserialize, Debug)]
56#[serde(rename_all = "camelCase")]
57pub struct ApiErrorDetail(pub Vec<HashMap<String, Box<IntegerStringOrObject>>>);
59
60#[derive(Serialize, Deserialize, Debug)]
61#[serde(rename_all = "camelCase")]
62pub struct ApiErrorMessage {
64 pub code: u32,
66 pub message: String,
68 pub missing: Option<ApiErrorDetail>,
70 pub duplicated: Option<ApiErrorDetail>,
72}
73
74impl ApiErrorDetail {
75 pub fn get_values<T: FromErrorDetail>(&self) -> impl Iterator<Item = T> + '_ {
77 self.iter().filter_map(|m| T::from_detail(m))
78 }
79
80 pub fn get_integer(
82 map: &HashMap<String, Box<IntegerStringOrObject>>,
83 key: &str,
84 ) -> Option<i64> {
85 map.get(key).and_then(|f| f.integer())
86 }
87 pub fn get_string<'a>(
89 map: &'a HashMap<String, Box<IntegerStringOrObject>>,
90 key: &str,
91 ) -> Option<&'a String> {
92 map.get(key).and_then(|f| f.string())
93 }
94
95 pub fn get_object<'a>(
97 map: &'a HashMap<String, Box<IntegerStringOrObject>>,
98 key: &str,
99 ) -> Option<&'a HashMap<String, Box<IntegerStringOrObject>>> {
100 map.get(key).and_then(|f| f.object())
101 }
102
103 pub fn iter(&self) -> impl Iterator<Item = &HashMap<String, Box<IntegerStringOrObject>>> + '_ {
105 self.0.iter()
106 }
107}
108
109#[derive(Debug, Default)]
110pub struct CdfApiError {
112 pub code: u32,
114 pub message: String,
116 pub missing: Option<ApiErrorDetail>,
118 pub duplicated: Option<ApiErrorDetail>,
120 pub request_id: Option<String>,
122}
123
124impl fmt::Display for CdfApiError {
125 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126 write!(
127 f,
128 "{}: {}. RequestId: {}",
129 &self.code,
130 &self.message,
131 self.request_id.as_deref().unwrap_or("")
132 )
133 }
134}
135
136impl CdfApiError {
137 pub(crate) fn new(raw: ApiErrorMessage, request_id: Option<String>) -> Self {
138 CdfApiError {
139 code: raw.code,
140 message: raw.message,
141 missing: raw.missing,
142 duplicated: raw.duplicated,
143 request_id,
144 }
145 }
146}
147
148impl Error {
149 fn new_from_code(code: StatusCode, err: CdfApiError) -> Error {
150 match code {
151 StatusCode::BAD_REQUEST => Error::BadRequest(err),
152 StatusCode::UNAUTHORIZED => Error::Unauthorized(err),
153 StatusCode::FORBIDDEN => Error::Forbidden(err),
154 StatusCode::NOT_FOUND => Error::NotFound(err),
155 StatusCode::CONFLICT => Error::Conflict(err),
156 StatusCode::UNPROCESSABLE_ENTITY => Error::UnprocessableEntity(err),
157 _ => Error::OtherApiError(err),
158 }
159 }
160
161 pub(crate) fn new_from_cdf(
162 code: StatusCode,
163 err: ApiErrorWrapper,
164 request_id: Option<String>,
165 ) -> Error {
166 let cdf_err = CdfApiError::new(err.error, request_id);
167 Self::new_from_code(code, cdf_err)
168 }
169
170 pub(crate) fn new_without_json(
171 code: StatusCode,
172 err: String,
173 request_id: Option<String>,
174 ) -> Error {
175 let err = CdfApiError {
176 code: code.to_owned().as_u16().into(),
177 message: err,
178 request_id,
179 ..Default::default()
180 };
181 Self::new_from_code(code, err)
182 }
183}
184
185pub type Result<T> = ::std::result::Result<T, Error>;
187
188#[derive(Debug, Error)]
189pub enum Error {
191 #[error("Bad request (400): {0}")]
192 BadRequest(CdfApiError),
194 #[error("Unauthorized (401): {0}")]
195 Unauthorized(CdfApiError),
198 #[error("Forbidden (403): {0}")]
199 Forbidden(CdfApiError),
202 #[error("Not Found (404): {0}")]
203 NotFound(CdfApiError),
205 #[error("Conflict (409): {0}")]
206 Conflict(CdfApiError),
208 #[error("Unprocessable Entity (422): {0}")]
209 UnprocessableEntity(CdfApiError),
211 #[error("Other Api Error: {0}")]
212 OtherApiError(CdfApiError),
214 #[error("Environment Variable Missing: {0}")]
215 EnvironmentVariableMissing(String),
217 #[error("Error from authenticator: {0}")]
218 Authenticator(AuthenticatorError),
220 #[error("Invalid header value: {0}")]
221 InvalidHeader(#[from] InvalidHeaderValue),
223 #[error("Error accessing file: {0}")]
224 IOError(#[from] std::io::Error),
226 #[error("Error collecting stream: {0:#}")]
227 StreamError(anyhow::Error),
229 #[error("Error in middleware: {0:#}")]
230 Middleware(anyhow::Error),
232 #[error("Error in configuration: {0}")]
233 Config(String),
235 #[error("Unexpected request error: {0}")]
236 Reqwest(#[from] reqwest::Error),
238 #[error("Unexpected JSON error: {0}")]
240 SerdeJson(#[from] ::serde_json::Error),
242 #[error("Unexpected protobuf error: {0}")]
243 Prost(#[from] ::prost::DecodeError),
245 #[error("{0}")]
246 Other(String),
248}
249
250impl From<reqwest_middleware::Error> for Error {
251 fn from(err: reqwest_middleware::Error) -> Self {
252 match err {
253 reqwest_middleware::Error::Middleware(x) => Error::Middleware(x),
254 reqwest_middleware::Error::Reqwest(x) => Self::from(x),
255 }
256 }
257}