Skip to main content

concur_api/
error.rs

1use reqwest::StatusCode;
2use std::fmt;
3
4/// Errors returned by the SAP Concur API client.
5#[derive(Debug, thiserror::Error)]
6pub enum ConcurError {
7    /// HTTP transport error.
8    #[error("HTTP error: {0}")]
9    Http(#[from] reqwest::Error),
10
11    /// JSON serialization / deserialization error.
12    #[error("JSON error: {0}")]
13    Json(#[from] serde_json::Error),
14
15    /// URL parsing error.
16    #[error("URL error: {0}")]
17    Url(#[from] url::ParseError),
18
19    /// An error returned by the SAP Concur API.
20    #[error("API error {status}: {message}")]
21    Api {
22        status: StatusCode,
23        message: String,
24        correlation_id: Option<String>,
25    },
26
27    /// Authentication error.
28    #[error("Authentication error: {0}")]
29    Auth(String),
30
31    /// Retry budget exhausted.
32    #[error("Retry budget exhausted after {attempts} attempts")]
33    RetryExhausted { attempts: u32 },
34
35    /// A required field or parameter was missing.
36    #[error("Missing required parameter: {0}")]
37    MissingParameter(String),
38
39    /// Generic error message.
40    #[error("{0}")]
41    Other(String),
42}
43
44impl ConcurError {
45    pub fn api(status: StatusCode, message: impl Into<String>) -> Self {
46        Self::Api {
47            status,
48            message: message.into(),
49            correlation_id: None,
50        }
51    }
52
53    pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
54        if let Self::Api { correlation_id, .. } = &mut self {
55            *correlation_id = Some(id.into());
56        }
57        self
58    }
59}
60
61/// A generic result type used throughout the crate.
62pub type Result<T> = std::result::Result<T, ConcurError>;
63
64/// Standard SAP Concur error response body.
65#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
66pub struct ConcurApiError {
67    pub error: Option<String>,
68    #[serde(rename = "error_description")]
69    pub error_description: Option<String>,
70    pub code: Option<i32>,
71    pub timestamp: Option<String>,
72    pub error_message: Option<String>,
73    pub error_id: Option<String>,
74    pub path: Option<String>,
75}
76
77impl fmt::Display for ConcurApiError {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        if let Some(msg) = &self.error_message {
80            write!(f, "{}", msg)
81        } else if let Some(desc) = &self.error_description {
82            write!(f, "{}", desc)
83        } else if let Some(err) = &self.error {
84            write!(f, "{}", err)
85        } else {
86            write!(f, "{:?}", self)
87        }
88    }
89}