rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use sea_orm::Condition;

/// Represents a node in a WHERE clause tree.
/// Wraps SeaORM's Condition with Django-style operations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WhereNode {
    And(Vec<WhereNode>),
    Or(Vec<WhereNode>),
    Not(Box<WhereNode>),
    Leaf(WhereLeaf),
    Empty,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WhereLeaf {
    pub field: String,
    pub lookup: String,
    pub value: String,
}

impl WhereNode {
    #[must_use]
    pub fn and(nodes: Vec<WhereNode>) -> Self {
        Self::And(nodes)
    }

    #[must_use]
    pub fn or(nodes: Vec<WhereNode>) -> Self {
        Self::Or(nodes)
    }

    #[must_use]
    pub fn not(node: WhereNode) -> Self {
        Self::Not(Box::new(node))
    }

    #[must_use]
    pub fn leaf(
        field: impl Into<String>,
        lookup: impl Into<String>,
        value: impl Into<String>,
    ) -> Self {
        Self::Leaf(WhereLeaf {
            field: field.into(),
            lookup: lookup.into(),
            value: value.into(),
        })
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        matches!(self, Self::Empty)
    }

    #[must_use]
    pub fn from_condition(_condition: Condition) -> Self {
        Self::Empty
    }

    /// Count leaf nodes in this tree.
    #[must_use]
    pub fn leaf_count(&self) -> usize {
        match self {
            Self::And(nodes) | Self::Or(nodes) => nodes.iter().map(WhereNode::leaf_count).sum(),
            Self::Not(node) => node.leaf_count(),
            Self::Leaf(_) => 1,
            Self::Empty => 0,
        }
    }
}

#[cfg(test)]
mod tests {
    use sea_orm::Condition;

    use super::{WhereLeaf, WhereNode};

    #[test]
    fn and_node_stores_children() {
        let node = WhereNode::and(vec![WhereNode::leaf("name", "exact", "Ada")]);

        match node {
            WhereNode::And(nodes) => assert_eq!(nodes.len(), 1),
            other => panic!("expected And node, got {other:?}"),
        }
    }

    #[test]
    fn or_node_stores_children() {
        let node = WhereNode::or(vec![WhereNode::leaf("name", "exact", "Ada")]);

        match node {
            WhereNode::Or(nodes) => assert_eq!(nodes.len(), 1),
            other => panic!("expected Or node, got {other:?}"),
        }
    }

    #[test]
    fn not_node_wraps_child() {
        let node = WhereNode::not(WhereNode::leaf("name", "exact", "Ada"));

        match node {
            WhereNode::Not(child) => assert_eq!(child.leaf_count(), 1),
            other => panic!("expected Not node, got {other:?}"),
        }
    }

    #[test]
    fn leaf_constructor_populates_leaf_fields() {
        let node = WhereNode::leaf("name", "icontains", "Ada");

        match node {
            WhereNode::Leaf(leaf) => {
                assert_eq!(leaf.field, "name");
                assert_eq!(leaf.lookup, "icontains");
                assert_eq!(leaf.value, "Ada");
            }
            other => panic!("expected Leaf node, got {other:?}"),
        }
    }

    #[test]
    fn empty_node_reports_empty() {
        assert!(WhereNode::Empty.is_empty());
    }

    #[test]
    fn non_empty_leaf_reports_not_empty() {
        assert!(!WhereNode::leaf("id", "exact", "1").is_empty());
    }

    #[test]
    fn from_condition_currently_returns_empty_placeholder() {
        let node = WhereNode::from_condition(Condition::all());

        assert!(node.is_empty());
        assert_eq!(node.leaf_count(), 0);
    }

    #[test]
    fn leaf_count_counts_nested_tree_leaves() {
        let node = WhereNode::and(vec![
            WhereNode::leaf("name", "exact", "Ada"),
            WhereNode::or(vec![
                WhereNode::leaf("age", "gte", "18"),
                WhereNode::not(WhereNode::leaf("active", "exact", "false")),
            ]),
        ]);

        assert_eq!(node.leaf_count(), 3);
    }

    #[test]
    fn leaf_count_for_empty_node_is_zero() {
        assert_eq!(WhereNode::Empty.leaf_count(), 0);
    }

    #[test]
    fn where_leaf_stores_all_fields() {
        let leaf = WhereLeaf {
            field: "status".to_string(),
            lookup: "exact".to_string(),
            value: "published".to_string(),
        };

        assert_eq!(leaf.field, "status");
        assert_eq!(leaf.lookup, "exact");
        assert_eq!(leaf.value, "published");
    }
}