jslt 0.0.6

Everyones favorite xslt but for json
Documentation
use std::{borrow::Cow, fmt, fmt::Write, ops::Deref};

use jslt_macro::expect_inner;
use pest::iterators::Pairs;
use serde_json::Value;

use crate::{
  context::{Context, builtins::boolean_cast},
  error::{JsltError, Result},
  format,
  parser::{FromPairs, Rule},
  transform::{
    Transform,
    expr::{ExprTransformer, ForTransformer},
  },
};

#[derive(Debug)]
pub struct PairTransformer(ExprTransformer, ExprTransformer);

impl FromPairs for PairTransformer {
  fn from_pairs(pairs: &mut Pairs<Rule>) -> Result<Self> {
    let mut inner = expect_inner!(pairs, Rule::Pair)?;

    let key = ExprTransformer::from_pairs(&mut inner)?;
    let value = ExprTransformer::from_pairs(&mut inner)?;

    Ok(PairTransformer(key, value))
  }
}

impl format::Display for PairTransformer {
  fn fmt(&self, f: &mut format::Formatter<'_>) -> fmt::Result {
    let PairTransformer(key, value) = self;

    format::Display::fmt(key, f)?;
    f.write_str(": ")?;
    format::Display::fmt(value, f)
  }
}

#[derive(Debug, Default)]
pub struct ObjectTransformer {
  inner: Vec<ObjectTransformerInner>,
}

impl FromPairs for ObjectTransformer {
  fn from_pairs(pairs: &mut Pairs<Rule>) -> Result<Self> {
    let pairs = expect_inner!(pairs, Rule::Object)?;

    let mut builder = ObjectTransformer::default();

    for pair in pairs {
      match pair.as_rule() {
        Rule::Pair => {
          builder
            .inner
            .push(ObjectTransformerInner::Pair(PairTransformer::from_pairs(
              &mut Pairs::single(pair),
            )?));
        }
        Rule::ObjectFor => {
          let mut inner_pairs = pair.into_inner();

          builder.inner.push(ObjectTransformerInner::For(
            ObjectForTransformer::from_pairs(&mut inner_pairs)?,
          ));
        }
        Rule::ObjectSpread => {
          let mut pairs = pair.into_inner();

          builder
            .inner
            .push(ObjectTransformerInner::Spread(ExprTransformer::from_pairs(
              &mut pairs,
            )?))
        }
        _ => unimplemented!("for Pair: {pair:#?}"),
      }
    }

    Ok(builder)
  }
}

impl Transform for ObjectTransformer {
  fn transform_value(&self, context: Context<'_>, input: &Value) -> Result<Value> {
    let mut items = serde_json::Map::new();

    for inner in &self.inner {
      match inner {
        ObjectTransformerInner::Pair(PairTransformer(key, value)) => {
          let key = key.transform_value(Context::Borrowed(&context), input)?;

          let key = key.as_str().ok_or_else(|| {
            JsltError::RuntimeError(format!(
              "expression outout for key should be a string but got {key}"
            ))
          })?;

          items.insert(
            key.to_owned(),
            value.transform_value(Context::Borrowed(&context), input)?,
          );
        }
        ObjectTransformerInner::For(ObjectForTransformer {
          source,
          output,
          condition,
        }) => {
          let PairTransformer(key, value) = output.deref();
          let source = source.transform_value(Context::Borrowed(&context), input)?;

          let input_iter: Box<dyn Iterator<Item = Cow<Value>>> = if source.is_object() {
            Box::new(
              source
                .as_object()
                .expect("Should be object")
                .into_iter()
                .map(|(key, value)| Cow::Owned(serde_json::json!({ "key": key, "value": value }))),
            )
          } else {
            Box::new(
              source
                .as_array()
                .expect("Should be array")
                .iter()
                .map(Cow::Borrowed),
            )
          };

          for input in input_iter {
            if let Some(condition) = condition {
              if !boolean_cast(&condition.transform_value(Context::Borrowed(&context), &input)?) {
                continue;
              }
            }

            let key = key.transform_value(Context::Borrowed(&context), &input)?;

            let key = key.as_str().ok_or_else(|| {
              JsltError::RuntimeError(format!(
                "expression outout for key should be a string but got {key}"
              ))
            })?;

            items.insert(
              key.to_owned(),
              value.transform_value(Context::Borrowed(&context), &input)?,
            );
          }
        }
        ObjectTransformerInner::Spread(expr) => {
          let source = input.as_object().ok_or_else(|| {
            JsltError::RuntimeError(format!("object spread expected and object but got {input}"))
          })?;

          for key in source.keys() {
            if items.contains_key(key) {
              continue;
            }

            items.insert(
              key.clone(),
              expr.transform_value(Context::Borrowed(&context), &source[key])?,
            );
          }
        }
      }
    }

    Ok(Value::Object(items))
  }
}

impl format::Display for ObjectTransformer {
  fn fmt(&self, f: &mut format::Formatter<'_>) -> fmt::Result {
    if self.inner.is_empty() {
      f.write_str("{}")
    } else {
      f.write_str("{\n")?;

      let last_item_index = self.inner.len() - 1;
      let mut slot = None;
      let mut state = Default::default();

      let mut writer = format::PadAdapter::wrap(f, &mut slot, &mut state);

      for (index, item) in self.inner.iter().enumerate() {
        format::Display::fmt(item, &mut writer)?;

        if index != last_item_index {
          writer.write_str(",\n")?;
        } else {
          writer.write_str("\n")?;
        }
      }

      f.write_str("}")?;

      Ok(())
    }
  }
}

#[derive(Debug)]
pub enum ObjectTransformerInner {
  Pair(PairTransformer),
  For(ObjectForTransformer),
  Spread(ExprTransformer),
}

impl format::Display for ObjectTransformerInner {
  fn fmt(&self, f: &mut format::Formatter<'_>) -> fmt::Result {
    match self {
      ObjectTransformerInner::Pair(pair) => format::Display::fmt(pair, f),
      ObjectTransformerInner::For(object_for) => format::Display::fmt(object_for, f),
      ObjectTransformerInner::Spread(expr) => {
        f.write_str("*: ")?;

        format::Display::fmt(expr, f)
      }
    }
  }
}

pub type ObjectForTransformer = ForTransformer<PairTransformer>;