odata-params 0.4.0

OData v4 query parameters parsers for handling paginated requests.
Documentation
use bigdecimal::BigDecimal;
use odata_params::filters::{
    CompareOperator, Expr, FunctionsTypeMap, IdentifiersTypeMap, Type, ValidationError, Value,
};
use std::collections::HashMap;
use std::str::FromStr;

#[test]
fn test_literal_values() {
    let type_map = IdentifiersTypeMap::from(HashMap::new());
    let functions_map = FunctionsTypeMap::from(HashMap::new());

    assert_eq!(
        Expr::Value(Value::Bool(true)).validate(&type_map, &functions_map),
        Ok(Type::Boolean)
    );
    assert_eq!(
        Expr::Value(Value::Number(BigDecimal::from_str("42").unwrap()))
            .validate(&type_map, &functions_map),
        Ok(Type::Number)
    );
    assert_eq!(
        Expr::Value(Value::String("hello".to_string())).validate(&type_map, &functions_map),
        Ok(Type::String)
    );
}

#[test]
fn test_identifiers() {
    let mut id_map = HashMap::new();
    id_map.insert("abc".to_string(), Type::Number);
    let type_map = IdentifiersTypeMap::from(id_map);
    let functions_map = FunctionsTypeMap::from(HashMap::new());

    assert_eq!(
        Expr::Identifier("abc".to_string()).validate(&type_map, &functions_map),
        Ok(Type::Number)
    );
    assert_eq!(
        Expr::Identifier("unknown".to_string()).validate(&type_map, &functions_map),
        Err(ValidationError::UndefinedIdentifier {
            name: "unknown".to_string()
        })
    );
}

#[test]
fn test_comparisons() {
    let mut id_map = HashMap::new();
    id_map.insert("abc".to_string(), Type::Number);
    let type_map = IdentifiersTypeMap::from(id_map);
    let functions_map = FunctionsTypeMap::from(HashMap::new());

    let expr = Expr::Compare(
        Box::new(Expr::Identifier("abc".to_string())),
        CompareOperator::Equal,
        Box::new(Expr::Value(Value::Number(
            BigDecimal::from_str("42").unwrap(),
        ))),
    );
    assert_eq!(expr.validate(&type_map, &functions_map), Ok(Type::Boolean));

    let expr = Expr::Compare(
        Box::new(Expr::Identifier("abc".to_string())),
        CompareOperator::Equal,
        Box::new(Expr::Value(Value::String("42".to_string()))),
    );
    assert_eq!(
        expr.validate(&type_map, &functions_map),
        Err(ValidationError::ComparingIncompatibleTypes {
            lhs: Type::Number,
            rhs: Type::String,
        })
    );
}

#[test]
fn test_logical_operations() {
    let mut id_map = HashMap::new();
    id_map.insert("flag".to_string(), Type::Boolean);
    let type_map = IdentifiersTypeMap::from(id_map);
    let functions_map = FunctionsTypeMap::from(HashMap::new());

    let expr = Expr::And(
        Box::new(Expr::Identifier("flag".to_string())),
        Box::new(Expr::Value(Value::Bool(true))),
    );
    assert_eq!(expr.validate(&type_map, &functions_map), Ok(Type::Boolean));

    let expr = Expr::Or(
        Box::new(Expr::Identifier("flag".to_string())),
        Box::new(Expr::Value(Value::Bool(false))),
    );
    assert_eq!(expr.validate(&type_map, &functions_map), Ok(Type::Boolean));

    let expr = Expr::Not(Box::new(Expr::Identifier("flag".to_string())));
    assert_eq!(expr.validate(&type_map, &functions_map), Ok(Type::Boolean));
}

#[test]
fn test_in_operator() {
    let mut id_map = HashMap::new();
    id_map.insert("id".to_string(), Type::Number);
    let type_map = IdentifiersTypeMap::from(id_map);
    let functions_map = FunctionsTypeMap::from(HashMap::new());

    let expr = Expr::In(
        Box::new(Expr::Identifier("id".to_string())),
        vec![
            Expr::Value(Value::Number(BigDecimal::from_str("1").unwrap())),
            Expr::Value(Value::Number(BigDecimal::from_str("2").unwrap())),
        ],
    );
    assert_eq!(expr.validate(&type_map, &functions_map), Ok(Type::Boolean));

    let expr = Expr::In(
        Box::new(Expr::Identifier("id".to_string())),
        vec![
            Expr::Value(Value::Number(BigDecimal::from_str("1").unwrap())),
            Expr::Value(Value::String("2".to_string())),
        ],
    );
    assert_eq!(
        expr.validate(&type_map, &functions_map),
        Err(ValidationError::ComparingIncompatibleTypes {
            lhs: Type::Number,
            rhs: Type::String,
        })
    );
}

#[test]
fn test_function_call() {
    let mut id_map = HashMap::new();
    id_map.insert("arg1".to_string(), Type::Number);
    id_map.insert("arg2".to_string(), Type::String);
    let type_map = IdentifiersTypeMap::from(id_map);

    let mut func_map = HashMap::new();
    func_map.insert(
        "test_func".to_string(),
        (vec![Type::Number, Type::Number], None, Type::Boolean),
    );
    func_map.insert(
        "variadic_func".to_string(),
        (vec![Type::String], Some(Type::String), Type::Boolean),
    );
    let functions_map = FunctionsTypeMap::from(func_map);

    let expr = Expr::Function(
        "test_func".to_string(),
        vec![
            Expr::Identifier("arg1".to_string()),
            Expr::Value(Value::Number(BigDecimal::from_str("42").unwrap())),
        ],
    );
    assert_eq!(expr.validate(&type_map, &functions_map), Ok(Type::Boolean));

    let expr = Expr::Function(
        "test_func".to_string(),
        vec![
            Expr::Identifier("arg1".to_string()),
            Expr::Value(Value::String("42".to_string())),
        ],
    );
    assert_eq!(
        expr.validate(&type_map, &functions_map),
        Err(ValidationError::IncorrectFunctionArgumentType {
            name: "test_func".to_string(),
            position: 2,
            expected: Type::Number,
            given: Type::String,
        })
    );

    let expr = Expr::Function(
        "variadic_func".to_string(),
        vec![
            Expr::Identifier("arg2".to_string()),
            Expr::Value(Value::String("foo".to_string())),
            Expr::Value(Value::String("bar".to_string())),
        ],
    );
    assert_eq!(expr.validate(&type_map, &functions_map), Ok(Type::Boolean));
}