mod interpreter
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
4688712904
commit
934376e974
4 changed files with 191 additions and 1 deletions
|
@ -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" }
|
||||
|
|
|
@ -201,6 +201,7 @@ impl<'a> PartialEq<Vec<Node>> 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,
|
||||
|
|
186
src/interpreter.rs
Normal file
186
src/interpreter.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<ScanElement<'a>>,
|
||||
pub outbound: Vec<ScanElement<'a>>,
|
||||
}
|
||||
|
||||
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 <bind/>. 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::<Context>()
|
||||
}
|
||||
|
||||
pub fn read_actions<'a>(spec: Spec, context: &'a Context) -> Result<InOutStanza<'a>, 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::<Element>()?;
|
||||
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::<Element>()?;
|
||||
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:
|
||||
<presence from="${louise's full JID}" to="room@muc.localhost/louise" xmlns="jabber:client">
|
||||
<x xmlns="http://jabber.org/protocol/muc"/>
|
||||
</presence>
|
||||
|
||||
louise receives:
|
||||
<presence to="${louise's full JID}" from="room@muc.localhost/louise" id="{scansion:any}" xmlns="jabber:client">
|
||||
<x xmlns="http://jabber.org/protocol/muc#user">
|
||||
<status code="201"/>
|
||||
<item affiliation="owner" jid="${louise's full JID}" role="moderator"/>
|
||||
<status code="110"/>
|
||||
</x>
|
||||
</presence>
|
||||
"#;
|
||||
|
||||
static JOIN: &str = r#"<presence from="${louise's full JID}" to="room@muc.localhost/louise" xmlns="jabber:client">
|
||||
<x xmlns="http://jabber.org/protocol/muc"/>
|
||||
</presence>"#;
|
||||
static CONFIRM: &str = r#"<presence to="${louise's full JID}" from="room@muc.localhost/louise" id="{scansion:any}" xmlns="jabber:client">
|
||||
<x xmlns="http://jabber.org/protocol/muc#user">
|
||||
<status code="201"/>
|
||||
<item affiliation="owner" jid="${louise's full JID}" role="moderator"/>
|
||||
<status code="110"/>
|
||||
</x>
|
||||
</presence>"#;
|
||||
|
||||
#[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::<Element>().unwrap()).with_context(Some(&context))
|
||||
],
|
||||
outbound: vec![
|
||||
ScanElement::new(CONFIRM.parse::<Element>().unwrap()).with_context(Some(&context))
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(read_actions(spec, &context).unwrap(), res);
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
|
Loading…
Reference in a new issue