Skip to main content

scouter_types/contracts/
types.rs

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