1use std::fmt::Display;
2
3use crate::error::{ContractError, TypeError};
4use crate::llm::PaginationRequest;
5use crate::{CustomInterval, Status};
6use crate::{DriftType, PyHelperFuncs, TimeInterval};
7use chrono::{DateTime, Utc};
8use pyo3::prelude::*;
9use scouter_semver::VersionType;
10use serde::Deserialize;
11use serde::Serialize;
12use std::collections::BTreeMap;
13use tracing::error;
14
15#[pyclass]
16#[derive(Serialize, Deserialize, Debug, Clone)]
17pub struct GetProfileRequest {
18 pub name: String,
19 pub space: String,
20 pub version: String,
21 pub drift_type: DriftType,
22}
23
24#[pymethods]
25impl GetProfileRequest {
26 #[new]
27 #[pyo3(signature = (name, space, version, drift_type))]
28 pub fn new(name: String, space: String, version: String, drift_type: DriftType) -> Self {
29 GetProfileRequest {
30 name,
31 space,
32 version,
33 drift_type,
34 }
35 }
36}
37
38#[pyclass]
39#[derive(Serialize, Deserialize, Debug, Clone, Default)]
40pub struct DriftRequest {
41 pub space: String,
42 pub name: String,
43 pub version: String,
44 pub time_interval: TimeInterval,
45 pub max_data_points: i32,
46 pub drift_type: DriftType,
47 pub begin_custom_datetime: Option<DateTime<Utc>>,
48 pub end_custom_datetime: Option<DateTime<Utc>>,
49}
50
51#[pymethods]
52impl DriftRequest {
53 #[new]
54 #[pyo3(signature = (name, space, version, time_interval, max_data_points, drift_type, begin_datetime=None, end_datetime=None))]
55 #[allow(clippy::too_many_arguments)]
56 pub fn new(
57 name: String,
58 space: String,
59 version: String,
60 time_interval: TimeInterval,
61 max_data_points: i32,
62 drift_type: DriftType,
63 begin_datetime: Option<DateTime<Utc>>,
64 end_datetime: Option<DateTime<Utc>>,
65 ) -> Result<Self, ContractError> {
66 let custom_interval = match (begin_datetime, end_datetime) {
68 (Some(begin), Some(end)) => Some(CustomInterval::new(begin, end)?),
69 _ => None,
70 };
71
72 Ok(DriftRequest {
73 name,
74 space,
75 version,
76 time_interval,
77 max_data_points,
78 drift_type,
79 begin_custom_datetime: custom_interval.as_ref().map(|interval| interval.start),
80 end_custom_datetime: custom_interval.as_ref().map(|interval| interval.end),
81 })
82 }
83}
84
85impl DriftRequest {
86 pub fn has_custom_interval(&self) -> bool {
87 self.begin_custom_datetime.is_some() && self.end_custom_datetime.is_some()
88 }
89
90 pub fn to_custom_interval(&self) -> Option<CustomInterval> {
91 if self.has_custom_interval() {
92 Some(
93 CustomInterval::new(
94 self.begin_custom_datetime.unwrap(),
95 self.end_custom_datetime.unwrap(),
96 )
97 .unwrap(),
98 )
99 } else {
100 None
101 }
102 }
103}
104
105#[derive(Serialize, Deserialize, Debug, Clone)]
106pub struct VersionRequest {
107 pub version: Option<String>,
108 pub version_type: VersionType,
109 pub pre_tag: Option<String>,
110 pub build_tag: Option<String>,
111}
112
113impl Default for VersionRequest {
114 fn default() -> Self {
115 VersionRequest {
116 version: None,
117 version_type: VersionType::Minor,
118 pre_tag: None,
119 build_tag: None,
120 }
121 }
122}
123
124#[pyclass]
125#[derive(Serialize, Deserialize, Debug, Clone)]
126pub struct ProfileRequest {
127 pub space: String,
128 pub drift_type: DriftType,
129 pub profile: String,
130 pub version_request: VersionRequest,
131}
132
133#[pyclass]
134#[derive(Serialize, Deserialize, Debug, Clone)]
135pub struct ProfileStatusRequest {
136 pub name: String,
137 pub space: String,
138 pub version: String,
139 pub active: bool,
140 pub drift_type: Option<DriftType>,
141 pub deactivate_others: bool,
142}
143
144#[pymethods]
145impl ProfileStatusRequest {
146 #[new]
147 #[pyo3(signature = (name, space, version, drift_type=None, active=false, deactivate_others=false))]
148 pub fn new(
149 name: String,
150 space: String,
151 version: String,
152 drift_type: Option<DriftType>,
153 active: bool,
154 deactivate_others: bool,
155 ) -> Self {
156 ProfileStatusRequest {
157 name,
158 space,
159 version,
160 active,
161 drift_type,
162 deactivate_others,
163 }
164 }
165}
166
167#[pyclass]
168#[derive(Serialize, Deserialize, Debug, Clone)]
169pub struct DriftAlertRequest {
170 pub name: String,
171 pub space: String,
172 pub version: String,
173 pub limit_datetime: Option<DateTime<Utc>>,
174 pub active: Option<bool>,
175 pub limit: Option<i32>,
176}
177
178#[pymethods]
179impl DriftAlertRequest {
180 #[new]
181 #[pyo3(signature = (name, space, version, active=false, limit_datetime=None, limit=None))]
182 pub fn new(
183 name: String,
184 space: String,
185 version: String,
186 active: bool,
187 limit_datetime: Option<DateTime<Utc>>,
188 limit: Option<i32>,
189 ) -> Self {
190 DriftAlertRequest {
191 name,
192 space,
193 version,
194 limit_datetime,
195 active: Some(active),
196 limit,
197 }
198 }
199}
200
201#[derive(Serialize, Deserialize, Debug, Clone)]
202pub struct ServiceInfo {
203 pub space: String,
204 pub name: String,
205 pub version: String,
206}
207
208#[derive(Serialize, Deserialize, Debug, Clone)]
209pub struct LLMServiceInfo {
210 pub space: String,
211 pub name: String,
212 pub version: String,
213}
214
215#[derive(Serialize, Deserialize, Debug, Clone)]
216pub struct DriftTaskInfo {
217 pub space: String,
218 pub name: String,
219 pub version: String,
220 pub uid: String,
221 pub drift_type: DriftType,
222}
223
224#[derive(Serialize, Deserialize, Debug, Clone)]
225pub struct ObservabilityMetricRequest {
226 pub name: String,
227 pub space: String,
228 pub version: String,
229 pub time_interval: String,
230 pub max_data_points: i32,
231}
232
233#[derive(Serialize, Deserialize, Debug, Clone)]
234pub struct UpdateAlertStatus {
235 pub id: i32,
236 pub active: bool,
237 pub space: String,
238}
239
240#[derive(Serialize, Deserialize, Debug, Clone)]
242pub struct ScouterServerError {
243 pub error: String,
244}
245
246impl ScouterServerError {
247 pub fn permission_denied() -> Self {
248 ScouterServerError {
249 error: "Permission denied".to_string(),
250 }
251 }
252
253 pub fn need_admin_permission() -> Self {
254 error!("User does not have admin permissions");
255 ScouterServerError {
256 error: "Need admin permission".to_string(),
257 }
258 }
259
260 pub fn user_already_exists() -> Self {
261 ScouterServerError {
262 error: "User already exists".to_string(),
263 }
264 }
265
266 pub fn create_user_error<T: Display>(e: T) -> Self {
267 error!("Failed to create user: {}", e);
268 ScouterServerError {
269 error: "Failed to create user".to_string(),
270 }
271 }
272
273 pub fn user_not_found() -> Self {
274 ScouterServerError {
275 error: "User not found".to_string(),
276 }
277 }
278
279 pub fn get_user_error<T: Display>(e: T) -> Self {
280 error!("Failed to get user: {}", e);
281 ScouterServerError {
282 error: "Failed to get user".to_string(),
283 }
284 }
285
286 pub fn list_users_error<T: Display>(e: T) -> Self {
287 error!("Failed to list users: {}", e);
288 ScouterServerError {
289 error: "Failed to list users".to_string(),
290 }
291 }
292
293 pub fn update_user_error<T: Display>(e: T) -> Self {
294 error!("Failed to update user: {}", e);
295 ScouterServerError {
296 error: "Failed to update user".to_string(),
297 }
298 }
299 pub fn delete_user_error<T: Display>(e: T) -> Self {
300 error!("Failed to delete user: {}", e);
301 ScouterServerError {
302 error: "Failed to delete user".to_string(),
303 }
304 }
305
306 pub fn check_last_admin_error<T: Display>(e: T) -> Self {
307 error!("Failed to check admin status: {}", e);
308 ScouterServerError {
309 error: "Failed to check admin status".to_string(),
310 }
311 }
312
313 pub fn cannot_delete_last_admin() -> Self {
314 error!("Cannot delete the last admin user");
315 ScouterServerError {
316 error: "Cannot delete the last admin user".to_string(),
317 }
318 }
319 pub fn username_header_not_found() -> Self {
320 error!("Username header not found");
321 ScouterServerError {
322 error: "Username header not found".to_string(),
323 }
324 }
325
326 pub fn invalid_username_format() -> Self {
327 error!("Invalid username format");
328 ScouterServerError {
329 error: "Invalid username format".to_string(),
330 }
331 }
332
333 pub fn password_header_not_found() -> Self {
334 error!("Password header not found");
335 ScouterServerError {
336 error: "Password header not found".to_string(),
337 }
338 }
339 pub fn invalid_password_format() -> Self {
340 error!("Invalid password format");
341 ScouterServerError {
342 error: "Invalid password format".to_string(),
343 }
344 }
345
346 pub fn user_validation_error() -> Self {
347 error!("User validation failed");
348 ScouterServerError {
349 error: "User validation failed".to_string(),
350 }
351 }
352
353 pub fn failed_token_validation() -> Self {
354 error!("Failed to validate token");
355 ScouterServerError {
356 error: "Failed to validate token".to_string(),
357 }
358 }
359
360 pub fn bearer_token_not_found() -> Self {
361 error!("Bearer token not found");
362 ScouterServerError {
363 error: "Bearer token not found".to_string(),
364 }
365 }
366
367 pub fn refresh_token_error<T: Display>(e: T) -> Self {
368 error!("Failed to refresh token: {}", e);
369 ScouterServerError {
370 error: "Failed to refresh token".to_string(),
371 }
372 }
373
374 pub fn unauthorized<T: Display>(e: T) -> Self {
375 error!("Unauthorized: {}", e);
376 ScouterServerError {
377 error: "Unauthorized".to_string(),
378 }
379 }
380
381 pub fn jwt_decode_error(e: String) -> Self {
382 error!("Failed to decode JWT token: {}", e);
383 ScouterServerError {
384 error: "Failed to decode JWT token".to_string(),
385 }
386 }
387
388 pub fn no_refresh_token() -> Self {
389 error!("No refresh token provided");
390 ScouterServerError {
391 error: "No refresh token provided".to_string(),
392 }
393 }
394
395 pub fn new(error: String) -> Self {
396 ScouterServerError { error }
397 }
398
399 pub fn query_records_error<T: Display>(e: T) -> Self {
400 let msg = format!("Failed to query records: {e}");
401 ScouterServerError { error: msg }
402 }
403
404 pub fn query_alerts_error<T: Display>(e: T) -> Self {
405 let msg = format!("Failed to query alerts: {e}");
406 ScouterServerError { error: msg }
407 }
408
409 pub fn query_profile_error<T: Display>(e: T) -> Self {
410 let msg = format!("Failed to query profile: {e}");
411 ScouterServerError { error: msg }
412 }
413}
414
415#[derive(Serialize, Deserialize, Debug, Clone)]
416pub struct ScouterResponse {
417 pub status: String,
418 pub message: String,
419}
420
421impl ScouterResponse {
422 pub fn new(status: String, message: String) -> Self {
423 ScouterResponse { status, message }
424 }
425}
426
427#[derive(Serialize, Deserialize, Debug, Clone)]
428pub struct RegisteredProfileResponse {
429 pub space: String,
430 pub name: String,
431 pub version: String,
432 pub status: String,
433}
434
435#[derive(Serialize, Deserialize, Debug, Clone)]
436pub struct UpdateAlertResponse {
437 pub updated: bool,
438}
439
440#[derive(Serialize, Deserialize, Debug, Clone)]
441pub struct LLMDriftRecordPaginationRequest {
442 pub service_info: ServiceInfo,
443 pub status: Option<Status>,
444 pub pagination: PaginationRequest,
445}
446
447#[pyclass]
448#[derive(Debug, Clone, Serialize, Deserialize, Default)]
449pub struct BinnedMetricStats {
450 #[pyo3(get)]
451 pub avg: f64,
452
453 #[pyo3(get)]
454 pub lower_bound: f64,
455
456 #[pyo3(get)]
457 pub upper_bound: f64,
458}
459
460#[pymethods]
461impl BinnedMetricStats {
462 pub fn __str__(&self) -> String {
463 PyHelperFuncs::__str__(self)
465 }
466}
467
468#[pyclass]
469#[derive(Debug, Clone, Serialize, Deserialize, Default)]
470pub struct BinnedMetric {
471 #[pyo3(get)]
472 pub metric: String,
473
474 #[pyo3(get)]
475 pub created_at: Vec<DateTime<Utc>>,
476
477 #[pyo3(get)]
478 pub stats: Vec<BinnedMetricStats>,
479}
480
481#[pymethods]
482impl BinnedMetric {
483 pub fn __str__(&self) -> String {
484 PyHelperFuncs::__str__(self)
486 }
487}
488
489#[pyclass]
490#[derive(Debug, Clone, Serialize, Deserialize, Default)]
491pub struct BinnedMetrics {
492 #[pyo3(get)]
493 pub metrics: BTreeMap<String, BinnedMetric>,
494}
495
496#[pymethods]
497impl BinnedMetrics {
498 pub fn __str__(&self) -> String {
499 PyHelperFuncs::__str__(self)
501 }
502
503 pub fn __getitem__<'py>(
504 &self,
505 py: Python<'py>,
506 key: &str,
507 ) -> Result<Option<Py<BinnedMetric>>, TypeError> {
508 match self.metrics.get(key) {
509 Some(metric) => {
510 let metric = Py::new(py, metric.clone())?;
511 Ok(Some(metric))
512 }
513 None => Ok(None),
514 }
515 }
516}
517
518impl BinnedMetrics {
519 pub fn from_vec(metrics: Vec<BinnedMetric>) -> Self {
520 let mapped: BTreeMap<String, BinnedMetric> = metrics
521 .into_iter()
522 .map(|metric| (metric.metric.clone(), metric))
523 .collect();
524 BinnedMetrics { metrics: mapped }
525 }
526}