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 pub uid: String,
58 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 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>, 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, pub active: bool,
295 pub space: String,
296}
297
298#[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 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 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 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 pub start_time: Option<String>,
690 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}