scouter_types/contracts/
types.rs

1use std::fmt::Display;
2
3use crate::error::{ContractError, TypeError};
4use crate::llm::PaginationRequest;
5use crate::{CustomInterval, Status};
6use crate::{DriftType, ProfileFuncs, 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        // validate time interval
67        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/// Common struct for returning errors from scouter server (axum response)
241#[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        // serialize the struct to a string
464        ProfileFuncs::__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        // serialize the struct to a string
485        ProfileFuncs::__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        // serialize the struct to a string
500        ProfileFuncs::__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}