cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
use super::identifier::QueryIdentifier;
use super::value::Query;

/// Ordered collection of queries. The store hands one back; the
/// use case applies display ordering via [`Queries::sort_by_name`].
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Queries(Vec<Query>);

impl Queries {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn iter(&self) -> impl Iterator<Item = &Query> {
        self.0.iter()
    }

    pub fn get(&self, name: &QueryIdentifier) -> Option<&Query> {
        self.0.iter().find(|q| &q.name == name)
    }

    pub fn sort_by_name(&mut self) {
        self.0.sort_by(|a, b| a.name.cmp(&b.name));
    }
}

impl FromIterator<Query> for Queries {
    fn from_iter<I: IntoIterator<Item = Query>>(iter: I) -> Self {
        Self(iter.into_iter().collect())
    }
}

impl<'a> IntoIterator for &'a Queries {
    type Item = &'a Query;
    type IntoIter = std::slice::Iter<'a, Query>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}

#[cfg(test)]
pub mod strategy {
    use super::super::value::strategy::query;
    use super::Queries;
    use proptest::prelude::*;

    pub fn queries() -> impl Strategy<Value = Queries> {
        proptest::collection::vec(query(), 0..=6).prop_map(|v| v.into_iter().collect())
    }
}

#[cfg(test)]
mod tests {
    use super::super::identifier::QueryIdentifier;
    use super::super::script::QueryScript;
    use super::*;
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn sort_by_name_yields_alphabetical_order(mut qs in strategy::queries()) {
            qs.sort_by_name();
            let names: Vec<&str> = qs.iter().map(|q| q.name.as_str()).collect();
            for w in names.windows(2) {
                prop_assert!(w[0] <= w[1]);
            }
        }
    }

    fn nq(s: &str) -> Query {
        Query::new(QueryIdentifier::new(s).unwrap(), QueryScript::new(""), None)
    }

    #[test]
    fn default_and_new_are_empty() {
        assert!(Queries::new().is_empty());
        assert_eq!(Queries::default().len(), 0);
    }

    #[test]
    fn from_iterator_preserves_order() {
        let qs: Queries = [nq("c"), nq("a"), nq("b")].into_iter().collect();
        let names: Vec<&str> = qs.iter().map(|q| q.name.as_str()).collect();
        assert_eq!(names, vec!["c", "a", "b"]);
        assert_eq!(qs.len(), 3);
        assert!(!qs.is_empty());
    }

    #[test]
    fn sort_by_name_orders_alphabetically() {
        let mut qs: Queries = [nq("charlie"), nq("alpha"), nq("bravo")]
            .into_iter()
            .collect();
        qs.sort_by_name();
        let names: Vec<&str> = qs.iter().map(|q| q.name.as_str()).collect();
        assert_eq!(names, vec!["alpha", "bravo", "charlie"]);
    }

    #[test]
    fn get_returns_the_matching_entry() {
        let qs: Queries = [nq("alpha"), nq("bravo")].into_iter().collect();
        let found = qs.get(&QueryIdentifier::new("bravo").unwrap()).unwrap();
        assert_eq!(found.name.as_str(), "bravo");
    }

    #[test]
    fn get_returns_none_when_absent() {
        let qs: Queries = [nq("alpha")].into_iter().collect();
        assert!(qs.get(&QueryIdentifier::new("missing").unwrap()).is_none());
    }

    #[test]
    fn into_iterator_for_ref_yields_each_query() {
        let qs: Queries = [nq("a"), nq("b")].into_iter().collect();
        let collected: Vec<&str> = (&qs).into_iter().map(|q| q.name.as_str()).collect();
        assert_eq!(collected, vec!["a", "b"]);
    }
}