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 pub uid: String,
60 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 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>, 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, pub active: bool,
297 pub space: String,
298}
299
300#[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 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 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 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}