scouter_types/contracts/
types.rs

1use std::fmt::Display;
2
3use crate::error::{ContractError, TypeError};
4use crate::llm::PaginationRequest;
5use crate::sql::{TraceListItem, TraceMetricBucket, TraceSpan};
6use crate::{CustomInterval, DriftProfile, Status, TagRecord, TraceBaggageRecord};
7use crate::{DriftType, PyHelperFuncs, TimeInterval};
8use chrono::{DateTime, Utc};
9use pyo3::prelude::*;
10use scouter_semver::VersionType;
11use serde::Deserialize;
12use serde::Serialize;
13use std::collections::BTreeMap;
14use tracing::error;
15
16#[derive(Serialize, Deserialize, Debug, Clone)]
17pub struct ListProfilesRequest {
18    pub space: String,
19    pub name: String,
20    pub version: String,
21}
22
23#[derive(Serialize, Deserialize, Debug, Clone)]
24pub struct ListedProfile {
25    pub profile: DriftProfile,
26    pub active: bool,
27}
28
29#[pyclass]
30#[derive(Serialize, Deserialize, Debug, Clone)]
31pub struct GetProfileRequest {
32    pub name: String,
33    pub space: String,
34    pub version: String,
35    pub drift_type: DriftType,
36}
37
38#[pymethods]
39impl GetProfileRequest {
40    #[new]
41    #[pyo3(signature = (name, space, version, drift_type))]
42    pub fn new(name: String, space: String, version: String, drift_type: DriftType) -> Self {
43        GetProfileRequest {
44            name,
45            space,
46            version,
47            drift_type,
48        }
49    }
50}
51
52#[pyclass]
53#[derive(Serialize, Deserialize, Debug, Clone, Default)]
54pub struct DriftRequest {
55    pub space: String,
56    pub name: String,
57    pub version: String,
58    pub time_interval: TimeInterval,
59    pub max_data_points: i32,
60    pub drift_type: DriftType,
61    pub begin_custom_datetime: Option<DateTime<Utc>>,
62    pub end_custom_datetime: Option<DateTime<Utc>>,
63}
64
65#[pymethods]
66impl DriftRequest {
67    #[new]
68    #[pyo3(signature = (name, space, version, time_interval, max_data_points, drift_type, begin_datetime=None, end_datetime=None))]
69    #[allow(clippy::too_many_arguments)]
70    pub fn new(
71        name: String,
72        space: String,
73        version: String,
74        time_interval: TimeInterval,
75        max_data_points: i32,
76        drift_type: DriftType,
77        begin_datetime: Option<DateTime<Utc>>,
78        end_datetime: Option<DateTime<Utc>>,
79    ) -> Result<Self, ContractError> {
80        // validate time interval
81        let custom_interval = match (begin_datetime, end_datetime) {
82            (Some(begin), Some(end)) => Some(CustomInterval::new(begin, end)?),
83            _ => None,
84        };
85
86        Ok(DriftRequest {
87            name,
88            space,
89            version,
90            time_interval,
91            max_data_points,
92            drift_type,
93            begin_custom_datetime: custom_interval.as_ref().map(|interval| interval.start),
94            end_custom_datetime: custom_interval.as_ref().map(|interval| interval.end),
95        })
96    }
97}
98
99impl DriftRequest {
100    pub fn has_custom_interval(&self) -> bool {
101        self.begin_custom_datetime.is_some() && self.end_custom_datetime.is_some()
102    }
103
104    pub fn to_custom_interval(&self) -> Option<CustomInterval> {
105        if self.has_custom_interval() {
106            Some(
107                CustomInterval::new(
108                    self.begin_custom_datetime.unwrap(),
109                    self.end_custom_datetime.unwrap(),
110                )
111                .unwrap(),
112            )
113        } else {
114            None
115        }
116    }
117}
118
119#[derive(Serialize, Deserialize, Debug, Clone)]
120pub struct VersionRequest {
121    pub version: Option<String>,
122    pub version_type: VersionType,
123    pub pre_tag: Option<String>,
124    pub build_tag: Option<String>,
125}
126
127impl Default for VersionRequest {
128    fn default() -> Self {
129        VersionRequest {
130            version: None,
131            version_type: VersionType::Minor,
132            pre_tag: None,
133            build_tag: None,
134        }
135    }
136}
137
138#[pyclass]
139#[derive(Serialize, Deserialize, Debug, Clone)]
140pub struct ProfileRequest {
141    pub space: String,
142    pub drift_type: DriftType,
143    pub profile: String,
144    pub version_request: VersionRequest,
145
146    #[serde(default)]
147    pub active: bool,
148
149    #[serde(default)]
150    pub deactivate_others: bool,
151}
152
153#[pyclass]
154#[derive(Serialize, Deserialize, Debug, Clone)]
155pub struct ProfileStatusRequest {
156    pub name: String,
157    pub space: String,
158    pub version: String,
159    pub active: bool,
160    pub drift_type: Option<DriftType>,
161    pub deactivate_others: bool,
162}
163
164#[pymethods]
165impl ProfileStatusRequest {
166    #[new]
167    #[pyo3(signature = (name, space, version, drift_type=None, active=false, deactivate_others=false))]
168    pub fn new(
169        name: String,
170        space: String,
171        version: String,
172        drift_type: Option<DriftType>,
173        active: bool,
174        deactivate_others: bool,
175    ) -> Self {
176        ProfileStatusRequest {
177            name,
178            space,
179            version,
180            active,
181            drift_type,
182            deactivate_others,
183        }
184    }
185}
186
187#[pyclass]
188#[derive(Serialize, Deserialize, Debug, Clone)]
189pub struct DriftAlertRequest {
190    pub name: String,
191    pub space: String,
192    pub version: String,
193    pub limit_datetime: Option<DateTime<Utc>>,
194    pub active: Option<bool>,
195    pub limit: Option<i32>,
196}
197
198#[pymethods]
199impl DriftAlertRequest {
200    #[new]
201    #[pyo3(signature = (name, space, version, active=false, limit_datetime=None, limit=None))]
202    pub fn new(
203        name: String,
204        space: String,
205        version: String,
206        active: bool,
207        limit_datetime: Option<DateTime<Utc>>,
208        limit: Option<i32>,
209    ) -> Self {
210        DriftAlertRequest {
211            name,
212            space,
213            version,
214            limit_datetime,
215            active: Some(active),
216            limit,
217        }
218    }
219}
220
221#[derive(Serialize, Deserialize, Debug, Clone)]
222pub struct ServiceInfo {
223    pub space: String,
224    pub name: String,
225    pub version: String,
226}
227
228#[derive(Serialize, Deserialize, Debug, Clone)]
229pub struct LLMServiceInfo {
230    pub space: String,
231    pub name: String,
232    pub version: String,
233}
234
235#[derive(Serialize, Deserialize, Debug, Clone)]
236pub struct DriftTaskInfo {
237    pub space: String,
238    pub name: String,
239    pub version: String,
240    pub uid: String,
241    pub drift_type: DriftType,
242}
243
244#[derive(Serialize, Deserialize, Debug, Clone)]
245pub struct ObservabilityMetricRequest {
246    pub name: String,
247    pub space: String,
248    pub version: String,
249    pub time_interval: String,
250    pub max_data_points: i32,
251}
252
253#[derive(Serialize, Deserialize, Debug, Clone)]
254pub struct UpdateAlertStatus {
255    pub id: i32,
256    pub active: bool,
257    pub space: String,
258}
259
260/// Common struct for returning errors from scouter server (axum response)
261#[derive(Serialize, Deserialize, Debug, Clone)]
262pub struct ScouterServerError {
263    pub error: String,
264}
265
266impl ScouterServerError {
267    pub fn permission_denied() -> Self {
268        ScouterServerError {
269            error: "Permission denied".to_string(),
270        }
271    }
272
273    pub fn need_admin_permission() -> Self {
274        error!("User does not have admin permissions");
275        ScouterServerError {
276            error: "Need admin permission".to_string(),
277        }
278    }
279
280    pub fn user_already_exists() -> Self {
281        ScouterServerError {
282            error: "User already exists".to_string(),
283        }
284    }
285
286    pub fn create_user_error<T: Display>(e: T) -> Self {
287        error!("Failed to create user: {}", e);
288        ScouterServerError {
289            error: "Failed to create user".to_string(),
290        }
291    }
292
293    pub fn user_not_found() -> Self {
294        ScouterServerError {
295            error: "User not found".to_string(),
296        }
297    }
298
299    pub fn get_user_error<T: Display>(e: T) -> Self {
300        error!("Failed to get user: {}", e);
301        ScouterServerError {
302            error: "Failed to get user".to_string(),
303        }
304    }
305
306    pub fn list_users_error<T: Display>(e: T) -> Self {
307        error!("Failed to list users: {}", e);
308        ScouterServerError {
309            error: "Failed to list users".to_string(),
310        }
311    }
312
313    pub fn update_user_error<T: Display>(e: T) -> Self {
314        error!("Failed to update user: {}", e);
315        ScouterServerError {
316            error: "Failed to update user".to_string(),
317        }
318    }
319    pub fn delete_user_error<T: Display>(e: T) -> Self {
320        error!("Failed to delete user: {}", e);
321        ScouterServerError {
322            error: "Failed to delete user".to_string(),
323        }
324    }
325
326    pub fn check_last_admin_error<T: Display>(e: T) -> Self {
327        error!("Failed to check admin status: {}", e);
328        ScouterServerError {
329            error: "Failed to check admin status".to_string(),
330        }
331    }
332
333    pub fn cannot_delete_last_admin() -> Self {
334        error!("Cannot delete the last admin user");
335        ScouterServerError {
336            error: "Cannot delete the last admin user".to_string(),
337        }
338    }
339    pub fn username_header_not_found() -> Self {
340        error!("Username header not found");
341        ScouterServerError {
342            error: "Username header not found".to_string(),
343        }
344    }
345
346    pub fn invalid_username_format() -> Self {
347        error!("Invalid username format");
348        ScouterServerError {
349            error: "Invalid username format".to_string(),
350        }
351    }
352
353    pub fn password_header_not_found() -> Self {
354        error!("Password header not found");
355        ScouterServerError {
356            error: "Password header not found".to_string(),
357        }
358    }
359    pub fn invalid_password_format() -> Self {
360        error!("Invalid password format");
361        ScouterServerError {
362            error: "Invalid password format".to_string(),
363        }
364    }
365
366    pub fn user_validation_error() -> Self {
367        error!("User validation failed");
368        ScouterServerError {
369            error: "User validation failed".to_string(),
370        }
371    }
372
373    pub fn failed_token_validation() -> Self {
374        error!("Failed to validate token");
375        ScouterServerError {
376            error: "Failed to validate token".to_string(),
377        }
378    }
379
380    pub fn bearer_token_not_found() -> Self {
381        error!("Bearer token not found");
382        ScouterServerError {
383            error: "Bearer token not found".to_string(),
384        }
385    }
386
387    pub fn refresh_token_error<T: Display>(e: T) -> Self {
388        error!("Failed to refresh token: {}", e);
389        ScouterServerError {
390            error: "Failed to refresh token".to_string(),
391        }
392    }
393
394    pub fn unauthorized<T: Display>(e: T) -> Self {
395        error!("Unauthorized: {}", e);
396        ScouterServerError {
397            error: "Unauthorized".to_string(),
398        }
399    }
400
401    pub fn jwt_decode_error(e: String) -> Self {
402        error!("Failed to decode JWT token: {}", e);
403        ScouterServerError {
404            error: "Failed to decode JWT token".to_string(),
405        }
406    }
407
408    pub fn no_refresh_token() -> Self {
409        error!("No refresh token provided");
410        ScouterServerError {
411            error: "No refresh token provided".to_string(),
412        }
413    }
414
415    pub fn new(error: String) -> Self {
416        ScouterServerError { error }
417    }
418
419    pub fn query_records_error<T: Display>(e: T) -> Self {
420        let msg = format!("Failed to query records: {e}");
421        ScouterServerError { error: msg }
422    }
423
424    pub fn query_alerts_error<T: Display>(e: T) -> Self {
425        let msg = format!("Failed to query alerts: {e}");
426        ScouterServerError { error: msg }
427    }
428
429    pub fn query_profile_error<T: Display>(e: T) -> Self {
430        let msg = format!("Failed to query profile: {e}");
431        ScouterServerError { error: msg }
432    }
433    pub fn query_tags_error<T: Display>(e: T) -> Self {
434        let msg = format!("Failed to query tags: {e}");
435        ScouterServerError { error: msg }
436    }
437
438    pub fn get_baggage_error<T: Display>(e: T) -> Self {
439        let msg = format!("Failed to get trace baggage records: {e}");
440        ScouterServerError { error: msg }
441    }
442
443    pub fn get_paginated_traces_error<T: Display>(e: T) -> Self {
444        let msg = format!("Failed to get paginated traces: {e}");
445        ScouterServerError { error: msg }
446    }
447
448    pub fn get_trace_spans_error<T: Display>(e: T) -> Self {
449        let msg = format!("Failed to get trace spans: {e}");
450        ScouterServerError { error: msg }
451    }
452
453    pub fn get_trace_metrics_error<T: Display>(e: T) -> Self {
454        let msg = format!("Failed to get trace metrics: {e}");
455        ScouterServerError { error: msg }
456    }
457
458    pub fn insert_tags_error<T: Display>(e: T) -> Self {
459        let msg = format!("Failed to insert tags: {e}");
460        ScouterServerError { error: msg }
461    }
462
463    pub fn refresh_trace_summary_error<T: Display>(e: T) -> Self {
464        let msg = format!("Failed to refresh trace summary: {e}");
465        ScouterServerError { error: msg }
466    }
467}
468
469#[derive(Serialize, Deserialize, Debug, Clone)]
470pub struct ScouterResponse {
471    pub status: String,
472    pub message: String,
473}
474
475impl ScouterResponse {
476    pub fn new(status: String, message: String) -> Self {
477        ScouterResponse { status, message }
478    }
479}
480
481#[derive(Serialize, Deserialize, Debug, Clone)]
482pub struct RegisteredProfileResponse {
483    pub space: String,
484    pub name: String,
485    pub version: String,
486    pub status: String,
487    pub active: bool,
488}
489
490#[derive(Serialize, Deserialize, Debug, Clone)]
491pub struct UpdateAlertResponse {
492    pub updated: bool,
493}
494
495#[derive(Serialize, Deserialize, Debug, Clone)]
496pub struct LLMDriftRecordPaginationRequest {
497    pub service_info: ServiceInfo,
498    pub status: Option<Status>,
499    pub pagination: PaginationRequest,
500}
501
502#[pyclass]
503#[derive(Debug, Clone, Serialize, Deserialize, Default)]
504pub struct BinnedMetricStats {
505    #[pyo3(get)]
506    pub avg: f64,
507
508    #[pyo3(get)]
509    pub lower_bound: f64,
510
511    #[pyo3(get)]
512    pub upper_bound: f64,
513}
514
515#[pymethods]
516impl BinnedMetricStats {
517    pub fn __str__(&self) -> String {
518        // serialize the struct to a string
519        PyHelperFuncs::__str__(self)
520    }
521}
522
523#[pyclass]
524#[derive(Debug, Clone, Serialize, Deserialize, Default)]
525pub struct BinnedMetric {
526    #[pyo3(get)]
527    pub metric: String,
528
529    #[pyo3(get)]
530    pub created_at: Vec<DateTime<Utc>>,
531
532    #[pyo3(get)]
533    pub stats: Vec<BinnedMetricStats>,
534}
535
536#[pymethods]
537impl BinnedMetric {
538    pub fn __str__(&self) -> String {
539        // serialize the struct to a string
540        PyHelperFuncs::__str__(self)
541    }
542}
543
544#[pyclass]
545#[derive(Debug, Clone, Serialize, Deserialize, Default)]
546pub struct BinnedMetrics {
547    #[pyo3(get)]
548    pub metrics: BTreeMap<String, BinnedMetric>,
549}
550
551#[pymethods]
552impl BinnedMetrics {
553    pub fn __str__(&self) -> String {
554        // serialize the struct to a string
555        PyHelperFuncs::__str__(self)
556    }
557
558    pub fn __getitem__<'py>(
559        &self,
560        py: Python<'py>,
561        key: &str,
562    ) -> Result<Option<Py<BinnedMetric>>, TypeError> {
563        match self.metrics.get(key) {
564            Some(metric) => {
565                let metric = Py::new(py, metric.clone())?;
566                Ok(Some(metric))
567            }
568            None => Ok(None),
569        }
570    }
571}
572
573impl BinnedMetrics {
574    pub fn from_vec(metrics: Vec<BinnedMetric>) -> Self {
575        let mapped: BTreeMap<String, BinnedMetric> = metrics
576            .into_iter()
577            .map(|metric| (metric.metric.clone(), metric))
578            .collect();
579        BinnedMetrics { metrics: mapped }
580    }
581}
582
583#[derive(Serialize, Deserialize, Debug, Clone)]
584pub struct TagsRequest {
585    pub entity_type: String,
586    pub entity_id: String,
587}
588
589#[derive(Serialize, Deserialize, Debug, Clone)]
590pub struct InsertTagsRequest {
591    pub tags: Vec<TagRecord>,
592}
593
594#[derive(Serialize, Deserialize, Debug, Clone)]
595#[pyclass]
596pub struct TraceRequest {
597    pub trace_id: String,
598}
599
600#[derive(Serialize, Deserialize, Debug, Clone)]
601#[pyclass]
602pub struct TraceMetricsRequest {
603    pub space: Option<String>,
604    pub name: Option<String>,
605    pub version: Option<String>,
606    pub start_time: DateTime<Utc>,
607    pub end_time: DateTime<Utc>,
608    pub bucket_interval: String,
609}
610
611#[pymethods]
612impl TraceMetricsRequest {
613    #[new]
614    #[pyo3(signature = (start_time, end_time, bucket_interval,space=None, name=None, version=None))]
615    pub fn new(
616        start_time: DateTime<Utc>,
617        end_time: DateTime<Utc>,
618        bucket_interval: String,
619        space: Option<String>,
620        name: Option<String>,
621        version: Option<String>,
622    ) -> Self {
623        TraceMetricsRequest {
624            space,
625            name,
626            version,
627            start_time,
628            end_time,
629            bucket_interval,
630        }
631    }
632}
633
634#[derive(Serialize, Deserialize, Debug, Clone)]
635#[pyclass]
636pub struct TracePaginationResponse {
637    #[pyo3(get)]
638    pub items: Vec<TraceListItem>,
639
640    #[pyo3(get)]
641    pub has_next: bool,
642
643    #[pyo3(get)]
644    pub next_cursor: Option<TraceCursor>,
645
646    #[pyo3(get)]
647    pub has_previous: bool,
648
649    #[pyo3(get)]
650    pub previous_cursor: Option<TraceCursor>,
651}
652
653#[derive(Serialize, Deserialize, Debug, Clone)]
654#[pyclass]
655pub struct TraceCursor {
656    #[pyo3(get)]
657    pub created_at: DateTime<Utc>,
658
659    #[pyo3(get)]
660    pub trace_id: String,
661}
662
663#[pymethods]
664impl TraceCursor {
665    #[new]
666    pub fn new(created_at: DateTime<Utc>, trace_id: String) -> Self {
667        TraceCursor {
668            created_at,
669            trace_id,
670        }
671    }
672}
673
674#[pymethods]
675impl TracePaginationResponse {
676    pub fn __str__(&self) -> String {
677        PyHelperFuncs::__str__(self)
678    }
679}
680
681#[derive(Serialize, Deserialize, Debug, Clone)]
682#[pyclass]
683pub struct TraceBaggageResponse {
684    #[pyo3(get)]
685    pub baggage: Vec<TraceBaggageRecord>,
686}
687
688#[pymethods]
689impl TraceBaggageResponse {
690    pub fn __str__(&self) -> String {
691        PyHelperFuncs::__str__(self)
692    }
693}
694
695#[derive(Serialize, Deserialize, Debug, Clone)]
696#[pyclass]
697pub struct TraceSpansResponse {
698    #[pyo3(get)]
699    pub spans: Vec<TraceSpan>,
700}
701
702#[pymethods]
703impl TraceSpansResponse {
704    pub fn __str__(&self) -> String {
705        PyHelperFuncs::__str__(self)
706    }
707}
708
709#[derive(Serialize, Deserialize, Debug, Clone)]
710#[pyclass]
711pub struct TraceMetricsResponse {
712    #[pyo3(get)]
713    pub metrics: Vec<TraceMetricBucket>,
714}
715#[pymethods]
716impl TraceMetricsResponse {
717    pub fn __str__(&self) -> String {
718        PyHelperFuncs::__str__(self)
719    }
720}
721
722#[derive(Serialize, Deserialize, Debug, Clone)]
723#[pyclass]
724pub struct TagsResponse {
725    #[pyo3(get)]
726    pub tags: Vec<TagRecord>,
727}
728
729#[pymethods]
730impl TagsResponse {
731    pub fn __str__(&self) -> String {
732        PyHelperFuncs::__str__(self)
733    }
734}