1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
8pub struct ServerError {
9 pub message: String,
10 #[serde(default, skip_serializing_if = "Option::is_none")]
11 pub request_id: Option<String>,
12 #[serde(default, skip_serializing_if = "Option::is_none")]
13 pub documentation_url: Option<String>,
14}
15
16#[derive(Debug, thiserror::Error)]
18pub enum ApiError {
19 #[error("transport error: {0}")]
21 Transport(#[from] reqwest::Error),
22
23 #[error("server returned status {status}{}", body.as_ref().map(|b| format!(": {}", b.message)).unwrap_or_default())]
27 Status {
28 status: u16,
29 lfs_authenticate: Option<String>,
30 body: Option<ServerError>,
31 },
32
33 #[error("malformed response body: {0}")]
35 Decode(String),
36
37 #[error("url error: {0}")]
39 Url(#[from] url::ParseError),
40}
41
42impl ApiError {
43 pub fn is_unauthorized(&self) -> bool {
45 matches!(self, ApiError::Status { status: 401, .. })
46 }
47
48 pub fn is_forbidden(&self) -> bool {
50 matches!(self, ApiError::Status { status: 403, .. })
51 }
52
53 pub fn is_not_found(&self) -> bool {
55 matches!(self, ApiError::Status { status: 404, .. })
56 }
57
58 pub fn is_retryable(&self) -> bool {
60 matches!(
61 self,
62 ApiError::Transport(_)
63 | ApiError::Status { status: 408 | 429 | 500..=599, .. }
64 )
65 }
66}