use serde::Deserialize;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SolariErrorCode {
FeatureRequiresPlan,
ConcurrencyLimitExceeded,
PlanLimitExceeded,
BrowserUnhealthy,
InvalidSessionId,
Other(String),
}
impl SolariErrorCode {
pub const FEATURE_REQUIRES_PLAN: &'static str = "FeatureRequiresPlan";
pub const CONCURRENCY_LIMIT_EXCEEDED: &'static str = "ConcurrencyLimitExceeded";
pub const PLAN_LIMIT_EXCEEDED: &'static str = "PlanLimitExceeded";
pub const BROWSER_UNHEALTHY: &'static str = "BrowserUnhealthy";
pub const INVALID_SESSION_ID: &'static str = "InvalidSessionId";
pub fn as_str(&self) -> &str {
match self {
SolariErrorCode::FeatureRequiresPlan => Self::FEATURE_REQUIRES_PLAN,
SolariErrorCode::ConcurrencyLimitExceeded => Self::CONCURRENCY_LIMIT_EXCEEDED,
SolariErrorCode::PlanLimitExceeded => Self::PLAN_LIMIT_EXCEEDED,
SolariErrorCode::BrowserUnhealthy => Self::BROWSER_UNHEALTHY,
SolariErrorCode::InvalidSessionId => Self::INVALID_SESSION_ID,
SolariErrorCode::Other(s) => s,
}
}
}
impl From<&str> for SolariErrorCode {
fn from(s: &str) -> Self {
match s {
Self::FEATURE_REQUIRES_PLAN => SolariErrorCode::FeatureRequiresPlan,
Self::CONCURRENCY_LIMIT_EXCEEDED => SolariErrorCode::ConcurrencyLimitExceeded,
Self::PLAN_LIMIT_EXCEEDED => SolariErrorCode::PlanLimitExceeded,
Self::BROWSER_UNHEALTHY => SolariErrorCode::BrowserUnhealthy,
Self::INVALID_SESSION_ID => SolariErrorCode::InvalidSessionId,
other => SolariErrorCode::Other(other.to_string()),
}
}
}
impl From<String> for SolariErrorCode {
fn from(s: String) -> Self {
SolariErrorCode::from(s.as_str())
}
}
impl std::fmt::Display for SolariErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, thiserror::Error)]
pub enum SolariError {
#[error("{message}")]
Api {
status: u16,
code: Option<SolariErrorCode>,
message: String,
},
#[error("{message}")]
Transport { message: String },
#[error("{message}")]
Protocol { message: String },
#[error("{message}")]
Config { message: String },
#[error("{message}")]
Connect { message: String },
}
impl SolariError {
pub(crate) fn transport(msg: impl Into<String>) -> Self {
SolariError::Transport { message: msg.into() }
}
pub(crate) fn protocol(msg: impl Into<String>) -> Self {
SolariError::Protocol { message: msg.into() }
}
pub(crate) fn config(msg: impl Into<String>) -> Self {
SolariError::Config { message: msg.into() }
}
pub fn status(&self) -> Option<u16> {
match self {
SolariError::Api { status, .. } => Some(*status),
_ => None,
}
}
pub fn code(&self) -> Option<&SolariErrorCode> {
match self {
SolariError::Api { code, .. } => code.as_ref(),
_ => None,
}
}
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct ApiErrorBody {
pub code: Option<String>,
pub error: Option<String>,
pub message: Option<String>,
}
pub(crate) fn api_error(method: &str, path: &str, status: u16, body: &str) -> SolariError {
let parsed: Option<ApiErrorBody> = serde_json::from_str(body).ok();
let code = parsed
.as_ref()
.and_then(|b| b.code.as_deref())
.map(SolariErrorCode::from);
SolariError::Api {
status,
code,
message: format!("Solari {method} {path} failed: {status} {body}"),
}
}