use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApiError {
pub code: i32,
pub key: String,
pub message: String,
#[serde(default)]
pub details: Vec<ApiErrorDetail>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApiErrorDetail {
#[serde(default)]
pub param: Option<String>,
pub key: String,
pub message: String,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct ErrorEnvelope {
pub error: ApiError,
}
#[derive(Debug, Error)]
pub enum JorttError {
#[error("invalid SDK configuration: {0}")]
Config(String),
#[error("missing path parameter `{name}` for template `{template}`")]
MissingPathParam {
name: String,
template: String,
},
#[error("transport error: {0}")]
Transport(#[from] reqwest::Error),
#[error("response deserialization failed: {0}")]
Deserialize(#[from] serde_json::Error),
#[error("jortt api error {status}: {error_key} - {message}")]
Api {
status: StatusCode,
error_key: String,
message: String,
payload: Box<ApiError>,
},
#[error("http error {status}: {body}")]
Http {
status: StatusCode,
body: String,
},
#[error("request serialization failed: {0}")]
Serialize(String),
}
impl JorttError {
pub(crate) fn from_serialize(err: serde_json::Error) -> Self {
Self::Serialize(err.to_string())
}
}
#[derive(Debug, Clone, Default)]
pub struct ErrorBuilder {
status: Option<StatusCode>,
error_key: Option<String>,
message: Option<String>,
payload: Option<ApiError>,
body: Option<String>,
config: Option<String>,
missing_name: Option<String>,
missing_template: Option<String>,
}
impl ErrorBuilder {
pub fn api(status: StatusCode, payload: ApiError) -> Self {
Self {
status: Some(status),
error_key: Some(payload.key.clone()),
message: Some(payload.message.clone()),
payload: Some(payload),
..Self::default()
}
}
pub fn http(status: StatusCode) -> Self {
Self {
status: Some(status),
..Self::default()
}
}
pub fn config(message: impl Into<String>) -> Self {
Self {
config: Some(message.into()),
..Self::default()
}
}
pub fn missing_path_param(name: impl Into<String>, template: impl Into<String>) -> Self {
Self {
missing_name: Some(name.into()),
missing_template: Some(template.into()),
..Self::default()
}
}
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = Some(body.into());
self
}
pub fn error_key(mut self, key: impl Into<String>) -> Self {
self.error_key = Some(key.into());
self
}
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn build(self) -> JorttError {
if let Some(message) = self.config {
return JorttError::Config(message);
}
if let Some(name) = self.missing_name {
return JorttError::MissingPathParam {
name,
template: self
.missing_template
.unwrap_or_else(|| "<unknown template>".to_string()),
};
}
if let Some(payload) = self.payload {
return JorttError::Api {
status: self.status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
error_key: self.error_key.unwrap_or_else(|| payload.key.clone()),
message: self.message.unwrap_or_else(|| payload.message.clone()),
payload: Box::new(payload),
};
}
if let Some(status) = self.status {
return JorttError::Http {
status,
body: self.body.unwrap_or_default(),
};
}
JorttError::Config("invalid ErrorBuilder state".to_string())
}
}