rulemorph 0.3.2

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

mod slicing;

pub(in crate::transform::operators) use self::slicing::{
    eval_array_chunk, eval_array_drop, eval_array_slice, eval_array_take,
};

fn flatten_value(
    value: &JsonValue,
    depth: usize,
    out: &mut Vec<JsonValue>,
    limits: EvalLimits,
    path: &str,
    generated_items: &mut usize,
) -> Result<(), TransformError> {
    if depth == 0 {
        push_generated_array_item(out, value.clone(), limits, path, generated_items)?;
        return Ok(());
    }

    if let JsonValue::Array(items) = value {
        for item in items {
            flatten_value(item, depth - 1, out, limits, path, generated_items)?;
        }
    } else {
        push_generated_array_item(out, value.clone(), limits, path, generated_items)?;
    }
    Ok(())
}

pub(in crate::transform::operators) fn eval_array_flatten(
    args: &[Expr],
    injected: Option<&EvalValue>,
    record: &JsonValue,
    context: Option<&JsonValue>,
    out: &JsonValue,
    base_path: &str,
    locals: Option<&EvalLocals<'_>>,
) -> Result<EvalValue, TransformError> {
    let total_len = args_len(args, injected);
    if !(1..=2).contains(&total_len) {
        return Err(TransformError::new(
            TransformErrorKind::ExprError,
            "expr.args must contain one or two items",
        )
        .with_path(format!("{}.args", base_path)));
    }

    let array = eval_array_arg(0, args, injected, record, context, out, base_path, locals)?;
    let depth = if total_len == 2 {
        let depth_path = format!("{}.args[1]", base_path);
        let depth_value =
            match eval_arg_value_at(1, args, injected, record, context, out, base_path, locals)? {
                None => return Ok(EvalValue::Missing),
                Some(value) => value,
            };
        if depth_value.is_null() {
            return Err(TransformError::new(
                TransformErrorKind::ExprError,
                "expr arg must not be null",
            )
            .with_path(depth_path));
        }
        let depth = value_to_i64(
            &depth_value,
            &depth_path,
            "depth must be a non-negative integer",
        )?;
        if depth < 0 {
            return Err(TransformError::new(
                TransformErrorKind::ExprError,
                "depth must be a non-negative integer",
            )
            .with_path(depth_path));
        }
        usize::try_from(depth).map_err(|_| {
            TransformError::new(TransformErrorKind::ExprError, "depth is too large")
                .with_path(depth_path)
        })?
    } else {
        1
    };

    let limits = locals.map(|locals| locals.limits).unwrap_or_default();
    let mut generated_items = 0usize;
    let mut results = Vec::new();
    for item in &array {
        flatten_value(
            item,
            depth,
            &mut results,
            limits,
            base_path,
            &mut generated_items,
        )?;
    }

    Ok(EvalValue::Value(JsonValue::Array(results)))
}