use indexmap::IndexMap;
use vantage_core::{Result, VantageError, error};
use crate::capabilities::{Capability, VistaCapabilities};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Denials {
capabilities: IndexMap<Capability, String>,
columns: IndexMap<String, String>,
}
impl Denials {
pub fn new() -> Self {
Self::default()
}
pub fn deny(&mut self, capability: Capability, rule: impl Into<String>) {
self.capabilities
.entry(capability)
.or_insert_with(|| rule.into());
}
pub fn deny_column(&mut self, column: impl Into<String>, rule: impl Into<String>) {
self.columns
.entry(column.into())
.or_insert_with(|| rule.into());
}
pub fn rule_for(&self, capability: Capability) -> Option<&str> {
self.capabilities.get(&capability).map(String::as_str)
}
pub fn column_rule(&self, column: &str) -> Option<&str> {
self.columns.get(column).map(String::as_str)
}
pub fn columns(&self) -> impl Iterator<Item = (&str, &str)> {
self.columns.iter().map(|(c, r)| (c.as_str(), r.as_str()))
}
pub fn capabilities(&self) -> VistaCapabilities {
self.capabilities.keys().copied().collect()
}
pub fn merge(&mut self, other: &Denials) {
for (capability, rule) in &other.capabilities {
self.deny(*capability, rule.as_str());
}
for (column, rule) in &other.columns {
self.deny_column(column.as_str(), rule.as_str());
}
}
pub fn is_empty(&self) -> bool {
self.capabilities.is_empty() && self.columns.is_empty()
}
}
pub fn forbidden<T>(capability: Capability, rule: &str, table: &str) -> Result<T> {
Err(refusal(
"write forbidden",
[
("forbidden", table),
("rule", rule),
("capability", capability.as_str()),
],
))
}
pub fn forbidden_column<T>(column: &str, rule: &str, table: &str) -> Result<T> {
Err(refusal(
"column is read-only",
[("forbidden", table), ("rule", rule), ("column", column)],
))
}
fn refusal(message: &str, context: [(&str, &str); 3]) -> VantageError {
let mut e = error!(message).mark_forbidden();
for (key, value) in context {
e.context.insert(key.to_string(), value.to_string());
}
e
}