Эх сурвалжийг харах

Decoupled btproto::validation tests from the language syntax
by using `Protocol` values instead of parsing text.

Matthew Carr 1 жил өмнө
parent
commit
14e08f367a

+ 2 - 2
crates/btfs/src/lib.rs

@@ -1,7 +1,7 @@
+use btlib::Result;
 use btproto::protocol;
 use btrun::{ActorName, CallMsg};
-use btsector::FileId; 
-use btlib::Result;
+use btsector::FileId;
 use serde::{Deserialize, Serialize};
 
 #[derive(Serialize, Deserialize)]

+ 16 - 2
crates/btproto/src/lib.rs

@@ -19,9 +19,23 @@ macro_rules! unwrap_or_compile_err {
     };
 }
 
-/// Generates types for the parties participating in a messaging protocol.
+/// Generates types for the actors participating in a messaging protocol.
+///
+/// ## Usage Restrictions
+/// A type named `Result` which accepts a single type parameter must be in scope where you call this
+/// macro. This allows you to define the error type used by the generated traits by defining a
+/// type alias:
+/// ```
+/// struct MyError;
+/// type Result<T> = std::result::Result<T, MyError>;
+/// ```
+///
+/// You must also ensure all message types referenced by the protocol are in scope.
+///
+/// ## Grammar
 /// The grammar recognized by this macro is given below in the dialect of Extended Backus-Naur Form
-/// recognized by the `llgen` tool:
+/// recognized by the `llgen` tool. The terminal symbol `Ident` has the same meaning as it does in
+/// the regular Rust syntax.
 ///
 /// ```ebnf
 /// protocol : name_def states_def ( transition ';' )* ;

+ 184 - 187
crates/btproto/src/parsing.rs

@@ -26,6 +26,23 @@ impl Parse for Protocol {
     }
 }
 
