Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test delegation store #12

Merged
merged 4 commits into from Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions proptest-regressions/delegation/policy/selector/select.txt
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 6496f8ae07f0fa0d57c9bc4d581551bc9940c50fe830880006156471a72e806b # shrinks to data = Newtype(null), more = [ArrayIndex(0)]
4 changes: 2 additions & 2 deletions src/crypto/signature/envelope.rs
Expand Up @@ -186,11 +186,11 @@ pub trait Envelope: Sized {

fn cid(&self) -> Result<Cid, libipld_core::error::Error>
where
Self: Encode<Self::Encoder>,
Ipld: Encode<Self::Encoder>,
{
let codec = varsig::header::Header::codec(self.varsig_header()).clone();
let mut ipld_buffer = vec![];
self.encode(codec, &mut ipld_buffer)?;
self.to_ipld_envelope().encode(codec, &mut ipld_buffer)?;

let multihash = Code::Sha2_256.digest(&ipld_buffer);
Ok(Cid::new_v1(
Expand Down
32 changes: 18 additions & 14 deletions src/delegation/agent.rs
Expand Up @@ -20,29 +20,33 @@ use web_time::SystemTime;
/// This is helpful for sessions where more than one delegation will be made.
#[derive(Debug)]
pub struct Agent<
S: Store<DID, V, Enc>,
DID: Did = did::preset::Verifier,
V: varsig::Header<Enc> + Clone = varsig::header::Preset,
Enc: Codec + Into<u64> + TryFrom<u64> = varsig::encoding::Preset,
> {
S: Store<DID, V, C>,
DID: Did + Clone = did::preset::Verifier,
V: varsig::Header<C> + Clone = varsig::header::Preset,
C: Codec + Into<u64> + TryFrom<u64> = varsig::encoding::Preset,
> where
Ipld: Encode<C>,
Payload<DID>: TryFrom<Named<Ipld>>,
Named<Ipld>: From<Payload<DID>>,
{
Comment on lines +27 to +31
Copy link
Member Author

@expede expede Mar 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change in the above file using to_ipld_envelope was way more subtle than it should have been. basically previously the payload types individually implemented libipld::Encode<C>, but that was (evidently) confusing. It took longer than I'd like to admit to find the bug it caused.

/// The [`Did`][Did] of the agent.
pub did: DID,

/// The attached [`deleagtion::Store`][super::store::Store].
pub store: S,

signer: <DID as Did>::Signer,
_marker: PhantomData<(V, Enc)>,
_marker: PhantomData<(V, C)>,
}

impl<
S: Store<DID, V, Enc> + Clone,
S: Store<DID, V, C> + Clone,
DID: Did + Clone,
V: varsig::Header<Enc> + Clone,
Enc: Codec + TryFrom<u64> + Into<u64>,
> Agent<S, DID, V, Enc>
V: varsig::Header<C> + Clone,
C: Codec + TryFrom<u64> + Into<u64>,
> Agent<S, DID, V, C>
where
Ipld: Encode<Enc>,
Ipld: Encode<C>,
Payload<DID>: TryFrom<Named<Ipld>>,
Named<Ipld>: From<Payload<DID>>,
{
Expand All @@ -67,7 +71,7 @@ where
not_before: Option<Timestamp>,
now: SystemTime,
varsig_header: V,
) -> Result<Delegation<DID, V, Enc>, DelegateError<S::DelegationStoreError>> {
) -> Result<Delegation<DID, V, C>, DelegateError<S::DelegationStoreError>> {
let mut salt = self.did.clone().to_string().into_bytes();
let nonce = Nonce::generate_12(&mut salt);

Expand Down Expand Up @@ -121,7 +125,7 @@ where
pub fn receive(
&self,
cid: Cid, // FIXME remove and generate from the capsule header?
delegation: Delegation<DID, V, Enc>,
delegation: Delegation<DID, V, C>,
) -> Result<(), ReceiveError<S::DelegationStoreError, DID>> {
if self.store.get(&cid).is_ok() {
return Ok(());
Expand All @@ -135,7 +139,7 @@ where
.validate_signature()
.map_err(|_| ReceiveError::InvalidSignature(cid))?;

self.store.insert(cid, delegation).map_err(Into::into)
self.store.insert_keyed(cid, delegation).map_err(Into::into)
}
}

Expand Down
33 changes: 14 additions & 19 deletions src/delegation/policy/predicate.rs
Expand Up @@ -79,24 +79,20 @@ impl Predicate {
Predicate::Not(inner) => !inner.run(data)?,
Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?,
Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?,
Predicate::Every(xs, p) => {
xs.get(data)?
.to_vec()
.iter()
.try_fold(true, |acc, each_datum| {
dbg!("every", &p, acc, each_datum);
Ok(acc && p.clone().run(&each_datum.0)?)
})?
}
Predicate::Some(xs, p) => {
xs.get(data)?
.to_vec()
.iter()
.try_fold(false, |acc, each_datum| {
dbg!("some", &p, acc, each_datum);
Ok(acc || p.clone().run(&each_datum.0)?)
})?
}
Predicate::Every(xs, p) => xs
.get(data)?
.to_vec()
.iter()
.try_fold(true, |acc, each_datum| {
Ok(acc && p.clone().run(&each_datum.0)?)
})?,
Predicate::Some(xs, p) => xs
.get(data)?
.to_vec()
.iter()
.try_fold(false, |acc, each_datum| {
Ok(acc || p.clone().run(&each_datum.0)?)
})?,
})
}

Expand Down Expand Up @@ -1136,7 +1132,6 @@ mod tests {

#[test_log::test]
fn test_eq_dot_field_inner_try_null() -> TestResult {
// FIXME double check against jq
let p = Predicate::Equal(Select::from_str(".nope?.not").unwrap(), Ipld::Null.into());

assert!(p.run(&email())?);
Expand Down
12 changes: 12 additions & 0 deletions src/delegation/policy/selector.rs
Expand Up @@ -200,6 +200,18 @@ mod tests {
Ok(())
}

#[test_log::test]
fn test_inner_try_is_null() -> TestResult {
pretty::assert_eq!(
Selector::from_str(".nope?.not"),
Ok(Selector(vec![
Filter::Try(Box::new(Filter::Field("nope".into()))),
Filter::Field("not".into())
]))
);
Ok(())
}

#[test_log::test]
fn test_dot_many_tries_and_dot_field() -> TestResult {
pretty::assert_eq!(
Expand Down
152 changes: 122 additions & 30 deletions src/delegation/policy/selector/select.rs
Expand Up @@ -46,9 +46,9 @@ impl<T> Select<T> {

impl<T: Selectable> Select<T> {
pub fn get(self, ctx: &Ipld) -> Result<T, SelectorError> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Try behaviour in this method could probably be more elegant, but I really don't want to have to write more custom Try (AKA .?) parsing 😅

self.filters
.iter()
.try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| {
let got = self.filters.iter().try_fold(
(ctx.clone(), vec![], false),
|(ipld, mut seen_ops, is_try), op| {
seen_ops.push(op);

match op {
Expand All @@ -57,70 +57,95 @@ impl<T: Selectable> Select<T> {
let ipld: Ipld =
Select::<Ipld>::new(vec![op]).get(ctx).unwrap_or(Ipld::Null);

Ok((ipld, seen_ops))
Ok((ipld, seen_ops.clone(), true))
}
Filter::ArrayIndex(i) => {
let result = {
match ipld {
Ipld::List(xs) => {
if i.abs() as usize > xs.len() {
return Err(SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::IndexOutOfBounds,
return Err((
is_try,
SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::IndexOutOfBounds,
),
));
};

xs.get((xs.len() as i32 + *i) as usize)
.ok_or(SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::IndexOutOfBounds,
.ok_or((
is_try,
SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::IndexOutOfBounds,
),
))
.cloned()
}
// FIXME behaviour on maps? type error
_ => Err(SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::NotAList,
_ => Err((
is_try,
SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::NotAList,
),
)),
}
};

Ok((result?, seen_ops))
Ok((result?, seen_ops.clone(), is_try))
}
Filter::Field(k) => {
let result = match ipld {
Ipld::Map(xs) => xs
.get(k)
.ok_or(SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::KeyNotFound,
.ok_or((
is_try,
SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::KeyNotFound,
),
))
.cloned(),
_ => Err(SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::NotAMap,
_ => Err((
is_try,
SelectorError::from_refs(&seen_ops, SelectorErrorReason::NotAMap),
)),
};

Ok((result?, seen_ops))
Ok((result?, seen_ops.clone(), is_try))
}
Filter::Values => {
let result = match ipld {
Ipld::List(xs) => Ok(Ipld::List(xs)),
Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())),
_ => Err(SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::NotACollection,
_ => Err((
is_try,
SelectorError::from_refs(
&seen_ops,
SelectorErrorReason::NotACollection,
),
)),
};

Ok((result?, seen_ops))
Ok((result?, seen_ops.clone(), is_try))
}
}
})
.and_then(|(ipld, ref path)| {
T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e))
})
},
);

let (ipld, path) = match got {
Ok((ipld, seen_ops, _)) => Ok((ipld, seen_ops)),
Err((is_try, ref e @ SelectorError { ref selector, .. })) => {
if is_try {
Ok((Ipld::Null, selector.0.iter().map(|x| x).collect::<Vec<_>>()))
} else {
Err(e.clone())
}
}
}?;

T::try_select(ipld).map_err(|e| SelectorError::from_refs(&path, e))
}
}

Expand Down Expand Up @@ -166,3 +191,70 @@ impl<T: 'static> Arbitrary for Select<T> {
.boxed()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::ipld;
use pretty_assertions as pretty;
use proptest::prelude::*;
use testresult::TestResult;

mod get {
use super::*;

fn nested_data() -> Ipld {
Ipld::Map(
vec![
("name".to_string(), Ipld::String("Alice".to_string())),
("age".to_string(), Ipld::Integer(42)),
(
"friends".to_string(),
Ipld::List(vec![
Ipld::String("Bob".to_string()),
Ipld::String("Charlie".to_string()),
]),
),
]
.into_iter()
.collect(),
)
}

proptest! {
#[test_log::test]
fn test_identity(data: ipld::Newtype) {
let selector = Select::<ipld::Newtype>::from_str(".")?;
prop_assert_eq!(selector.get(&data.0)?, data);
}

#[test_log::test]
fn test_try_missing_is_null(data: ipld::Newtype) {
let selector = Select::<Ipld>::from_str(".foo?")?;
let cleaned_data = match data.0.clone() {
Ipld::Map(mut m) => {
m.remove("foo").map_or(Ipld::Null, |v| v)
}
ipld => ipld
};
prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null);
}

#[test_log::test]
fn test_try_missing_plus_trailing_is_null(data: ipld::Newtype, more: Vec<Filter>) {
let mut filters = vec![Filter::Try(Box::new(Filter::Field("foo".into())))];
filters.append(&mut more.clone());

let selector: Select<Ipld> = Select::new(filters);

let cleaned_data = match data.0.clone() {
Ipld::Map(mut m) => {
m.remove("foo").map_or(Ipld::Null, |v| v)
}
ipld => ipld
};
prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null);
}
}
}
}