ferrin_spec/error/
api_call.rs1use http::StatusCode;
4use url::Url;
5
6use super::BoxError;
7use super::truncate_for_display;
8use crate::json::JsonValue;
9use crate::shared::Headers;
10
11const DISPLAY_MAX_BYTES: usize = 2048;
13
14#[must_use]
20pub fn default_retryable(status_code: Option<StatusCode>) -> bool {
21 match status_code {
22 None => true,
23 Some(status) => {
24 status == StatusCode::REQUEST_TIMEOUT
25 || status == StatusCode::CONFLICT
26 || status == StatusCode::TOO_MANY_REQUESTS
27 || status.is_server_error()
28 }
29 }
30}
31
32#[derive(Debug)]
34pub struct ApiCallError {
35 pub message: String,
37 pub url: Url,
39 pub request_body: Option<JsonValue>,
41 pub status_code: Option<StatusCode>,
43 pub response_headers: Option<Headers>,
45 pub response_body: Option<String>,
47 pub is_retryable: bool,
49 pub data: Option<JsonValue>,
51 pub cause: Option<BoxError>,
53}
54
55impl ApiCallError {
56 #[must_use]
58 pub fn new(message: impl Into<String>, url: Url) -> Self {
59 Self {
60 message: message.into(),
61 url,
62 request_body: None,
63 status_code: None,
64 response_headers: None,
65 response_body: None,
66 is_retryable: default_retryable(None),
67 data: None,
68 cause: None,
69 }
70 }
71
72 #[must_use]
74 pub fn with_status(mut self, status_code: StatusCode) -> Self {
75 self.status_code = Some(status_code);
76 self.is_retryable = default_retryable(Some(status_code));
77 self
78 }
79
80 #[must_use]
82 pub fn with_request_body(mut self, body: JsonValue) -> Self {
83 self.request_body = Some(body);
84 self
85 }
86
87 #[must_use]
89 pub fn with_response(mut self, headers: Headers, body: Option<String>) -> Self {
90 self.response_headers = Some(headers);
91 self.response_body = body;
92 self
93 }
94
95 #[must_use]
97 pub fn with_data(mut self, data: JsonValue) -> Self {
98 self.data = Some(data);
99 self
100 }
101
102 #[must_use]
104 pub fn with_cause(mut self, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
105 self.cause = Some(Box::new(cause));
106 self
107 }
108
109 #[must_use]
111 pub fn retryable(mut self, is_retryable: bool) -> Self {
112 self.is_retryable = is_retryable;
113 self
114 }
115}
116
117impl std::fmt::Display for ApiCallError {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.write_str(&truncate_for_display(&self.message, DISPLAY_MAX_BYTES))
120 }
121}
122
123impl std::error::Error for ApiCallError {
124 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
125 self.cause
126 .as_deref()
127 .map(|cause| cause as &(dyn std::error::Error + 'static))
128 }
129}