use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
collections::{btree_map::Iter as BTreeMapIter, BTreeMap},
fmt::Debug,
iter::FlatMap,
ops::Deref,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Capability {
pub resource: String,
pub ability: String,
pub caveat: Value,
}
impl Capability {
pub fn new(resource: String, ability: String, caveat: Value) -> Self {
Capability {
resource,
ability,
caveat,
}
}
}
impl From<&Capability> for Capability {
fn from(value: &Capability) -> Self {
value.to_owned()
}
}
impl From<(String, String, Value)> for Capability {
fn from(value: (String, String, Value)) -> Self {
Capability::new(value.0, value.1, value.2)
}
}
impl From<(&str, &str, &Value)> for Capability {
fn from(value: (&str, &str, &Value)) -> Self {
Capability::new(value.0.to_owned(), value.1.to_owned(), value.2.to_owned())
}
}
impl From<Capability> for (String, String, Value) {
fn from(value: Capability) -> Self {
(value.resource, value.ability, value.caveat)
}
}
type MapImpl<K, V> = BTreeMap<K, V>;
type MapIter<'a, K, V> = BTreeMapIter<'a, K, V>;
type AbilitiesImpl = MapImpl<String, Vec<Value>>;
type CapabilitiesImpl = MapImpl<String, AbilitiesImpl>;
type AbilitiesMapClosure<'a> = Box<dyn Fn((&'a String, &'a Vec<Value>)) -> Vec<Capability> + 'a>;
type AbilitiesMap<'a> =
FlatMap<MapIter<'a, String, Vec<Value>>, Vec<Capability>, AbilitiesMapClosure<'a>>;
type CapabilitiesIterator<'a> = FlatMap<
MapIter<'a, String, AbilitiesImpl>,
AbilitiesMap<'a>,
fn((&'a String, &'a AbilitiesImpl)) -> AbilitiesMap<'a>,
>;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Capabilities(CapabilitiesImpl);
impl Capabilities {
pub fn iter(&self) -> CapabilitiesIterator {
self.0
.iter()
.flat_map(|(resource, abilities): (&String, &AbilitiesImpl)| {
abilities
.iter()
.flat_map(Box::new(
|(ability, caveats): (&String, &Vec<Value>)| match caveats.len() {
0 => vec![], _ => caveats
.iter()
.map(|caveat| {
Capability::from((
resource.to_owned(),
ability.to_owned(),
caveat.to_owned(),
))
})
.collect(),
},
))
})
}
}
impl Deref for Capabilities {
type Target = CapabilitiesImpl;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl TryFrom<Vec<Capability>> for Capabilities {
type Error = anyhow::Error;
fn try_from(value: Vec<Capability>) -> Result<Self, Self::Error> {
let mut resources: CapabilitiesImpl = BTreeMap::new();
for capability in value.into_iter() {
let (resource_name, ability, caveat) = <(String, String, Value)>::from(capability);
let resource = if let Some(resource) = resources.get_mut(&resource_name) {
resource
} else {
let resource: AbilitiesImpl = BTreeMap::new();
resources.insert(resource_name.clone(), resource);
resources.get_mut(&resource_name).unwrap()
};
if !caveat.is_object() {
return Err(anyhow!("Caveat must be an object: {}", caveat));
}
if let Some(ability_vec) = resource.get_mut(&ability) {
ability_vec.push(caveat);
} else {
resource.insert(ability, vec![caveat]);
}
}
Capabilities::try_from(resources)
}
}
impl TryFrom<CapabilitiesImpl> for Capabilities {
type Error = anyhow::Error;
fn try_from(value: CapabilitiesImpl) -> Result<Self, Self::Error> {
for (resource, abilities) in value.iter() {
if abilities.is_empty() {
return Err(anyhow!("No abilities given for resource: {}", resource));
}
}
Ok(Capabilities(value))
}
}
impl TryFrom<&Value> for Capabilities {
type Error = anyhow::Error;
fn try_from(value: &Value) -> Result<Self, Self::Error> {
let map = value
.as_object()
.ok_or_else(|| anyhow!("Capabilities must be an object."))?;
let mut resources: CapabilitiesImpl = BTreeMap::new();
for (key, value) in map.iter() {
let resource = key.to_owned();
let abilities_object = value
.as_object()
.ok_or_else(|| anyhow!("Abilities must be an object."))?;
let abilities = {
let mut abilities: AbilitiesImpl = BTreeMap::new();
for (key, value) in abilities_object.iter() {
let ability = key.to_owned();
let mut caveats: Vec<Value> = vec![];
let array = value
.as_array()
.ok_or_else(|| anyhow!("Caveats must be defined as an array."))?;
for value in array.iter() {
if !value.is_object() {
return Err(anyhow!("Caveat must be an object: {}", value));
}
caveats.push(value.to_owned());
}
abilities.insert(ability, caveats);
}
abilities
};
resources.insert(resource, abilities);
}
Capabilities::try_from(resources)
}
}