Skip to main content

postcrate_core/db/
chaos_configs.rs

1//! Chaos config storage (one row per mailbox).
2
3use serde::{Deserialize, Serialize};
4use sqlx::{Row, SqlitePool};
5
6use crate::error::Result;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[cfg_attr(feature = "specta", derive(specta::Type))]
10#[serde(rename_all = "camelCase")]
11pub struct ChaosConfig {
12    #[serde(default)]
13    pub enabled: bool,
14    #[serde(default)]
15    pub reject_4xx_prob: f32,
16    #[serde(default)]
17    pub reject_5xx_prob: f32,
18    #[serde(default)]
19    pub delay_ms_min: u32,
20    #[serde(default)]
21    pub delay_ms_max: u32,
22    #[serde(default)]
23    pub drop_during_data_prob: f32,
24    #[serde(default)]
25    pub malformed_resp_prob: f32,
26    #[serde(default)]
27    pub seed: Option<u64>,
28}
29
30impl Default for ChaosConfig {
31    fn default() -> Self {
32        Self {
33            enabled: false,
34            reject_4xx_prob: 0.0,
35            reject_5xx_prob: 0.0,
36            delay_ms_min: 0,
37            delay_ms_max: 0,
38            drop_during_data_prob: 0.0,
39            malformed_resp_prob: 0.0,
40            seed: None,
41        }
42    }
43}
44
45pub(crate) async fn get(pool: &SqlitePool, mailbox_id: &str) -> Result<ChaosConfig> {
46    let row = sqlx::query(
47        r"SELECT enabled, reject_4xx_prob, reject_5xx_prob, delay_ms_min, delay_ms_max,
48                 drop_during_data_prob, malformed_resp_prob, seed
49          FROM chaos_configs WHERE mailbox_id = ?",
50    )
51    .bind(mailbox_id)
52    .fetch_optional(pool)
53    .await?;
54
55    let Some(row) = row else {
56        return Ok(ChaosConfig::default());
57    };
58
59    Ok(ChaosConfig {
60        enabled: row.try_get::<i64, _>("enabled").unwrap_or(0) != 0,
61        reject_4xx_prob: row.try_get::<f64, _>("reject_4xx_prob").unwrap_or(0.0) as f32,
62        reject_5xx_prob: row.try_get::<f64, _>("reject_5xx_prob").unwrap_or(0.0) as f32,
63        delay_ms_min: row.try_get::<i64, _>("delay_ms_min").unwrap_or(0) as u32,
64        delay_ms_max: row.try_get::<i64, _>("delay_ms_max").unwrap_or(0) as u32,
65        drop_during_data_prob: row
66            .try_get::<f64, _>("drop_during_data_prob")
67            .unwrap_or(0.0) as f32,
68        malformed_resp_prob: row.try_get::<f64, _>("malformed_resp_prob").unwrap_or(0.0) as f32,
69        seed: row.try_get::<i64, _>("seed").ok().map(|x| x as u64),
70    })
71}
72
73pub(crate) async fn upsert(
74    pool: &SqlitePool,
75    mailbox_id: &str,
76    cfg: &ChaosConfig,
77) -> Result<()> {
78    sqlx::query(
79        r"INSERT INTO chaos_configs
80            (mailbox_id, enabled, reject_4xx_prob, reject_5xx_prob,
81             delay_ms_min, delay_ms_max, drop_during_data_prob,
82             malformed_resp_prob, seed)
83          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
84          ON CONFLICT(mailbox_id) DO UPDATE SET
85            enabled = excluded.enabled,
86            reject_4xx_prob = excluded.reject_4xx_prob,
87            reject_5xx_prob = excluded.reject_5xx_prob,
88            delay_ms_min = excluded.delay_ms_min,
89            delay_ms_max = excluded.delay_ms_max,
90            drop_during_data_prob = excluded.drop_during_data_prob,
91            malformed_resp_prob = excluded.malformed_resp_prob,
92            seed = excluded.seed",
93    )
94    .bind(mailbox_id)
95    .bind(i64::from(cfg.enabled))
96    .bind(f64::from(cfg.reject_4xx_prob))
97    .bind(f64::from(cfg.reject_5xx_prob))
98    .bind(i64::from(cfg.delay_ms_min))
99    .bind(i64::from(cfg.delay_ms_max))
100    .bind(f64::from(cfg.drop_during_data_prob))
101    .bind(f64::from(cfg.malformed_resp_prob))
102    .bind(cfg.seed.map(|s| s as i64))
103    .execute(pool)
104    .await?;
105    Ok(())
106}