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