-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: Update capabilites in line with Ucan 0.9.0/0.10.0. Fixes #22.
* Represents capabilities as map-of-maps rather than array of tuples. * Renames 'att' to 'cap' (Ucan spec 0.10.0). * Renames 'Capability' to 'CapabilityView'. * Leaves caveat parsing up to consumer.
- Loading branch information
Showing
14 changed files
with
397 additions
and
264 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
use anyhow::anyhow; | ||
use serde::{Deserialize, Serialize}; | ||
use serde_json::Value; | ||
use std::{ | ||
collections::btree_map::Iter as BTreeMapIter, | ||
collections::BTreeMap, | ||
fmt::Debug, | ||
iter::{FlatMap, Map}, | ||
ops::Deref, | ||
}; | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
/// Represents a single, flattened capability containing a resource, ability, and caveats. | ||
pub struct Capability { | ||
pub resource: String, | ||
pub ability: String, | ||
pub caveats: Vec<Value>, | ||
} | ||
|
||
impl Capability { | ||
pub fn new(resource: String, ability: String, caveats: Vec<Value>) -> Self { | ||
Capability { | ||
resource, | ||
ability, | ||
caveats, | ||
} | ||
} | ||
} | ||
|
||
impl From<(String, String, Vec<Value>)> for Capability { | ||
fn from(value: (String, String, Vec<Value>)) -> Self { | ||
Capability::new(value.0, value.1, value.2) | ||
} | ||
} | ||
|
||
impl From<(&str, &str, &Vec<Value>)> for Capability { | ||
fn from(value: (&str, &str, &Vec<Value>)) -> Self { | ||
Capability::new(value.0.to_owned(), value.1.to_owned(), value.2.to_owned()) | ||
} | ||
} | ||
|
||
impl From<Capability> for (String, String, Vec<Value>) { | ||
fn from(value: Capability) -> Self { | ||
(value.resource, value.ability, value.caveats) | ||
} | ||
} | ||
|
||
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>)) -> Capability + 'a>; | ||
type AbilitiesMap<'a> = Map<MapIter<'a, String, Vec<Value>>, 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)] | ||
/// The [Capabilities] struct contains capability data in resource-map to ability-map | ||
/// form. Use `iter()` to deconstruct this map into a sequence of [Capability] datas. | ||
/// | ||
/// ``` | ||
/// use ucan::capability::{Capabilities, Capability}; | ||
/// use serde_json::json; | ||
/// | ||
/// let capabilities = Capabilities::try_from(&json!({ | ||
/// "example://example.com/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr": { | ||
/// "wnfs/append": [{}] | ||
/// }, | ||
/// "mailto:username@example.com": { | ||
/// "msg/receive": [{ "max_count": 5 }], | ||
/// "msg/send": [{}] | ||
/// } | ||
/// })).unwrap(); | ||
/// | ||
/// assert_eq!(capabilities.iter().collect::<Vec<Capability>>(), vec![ | ||
/// Capability::from(("example://example.com/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr", "wnfs/append", &vec![json!({})])), | ||
/// Capability::from(("mailto:username@example.com", "msg/receive", &vec![json!({ "max_count": 5 })])), | ||
/// Capability::from(("mailto:username@example.com", "msg/send", &vec![json!({})])), | ||
/// ]); | ||
/// ``` | ||
pub struct Capabilities(CapabilitiesImpl); | ||
|
||
impl Capabilities { | ||
pub fn iter(&self) -> CapabilitiesIterator { | ||
self.0 | ||
.iter() | ||
.flat_map(|(resource, abilities): (&String, &AbilitiesImpl)| { | ||
abilities | ||
.iter() | ||
.map(Box::new(|(ability, caveats): (&String, &Vec<Value>)| { | ||
Capability::new(resource.to_owned(), ability.to_owned(), caveats.to_owned()) | ||
})) | ||
}) | ||
} | ||
} | ||
|
||
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, caveats) = | ||
<(String, String, Vec<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() | ||
}; | ||
|
||
for value in caveats.iter() { | ||
if !value.is_object() { | ||
return Err(anyhow!("Caveat must be an object: {}", value)); | ||
} | ||
} | ||
resource.insert(ability, caveats); | ||
} | ||
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() { | ||
// 0.10.0: 3.2.6.2 One or more abilities MUST be given for each resource. | ||
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 | ||
}; | ||
|
||
if resources.insert(resource, abilities).is_some() { | ||
// 0.10.0: 3.2.6.1 Resources MUST be unique and given as URIs. | ||
return Err(anyhow!("Capability resource is not unique: {}", key)); | ||
} | ||
} | ||
|
||
Capabilities::try_from(resources) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
pub mod proof; | ||
|
||
mod iterator; | ||
mod data; | ||
mod semantics; | ||
|
||
pub use iterator::*; | ||
pub use data::*; | ||
pub use semantics::*; |
Oops, something went wrong.