+#[cfg(test)]
+impl Protocol {
+    pub(crate) fn new(
+        name_def: NameDef,
+        states_def: StatesDef,
+        transitions: impl IntoIterator<Item = Transition>,
+    ) -> Self {
+        let mut transitions: Punctuated<Transition, Token![;]> = transitions.into_iter().collect();
+        transitions.push_punct(Token![;](Span::call_site()));
+        Self {
+            name_def,
+            states_def,
+            transitions,
+        }
+    }
+}
+
 impl GetSpan for Protocol {
     fn span(&self) -> Span {
         self.name_def
@@ -49,6 +66,19 @@ impl NameDef {
     const NAME_IDENT_ERR: &str = "invalid name declaration identifier";
 }
 
+#[cfg(test)]
+impl NameDef {
+    pub(crate) fn new(name: &str) -> Self {
+        Self {
+            let_token: Token![let](Span::call_site()),
+            name_ident: new_ident("name"),
+            eq_token: Token![=](Span::call_site()),
+            name: new_ident(name),
+            semi_token: Token![;](Span::call_site()),
+        }
+    }
+}
+
 impl Parse for NameDef {
     /// name_def : "let" "name" '=' Ident ';' ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
@@ -83,6 +113,15 @@ impl StatesDef {
     const ARRAY_IDENT_ERR: &str = "invalid states array identifier. Expected 'states'.";
 }
 
+#[cfg(test)]
+impl StatesDef {
+    pub(crate) fn new(state_names: impl IntoIterator<Item = &'static str>) -> Self {
+        Self {
+            states: IdentArray::new(state_names),
+        }
+    }
+}
+
 impl Parse for StatesDef {
     /// states_def : "let" "states" '=' ident_array ';' ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
@@ -117,6 +156,13 @@ impl IdentArray {
     }
 }
 
+#[cfg(test)]
+impl IdentArray {
+    pub(crate) fn new(state_names: impl IntoIterator<Item = &'static str>) -> Self {
+        Self(state_names.into_iter().map(new_ident).collect())
+    }
+}
+
 impl GetSpan for IdentArray {
     fn span(&self) -> Span {
         self.0.span()
@@ -152,6 +198,31 @@ pub(crate) struct Transition {
     pub(crate) out_msgs: DestList,
 }
 
+#[cfg(test)]
+impl Transition {
+    pub(crate) fn new(
+        in_state: State,
+        in_msg: Option<Message>,
+        out_states: impl IntoIterator<Item = State>,
+        out_msgs: impl IntoIterator<Item = Dest>,
+    ) -> Self {
+        let out_msgs = DestList(out_msgs.into_iter().collect());
+        let redirect = if out_msgs.as_ref().is_empty() {
+            None
+        } else {
+            Some(Token![>](Span::call_site()))
+        };
+        Self {
+            in_state,
+            in_msg,
+            arrow: Token![->](Span::call_site()),
+            out_states: StatesList(out_states.into_iter().collect()),
+            redirect,
+            out_msgs,
+        }
+    }
+}
+
 impl Parse for Transition {
     /// transition : state ( '?' message )?  "->" states_list ( '>' dest_list )? ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
@@ -198,6 +269,23 @@ pub(crate) struct State {
     pub(crate) owned_states: IdentArray,
 }
 
+#[cfg(test)]
+impl State {
+    pub(crate) fn new(
+        state_trait: &str,
+        owned_states: impl IntoIterator<Item = &'static str>,
+    ) -> Self {
+        Self {
+            state_trait: new_ident(state_trait),
+            owned_states: IdentArray::new(owned_states),
+        }
+    }
+
+    fn new_empty_owned(state_trait: &str) -> Self {
+        Self::new(state_trait, std::iter::empty())
+    }
+}
+
 impl Parse for State {
     /// state : Ident ident_array? ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
@@ -297,6 +385,13 @@ pub(crate) struct Dest {
     pub(crate) msg: Message,
 }
 
+#[cfg(test)]
+impl Dest {
+    pub(crate) fn new(state: DestinationState, msg: Message) -> Self {
+        Self { state, msg }
+    }
+}
+
 impl Parse for Dest {
     /// dest : dest_state '!' message
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
@@ -387,6 +482,32 @@ impl Message {
     }
 }
 
+#[cfg(test)]
+impl Message {
+    pub(crate) fn new(
+        msg_type: &str,
+        is_reply: bool,
+        owned_states: impl IntoIterator<Item = &'static str>,
+    ) -> Self {
+        let (reply_part, ident_field) = if is_reply {
+            let reply_part = MessageReplyPart {
+                colons: Token![::](Span::call_site()),
+                reply: new_ident(MessageReplyPart::REPLY_IDENT),
+            };
+            let variant = format_ident!("{}{}", msg_type, MessageReplyPart::REPLY_IDENT);
+            (Some(reply_part), Some(variant))
+        } else {
+            (None, None)
+        };
+        Self {
+            msg_type: new_ident(msg_type),
+            reply_part,
+            owned_states: IdentArray::new(owned_states),
+            ident: ident_field,
+        }
+    }
+}
+
 impl Parse for Message {
     /// message : Ident ( "::" "Reply" )? ident_array? ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
@@ -516,33 +637,17 @@ impl<R: GetSpan> LeftJoin<Option<R>> for Span {
     }
 }
 
+/// Returns a new span whose text content is the given string and whose span is `Span::call_site`.
+#[cfg(test)]
+pub(crate) fn new_ident(str: &str) -> Ident {
+    Ident::new(str, Span::call_site())
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use proc_macro2::Span;
-    use std::iter::{self, IntoIterator};
     use syn::parse_str;
 
-    fn ident(str: &str) -> Ident {
-        Ident::new(str, Span::call_site())
-    }
-
-    impl Protocol {
-        fn new(
-            name_def: NameDef,
-            states_def: StatesDef,
-            transitions: impl Iterator<Item = Transition>,
-        ) -> Self {
-            let mut transitions: Punctuated<Transition, Token![;]> = transitions.collect();
-            transitions.push_punct(Token![;](Span::call_site()));
-            Self {
-                name_def,
-                states_def,
-                transitions,
-            }
-        }
-    }
-
     #[test]
     fn protocol_parse_minimal() {
         const EXPECTED_NAME: &str = "Foo";
@@ -560,22 +665,21 @@ let states = [{}];
         );
         let expected = Protocol::new(
             NameDef::new(EXPECTED_NAME),
-            StatesDef::new(EXPECTED_STATES.into_iter()),
+            StatesDef::new(EXPECTED_STATES),
             [
                 Transition::new(
-                    State::new(EXPECTED_STATES[0], iter::empty()),
+                    State::new(EXPECTED_STATES[0], []),
                     None,
-                    [State::new(EXPECTED_STATES[1], iter::empty())].into_iter(),
-                    iter::empty(),
+                    [State::new(EXPECTED_STATES[1], [])],
+                    [],
                 ),
                 Transition::new(
-                    State::new(EXPECTED_STATES[1], iter::empty()),
+                    State::new(EXPECTED_STATES[1], []),
                     None,
-                    [State::new(EXPECTED_STATES[2], iter::empty())].into_iter(),
-                    iter::empty(),
+                    [State::new(EXPECTED_STATES[2], [])],
+                    [],
                 ),
-            ]
-            .into_iter(),
+            ],
         );
 
         let actual = parse_str::<Protocol>(&input).unwrap();
@@ -583,18 +687,6 @@ let states = [{}];
         assert_eq!(expected, actual);
     }
 
-    impl NameDef {
-        fn new(name: &str) -> Self {
-            Self {
-                let_token: Token![let](Span::call_site()),
-                name_ident: ident("name"),
-                eq_token: Token![=](Span::call_site()),
-                name: ident(name),
-                semi_token: Token![;](Span::call_site()),
-            }
-        }
-    }
-
     #[test]
     fn name_def_parse() {
         const EXPECTED_NAME: &str = "Foofercorg";
@@ -615,19 +707,11 @@ let states = [{}];
         assert_eq!(NameDef::NAME_IDENT_ERR, err_str);
     }
 
-    impl StatesDef {
-        fn new(state_names: impl Iterator<Item = &'static str>) -> Self {
-            Self {
-                states: IdentArray::new(state_names),
-            }
-        }
-    }
-
     #[test]
     fn states_def_parse() {
         const EXPECTED_STATES: [&str; 2] = ["First", "Second"];
         let input = format!("let states = [{}];", EXPECTED_STATES.join(", "));
-        let expected = StatesDef::new(EXPECTED_STATES.into_iter());
+        let expected = StatesDef::new(EXPECTED_STATES);
 
         let actual = parse_str::<StatesDef>(&input).unwrap();
 
@@ -643,17 +727,11 @@ let states = [{}];
         assert_eq!(StatesDef::ARRAY_IDENT_ERR, err_str);
     }
 
-    impl IdentArray {
-        fn new(state_names: impl Iterator<Item = &'static str>) -> Self {
-            Self(state_names.map(ident).collect())
-        }
-    }
-
     #[test]
     fn ident_array_new() {
         const EXPECTED: [&str; 2] = ["Red", "Green"];
 
-        let actual = IdentArray::new(EXPECTED.into_iter());
+        let actual = IdentArray::new(EXPECTED);
 
         assert_eq!(EXPECTED.len(), actual.0.len());
         assert_eq!(actual.0[0], EXPECTED[0]);
@@ -662,18 +740,18 @@ let states = [{}];
 
     #[test]
     fn ident_array_not_equal() {
-        let expected = IdentArray::new(["Red", "Green"].into_iter());
+        let expected = IdentArray::new(["Red", "Green"]);
 
-        let actual = IdentArray::new(["Blue", "Gold"].into_iter());
+        let actual = IdentArray::new(["Blue", "Gold"]);
 
         assert_ne!(expected, actual);
     }
 
     #[test]
     fn ident_array_not_equal_different_lens() {
-        let expected = IdentArray::new(["Red", "Green"].into_iter());
+        let expected = IdentArray::new(["Red", "Green"]);
 
-        let actual = IdentArray::new(["Red"].into_iter());
+        let actual = IdentArray::new(["Red"]);
 
         assert_ne!(expected, actual);
     }
@@ -682,7 +760,7 @@ let states = [{}];
     fn ident_array_parse() {
         const EXPECTED_STATES: [&str; 2] = ["Sad", "Glad"];
         let input = format!("[{}, {}]", EXPECTED_STATES[0], EXPECTED_STATES[1]);
-        let expected = IdentArray::new(EXPECTED_STATES.into_iter());
+        let expected = IdentArray::new(EXPECTED_STATES);
 
         let actual = parse_str::<IdentArray>(&input).unwrap();
 
@@ -698,40 +776,16 @@ let states = [{}];
         assert_eq!(IdentArray::EMPTY_ERR, err_str);
     }
 
-    impl Transition {
-        fn new(
-            in_state: State,
-            in_msg: Option<Message>,
-            out_states: impl Iterator<Item = State>,
-            out_msgs: impl Iterator<Item = Dest>,
-        ) -> Self {
-            let out_msgs = DestList(out_msgs.collect());
-            let redirect = if out_msgs.as_ref().is_empty() {
-                None
-            } else {
-                Some(Token![>](Span::call_site()))
-            };
-            Self {
-                in_state,
-                in_msg,
-                arrow: Token![->](Span::call_site()),
-                out_states: StatesList(out_states.collect()),
-                redirect,
-                out_msgs,
-            }
-        }
-    }
-
     #[test]
     fn transition_parse_minimal() {
         const EXPECTED_IN_STATE: &str = "Catcher";
         const EXPECTED_OUT_STATE: &str = "End";
         let input = format!("{EXPECTED_IN_STATE} -> {EXPECTED_OUT_STATE}");
         let expected = Transition::new(
-            State::new(EXPECTED_IN_STATE, iter::empty()),
+            State::new(EXPECTED_IN_STATE, []),
             None,
-            [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
-            iter::empty(),
+            [EXPECTED_OUT_STATE].map(State::new_empty_owned),
+            [],
         );
 
         let actual = parse_str::<Transition>(&input).unwrap();
@@ -746,10 +800,10 @@ let states = [{}];
         const EXPECTED_OUT_STATE: &str = "End";
         let input = format!("{EXPECTED_IN_STATE}?{EXPECTED_IN_MSG} -> {EXPECTED_OUT_STATE}");
         let expected = Transition::new(
-            State::new(EXPECTED_IN_STATE, iter::empty()),
-            Some(Message::new(EXPECTED_IN_MSG, false, iter::empty())),
-            [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
-            iter::empty(),
+            State::new(EXPECTED_IN_STATE, []),
+            Some(Message::new(EXPECTED_IN_MSG, false, [])),
+            [EXPECTED_OUT_STATE].map(State::new_empty_owned),
+            [],
         );
 
         let actual = parse_str::<Transition>(&input).unwrap();
@@ -764,10 +818,10 @@ let states = [{}];
         const EXPECTED_OUT_STATE: &str = "End";
         let input = format!("{EXPECTED_IN_STATE}?{EXPECTED_IN_MSG}::Reply -> {EXPECTED_OUT_STATE}");
         let expected = Transition::new(
-            State::new(EXPECTED_IN_STATE, iter::empty()),
-            Some(Message::new(EXPECTED_IN_MSG, true, iter::empty())),
-            [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
-            iter::empty(),
+            State::new(EXPECTED_IN_STATE, []),
+            Some(Message::new(EXPECTED_IN_MSG, true, [])),
+            [EXPECTED_OUT_STATE].map(State::new_empty_owned),
+            [],
         );
 
         let actual = parse_str::<Transition>(&input).unwrap();
@@ -786,14 +840,10 @@ let states = [{}];
             EXPECTED_OWNED_STATES[0], EXPECTED_OWNED_STATES[1]
         );
         let expected = Transition::new(
-            State::new(EXPECTED_IN_STATE, iter::empty()),
-            Some(Message::new(
-                EXPECTED_IN_MSG,
-                true,
-                EXPECTED_OWNED_STATES.into_iter(),
-            )),
-            [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
-            iter::empty(),
+            State::new(EXPECTED_IN_STATE, []),
+            Some(Message::new(EXPECTED_IN_MSG, true, EXPECTED_OWNED_STATES)),
+            [EXPECTED_OUT_STATE].map(State::new_empty_owned),
+            [],
         );
 
         let actual = parse_str::<Transition>(&input).unwrap();
@@ -809,14 +859,13 @@ let states = [{}];
         const EXPECTED_DEST_MSG: &str = "DeathNote";
         let input = format!("{EXPECTED_IN_STATE} -> {EXPECTED_OUT_STATE} >{EXPECTED_DEST_STATE}!{EXPECTED_DEST_MSG}");
         let expected = Transition::new(
-            State::new(EXPECTED_IN_STATE, iter::empty()),
+            State::new(EXPECTED_IN_STATE, []),
             None,
-            [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
+            [EXPECTED_OUT_STATE].map(State::new_empty_owned),
             [Dest::new(
-                DestinationState::Individual(State::new(EXPECTED_DEST_STATE, iter::empty())),
-                Message::new(EXPECTED_DEST_MSG, false, iter::empty()),
-            )]
-            .into_iter(),
+                DestinationState::Individual(State::new(EXPECTED_DEST_STATE, [])),
+                Message::new(EXPECTED_DEST_MSG, false, []),
+            )],
         );
 
         let actual = parse_str::<Transition>(&input).unwrap();
@@ -834,17 +883,15 @@ let states = [{}];
         let dest_list = EXPECTED_DESTS.map(|(l, r)| format!("{l}!{r}")).join(", ");
         let input = format!("{EXPECTED_IN_STATE}?{EXPECTED_IN_MSG} -> {states_list} >{dest_list}");
         let expected = Transition::new(
-            State::new(EXPECTED_IN_STATE, iter::empty()),
-            Some(Message::new(EXPECTED_IN_MSG, false, iter::empty())),
-            EXPECTED_OUT_STATES.map(State::new_empty_owned).into_iter(),
-            EXPECTED_DESTS
-                .map(|(l, r)| {
-                    Dest::new(
-                        DestinationState::Individual(State::new(l, iter::empty())),
-                        Message::new(r, false, iter::empty()),
-                    )
-                })
-                .into_iter(),
+            State::new(EXPECTED_IN_STATE, []),
+            Some(Message::new(EXPECTED_IN_MSG, false, [])),
+            EXPECTED_OUT_STATES.map(State::new_empty_owned),
+            EXPECTED_DESTS.map(|(l, r)| {
+                Dest::new(
+                    DestinationState::Individual(State::new(l, [])),
+                    Message::new(r, false, []),
+                )
+            }),
         );
 
         let actual = parse_str::<Transition>(&input).unwrap();
@@ -852,23 +899,10 @@ let states = [{}];
         assert_eq!(expected, actual);
     }
 
-    impl State {
-        fn new(state_trait: &str, owned_states: impl Iterator<Item = &'static str>) -> Self {
-            Self {
-                state_trait: ident(state_trait),
-                owned_states: IdentArray::new(owned_states),
-            }
-        }
-
-        fn new_empty_owned(state_trait: &str) -> Self {
-            Self::new(state_trait, iter::empty())
-        }
-    }
-
     #[test]
     fn state_parse_no_owned_states() {
         const EXPECTED_TRAIT: &str = "Contained";
-        let expected = State::new(EXPECTED_TRAIT, iter::empty());
+        let expected = State::new(EXPECTED_TRAIT, []);
 
         let actual = parse_str::<State>(EXPECTED_TRAIT).unwrap();
 
@@ -883,27 +917,21 @@ let states = [{}];
             "{EXPECTED_TRAIT}[{}, {}]",
             EXPECTED_OWNED[0], EXPECTED_OWNED[1]
         );
-        let expected = State::new(EXPECTED_TRAIT, EXPECTED_OWNED.into_iter());
+        let expected = State::new(EXPECTED_TRAIT, EXPECTED_OWNED);
 
         let actual = parse_str::<State>(&input).unwrap();
 
         assert_eq!(expected, actual);
     }
 
-    impl Dest {
-        fn new(state: DestinationState, msg: Message) -> Self {
-            Self { state, msg }
-        }
-    }
-
     #[test]
     fn dest_parse() {
         const EXPECTED_STATE: &str = "Opened";
         const EXPECTED_MSG: &str = "Read";
         let input = format!("{EXPECTED_STATE}!{EXPECTED_MSG}");
         let expected = Dest::new(
-            DestinationState::Individual(State::new(EXPECTED_STATE, iter::empty())),
-            Message::new(EXPECTED_MSG, false, iter::empty()),
+            DestinationState::Individual(State::new(EXPECTED_STATE, [])),
+            Message::new(EXPECTED_MSG, false, []),
         );
 
         let actual = parse_str::<Dest>(&input).unwrap();
@@ -914,7 +942,7 @@ let states = [{}];
     #[test]
     fn destination_state_parse_regular() {
         const EXPECTED_DEST_STATE: &str = "Listening";
-        let expected = DestinationState::Individual(State::new(EXPECTED_DEST_STATE, iter::empty()));
+        let expected = DestinationState::Individual(State::new(EXPECTED_DEST_STATE, []));
 
         let actual = parse_str::<DestinationState>(EXPECTED_DEST_STATE).unwrap();
 
@@ -929,10 +957,8 @@ let states = [{}];
             "{EXPECTED_DEST_STATE}[{}]",
             EXPECTED_OWNED_STATES.join(", ")
         );
-        let expected = DestinationState::Individual(State::new(
-            EXPECTED_DEST_STATE,
-            EXPECTED_OWNED_STATES.into_iter(),
-        ));
+        let expected =
+            DestinationState::Individual(State::new(EXPECTED_DEST_STATE, EXPECTED_OWNED_STATES));
 
         let actual = parse_str::<DestinationState>(&input).unwrap();
 
@@ -942,7 +968,7 @@ let states = [{}];
     #[test]
     fn destination_state_parse_service() {
         const EXPECTED_DEST_STATE: &str = "Listening";
-        let expected = DestinationState::Service(State::new(EXPECTED_DEST_STATE, iter::empty()));
+        let expected = DestinationState::Service(State::new(EXPECTED_DEST_STATE, []));
         let input = format!("service({EXPECTED_DEST_STATE})");
 
         let actual = parse_str::<DestinationState>(&input).unwrap();
@@ -968,42 +994,13 @@ let states = [{}];
         assert_eq!(DestinationState::MULTI_STATE_ERR, err_str);
     }
 
-    impl Message {
-        fn new(
-            msg_type: &str,
-            is_reply: bool,
-            owned_states: impl Iterator<Item = &'static str>,
-        ) -> Self {
-            let (reply_part, ident_field) = if is_reply {
-                let reply_part = MessageReplyPart {
-                    colons: Token![::](Span::call_site()),
-                    reply: ident(MessageReplyPart::REPLY_IDENT),
-                };
-                let variant = format_ident!("{}{}", msg_type, MessageReplyPart::REPLY_IDENT);
-                (Some(reply_part), Some(variant))
-            } else {
-                (None, None)
-            };
-            Self {
-                msg_type: ident(msg_type),
-                reply_part,
-                owned_states: IdentArray::new(owned_states),
-                ident: ident_field,
-            }
-        }
-    }
-
     #[test]
     fn message_new() {
         const EXPECTED_MSG_TYPE: &str = "Orders";
         const EXPECTED_IS_REPLY: bool = true;
         const EXPECTED_OWNED_STATES: [&str; 2] = ["First", "Second"];
 
-        let actual = Message::new(
-            EXPECTED_MSG_TYPE,
-            EXPECTED_IS_REPLY,
-            EXPECTED_OWNED_STATES.into_iter(),
-        );
+        let actual = Message::new(EXPECTED_MSG_TYPE, EXPECTED_IS_REPLY, EXPECTED_OWNED_STATES);
 
         assert_eq!(actual.msg_type, EXPECTED_MSG_TYPE);
         assert_eq!(actual.is_reply(), EXPECTED_IS_REPLY);
@@ -1015,7 +1012,7 @@ let states = [{}];
     #[test]
     fn message_parse_regular() {
         const EXPECTED_MSG: &str = "Write";
-        let expected = Message::new(EXPECTED_MSG, false, iter::empty());
+        let expected = Message::new(EXPECTED_MSG, false, []);
 
         let actual = parse_str::<Message>(EXPECTED_MSG).unwrap();
 
@@ -1026,7 +1023,7 @@ let states = [{}];
     fn message_parse_reply() {
         const EXPECTED_MSG: &str = "ReadAttr";
         let input = format!("{EXPECTED_MSG}::Reply");
-        let expected = Message::new(EXPECTED_MSG, true, iter::empty());
+        let expected = Message::new(EXPECTED_MSG, true, []);
 
         let actual = parse_str::<Message>(&input).unwrap();
 
@@ -1051,7 +1048,7 @@ let states = [{}];
         const EXPECTED_MSG: &str = "Open";
         const OWNED_STATES: [&str; 2] = ["Handle0", "Handle1"];
         let input = format!("{EXPECTED_MSG}[{}, {}]", OWNED_STATES[0], OWNED_STATES[1]);
-        let expected = Message::new(EXPECTED_MSG, false, OWNED_STATES.into_iter());
+        let expected = Message::new(EXPECTED_MSG, false, OWNED_STATES);
 
         let actual = parse_str::<Message>(&input).unwrap();
 
@@ -1066,7 +1063,7 @@ let states = [{}];
             "{EXPECTED_MSG}::Reply[{}, {}]",
             OWNED_STATES[0], OWNED_STATES[1]
         );
-        let expected = Message::new(EXPECTED_MSG, true, OWNED_STATES.into_iter());
+        let expected = Message::new(EXPECTED_MSG, true, OWNED_STATES);
 
         let actual = parse_str::<Message>(&input).unwrap();
 

+ 279 - 164
crates/btproto/src/validation.rs

@@ -202,7 +202,7 @@ impl Protocol {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use syn::parse_str;
+    use crate::parsing::{Dest, NameDef, StatesDef, Transition};
 
     macro_rules! assert_ok {
         ($maybe_err:expr) => {
@@ -219,292 +219,407 @@ mod tests {
         };
     }
 
-    /// A minimal valid protocol definition.
-    const MIN_PROTOCOL: &str = "
-let name = Test;
-let states = [Init];
-Init?Activate -> End;
-";
+    /// Creates minimal [Protocol] value.
+    fn min_protocol() -> Protocol {
+        const STATE_NAME: &str = "Init";
+        Protocol::new(
+            NameDef::new("Test"),
+            StatesDef::new([STATE_NAME]),
+            [Transition::new(
+                State::new(STATE_NAME, []),
+                Some(Message::new("Activate", false, [])),
+                [State::new("End", [])],
+                [],
+            )],
+        )
+    }
 
     #[test]
     fn all_states_declared_and_used_ok() {
-        let result = parse_str::<Protocol>(MIN_PROTOCOL)
-            .unwrap()
-            .all_states_declared_and_used();
+        let result = min_protocol().all_states_declared_and_used();
 
         assert_ok!(result);
     }
 
     #[test]
     fn all_states_declared_and_used_end_not_used_ok() {
-        const INPUT: &str = "
-let name = Test;
-let states = [Init];
-Init?Activate -> Init;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .all_states_declared_and_used();
+        const STATE_NAME: &str = "Init";
+        let input = Protocol::new(
+            NameDef::new("Test"),
+            StatesDef::new([STATE_NAME]),
+            [Transition::new(
+                State::new(STATE_NAME, []),
+                Some(Message::new("Activate", false, [])),
+                [State::new(STATE_NAME, [])],
+                [],
+            )],
+        );
+
+        let result = input.all_states_declared_and_used();
 
         assert_ok!(result);
     }
 
     #[test]
     fn all_states_declared_and_used_undeclared_err() {
-        const INPUT: &str = "
-let name = Undeclared;
-let states = [Init];
-Init?Activate -> Next;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .all_states_declared_and_used();
+        let input = Protocol::new(
+            NameDef::new("Undeclared"),
+            StatesDef::new(["Init"]),
+            [Transition::new(
+                State::new("Init", []),
+                Some(Message::new("Activate", false, [])),
+                [State::new("Next", [])],
+                [],
+            )],
+        );
+
+        let result = input.all_states_declared_and_used();
 
         assert_err!(result, Protocol::UNDECLARED_STATE_ERR);
     }
 
     #[test]
     fn all_states_declared_and_used_undeclared_out_state_owned_err() {
-        const INPUT: &str = "
-let name = Undeclared;
-let states = [Init, Next];
-Init?Activate -> Init, Next[Undeclared];
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .all_states_declared_and_used();
+        let input = Protocol::new(
+            NameDef::new("Undeclared"),
+            StatesDef::new(["Init", "Next"]),
+            [Transition::new(
+                State::new("Init", []),
+                Some(Message::new("Activate", false, [])),
+                [State::new("Init", []), State::new("Next", ["Undeclared"])],
+                [],
+            )],
+        );
+
+        let result = input.all_states_declared_and_used();
 
         assert_err!(result, Protocol::UNDECLARED_STATE_ERR);
     }
 
     #[test]
     fn all_states_declared_and_used_undeclared_in_state_owned_err() {
-        const INPUT: &str = "
-let name = Undeclared;
-let states = [Init, Next];
-Init[Undeclared]?Activate -> Next;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .all_states_declared_and_used();
+        let input = Protocol::new(
+            NameDef::new("Undeclared"),
+            StatesDef::new(["Init", "Next"]),
+            [Transition::new(
+                State::new("Init", ["Undeclared"]),
+                Some(Message::new("Activate", false, [])),
+                [State::new("Next", [])],
+                [],
+            )],
+        );
+
+        let result = input.all_states_declared_and_used();
 
         assert_err!(result, Protocol::UNDECLARED_STATE_ERR);
     }
 
     #[test]
     fn all_states_declared_and_used_unused_err() {
-        const INPUT: &str = "
-let name = Unused;
-let states = [Init, Extra];
-Init?Activate -> End;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .all_states_declared_and_used();
+        let input = Protocol::new(
+            NameDef::new("Unused"),
+            StatesDef::new(["Init", "Extra"]),
+            [Transition::new(
+                State::new("Init", []),
+                Some(Message::new("Activate", false, [])),
+                [State::new("End", [])],
+                [],
+            )],
+        );
+
+        let result = input.all_states_declared_and_used();
 
         assert_err!(result, Protocol::UNUSED_STATE_ERR);
     }
 
     #[test]
     fn match_receivers_and_senders_ok() {
-        let result = parse_str::<Protocol>(MIN_PROTOCOL)
-            .unwrap()
-            .match_receivers_and_senders();
+        let result = min_protocol().match_receivers_and_senders();
 
         assert_ok!(result);
     }
 
     #[test]
     fn match_receivers_and_senders_send_activate_ok() {
-        const INPUT: &str = "
-let name = Unbalanced;
-let states = [First, Second];
-First?Activate -> First, >Second!Activate;
-Second?Activate -> Second;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .match_receivers_and_senders();
+        let input = Protocol::new(
+            NameDef::new("Unbalanced"),
+            StatesDef::new(["First", "Second"]),
+            [
+                Transition::new(
+                    State::new("First", []),
+                    Some(Message::new("Activate", false, [])),
+                    [State::new("First", [])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Second", [])),
+                        Message::new("Activate", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Second", []),
+                    Some(Message::new("Activate", false, [])),
+                    [State::new("Second", [])],
+                    [],
+                ),
+            ],
+        );
+
+        let result = input.match_receivers_and_senders();
 
         assert_ok!(result);
     }
 
     #[test]
     fn match_receivers_and_senders_unmatched_sender_err() {
-        const INPUT: &str = "
-let name = Unbalanced;
-let states = [Init, Other];
-Init?Activate -> Init, >Other!Activate;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .match_receivers_and_senders();
+        let input = Protocol::new(
+            NameDef::new("Unbalanced"),
+            StatesDef::new(["Init", "Other"]),
+            [Transition::new(
+                State::new("Init", []),
+                Some(Message::new("Activate", false, [])),
+                [State::new("Init", [])],
+                [Dest::new(
+                    DestinationState::Individual(State::new("Other", [])),
+                    Message::new("Activate", false, []),
+                )],
+            )],
+        );
+
+        let result = input.match_receivers_and_senders();
 
         assert_err!(result, Protocol::UNMATCHED_SENDER_ERR);
     }
 
     #[test]
     fn match_receivers_and_senders_unmatched_receiver_err() {
-        const INPUT: &str = "
-let name = Unbalanced;
-let states = [Init];
-Init?NotExists -> Init;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .match_receivers_and_senders();
+        let input = Protocol::new(
+            NameDef::new("Unbalanced"),
+            StatesDef::new(["Init"]),
+            [Transition::new(
+                State::new("Init", []),
+                Some(Message::new("NotExists", false, [])),
+                [State::new("Init", [])],
+                [],
+            )],
+        );
+
+        let result = input.match_receivers_and_senders();
 
         assert_err!(result, Protocol::UNMATCHED_RECEIVER_ERR);
     }
 
     #[test]
     fn no_undeliverable_msgs_ok() {
-        let result = parse_str::<Protocol>(MIN_PROTOCOL)
-            .unwrap()
-            .no_undeliverable_msgs();
+        let result = min_protocol().no_undeliverable_msgs();
 
         assert_ok!(result);
     }
 
     #[test]
     fn no_undeliverable_msgs_reply_ok() {
-        const INPUT: &str = "
-let name = Undeliverable;
-let states = [Listening, Client];
-Listening?Msg -> Listening, >Client!Msg::Reply;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .no_undeliverable_msgs();
+        let input = Protocol::new(
+            NameDef::new("Undeliverable"),
+            StatesDef::new(["Listening", "Client"]),
+            [Transition::new(
+                State::new("Listening", []),
+                Some(Message::new("Msg", false, [])),
+                [State::new("Listening", [])],
+                [Dest::new(
+                    DestinationState::Individual(State::new("Client", [])),
+                    Message::new("Msg", true, []),
+                )],
+            )],
+        );
+
+        let result = input.no_undeliverable_msgs();
 
         assert_ok!(result);
     }
 
     #[test]
     fn no_undeliverable_msgs_service_ok() {
-        const INPUT: &str = "
-let name = Undeliverable;
-let states = [Client, Server];
-Client -> Client, >service(Server)!Msg;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .no_undeliverable_msgs();
+        let input = Protocol::new(
+            NameDef::new("Undeliverable"),
+            StatesDef::new(["Client", "Server"]),
+            [Transition::new(
+                State::new("Client", []),
+                None,
+                [State::new("Client", [])],
+                [Dest::new(
+                    DestinationState::Service(State::new("Server", [])),
+                    Message::new("Msg", false, []),
+                )],
+            )],
+        );
+
+        let result = input.no_undeliverable_msgs();
 
         assert_ok!(result);
     }
 
     #[test]
     fn no_undeliverable_msgs_owned_ok() {
-        const INPUT: &str = "
-let name = Undeliverable;
-let states = [FileClient, FileHandle];
-FileClient[FileHandle] -> FileClient, >FileHandle!FileOp;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .no_undeliverable_msgs();
+        let input = Protocol::new(
+            NameDef::new("Undeliverable"),
+            StatesDef::new(["FileClient", "FileHandle"]),
+            [Transition::new(
+                State::new("FileClient", ["FileHandle"]),
+                None,
+                [State::new("FileClient", [])],
+                [Dest::new(
+                    DestinationState::Individual(State::new("FileHandle", [])),
+                    Message::new("FileOp", false, []),
+                )],
+            )],
+        );
+
+        let result = input.no_undeliverable_msgs();
 
         assert_ok!(result);
     }
 
     #[test]
     fn no_undeliverable_msgs_err() {
-        const INPUT: &str = "
-let name = Undeliverable;
-let states = [Client, Server];
-Client -> Client, >Server!Msg;
-";
-
-        let result = parse_str::<Protocol>(INPUT)
-            .unwrap()
-            .no_undeliverable_msgs();
+        let input = Protocol::new(
+            NameDef::new("Undeliverable"),
+            StatesDef::new(["Client", "Server"]),
+            [Transition::new(
+                State::new("Client", []),
+                None,
+                [State::new("Client", [])],
+                [Dest::new(
+                    DestinationState::Individual(State::new("Server", [])),
+                    Message::new("Msg", false, []),
+                )],
+            )],
+        );
+
+        let result = input.no_undeliverable_msgs();
 
         assert_err!(result, Protocol::UNDELIVERABLE_ERR);
     }
 
     #[test]
     fn valid_replies_ok() {
-        const INPUT: &str = "
-let name = ValidReplies;
-let states = [Client, Server];
-Server?Msg -> Server, >Client!Msg::Reply;
-";
-
-        let result = parse_str::<Protocol>(INPUT).unwrap().valid_replies();
+        let input = Protocol::new(
+            NameDef::new("ValidReplies"),
+            StatesDef::new(["Client", "Server"]),
+            [Transition::new(
+                State::new("Server", []),
+                Some(Message::new("Msg", false, [])),
+                [State::new("Server", [])],
+                [Dest::new(
+                    DestinationState::Individual(State::new("Client", [])),
+                    Message::new("Msg", true, []),
+                )],
+            )],
+        );
+
+        let result = input.valid_replies();
 
         assert_ok!(result);
     }
 
     #[test]
     fn valid_replies_invalid_reply_err() {
-        const INPUT: &str = "
-let name = ValidReplies;
-let states = [Client, Server];
-Client -> Client, >Server!Msg::Reply;
-";
-
-        let result = parse_str::<Protocol>(INPUT).unwrap().valid_replies();
+        let input = Protocol::new(
+            NameDef::new("ValidReplies"),
+            StatesDef::new(["Client", "Server"]),
+            [Transition::new(
+                State::new("Client", []),
+                None,
+                [State::new("Client", [])],
+                [Dest::new(
+                    DestinationState::Individual(State::new("Server", [])),
+                    Message::new("Msg", true, []),
+                )],
+            )],
+        );
+
+        let result = input.valid_replies();
 
         assert_err!(result, Protocol::INVALID_REPLY_ERR);
     }
 
     #[test]
     fn valid_replies_multiple_replies_err() {
-        const INPUT: &str = "
-let name = ValidReplies;
-let states = [Client, OtherClient, Server];
-Server?Msg -> Server, >Client!Msg::Reply, OtherClient!Msg::Reply;
-";
-
-        let result = parse_str::<Protocol>(INPUT).unwrap().valid_replies();
+        let input = Protocol::new(
+            NameDef::new("ValidReplies"),
+            StatesDef::new(["Client", "OtherClient", "Server"]),
+            [Transition::new(
+                State::new("Server", []),
+                Some(Message::new("Msg", false, [])),
+                [State::new("Server", [])],
+                [
+                    Dest::new(
+                        DestinationState::Individual(State::new("Client", [])),
+                        Message::new("Msg", true, []),
+                    ),
+                    Dest::new(
+                        DestinationState::Individual(State::new("OtherClient", [])),
+                        Message::new("Msg", true, []),
+                    ),
+                ],
+            )],
+        );
+
+        let result = input.valid_replies();
 
         assert_err!(result, Protocol::MULTIPLE_REPLIES_ERR);
     }
 
     #[test]
     fn msg_sent_or_received_msg_received_ok() {
-        const INPUT: &str = "
-let name = Test;
-let states = [Init];
-Init?Activate -> End;
-";
-        let result = parse_str::<Protocol>(INPUT).unwrap().msg_sent_or_received();
+        let input = Protocol::new(
+            NameDef::new("Test"),
+            StatesDef::new(["Init"]),
+            [Transition::new(
+                State::new("Init", []),
+                Some(Message::new("Activate", false, [])),
+                [State::new("End", [])],
+                [],
+            )],
+        );
+
+        let result = input.msg_sent_or_received();
 
         assert_ok!(result);
     }
 
     #[test]
     fn msg_sent_or_received_msg_sent_ok() {
-        const INPUT: &str = "
-let name = Test;
-let states = [First, Second];
-First -> First, >Second!Msg;
-";
-        let result = parse_str::<Protocol>(INPUT).unwrap().msg_sent_or_received();
+        let input = Protocol::new(
+            NameDef::new("Test"),
+            StatesDef::new(["First", "Second"]),
+            [Transition::new(
+                State::new("First", []),
+                None,
+                [State::new("First", [])],
+                [Dest::new(
+                    DestinationState::Individual(State::new("Second", [])),
+                    Message::new("Msg", false, []),
+                )],
+            )],
+        );
+
+        let result = input.msg_sent_or_received();
 
         assert_ok!(result);
     }
 
     #[test]
     fn msg_sent_or_received_neither_err() {
-        const INPUT: &str = "
-let name = Test;
-let states = [First];
-First -> First;
-";
-        let result = parse_str::<Protocol>(INPUT).unwrap().msg_sent_or_received();
+        let input = Protocol::new(
+            NameDef::new("Test"),
+            StatesDef::new(["First"]),
+            [Transition::new(
+                State::new("First", []),
+                None,
+                [State::new("First", [])],
+                [],
+            )],
+        );
+
+        let result = input.msg_sent_or_received();
 
         assert_err!(result, Protocol::NO_MSG_SENT_OR_RECEIVED_ERR);
     }

+ 14 - 12
crates/btrun/tests/runtime_tests.rs

@@ -3,9 +3,9 @@
 use btrun::*;
 
 use btlib::{
-    Result,
     crypto::{ConcreteCreds, CredStore, CredsPriv},
     log::BuilderExt,
+    Result,
 };
 use btlib_tests::TEST_STORE;
 use btproto::protocol;
@@ -13,20 +13,17 @@ use btserde::to_vec;
 use bttp::{BlockAddr, Transmitter};
 use ctor::ctor;
 use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
 use std::{
+    future::{ready, Future, Ready},
     net::{IpAddr, Ipv4Addr},
     sync::{
+        atomic::{AtomicU8, Ordering},
         Arc,
-        atomic::{AtomicU8, Ordering}
     },
     time::{Duration, Instant},
-    future::{Future, ready, Ready},
 };
-use tokio::{
-    runtime::Builder,
-    sync::mpsc,
-};
-use serde::{Serialize, Deserialize};
+use tokio::{runtime::Builder, sync::mpsc};
 use uuid::Uuid;
 
 const RUNTIME_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
@@ -283,7 +280,10 @@ mod ping_pong {
     ) {
         let mut state = {
             let init = ServerInitState;
-            let state = init.handle_activate(Activate::new(rt, act_id)).await.unwrap();
+            let state = init
+                .handle_activate(Activate::new(rt, act_id))
+                .await
+                .unwrap();
             PingServerState::Listening(state)
         };
         while let Some(envelope) = mailbox.recv().await {
@@ -317,7 +317,10 @@ mod ping_pong {
         act_id: Uuid,
     ) {
         let init = ClientInitState;
-        let (state, msg) = init.handle_activate(Activate::new(rt, act_id)).await.unwrap();
+        let (state, msg) = init
+            .handle_activate(Activate::new(rt, act_id))
+            .await
+            .unwrap();
         let from = rt.actor_name(act_id);
         let reply = rt
             .call(server_name, from, PingProtocolMsg::Ping(msg))
@@ -365,7 +368,6 @@ mod ping_pong {
     }
 }
 
-
 mod travel_agency {
     use super::*;
 
@@ -410,4 +412,4 @@ mod travel_agency {
     impl CallMsg for Accept {
         type Reply = ();
     }
-}
+}

+ 3 - 3
crates/btsector/src/lib.rs

@@ -1,10 +1,10 @@
 //! Types which define the protocol used by the sector layer.
 
-use btrun::CallMsg;
-use btlib::{Result, crypto::merkle_stream::VariantMerkleTree, BlockMeta, Inode};
+use btlib::{crypto::merkle_stream::VariantMerkleTree, BlockMeta, Inode, Result};
 use btproto::protocol;
+use btrun::CallMsg;
 
-use serde::{Serialize, Deserialize};
+use serde::{Deserialize, Serialize};
 
 #[derive(Serialize, Deserialize)]
 pub struct SectorMsg {