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