fastlib 0.3.7

FAST (FIX Adapted for STreaming protocol) is a space and processing efficient encoding method for message oriented data streams.
Documentation
use rustc_hash::FxHashMap as HashMap;

use crate::Value;
use crate::base::instruction::Instruction;
use crate::base::message::MessageFactory;
use crate::base::types::{Dictionary, Operator, Presence};
use crate::base::value::ValueType;
use crate::decoder::decoder::Decoder;
use crate::encoder::encoder::Encoder;
use crate::model::template::TemplateData;
use crate::model::value::ValueData;
use crate::model::{ModelFactory, ModelVisitor};

mod base;
mod base_serde;
mod model;
mod spec;
mod spec2;

pub struct TestField {
    id: u32,
    name: &'static str,
    presence: Presence,
    operator: Operator,
    value: ValueType,
    instructions: Vec<TestField>,
    has_pmap: bool,
}

pub struct TestTemplate {
    id: u32,
    name: &'static str,
    dictionary: Dictionary,
    instructions: Vec<TestField>,
}

pub fn test_templates(d: &Decoder, tts: &Vec<TestTemplate>) {
    assert_eq!(
        d.definitions.templates.len(),
        tts.len(),
        "templates count mismatch"
    );
    for (t, tt) in d.definitions.templates.iter().zip(tts) {
        assert_eq!(t.id, tt.id, "{} id mismatch", t.name);
        assert_eq!(t.name, t.name, "{} name mismatch", t.name);
        assert_eq!(
            t.dictionary, tt.dictionary,
            "{} dictionary mismatch",
            t.name
        );
        test_instructions(&t.instructions, &tt.instructions, &tt.name);
    }
}

pub fn test_instructions(iss: &Vec<Instruction>, tis: &Vec<TestField>, name: &str) {
    assert_eq!(iss.len(), tis.len(), "{name} fields count mismatch");
    for (t, tt) in iss.iter().zip(tis) {
        assert_eq!(t.id, tt.id, "{} id mismatch", tt.name);
        assert_eq!(t.name, t.name, "{} name mismatch", tt.name);
        assert_eq!(t.presence, tt.presence, "{} presence mismatch", tt.name);
        assert_eq!(t.operator, tt.operator, "{} operator mismatch", tt.name);
        assert_eq!(t.value_type, tt.value, "{} value mismatch", tt.name);
        assert_eq!(
            t.has_pmap.get(),
            tt.has_pmap,
            "{} has_pmap mismatch",
            tt.name
        );
        test_instructions(&t.instructions, &tt.instructions, &tt.name);
    }
}

pub struct LoggingMessageFactory {
    pub calls: Vec<String>,
}

impl LoggingMessageFactory {
    pub fn new() -> Self {
        Self { calls: Vec::new() }
    }
}

impl MessageFactory for LoggingMessageFactory {
    fn start_template(&mut self, id: u32, name: &str) {
        self.calls.push(format!("start_template: {id}:{name}"));
    }

    fn stop_template(&mut self) {
        self.calls.push("stop_template".to_string());
    }

    fn set_value(&mut self, id: u32, name: &str, value: Option<Value>) {
        self.calls
            .push(format!("set_value: {id}:{name} {:?}", value));
    }

    fn start_sequence(&mut self, id: u32, name: &str, length: u32) {
        self.calls
            .push(format!("start_sequence: {id}:{name} {length}"));
    }

    fn start_sequence_item(&mut self, index: u32) {
        self.calls.push(format!("start_sequence_item: {index}"));
    }

    fn stop_sequence_item(&mut self) {
        self.calls.push("stop_sequence_item".to_string());
    }

    fn stop_sequence(&mut self) {
        self.calls.push("stop_sequence".to_string());
    }

    fn start_group(&mut self, name: &str) {
        self.calls.push(format!("start_group: {name}"));
    }

    fn stop_group(&mut self) {
        self.calls.push("stop_group".to_string());
    }

    fn start_template_ref(&mut self, name: &str, dynamic: bool) {
        self.calls
            .push(format!("start_template_ref: {name}:{dynamic}"));
    }

    fn stop_template_ref(&mut self) {
        self.calls.push("stop_template_ref".to_string());
    }
}

pub struct TestMsgValue<'a> {
    pub template: &'a str,
    pub value: Option<Value>,
}

pub struct TestCase<'a> {
    name: &'a str,
    raw: Vec<u8>,
    data: TestMsgValue<'a>,
}

pub struct TestCaseSeq<'a> {
    name: &'a str,
    raw: Vec<Vec<u8>>,
    data: Vec<TestMsgValue<'a>>,
}

fn extract_value(msg: ModelFactory, name: &str, test_name: &str) -> Option<Value> {
    match msg.data.unwrap().value {
        ValueData::Group(g) => match g.get(name).unwrap() {
            ValueData::Value(v) => v.clone(),
            _ => panic!("{} failed (Value expected)", test_name),
        },
        _ => panic!("{} failed (Group expected)", test_name),
    }
}

fn pack_value(templaet_name: &str, name: &str, value: Option<Value>) -> TemplateData {
    TemplateData {
        name: templaet_name.to_string(),
        value: ValueData::Group(HashMap::from_iter([(
            name.to_string(),
            ValueData::Value(value),
        )])),
    }
}

fn do_test(decode: bool, encode: bool, context: bool, definitions: &str, tt: TestCase) {
    let mut d = Decoder::new_from_xml(definitions).unwrap();
    let mut e = Encoder::new_from_xml(definitions).unwrap();
    if decode {
        test_decode(&mut d, &tt);
    }
    if encode {
        test_encode(&mut e, &tt);
    }
    if decode && encode && context {
        assert_eq!(d.context, e.context, "{} context mismatch", tt.name)
    }
}

fn do_test_seq(decode: bool, encode: bool, context: bool, definitions: &str, tt: TestCaseSeq) {
    let mut d = Decoder::new_from_xml(definitions).unwrap();
    let mut e = Encoder::new_from_xml(definitions).unwrap();
    for (i, (raw, data)) in tt.raw.into_iter().zip(tt.data).enumerate() {
        let name = format!("{} #{}", tt.name, i + 1);
        let tt = TestCase {
            name: &name,
            raw,
            data,
        };
        if decode {
            test_decode(&mut d, &tt);
        }
        if encode {
            test_encode(&mut e, &tt);
        }
        if decode && encode && context {
            assert_eq!(d.context, e.context, "{} context mismatch", tt.name)
        }
    }
}

fn test_decode(decoder: &mut Decoder, tt: &TestCase) {
    let mut msg = ModelFactory::new();
    decoder.decode_slice(&tt.raw, &mut msg).unwrap();
    let data = extract_value(msg, "Value", tt.name);
    assert_eq!(data, tt.data.value, "{} decode failed", tt.name);
}

fn test_encode(encoder: &mut Encoder, tt: &TestCase) {
    let mut msg = ModelVisitor::new(pack_value(tt.data.template, "Value", tt.data.value.clone()));
    let raw = encoder.encode_vec(&mut msg).unwrap();
    assert_eq!(raw, tt.raw, "{} encode failed", tt.name);
}