1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Debug)]
9pub enum RunbeamError {
10 JwtValidation(String),
12 Api(ApiError),
14 Storage(crate::storage::StorageError),
16 Config(String),
18}
19
20impl fmt::Display for RunbeamError {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 RunbeamError::JwtValidation(msg) => write!(f, "JWT validation failed: {}", msg),
24 RunbeamError::Api(err) => write!(f, "API error: {}", err),
25 RunbeamError::Storage(err) => write!(f, "Storage error: {}", err),
26 RunbeamError::Config(msg) => write!(f, "Configuration error: {}", msg),
27 }
28 }
29}
30
31impl std::error::Error for RunbeamError {}
32
33impl From<ApiError> for RunbeamError {
34 fn from(err: ApiError) -> Self {
35 RunbeamError::Api(err)
36 }
37}
38
39impl From<crate::storage::StorageError> for RunbeamError {
40 fn from(err: crate::storage::StorageError) -> Self {
41 RunbeamError::Storage(err)
42 }
43}
44
45impl From<jsonwebtoken::errors::Error> for RunbeamError {
46 fn from(err: jsonwebtoken::errors::Error) -> Self {
47 RunbeamError::JwtValidation(err.to_string())
48 }
49}
50
51#[derive(Debug)]
53pub enum ApiError {
54 Network(String),
56 Http { status: u16, message: String },
58 Parse(String),
60 Request(String),
62}
63
64impl fmt::Display for ApiError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 ApiError::Network(msg) => write!(f, "Network error: {}", msg),
68 ApiError::Http { status, message } => {
69 write!(f, "HTTP {} error: {}", status, message)
70 }
71 ApiError::Parse(msg) => write!(f, "Parse error: {}", msg),
72 ApiError::Request(msg) => write!(f, "Request error: {}", msg),
73 }
74 }
75}
76
77impl std::error::Error for ApiError {}
78
79impl From<reqwest::Error> for ApiError {
80 fn from(err: reqwest::Error) -> Self {
81 if err.is_timeout() {
82 ApiError::Network("Request timeout".to_string())
83 } else if err.is_connect() {
84 ApiError::Network(format!("Connection failed: {}", err))
85 } else if let Some(status) = err.status() {
86 ApiError::Http {
87 status: status.as_u16(),
88 message: err.to_string(),
89 }
90 } else {
91 ApiError::Network(err.to_string())
92 }
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct UserInfo {
99 pub id: String,
100 pub email: String,
101 pub name: String,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct UserToken {
111 pub token: String,
113 #[serde(default)]
115 pub expires_at: Option<i64>,
116 #[serde(default)]
118 pub user: Option<UserInfo>,
119}
120
121impl UserToken {
122 pub fn new(token: String, expires_at: Option<i64>, user: Option<UserInfo>) -> Self {
124 Self {
125 token,
126 expires_at,
127 user,
128 }
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct TeamInfo {
135 pub id: String,
136 pub name: String,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct GatewayInfo {
142 pub id: String,
143 pub code: String,
144 pub name: String,
145 #[serde(default)]
146 pub authorized_by: Option<AuthorizedBy>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct AuthorizedBy {
152 pub id: String,
153 pub name: String,
154 pub email: String,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct AuthorizeResponse {
160 pub machine_token: String,
161 pub expires_in: f64,
162 pub expires_at: String,
163 pub gateway: GatewayInfo,
164 #[serde(default)]
165 pub abilities: Vec<String>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct StoreConfigRequest {
174 #[serde(rename = "type")]
176 pub config_type: String,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub id: Option<String>,
180 pub config: String,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct StoreConfigResponse {
189 pub success: bool,
191 pub message: String,
193 pub data: StoreConfigResponseData,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct StoreConfigResponseData {
200 pub model: StoreConfigModel,
202 }
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct StoreConfigModel {
208 pub id: String,
210 #[serde(rename = "type")]
212 pub model_type: String,
213 pub action: String,
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_store_config_request_with_id() {
223 let request = StoreConfigRequest {
224 config_type: "gateway".to_string(),
225 id: Some("01k8ek6h9aahhnrv3benret1nn".to_string()),
226 config: "[proxy]\nid = \"test\"\n".to_string(),
227 };
228
229 let json = serde_json::to_string(&request).unwrap();
230 assert!(json.contains("\"type\":\"gateway\""));
231 assert!(json.contains("\"id\":\"01k8ek6h9aahhnrv3benret1nn\""));
232 assert!(json.contains("\"config\":"));
233 assert!(json.contains("[proxy]"));
234
235 let deserialized: StoreConfigRequest = serde_json::from_str(&json).unwrap();
237 assert_eq!(deserialized.config_type, "gateway");
238 assert_eq!(
239 deserialized.id,
240 Some("01k8ek6h9aahhnrv3benret1nn".to_string())
241 );
242 }
243
244 #[test]
245 fn test_store_config_request_without_id() {
246 let request = StoreConfigRequest {
247 config_type: "pipeline".to_string(),
248 id: None,
249 config: "[pipeline]\nname = \"test\"\n".to_string(),
250 };
251
252 let json = serde_json::to_string(&request).unwrap();
253 assert!(json.contains("\"type\":\"pipeline\""));
254 assert!(json.contains("\"config\":"));
255 assert!(!json.contains("\"id\""));
257
258 let deserialized: StoreConfigRequest = serde_json::from_str(&json).unwrap();
260 assert_eq!(deserialized.config_type, "pipeline");
261 assert_eq!(deserialized.id, None);
262 }
263
264 #[test]
265 fn test_store_config_request_type_field_rename() {
266 let json = r#"{"type":"transform","config":"[transform]\nname = \"test\"\n"}"#;
268 let request: StoreConfigRequest = serde_json::from_str(json).unwrap();
269 assert_eq!(request.config_type, "transform");
270 assert_eq!(request.id, None);
271 }
272
273 #[test]
274 fn test_store_config_response() {
275 let json = r#"{
276 "success": true,
277 "message": "Gateway configuration updated successfully",
278 "data": {
279 "model": {
280 "id": "01k9npa4tatmwddk66xxpcr2r0",
281 "type": "gateway",
282 "action": "updated"
283 },
284 "change": {}
285 }
286 }"#;
287
288 let response: StoreConfigResponse = serde_json::from_str(json).unwrap();
289 assert_eq!(response.success, true);
290 assert!(response.message.contains("updated successfully"));
291 assert_eq!(response.data.model.id, "01k9npa4tatmwddk66xxpcr2r0");
292 assert_eq!(response.data.model.model_type, "gateway");
293 assert_eq!(response.data.model.action, "updated");
294 }
295}