grpctestify 1.8.6

gRPC testing utility written in Rust
Documentation
use apif_source_row::SourceRow;
use serde::Deserialize;
use std::collections::HashSet;

#[derive(Debug, Clone, Deserialize)]
pub struct FilterCondition {
    pub field: String,
    #[serde(default)]
    pub equals: Option<String>,
    #[serde(default)]
    pub contains: Option<String>,
    #[serde(default)]
    pub gte: Option<String>,
    #[serde(default)]
    pub lt: Option<String>,
    #[serde(default, rename = "in")]
    pub in_values: Option<Vec<String>>,
    #[serde(skip)]
    in_set: Option<HashSet<String>>,
}

impl FilterCondition {
    pub fn matches(&self, row: &SourceRow) -> bool {
        let Some(actual) = row.get(&self.field) else {
            return false;
        };

        if let Some(expected) = &self.equals
            && actual != expected
        {
            return false;
        }

        if let Some(needle) = &self.contains
            && !actual.contains(needle)
        {
            return false;
        }

        if let Some(min) = &self.gte
            && actual < min.as_str()
        {
            return false;
        }

        if let Some(max) = &self.lt
            && actual >= max.as_str()
        {
            return false;
        }

        if let Some(values) = &self.in_values {
            let found = if let Some(set) = &self.in_set {
                set.contains(actual)
            } else {
                values.iter().any(|v| v == actual)
            };
            if !found {
                return false;
            }
        }

        true
    }

    pub fn optimize(&mut self) {
        if let Some(values) = &self.in_values {
            self.in_set = Some(values.iter().cloned().collect());
        }
    }
}

pub fn matches_all(row: &SourceRow, conditions: &[FilterCondition]) -> bool {
    conditions.iter().all(|c| c.matches(row))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn row() -> SourceRow {
        SourceRow::from_pairs(vec![
            ("status".into(), "active".into()),
            ("region_id".into(), "R01".into()),
            ("created_at".into(), "2024-02-15".into()),
            ("name".into(), "PVZ Alpha".into()),
        ])
    }

    #[test]
    fn equals_match() {
        let cond = FilterCondition {
            field: "status".into(),
            equals: Some("active".into()),
            contains: None,
            gte: None,
            lt: None,
            in_values: None,
            in_set: None,
        };
        assert!(cond.matches(&row()));
    }

    #[test]
    fn in_match() {
        let cond = FilterCondition {
            field: "status".into(),
            equals: None,
            contains: None,
            gte: None,
            lt: None,
            in_values: Some(vec!["inactive".into(), "active".into()]),
            in_set: None,
        };
        assert!(cond.matches(&row()));
    }

    #[test]
    fn contains_match() {
        let cond = FilterCondition {
            field: "name".into(),
            equals: None,
            contains: Some("Alpha".into()),
            gte: None,
            lt: None,
            in_values: None,
            in_set: None,
        };
        assert!(cond.matches(&row()));
    }

    #[test]
    fn range_match() {
        let cond = FilterCondition {
            field: "created_at".into(),
            equals: None,
            contains: None,
            gte: Some("2024-01-01".into()),
            lt: Some("2025-01-01".into()),
            in_values: None,
            in_set: None,
        };
        assert!(cond.matches(&row()));
    }

    #[test]
    fn matches_all_false_on_any_failure() {
        let conds = vec![
            FilterCondition {
                field: "status".into(),
                equals: Some("active".into()),
                contains: None,
                gte: None,
                lt: None,
                in_values: None,
                in_set: None,
            },
            FilterCondition {
                field: "region_id".into(),
                equals: Some("R02".into()),
                contains: None,
                gte: None,
                lt: None,
                in_values: None,
                in_set: None,
            },
        ];
        assert!(!matches_all(&row(), &conds));
    }
}