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