1use thiserror::Error;
2
3pub type Result<T> = std::result::Result<T, StatsigError>;
4
5#[derive(Error, Debug, Clone)]
6pub enum StatsigError {
7 #[error("API error: {status} - {message}")]
8 Api { status: u16, message: String },
9
10 #[error("Network error: {0}")]
11 Network(String),
12
13 #[error("Serialization error: {0}")]
14 Serialization(String),
15
16 #[error("Validation error: {0}")]
17 Validation(String),
18
19 #[error("Invalid configuration: {0}")]
20 Configuration(String),
21
22 #[error("Cache error: {0}")]
23 Cache(String),
24
25 #[error("Batch processor error: {0}")]
26 BatchProcessor(String),
27
28 #[error("Rate limited: retry after {retry_after_seconds} seconds")]
29 RateLimited { retry_after_seconds: u64 },
30
31 #[error("Unauthorized: invalid API key")]
32 Unauthorized,
33
34 #[error("User validation error: {0}")]
35 UserValidation(String),
36
37 #[error("Feature gate not found: {0}")]
38 GateNotFound(String),
39
40 #[error("Dynamic config not found: {0}")]
41 ConfigNotFound(String),
42
43 #[error("Internal error: {0}")]
44 Internal(String),
45}
46
47impl StatsigError {
48 pub fn api(status: u16, message: impl Into<String>) -> Self {
49 Self::Api {
50 status,
51 message: message.into(),
52 }
53 }
54
55 pub fn validation(message: impl Into<String>) -> Self {
56 Self::Validation(message.into())
57 }
58
59 pub fn configuration(message: impl Into<String>) -> Self {
60 Self::Configuration(message.into())
61 }
62
63 pub fn cache(message: impl Into<String>) -> Self {
64 Self::Cache(message.into())
65 }
66
67 pub fn batch_processor(message: impl Into<String>) -> Self {
68 Self::BatchProcessor(message.into())
69 }
70
71 pub fn rate_limited(retry_after_seconds: u64) -> Self {
72 Self::RateLimited {
73 retry_after_seconds,
74 }
75 }
76
77 pub fn user_validation(message: impl Into<String>) -> Self {
78 Self::UserValidation(message.into())
79 }
80
81 pub fn gate_not_found(name: impl Into<String>) -> Self {
82 Self::GateNotFound(name.into())
83 }
84
85 pub fn config_not_found(name: impl Into<String>) -> Self {
86 Self::ConfigNotFound(name.into())
87 }
88
89 pub fn internal(message: impl Into<String>) -> Self {
90 Self::Internal(message.into())
91 }
92
93 pub fn network(message: impl Into<String>) -> Self {
94 Self::Network(message.into())
95 }
96
97 pub fn serialization(message: impl Into<String>) -> Self {
98 Self::Serialization(message.into())
99 }
100
101 pub fn with_context(self, context: &str) -> Self {
103 match self {
104 Self::Api { status, message } => Self::Api {
105 status,
106 message: format!("{}: {}", context, message),
107 },
108 Self::Network(message) => Self::Network(format!("{}: {}", context, message)),
109 Self::Serialization(message) => {
110 Self::Serialization(format!("{}: {}", context, message))
111 }
112 Self::Validation(message) => Self::Validation(format!("{}: {}", context, message)),
113 Self::Configuration(message) => {
114 Self::Configuration(format!("{}: {}", context, message))
115 }
116 Self::Cache(message) => Self::Cache(format!("{}: {}", context, message)),
117 Self::BatchProcessor(message) => {
118 Self::BatchProcessor(format!("{}: {}", context, message))
119 }
120 Self::UserValidation(message) => {
121 Self::UserValidation(format!("{}: {}", context, message))
122 }
123 Self::GateNotFound(name) => Self::GateNotFound(format!("{}: {}", context, name)),
124 Self::ConfigNotFound(name) => Self::ConfigNotFound(format!("{}: {}", context, name)),
125 Self::Internal(message) => Self::Internal(format!("{}: {}", context, message)),
126 Self::RateLimited { .. } | Self::Unauthorized => self,
128 }
129 }
130}
131
132impl From<reqwest::Error> for StatsigError {
133 fn from(err: reqwest::Error) -> Self {
134 Self::Network(err.to_string())
135 }
136}
137
138impl From<reqwest_middleware::Error> for StatsigError {
139 fn from(err: reqwest_middleware::Error) -> Self {
140 Self::Network(err.to_string())
141 }
142}
143
144impl StatsigError {
145 pub fn is_retryable(&self) -> bool {
146 match self {
147 Self::Network(_) | Self::RateLimited { .. } => true,
148 Self::Api { status, .. } => matches!(status, 429 | 500..=599),
149 _ => false,
150 }
151 }
152
153 pub fn retry_after_seconds(&self) -> Option<u64> {
154 match self {
155 Self::RateLimited {
156 retry_after_seconds,
157 } => Some(*retry_after_seconds),
158 Self::Api { status, .. } if *status == 429 => Some(60),
159 _ => None,
160 }
161 }
162}