Skip to main content

qail_core/access/
config.rs

1use std::collections::BTreeMap;
2use std::path::Path;
3
4use super::AccessPolicy;
5use super::error::AccessPolicyLoadError;
6use super::ident::normalize_table_ref;
7use super::model::{AccessDecision, TableAccessPolicy};
8
9impl AccessPolicy {
10    /// Deny-by-default policy set.
11    pub fn new() -> Self {
12        Self::default()
13    }
14
15    /// Allow-by-default policy set for trusted/internal use.
16    pub fn allow_by_default() -> Self {
17        Self {
18            default_decision: AccessDecision::Allow,
19            tables: BTreeMap::new(),
20        }
21    }
22
23    /// Add or replace a table policy.
24    pub fn with_table(mut self, table: impl Into<String>, policy: TableAccessPolicy) -> Self {
25        self.tables
26            .insert(normalize_table_ref(&table.into()), policy);
27        self
28    }
29
30    /// Parse an access policy from TOML.
31    pub fn from_toml_str(input: &str) -> Result<Self, AccessPolicyLoadError> {
32        toml::from_str::<Self>(input)
33            .map(Self::normalize_table_keys)
34            .map_err(AccessPolicyLoadError::Toml)
35    }
36
37    /// Parse an access policy from JSON.
38    pub fn from_json_str(input: &str) -> Result<Self, AccessPolicyLoadError> {
39        serde_json::from_str::<Self>(input)
40            .map(Self::normalize_table_keys)
41            .map_err(AccessPolicyLoadError::Json)
42    }
43
44    /// Load an access policy from a `.toml` or `.json` file.
45    pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, AccessPolicyLoadError> {
46        let path = path.as_ref();
47        let raw = std::fs::read_to_string(path).map_err(AccessPolicyLoadError::Read)?;
48        match path
49            .extension()
50            .and_then(|extension| extension.to_str())
51            .map(str::to_ascii_lowercase)
52            .as_deref()
53        {
54            Some("toml") => Self::from_toml_str(&raw),
55            Some("json") => Self::from_json_str(&raw),
56            other => Err(AccessPolicyLoadError::UnsupportedExtension(
57                other.unwrap_or_default().to_string(),
58            )),
59        }
60    }
61
62    /// Mutably access a table policy, creating an empty policy if needed.
63    pub fn table_mut(&mut self, table: impl Into<String>) -> &mut TableAccessPolicy {
64        self.tables
65            .entry(normalize_table_ref(&table.into()))
66            .or_default()
67    }
68
69    fn normalize_table_keys(mut self) -> Self {
70        self.tables = self
71            .tables
72            .into_iter()
73            .map(|(table, policy)| (normalize_table_ref(&table), policy))
74            .collect();
75        self
76    }
77}
78
79impl Default for AccessPolicy {
80    fn default() -> Self {
81        Self {
82            default_decision: AccessDecision::Deny,
83            tables: BTreeMap::new(),
84        }
85    }
86}