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