Skip to content

Commit

Permalink
Not pretty, but got that last selector passing
Browse files Browse the repository at this point in the history
  • Loading branch information
expede committed Mar 25, 2024
1 parent 279f17c commit 6affc01
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 31 deletions.
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)]
1 change: 0 additions & 1 deletion src/delegation/policy/predicate.rs
Expand Up @@ -1132,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> {
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);
}
}
}
}

0 comments on commit 6affc01

Please sign in to comment.