cco 0.2.0

cascading configuration
Documentation
use super::*;
use crate::err::*;
use crate::eval::*;
use crate::value::*;

#[derive(Debug, Clone)]
pub struct MapperExpr {
    pub source: Index,
    pub key_name: Option<Index>,
    pub value_name: Index,
    pub output: Output,
}

impl Expr for MapperExpr {
    fn resolve(
        &self,
        index: Index,
        evaluation: &mut dyn ExprEvaluator,
    ) -> Result<ValueOrReference, ErrorKind> {
        let mut required = vec![self.value_name, self.source];
        if let Some(key_name) = self.key_name {
            required.push(key_name);
        }

        evaluation.ensure_resolved(required)?;

        let value_name_value = evaluation.get_value(self.value_name)?;
        let value_name = value_name_value
            .as_string()
            .ok_or(ErrorKind::TypeNotAllowed {
                item: index,
                expr: self.value_name,
                expected: ValueKind::String,
                actual: value_name_value.kind(),
            })?;

        let contexts: Vec<Index> = match evaluation.get_value(self.source)? {
            Value::List(items) => items
                .into_iter()
                .map(|value| {
                    let value_expr = evaluation.create(Expression::literal(LiteralExpr { value }));
                    evaluation.create(Expression::map(MapExpr {
                        items: vec![(MapKey::Literal(value_name.to_string()), value_expr)],
                    }))
                })
                .collect(),
            Value::Map(items) => items
                .into_iter()
                .map(|(key, value)| {
                    let value_expr = evaluation.create(Expression::literal(LiteralExpr { value }));

                    let mut items = vec![(MapKey::Literal(value_name.to_string()), value_expr)];

                    if let Some(key_name) = self.key_name {
                        let key_expr = evaluation
                            .create(Expression::literal(LiteralExpr { value: key.into() }));
                        items.push((MapKey::Expression(key_name), key_expr));
                    }

                    evaluation.create(Expression::map(MapExpr { items }))
                })
                .collect(),
            other => {
                let expected = if self.key_name.is_some() {
                    ValueKind::Object
                } else {
                    ValueKind::Array
                };
                return Err(ErrorKind::ForLoopTypeMismatch {
                    index,
                    expected,
                    actual: other.kind(),
                })?;
            }
        };

        match self.output {
            Output::List { expr } => {
                let expressions = contexts
                    .into_iter()
                    .map(|ctx| evaluation.clone_with_context(expr, ctx))
                    .collect();

                let referenced =
                    evaluation.create(Expression::list(ListExpr { items: expressions }));

                Ok(ValueOrReference::Reference(referenced))
            }
            Output::Map {
                key_expr,
                value_expr,
            } => {
                let expressions: Vec<(Index, Index)> = contexts
                    .into_iter()
                    .map(|ctx| {
                        (
                            evaluation.clone_with_context(key_expr, ctx),
                            evaluation.clone_with_context(value_expr, ctx),
                        )
                    })
                    .collect();

                let referenced = evaluation.create(Expression::map(MapExpr::from(expressions)));
                Ok(ValueOrReference::Reference(referenced))
            }
            Output::StringConcat { expr } => {
                let expressions = contexts
                    .into_iter()
                    .map(|ctx| evaluation.clone_with_context(expr, ctx))
                    .collect();

                let referenced = evaluation.create(Expression::concat_string(ConcatStringExpr {
                    parts: expressions,
                }));
                Ok(ValueOrReference::Reference(referenced))
            }
        }
    }

    fn traverse(
        &self,
        _evaluation: &dyn ExprEvaluator,
        expression: Index,
        _name: &Indexer,
    ) -> Result<Option<ValueOrReference>, ErrorKind> {
        Err(ErrorKind::DependenciesNotSatisfied {
            indices: vec![expression],
        })
    }
}

#[derive(Debug, Clone, Copy)]
pub enum Output {
    List { expr: Index },
    Map { key_expr: Index, value_expr: Index },
    StringConcat { expr: Index },
}