Skip to main content

atomr_persistence_azure/
config.rs

1//! Azure Table Storage connection configuration.
2
3use std::env;
4
5use atomr_persistence::JournalError;
6
7/// Azurite developer account / key defaults.
8pub const AZURITE_ACCOUNT: &str = "devstoreaccount1";
9pub const AZURITE_KEY: &str =
10    "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
11pub const AZURITE_ENDPOINT: &str = "http://127.0.0.1:10002/devstoreaccount1";
12
13#[derive(Debug, Clone)]
14pub struct AzureConfig {
15    pub account: String,
16    pub key: String,
17    pub endpoint: String,
18    pub journal_table: String,
19    pub snapshot_table: String,
20    pub auto_create_tables: bool,
21}
22
23impl AzureConfig {
24    pub fn new(account: impl Into<String>, key: impl Into<String>, endpoint: impl Into<String>) -> Self {
25        Self {
26            account: account.into(),
27            key: key.into(),
28            endpoint: endpoint.into(),
29            journal_table: "EventJournal".into(),
30            snapshot_table: "SnapshotStore".into(),
31            auto_create_tables: true,
32        }
33    }
34
35    /// Point the config at a local Azurite instance.
36    pub fn azurite() -> Self {
37        Self::new(AZURITE_ACCOUNT, AZURITE_KEY, AZURITE_ENDPOINT)
38    }
39
40    /// Connection-string style input, e.g.
41    /// `AccountName=...;AccountKey=...;TableEndpoint=...`.
42    pub fn from_connection_string(cs: &str) -> Result<Self, JournalError> {
43        let mut account = None;
44        let mut key = None;
45        let mut endpoint = None;
46        for part in cs.split(';').filter(|p| !p.is_empty()) {
47            let (k, v) =
48                part.split_once('=').ok_or_else(|| JournalError::backend("malformed connection string"))?;
49            match k.trim() {
50                "AccountName" => account = Some(v.to_string()),
51                "AccountKey" => key = Some(v.to_string()),
52                "TableEndpoint" => endpoint = Some(v.to_string()),
53                _ => {}
54            }
55        }
56        let account = account.ok_or_else(|| JournalError::backend("missing AccountName"))?;
57        let key = key.ok_or_else(|| JournalError::backend("missing AccountKey"))?;
58        let endpoint = endpoint.unwrap_or_else(|| format!("https://{account}.table.core.windows.net"));
59        Ok(Self::new(account, key, endpoint))
60    }
61
62    pub fn from_env() -> Self {
63        if let Ok(cs) = env::var("ATOMR_PERSISTENCE_AZURE_CONNECTION_STRING") {
64            if let Ok(cfg) = Self::from_connection_string(&cs) {
65                return cfg;
66            }
67        }
68        if let Ok(cs) = env::var("ATOMR_IT_AZURE_CONNECTION_STRING") {
69            if let Ok(cfg) = Self::from_connection_string(&cs) {
70                return cfg;
71            }
72        }
73        Self::azurite()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn parses_connection_string() {
83        let cs = "AccountName=acc;AccountKey=key;TableEndpoint=https://acc.table.core.windows.net";
84        let cfg = AzureConfig::from_connection_string(cs).unwrap();
85        assert_eq!(cfg.account, "acc");
86        assert_eq!(cfg.key, "key");
87        assert_eq!(cfg.endpoint, "https://acc.table.core.windows.net");
88    }
89
90    #[test]
91    fn rejects_bad_connection_string() {
92        assert!(AzureConfig::from_connection_string("no-equals-here").is_err());
93    }
94
95    #[test]
96    fn azurite_defaults() {
97        let cfg = AzureConfig::azurite();
98        assert_eq!(cfg.account, AZURITE_ACCOUNT);
99        assert!(cfg.endpoint.starts_with("http://"));
100    }
101}