wp-lang 0.3.0

WPL language crate with AST, parser, evaluator, builtins, and generators.
Documentation
use super::super::prelude::*;
use crate::eval::builtins::json_like::is_json_like_text;
use serde::Deserialize;
use serde_json::{Deserializer, Value as JsonValue};
use std::io::Cursor;
use wp_model_core::model::{DataField, DataType, FNameStr, Value};
use wp_primitives::symbol::ctx_desc;

use crate::eval::runtime::field::FieldEvalUnit;
use crate::eval::value::parse_def::PatternParser;

#[derive(Default)]
pub struct BadJsonP {}

impl PatternParser for BadJsonP {
    fn pattern_parse<'a>(
        &self,
        _e_id: u64,
        _fpu: &FieldEvalUnit,
        _ups_sep: &WplSep,
        data: &mut &str,
        name: FNameStr,
        out: &mut Vec<DataField>,
    ) -> ModalResult<()> {
        if !is_json_like_text(data) {
            return fail.parse_next(data);
        }

        let mut cursor = Cursor::new(data.as_bytes());
        let mut deserializer = Deserializer::from_reader(&mut cursor);
        if JsonValue::deserialize(&mut deserializer).is_ok() {
            return fail
                .context(ctx_desc("bad_json does not match valid json input"))
                .parse_next(data);
        }

        out.push(DataField::new_opt(
            DataType::Chars,
            Some(name),
            Value::Chars((*data).into()),
        ));
        *data = "";
        Ok(())
    }

    fn patten_gen(
        &self,
        _gen: &mut GenChannel,
        _f_conf: &WplField,
        _g_conf: Option<&FieldGenConf>,
    ) -> WplCodeResult<DataField> {
        unimplemented!("bad_json generate")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::WplField;
    use crate::eval::runtime::vm_unit::WplEvaluator;
    use crate::eval::value::test_utils::ParserTUnit;
    use crate::parser::error::WplCodeResult;
    use orion_error::dev::testing::TestAssert;

    const UNKNOWN_JSON_SAMPLE: &str = include_str!("../../../../../tests/unknow.json");

    use crate::parser::error::IntoWplCodeError;
    #[test]
    fn bad_json_parser_outputs_raw_chars() {
        let mut data = UNKNOWN_JSON_SAMPLE;
        let conf = WplField::try_parse("bad_json:raw").assert();
        let out = ParserTUnit::new(BadJsonP::default(), conf)
            .verify_parse_suc(&mut data)
            .assert();
        assert_eq!(out[0], DataField::from_chars("raw", UNKNOWN_JSON_SAMPLE));
    }

    #[test]
    fn evaluator_supports_bad_json_alias() -> WplCodeResult<()> {
        let rule = r#"rule test { (bad_json:raw) }"#;
        let pipe = WplEvaluator::from_code(rule)?;
        let (tdc, rest) = pipe
            .proc(0, UNKNOWN_JSON_SAMPLE, 0)
            .map_err(|e| e.into_wpl_err())?;
        assert_eq!(rest, "");
        assert_eq!(
            tdc.field("raw").map(|f| f.as_field()),
            Some(&DataField::from_chars("raw", UNKNOWN_JSON_SAMPLE))
        );
        Ok(())
    }

    #[test]
    fn bad_json_rejects_valid_json() -> WplCodeResult<()> {
        let rule = r#"rule test { (bad_json:raw) }"#;
        let pipe = WplEvaluator::from_code(rule)?;
        let err = pipe
            .proc(0, r#"{"host":"ok","method":"POST"}"#, 0)
            .expect_err("valid json should not match bad_json");
        let detail = err
            .detail()
            .as_ref()
            .expect("bad_json valid-json rejection should carry detail");
        assert!(detail.contains("bad_json does not match valid json input"));
        Ok(())
    }

    #[test]
    fn bad_json_rejects_plain_text() -> WplCodeResult<()> {
        let rule = r#"rule test { (bad_json:raw) }"#;
        let pipe = WplEvaluator::from_code(rule)?;
        assert!(pipe.proc(0, "plain text log line", 0).is_err());
        Ok(())
    }

    #[test]
    fn json_like_and_bad_json_work_together_end_to_end() -> WplCodeResult<()> {
        let rule = r#"rule test { |json_like| (bad_json:raw) }"#;
        let pipe = WplEvaluator::from_code(rule)?;
        let (tdc, rest) = pipe
            .proc(0, UNKNOWN_JSON_SAMPLE, 0)
            .map_err(|e| e.into_wpl_err())?;
        assert_eq!(rest, "");
        assert_eq!(
            tdc.field("raw").map(|f| f.as_field()),
            Some(&DataField::from_chars("raw", UNKNOWN_JSON_SAMPLE))
        );
        Ok(())
    }

    #[test]
    fn json_like_and_bad_json_reject_valid_json_end_to_end() -> WplCodeResult<()> {
        let rule = r#"rule test { |json_like| (bad_json:raw) }"#;
        let pipe = WplEvaluator::from_code(rule)?;
        assert!(pipe.proc(0, r#"{"host":"ok","method":"POST"}"#, 0).is_err());
        Ok(())
    }
}