use crate::{Error, Namespace, Set};
use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use serde_json::Value;
fn eq_set_is_empty<T: Eq>(s: &Set<T>) -> bool {
s.as_ref().is_empty()
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Capability {
#[serde(default, skip_serializing_if = "eq_set_is_empty", rename = "def")]
pub default_actions: Set<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "tar")]
pub targeted_actions: BTreeMap<String, Set<String>>,
#[serde(default, skip_serializing_if = "HashMap::is_empty", rename = "ext")]
pub extra_fields: HashMap<String, Value>,
}
impl Capability {
pub fn can(&self, target: &str, action: &str) -> bool {
self.can_default(action)
|| self
.targeted_actions
.get(target)
.map(|actions| actions.as_ref().contains_alike(action))
.unwrap_or(false)
}
pub fn can_default(&self, action: &str) -> bool {
self.default_actions.as_ref().contains_alike(action)
}
pub(crate) fn encode(&self) -> Result<String, Error> {
serde_json::to_vec(self)
.map_err(Error::Ser)
.map(|bytes| base64::encode_config(bytes, base64::URL_SAFE_NO_PAD))
}
pub(crate) fn decode(encoded: &str) -> Result<Self, Error> {
base64::decode_config(encoded, base64::URL_SAFE_NO_PAD)
.map_err(Error::Base64Decode)
.and_then(|bytes| serde_json::from_slice(&bytes).map_err(Error::De))
}
pub(crate) fn to_statement_lines<'l>(
&'l self,
namespace: &'l Namespace,
) -> impl Iterator<Item = String> + 'l {
let default_actions = std::iter::once(self.default_actions.as_ref().join(", "))
.filter(|actions| !actions.is_empty())
.map(move |actions| format!("{}: {} for any.", namespace, actions));
let action_sets: Set<&[String]> =
self.targeted_actions.values().map(AsRef::as_ref).collect();
let targeted_actions = action_sets.into_iter().map(move |action_set| {
let targets = self
.targeted_actions
.iter()
.filter(|(_, actions)| actions.as_ref() == action_set)
.map(|(target, _)| target.as_ref())
.collect::<Vec<&str>>();
format!(
"{}: {} for {}.",
namespace,
action_set.join(", "),
targets.join(", ")
)
});
default_actions.chain(targeted_actions)
}
}
trait Contains<T: ?Sized> {
fn contains_alike(&self, other: &T) -> bool;
}
impl Contains<str> for [String] {
fn contains_alike(&self, other: &str) -> bool {
self.iter().any(|i| i == other)
}
}