rulemorph 0.3.2

YAML-based declarative data transformation engine for CSV/JSON to JSON
Documentation
use super::*;

pub(in crate::transform) fn eval_when(
    mapping: &crate::model::Mapping,
    record: &JsonValue,
    context: Option<&JsonValue>,
    out: &JsonValue,
    mapping_path: &str,
    warnings: &mut Vec<TransformWarning>,
    rule_version: u8,
    limits: EvalLimits,
    ctx: &V2EvalContext<'_>,
) -> bool {
    let expr = match &mapping.when {
        Some(expr) => expr,
        None => return true,
    };

    let when_path = format!("{}.when", mapping_path);
    match eval_when_expr_with_v2_context(
        expr,
        record,
        context,
        out,
        &when_path,
        rule_version,
        limits,
        ctx,
    ) {
        Ok(flag) => flag,
        Err(err) => {
            warnings.push(err.into());
            false
        }
    }
}

#[allow(clippy::too_many_arguments)]
pub(in crate::transform) fn eval_when_traced(
    mapping: &crate::model::Mapping,
    record: &JsonValue,
    context: Option<&JsonValue>,
    out: &JsonValue,
    mapping_path: &str,
    warnings: &mut Vec<TransformWarning>,
    rule_version: u8,
    limits: EvalLimits,
    ctx: &V2EvalContext<'_>,
    collector: &mut TraceCollector,
) -> bool {
    let expr = match &mapping.when {
        Some(expr) => expr,
        None => return true,
    };

    let when_path = format!("{}.when", mapping_path);
    match eval_when_expr_traced_with_v2_context(
        expr,
        record,
        context,
        out,
        &when_path,
        rule_version,
        limits,
        ctx,
        collector,
    ) {
        Ok(flag) => flag,
        Err(err) => {
            warnings.push(err.into());
            false
        }
    }
}

pub(in crate::transform) fn eval_record_when(
    rule: &RuleFile,
    record: &JsonValue,
    context: Option<&JsonValue>,
    warnings: &mut Vec<TransformWarning>,
    limits: EvalLimits,
    ctx: &V2EvalContext<'_>,
) -> bool {
    let expr = match &rule.record_when {
        Some(expr) => expr,
        None => return true,
    };

    let empty_out = JsonValue::Object(Map::new());
    match eval_when_expr_with_v2_context(
        expr,
        record,
        context,
        &empty_out,
        "record_when",
        rule.version,
        limits,
        ctx,
    ) {
        Ok(flag) => flag,
        Err(err) => {
            warnings.push(err.into());
            false
        }
    }
}

pub(in crate::transform) fn eval_record_when_traced(
    rule: &RuleFile,
    record: &JsonValue,
    context: Option<&JsonValue>,
    warnings: &mut Vec<TransformWarning>,
    limits: EvalLimits,
    ctx: &V2EvalContext<'_>,
    collector: &mut TraceCollector,
) -> bool {
    let expr = match &rule.record_when {
        Some(expr) => expr,
        None => return true,
    };

    let empty_out = JsonValue::Object(Map::new());
    match eval_when_expr_traced_with_v2_context(
        expr,
        record,
        context,
        &empty_out,
        "record_when",
        rule.version,
        limits,
        ctx,
        collector,
    ) {
        Ok(flag) => flag,
        Err(err) => {
            warnings.push(err.into());
            false
        }
    }
}

fn eval_bool_expr(
    expr: &Expr,
    record: &JsonValue,
    context: Option<&JsonValue>,
    out: &JsonValue,
    path: &str,
    limits: EvalLimits,
) -> Result<bool, TransformError> {
    let locals = root_eval_locals(limits);
    let value = eval_expr(expr, record, context, out, path, Some(&locals))?;
    let value = match value {
        EvalValue::Missing => JsonValue::Null,
        EvalValue::Value(value) => value,
    };
    match value {
        JsonValue::Bool(flag) => Ok(flag),
        _ => Err(when_type_error(path)),
    }
}

fn eval_bool_expr_traced(
    expr: &Expr,
    record: &JsonValue,
    context: Option<&JsonValue>,
    out: &JsonValue,
    path: &str,
    limits: EvalLimits,
    collector: &mut TraceCollector,
) -> Result<bool, TransformError> {
    let locals = root_eval_locals(limits);
    let value = eval_expr_traced(expr, record, context, out, path, Some(&locals), collector)?;
    let value = match value {
        EvalValue::Missing => JsonValue::Null,
        EvalValue::Value(value) => value,
    };
    match value {
        JsonValue::Bool(flag) => Ok(flag),
        _ => Err(when_type_error(path)),
    }
}

#[allow(clippy::too_many_arguments)]
pub(in crate::transform) fn eval_when_expr_with_v2_context<'a>(
    expr: &Expr,
    record: &'a JsonValue,
    context: Option<&'a JsonValue>,
    out: &'a JsonValue,
    path: &str,
    rule_version: u8,
    limits: EvalLimits,
    ctx: &V2EvalContext<'a>,
) -> Result<bool, TransformError> {
    if rule_version >= 2 {
        if let Some(raw_value) = expr_to_json_for_v2_condition(expr) {
            let condition = parse_v2_condition(&raw_value).map_err(|err| {
                TransformError::new(
                    TransformErrorKind::ExprError,
                    format!("invalid v2 condition: {}", err),
                )
                .with_path(path)
            })?;
            let ctx = ctx.clone().with_limits(limits);
            return eval_v2_condition(&condition, record, context, out, path, &ctx);
        }
    }

    eval_bool_expr(expr, record, context, out, path, limits)
}

#[allow(clippy::too_many_arguments)]
pub(in crate::transform) fn eval_when_expr_traced_with_v2_context<'a>(
    expr: &Expr,
    record: &'a JsonValue,
    context: Option<&'a JsonValue>,
    out: &'a JsonValue,
    path: &str,
    rule_version: u8,
    limits: EvalLimits,
    ctx: &V2EvalContext<'a>,
    collector: &mut TraceCollector,
) -> Result<bool, TransformError> {
    if rule_version >= 2 {
        if let Some(raw_value) = expr_to_json_for_v2_condition(expr) {
            let condition = parse_v2_condition(&raw_value).map_err(|err| {
                TransformError::new(
                    TransformErrorKind::ExprError,
                    format!("invalid v2 condition: {}", err),
                )
                .with_path(path)
            })?;
            let ctx = ctx.clone().with_limits(limits);
            return eval_v2_condition_traced(
                &condition, record, context, out, path, &ctx, collector,
            );
        }
    }

    eval_bool_expr_traced(expr, record, context, out, path, limits, collector)
}

fn root_eval_locals(limits: EvalLimits) -> EvalLocals<'static> {
    EvalLocals {
        item: None,
        acc: None,
        pipe: None,
        locals: None,
        precomputed_op_args: None,
        limits,
    }
}

fn when_type_error(path: &str) -> TransformError {
    TransformError::new(
        TransformErrorKind::ExprError,
        "when/record_when must evaluate to boolean",
    )
    .with_path(path)
}