diff --git a/Cargo.toml b/Cargo.toml index 1ad54b6..0e8d802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ nom = "7.1" jid = "0.9" minidom = "0.15.1" nom_locate = "4.0.0" +rand = "0.8" # [patch.crates-io] # jid = { path = "../xmpp-rs/jid" } diff --git a/src/element.rs b/src/element.rs index f31e13f..f304800 100644 --- a/src/element.rs +++ b/src/element.rs @@ -201,6 +201,7 @@ impl<'a> PartialEq> for ScanNodes<'a, NonStrictComparison> { /// Comparison between elements needs to take into accounts the `scansion:strict` attribute which /// changes the way the comparison is done. /// Also uses the custom ScanNode implementation. +#[cfg_attr(test, derive(PartialEq))] #[derive(Debug, Clone)] pub struct ScanElement<'a> { elem: Element, diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000..37679a0 --- /dev/null +++ b/src/interpreter.rs @@ -0,0 +1,186 @@ +// Copyright (C) 2023-2099 The crate authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::element::ScanElement; +use crate::parsers::parse_spec; +use crate::types::{Action, Context, Entity, Spec}; +use jid::Jid; +use minidom::{Element, Error as MinidomError}; +use rand::{thread_rng, Rng}; + +#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct InOutStanza<'a> { + pub inbound: Vec>, + pub outbound: Vec>, +} + +impl<'a> InOutStanza<'a> { + fn new() -> Self { + InOutStanza { + inbound: Vec::new(), + outbound: Vec::new(), + } + } + + fn sends(&mut self, scan: ScanElement<'a>) { + self.inbound.push(scan) + } + + fn receives(&mut self, scan: ScanElement<'a>) { + self.outbound.push(scan) + } +} + +fn make_resource() -> String { + let id: u8 = thread_rng().gen(); + format!("{}", id) +} + +// We don't need information other than FullJid and name here as we're going to use that directly +// in tests. No need to connect anywhere. +/// When connecting, clients are assigned a FullJid during . This function adds a random +/// resource to clients that only have a BareJid specified. +fn bind_context(context: Context) -> Context { + context + .into_iter() + .map(|(name, mut context)| { + match context { + Entity::Client(ref mut client) => match &client.jid { + Jid::Bare(bare) => { + let jid = bare.clone().with_resource(make_resource()); + client.jid = Jid::Full(jid) + } + _ => (), + }, + }; + + (name, context) + }) + .collect::() +} + +pub fn read_actions<'a>(spec: Spec, context: &'a Context) -> Result, MinidomError> { + let mut inout = InOutStanza::new(); + for action in spec.actions { + match action { + Action::Receive(name, s) => { + let _ = context + .get(&name) + .expect(format!("Name '{}' not found in clients", name).as_str()); + + let elem = s.parse::()?; + inout.receives(ScanElement::new(elem).with_context(Some(&context))); + } + Action::Send(name, s) => { + let _ = context + .get(&name) + .expect(format!("Name '{}' not found in clients", name).as_str()); + + let elem = s.parse::()?; + inout.sends(ScanElement::new(elem).with_context(Some(&context))); + } + _ => (), + } + } + + Ok(inout) +} + +pub fn read_spec<'a>(buf: &str) -> Spec { + let mut spec = parse_spec(buf).unwrap(); + spec.context = bind_context(spec.context.clone()); + spec +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Client, Entity}; + + static BUF1: &str = r#"# MUC Creation +# Single user MUC creation +## muc + +[Client] louise + jid: louise@localhost + password: password + +----- + +louise connects + +louise sends: + + + + +louise receives: + + + + + + + +"#; + + static JOIN: &str = r#" + + "#; + static CONFIRM: &str = r#" + + + + + + "#; + + #[test] + fn test_bind_context() { + let spec = parse_spec(BUF1).unwrap(); + + let context = bind_context(spec.context); + assert_eq!(context.len(), 1); + match context.get("louise") { + Some(Entity::Client(Client { + jid: Jid::Full(full), + password, + .. + })) => { + assert_eq!(full.node, Some(String::from("louise"))); + assert_eq!(full.domain, String::from("localhost")); + assert_eq!(password, "password"); + } + other => panic!("Error: context: {other:?}"), + } + } + + #[test] + fn test_read_actions() { + let spec = parse_spec(BUF1.clone()).unwrap(); + let context: Context = bind_context(spec.context.clone()); + let res = InOutStanza { + inbound: vec![ + ScanElement::new(JOIN.parse::().unwrap()).with_context(Some(&context)) + ], + outbound: vec![ + ScanElement::new(CONFIRM.parse::().unwrap()).with_context(Some(&context)) + ], + }; + + assert_eq!(read_actions(spec, &context).unwrap(), res); + } +} diff --git a/src/lib.rs b/src/lib.rs index 86b20b1..5ea33f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,9 +7,11 @@ #![feature(let_chains)] pub mod element; +pub mod interpreter; pub mod parsers; pub mod types; pub use element::ScanElement; +pub use interpreter::{read_actions, read_spec}; pub use parsers::parse_spec; -pub use types::{Action, Client, Context, Metadata, Name, Spec}; +pub use types::{Action, Client, Context, Entity, Metadata, Name, Spec};