1use serde::Deserialize;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum SolariErrorCode {
16 FeatureRequiresPlan,
18 ConcurrencyLimitExceeded,
20 PlanLimitExceeded,
22 BrowserUnhealthy,
24 InvalidSessionId,
28 Other(String),
30}
31
32impl SolariErrorCode {
33 pub const FEATURE_REQUIRES_PLAN: &'static str = "FeatureRequiresPlan";
34 pub const CONCURRENCY_LIMIT_EXCEEDED: &'static str = "ConcurrencyLimitExceeded";
35 pub const PLAN_LIMIT_EXCEEDED: &'static str = "PlanLimitExceeded";
36 pub const BROWSER_UNHEALTHY: &'static str = "BrowserUnhealthy";
37 pub const INVALID_SESSION_ID: &'static str = "InvalidSessionId";
38
39 pub fn as_str(&self) -> &str {
41 match self {
42 SolariErrorCode::FeatureRequiresPlan => Self::FEATURE_REQUIRES_PLAN,
43 SolariErrorCode::ConcurrencyLimitExceeded => Self::CONCURRENCY_LIMIT_EXCEEDED,
44 SolariErrorCode::PlanLimitExceeded => Self::PLAN_LIMIT_EXCEEDED,
45 SolariErrorCode::BrowserUnhealthy => Self::BROWSER_UNHEALTHY,
46 SolariErrorCode::InvalidSessionId => Self::INVALID_SESSION_ID,
47 SolariErrorCode::Other(s) => s,
48 }
49 }
50}
51
52impl From<&str> for SolariErrorCode {
53 fn from(s: &str) -> Self {
54 match s {
55 Self::FEATURE_REQUIRES_PLAN => SolariErrorCode::FeatureRequiresPlan,
56 Self::CONCURRENCY_LIMIT_EXCEEDED => SolariErrorCode::ConcurrencyLimitExceeded,
57 Self::PLAN_LIMIT_EXCEEDED => SolariErrorCode::PlanLimitExceeded,
58 Self::BROWSER_UNHEALTHY => SolariErrorCode::BrowserUnhealthy,
59 Self::INVALID_SESSION_ID => SolariErrorCode::InvalidSessionId,
60 other => SolariErrorCode::Other(other.to_string()),
61 }
62 }
63}
64
65impl From<String> for SolariErrorCode {
66 fn from(s: String) -> Self {
67 SolariErrorCode::from(s.as_str())
68 }
69}
70
71impl std::fmt::Display for SolariErrorCode {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 f.write_str(self.as_str())
74 }
75}
76
77#[derive(Debug, thiserror::Error)]
79pub enum SolariError {
80 #[error("{message}")]
82 Api {
83 status: u16,
84 code: Option<SolariErrorCode>,
85 message: String,
86 },
87
88 #[error("{message}")]
90 Transport { message: String },
91
92 #[error("{message}")]
94 Protocol { message: String },
95
96 #[error("{message}")]
98 Config { message: String },
99
100 #[error("{message}")]
102 Connect { message: String },
103}
104
105impl SolariError {
106 pub(crate) fn transport(msg: impl Into<String>) -> Self {
107 SolariError::Transport { message: msg.into() }
108 }
109
110 pub(crate) fn protocol(msg: impl Into<String>) -> Self {
111 SolariError::Protocol { message: msg.into() }
112 }
113
114 pub(crate) fn config(msg: impl Into<String>) -> Self {
115 SolariError::Config { message: msg.into() }
116 }
117
118 pub fn status(&self) -> Option<u16> {
120 match self {
121 SolariError::Api { status, .. } => Some(*status),
122 _ => None,
123 }
124 }
125
126 pub fn code(&self) -> Option<&SolariErrorCode> {
128 match self {
129 SolariError::Api { code, .. } => code.as_ref(),
130 _ => None,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Default, Deserialize)]
138pub struct ApiErrorBody {
139 pub code: Option<String>,
140 pub error: Option<String>,
141 pub message: Option<String>,
142}
143
144pub(crate) fn api_error(method: &str, path: &str, status: u16, body: &str) -> SolariError {
148 let parsed: Option<ApiErrorBody> = serde_json::from_str(body).ok();
149 let code = parsed
150 .as_ref()
151 .and_then(|b| b.code.as_deref())
152 .map(SolariErrorCode::from);
153 SolariError::Api {
154 status,
155 code,
156 message: format!("Solari {method} {path} failed: {status} {body}"),
157 }
158}