Skip to main content

scouter_sql/sql/
schema.rs

1use crate::sql::error::SqlError;
2use chrono::{DateTime, Utc};
3use scouter_types::psi::DistributionData;
4use scouter_types::DriftType;
5use scouter_types::{get_utc_datetime, BinnedMetric, BinnedMetricStats, RecordType};
6use semver::{BuildMetadata, Prerelease, Version};
7use serde::{Deserialize, Serialize};
8use sqlx::{postgres::PgRow, Error, FromRow, Row};
9use std::collections::BTreeMap;
10use std::collections::HashMap;
11use std::str::FromStr;
12
13#[derive(Serialize, Deserialize, Debug, Clone)]
14pub struct DriftRecord {
15    pub created_at: DateTime<Utc>,
16    pub name: String,
17    pub space: String,
18    pub version: String,
19    pub feature: String,
20    pub value: f64,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct SpcFeatureResult {
25    pub feature: String,
26    pub created_at: Vec<DateTime<Utc>>,
27    pub values: Vec<f64>,
28}
29
30impl<'r> FromRow<'r, PgRow> for SpcFeatureResult {
31    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
32        Ok(SpcFeatureResult {
33            feature: row.try_get("feature")?,
34            created_at: row.try_get("created_at")?,
35            values: row.try_get("values")?,
36        })
37    }
38}
39
40#[derive(Debug)]
41pub struct FeatureDistributionWrapper(pub String, pub DistributionData);
42
43impl<'r> FromRow<'r, PgRow> for FeatureDistributionWrapper {
44    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
45        let feature: String = row.try_get("feature")?;
46        let sample_size: i64 = row.try_get("sample_size")?;
47        let bins_json: serde_json::Value = row.try_get("bins")?;
48        let bins: BTreeMap<i32, f64> =
49            serde_json::from_value(bins_json).map_err(|e| Error::Decode(e.into()))?;
50
51        Ok(FeatureDistributionWrapper(
52            feature,
53            DistributionData {
54                sample_size: sample_size as u64,
55                bins,
56            },
57        ))
58    }
59}
60
61pub struct BinnedMetricWrapper(pub BinnedMetric);
62
63impl<'r> FromRow<'r, PgRow> for BinnedMetricWrapper {
64    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
65        let stats_json: Vec<serde_json::Value> = row.try_get("stats")?;
66
67        let stats: Vec<BinnedMetricStats> = stats_json
68            .into_iter()
69            .map(|value| serde_json::from_value(value).unwrap_or_default())
70            .collect();
71
72        Ok(BinnedMetricWrapper(BinnedMetric {
73            metric: row.try_get("metric")?,
74            created_at: row.try_get("created_at")?,
75            stats,
76        }))
77    }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct TaskRequest {
82    pub entity_id: i32,
83    pub profile: String,
84    pub drift_type: DriftType,
85    pub previous_run: DateTime<Utc>,
86    pub schedule: String,
87    pub uid: String,
88}
89
90impl<'r> FromRow<'r, PgRow> for TaskRequest {
91    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
92        let profile: serde_json::Value = row.try_get("profile")?;
93        let drift_type: String = row.try_get("drift_type")?;
94
95        Ok(TaskRequest {
96            entity_id: row.try_get("entity_id")?,
97            profile: profile.to_string(),
98            drift_type: DriftType::from_str(&drift_type).map_err(|e| Error::Decode(e.into()))?,
99            previous_run: row.try_get("previous_run")?,
100            schedule: row.try_get("schedule")?,
101            uid: row.try_get("uid")?,
102        })
103    }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ObservabilityResult {
108    pub route_name: String,
109    pub created_at: Vec<DateTime<Utc>>,
110    pub p5: Vec<f64>,
111    pub p25: Vec<f64>,
112    pub p50: Vec<f64>,
113    pub p95: Vec<f64>,
114    pub p99: Vec<f64>,
115    pub total_request_count: Vec<i64>,
116    pub total_error_count: Vec<i64>,
117    pub error_latency: Vec<f64>,
118    pub status_counts: Vec<HashMap<String, i64>>,
119}
120
121impl<'r> FromRow<'r, PgRow> for ObservabilityResult {
122    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
123        // decode status counts to vec of jsonb
124        let status_counts: Vec<serde_json::Value> = row.try_get("status_counts")?;
125
126        // convert vec of jsonb to vec of hashmaps
127        let status_counts: Vec<HashMap<String, i64>> = status_counts
128            .into_iter()
129            .map(|value| serde_json::from_value(value).unwrap_or_default())
130            .collect();
131
132        Ok(ObservabilityResult {
133            route_name: row.try_get("route_name")?,
134            created_at: row.try_get("created_at")?,
135            p5: row.try_get("p5")?,
136            p25: row.try_get("p25")?,
137            p50: row.try_get("p50")?,
138            p95: row.try_get("p95")?,
139            p99: row.try_get("p99")?,
140            total_request_count: row.try_get("total_request_count")?,
141            total_error_count: row.try_get("total_error_count")?,
142            error_latency: row.try_get("error_latency")?,
143            status_counts,
144        })
145    }
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct BinProportion {
150    pub bin_id: usize,
151    pub proportion: f64,
152}
153
154#[derive(Debug, Clone, FromRow)]
155pub struct Entity {
156    pub entity_id: i32,
157    pub entity_uid: String,
158    pub begin_timestamp: DateTime<Utc>,
159    pub end_timestamp: DateTime<Utc>,
160}
161
162impl Entity {
163    pub fn get_write_path(&self, record_type: &RecordType) -> String {
164        format!("{}/{}", self.entity_uid, record_type)
165    }
166}
167
168#[derive(Debug, Serialize, Deserialize, Clone)]
169pub struct User {
170    pub id: Option<i32>,
171    pub created_at: DateTime<Utc>,
172    pub active: bool,
173    pub username: String,
174    pub password_hash: String,
175    pub hashed_recovery_codes: Vec<String>,
176    pub permissions: Vec<String>,
177    pub group_permissions: Vec<String>,
178    pub role: String,
179    pub favorite_spaces: Vec<String>,
180    pub refresh_token: Option<String>,
181    pub email: String,
182    pub updated_at: DateTime<Utc>,
183}
184
185impl User {
186    #[allow(clippy::too_many_arguments)]
187    pub fn new(
188        username: String,
189        password_hash: String,
190        email: String,
191        hashed_recovery_codes: Vec<String>,
192        permissions: Option<Vec<String>>,
193        group_permissions: Option<Vec<String>>,
194        role: Option<String>,
195        favorite_spaces: Option<Vec<String>>,
196    ) -> Self {
197        let created_at = get_utc_datetime();
198
199        User {
200            id: None,
201            created_at,
202            active: true,
203            username,
204            password_hash,
205            hashed_recovery_codes,
206            permissions: permissions.unwrap_or(vec!["read:all".to_string()]),
207            group_permissions: group_permissions.unwrap_or(vec!["user".to_string()]),
208            favorite_spaces: favorite_spaces.unwrap_or_default(),
209            role: role.unwrap_or("user".to_string()),
210            refresh_token: None,
211            email,
212            updated_at: created_at,
213        }
214    }
215}
216
217impl FromRow<'_, PgRow> for User {
218    fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
219        let id = row.try_get("id")?;
220        let created_at = row.try_get("created_at")?;
221        let updated_at = row.try_get("updated_at")?;
222        let active = row.try_get("active")?;
223        let username = row.try_get("username")?;
224        let password_hash = row.try_get("password_hash")?;
225        let email = row.try_get("email")?;
226        let role = row.try_get("role")?;
227        let refresh_token = row.try_get("refresh_token")?;
228
229        let group_permissions: Vec<String> =
230            serde_json::from_value(row.try_get("group_permissions")?).unwrap_or_default();
231
232        let permissions: Vec<String> =
233            serde_json::from_value(row.try_get("permissions")?).unwrap_or_default();
234
235        let hashed_recovery_codes: Vec<String> =
236            serde_json::from_value(row.try_get("hashed_recovery_codes")?).unwrap_or_default();
237
238        let favorite_spaces: Vec<String> =
239            serde_json::from_value(row.try_get("favorite_spaces")?).unwrap_or_default();
240
241        Ok(User {
242            id,
243            created_at,
244            updated_at,
245            active,
246            username,
247            password_hash,
248            email,
249            role,
250            refresh_token,
251            hashed_recovery_codes,
252            permissions,
253            group_permissions,
254            favorite_spaces,
255        })
256    }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
260pub struct UpdateAlertResult {
261    pub id: i32,
262    pub active: bool,
263    pub updated_at: DateTime<Utc>,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
267pub struct VersionResult {
268    pub created_at: DateTime<Utc>,
269    pub name: String,
270    pub space: String,
271    pub major: i32,
272    pub minor: i32,
273    pub patch: i32,
274    pub pre_tag: Option<String>,
275    pub build_tag: Option<String>,
276}
277
278impl VersionResult {
279    pub fn to_version(&self) -> Result<Version, SqlError> {
280        let mut version = Version::new(self.major as u64, self.minor as u64, self.patch as u64);
281
282        if let Some(pre_tag) = &self.pre_tag {
283            version.pre = Prerelease::new(pre_tag)?;
284        }
285
286        if let Some(build_tag) = &self.build_tag {
287            version.build = BuildMetadata::new(build_tag)?;
288        }
289
290        Ok(version)
291    }
292}