Metamorfish 0.0.1

Metamorfish is a data mutation library based on CSS-like selectors and mutation actions.
Documentation
use std::cell::RefCell;
use std::rc::Rc;

use crate::selector::Selector;
use protofish::context::{MessageInfo, MessageRef};
use protofish::decode::{MessageValue, Value};
use rpds::HashTrieSet;

pub struct Visitor {
    actions: Vec<Action>,
}

#[derive(Clone)]
struct Action {
    selector: Selector,
    action: Rc<RefCell<dyn FnMut(&mut Value)>>,
}

#[derive(Clone)]
struct VisitorState {
    selectors: Vec<Action>,
}

impl VisitorState {}

impl Visitor {
    pub fn visit(&self, value: Value) -> Value {
        let mut value = value;
        let state = VisitorState { selectors: vec![] };
        self.visit_impl(&mut value, state);

        value
    }

    fn visit_impl(&self, value: &mut Value, state: VisitorState) {
        let selectors = state
            .selectors
            .into_iter()
            .filter_map(|s| {
                let action = s.action;
                s.selector.next_value(&value).map(|next| Action {
                    selector: next,
                    action,
                })
            })
            .chain(self.actions.iter().filter_map(|s| {
                s.selector.start_value(&value).map(|next| Action {
                    selector: next,
                    action: s.action.clone(),
                })
            }));

        let (done, pending): (Vec<_>, Vec<_>) = selectors.partition(|s| s.selector.is_done());
        for d in done {
            log::trace!("Visitor::visit_impl, invoking {:?}", d.selector);
            d.action.borrow_mut()(value);
        }

        let next_state = VisitorState { selectors: pending };

        match value {
            Value::Message(msg) => self.visit_msg(msg, next_state),
            _ => {}
        }
    }

    fn visit_msg(&self, value: &mut Box<MessageValue>, state: VisitorState) {
        log::trace!("Visitor::visit_msg: {:?}", value.msg_ref);
        let msg_ref = value.msg_ref;
        for f in &mut value.fields {
            log::trace!("Visitor::visit_msg, field: {:?}", f.number);
            let selectors = state
                .clone()
                .selectors
                .into_iter()
                .filter_map(|s| {
                    let action = s.action;
                    s.selector.next_field(msg_ref, f).map(|next| Action {
                        selector: next,
                        action,
                    })
                })
                .chain(self.actions.iter().filter_map(|s| {
                    s.selector.start_field(msg_ref, f).map(|next| Action {
                        selector: next,
                        action: s.action.clone(),
                    })
                }));
            let (done, pending): (Vec<_>, Vec<_>) = selectors.partition(|s| s.selector.is_done());
            for d in done {
                log::trace!("Visitor::visit_msg, invoking action on {:?}", f.value);
                d.action.borrow_mut()(&mut f.value);
            }

            self.visit_impl(&mut f.value, VisitorState { selectors: pending })
        }
    }
}

#[cfg(test)]
mod test {
    use test_env_log::test;

    use super::*;
    use crate::selector::{Selector, SelectorItem};
    use protofish::Context;
    use protofish::decode::{Value, FieldValue, MessageValue};

    #[test]
    pub fn visit_msg() {
        let ctx = Context::parse(&[r#"
            syntax = "proto3";
            package Proto;

            message Foo {}
            message Bar {}
        "#])
        .unwrap();

        let foo_ref = ctx.get_message("Proto.Foo").unwrap().self_ref;
        let bar_ref = ctx.get_message("Proto.Bar").unwrap().self_ref;

        let msg = Value::Message(Box::new(MessageValue {
            msg_ref: foo_ref.clone(),
            garbage: None,
            fields: vec![
                FieldValue {
                    number: 1,
                    value: Value::Message(Box::new(MessageValue {
                        garbage: None,
                        msg_ref: bar_ref.clone(),
                        fields: vec![
                            FieldValue {
                                number: 1,
                                value: Value::UInt64(1),
                            },
                            FieldValue {
                                number: 2,
                                value: Value::Bool(false),
                            },
                            FieldValue {
                                number: 3,
                                value: Value::String("original".to_string()),
                            },
                        ],
                    })),
                },
                FieldValue {
                    number: 2,
                    value: Value::Message(Box::new(MessageValue {
                        garbage: None,
                        msg_ref: bar_ref.clone(),
                        fields: vec![
                            FieldValue {
                                number: 1,
                                value: Value::UInt64(1),
                            },
                            FieldValue {
                                number: 2,
                                value: Value::Bool(false),
                            },
                            FieldValue {
                                number: 3,
                                value: Value::String("original".to_string()),
                            },
                        ],
                    })),
                },
            ],
        }));

        let visitor = {
            Visitor {
            actions: vec![
                Action {
                    selector: Selector::new(vec![SelectorItem::Field(bar_ref, 2)]),
                    action: Rc::new(RefCell::new(|value: &mut Value| *value = Value::Bool(true))),
                },
                Action {
                    selector: Selector::new(vec![
                          SelectorItem::Field(foo_ref, 1),
                          SelectorItem::Field(bar_ref, 3)
                    ]),
                    action: Rc::new(RefCell::new(|value: &mut Value| *value = Value::String("changed".to_string()))),
                },
            ],
        }};

        let visited = visitor.visit(msg);

        let foo_msg = match visited {
            Value::Message(msg) => msg,
            _ => panic!("Not a message"),
        };

        assert_eq!(foo_msg.fields[0].number, 1);
        let bar1_msg = match &foo_msg.fields[0].value {
            Value::Message(msg) => msg,
            _ => panic!("Not a message"),
        };

        assert_eq!(bar1_msg.fields[1].number, 2);
        let bool1_value = match &bar1_msg.fields[1].value {
            Value::Bool(b) => b,
            _ => panic!("Not a bool value"),
        };
        assert_eq!(bool1_value, &true);

        assert_eq!(bar1_msg.fields[2].number, 3);
        let string1_value = match &bar1_msg.fields[2].value {
            Value::String(s) => s,
            _ => panic!("Not a string value"),
        };
        assert_eq!(string1_value, "changed");

        assert_eq!(foo_msg.fields[1].number, 2);
        let bar2_msg = match &foo_msg.fields[1].value {
            Value::Message(msg) => msg,
            _ => panic!("Not a message"),
        };

        assert_eq!(bar2_msg.fields[1].number, 2);
        let bool2_value = match &bar2_msg.fields[1].value {
            Value::Bool(b) => b,
            _ => panic!("Not a bool value"),
        };
        assert_eq!(bool2_value, &true);

        assert_eq!(bar2_msg.fields[2].number, 3);
        let string2_value = match &bar2_msg.fields[2].value {
            Value::String(s) => s,
            _ => panic!("Not a string value"),
        };
        assert_eq!(string2_value, "original");

    }
}