use shep_core::permissions::Grants;
use toml_edit::{Array, InlineTable, Item, Table, TableLike, Value};
use super::{ShepToml, ShepTomlError};
const KEYS: [&str; 4] = ["granted", "waiting", "revoked", "reviewed"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrantRecord {
pub granted: Grants,
pub waiting: Grants,
pub revoked: Grants,
pub reviewed: bool,
}
impl GrantRecord {
#[must_use]
pub fn reviewed(granted: Grants) -> Self {
Self {
granted,
waiting: Grants::default(),
revoked: Grants::default(),
reviewed: true,
}
}
#[must_use]
pub fn at_upgrade(granted: Grants) -> Self {
Self {
reviewed: false,
..Self::reviewed(granted)
}
}
}
impl ShepToml {
pub fn granted(&self, name: &str) -> Result<Option<Grants>, ShepTomlError> {
Ok(self.grant_record(name)?.map(|record| record.granted))
}
pub fn grant_record(&self, name: &str) -> Result<Option<GrantRecord>, ShepTomlError> {
let Some(entry) = self
.doc
.get("daemon")
.and_then(Item::as_table_like)
.and_then(|daemon| daemon.get("dog_grants"))
.and_then(Item::as_table_like)
.and_then(|grants| grants.get(name))
else {
return Ok(None);
};
let bad = |reason: String| ShepTomlError::BadGrants {
path: self.path.clone(),
dog: name.to_string(),
reason,
};
let entry = entry
.as_table_like()
.ok_or_else(|| bad("must be a table holding `granted`".to_string()))?;
if let Some((key, _)) = entry.iter().find(|(key, _)| !KEYS.contains(key)) {
return Err(bad(format!("`{key}` is not a key shep reads here")));
}
let list = |key: &str, required: bool| -> Result<Grants, ShepTomlError> {
let Some(item) = entry.get(key) else {
return if required {
Err(bad(format!("`{key}` must be a list of permission names")))
} else {
Ok(Grants::default())
};
};
let names = item
.as_array()
.and_then(|names| names.iter().map(|v| v.as_str()).collect::<Option<Vec<_>>>())
.ok_or_else(|| bad(format!("`{key}` must be a list of permission names")))?;
Grants::parse(names).map_err(|err| bad(format!("`{key}`: {err}")))
};
let reviewed = match entry.get("reviewed") {
None => true,
Some(item) => item
.as_bool()
.ok_or_else(|| bad("`reviewed` must be true or false".to_string()))?,
};
Ok(Some(GrantRecord {
granted: list("granted", true)?,
waiting: list("waiting", false)?,
revoked: list("revoked", false)?,
reviewed,
}))
}
pub fn set_granted(&mut self, name: &str, grants: &Grants) -> Result<(), ShepTomlError> {
self.set_grant_record(name, &GrantRecord::reviewed(grants.clone()))
}
pub fn set_grant_record(
&mut self,
name: &str,
record: &GrantRecord,
) -> Result<(), ShepTomlError> {
let path = self.path.clone();
let item = self
.daemon_table_mut()?
.entry("dog_grants")
.or_insert_with(|| {
let mut table = Table::new();
table.set_implicit(true);
Item::Table(table)
});
let found = item.type_name();
match item {
Item::Table(table) => {
let mut entry = Table::new();
fill(&mut entry, record);
table.insert(name, Item::Table(entry));
}
Item::Value(Value::InlineTable(table)) => {
let mut entry = InlineTable::new();
fill(&mut entry, record);
table.insert(name, Value::InlineTable(entry));
}
_ => {
return Err(ShepTomlError::WrongShape {
path,
key: "dog_grants",
expected: "a table",
found,
});
}
}
Ok(())
}
}
fn fill(entry: &mut dyn TableLike, record: &GrantRecord) {
let list = |grants: &Grants| Item::Value(Value::Array(grants.names().collect::<Array>()));
entry.insert("granted", list(&record.granted));
if record.waiting.names().next().is_some() {
entry.insert("waiting", list(&record.waiting));
}
if record.revoked.names().next().is_some() {
entry.insert("revoked", list(&record.revoked));
}
if !record.reviewed {
entry.insert("reviewed", Item::Value(Value::from(false)));
}
}