tokio_xmpp::AsyncClient and xmpp::Agent take a fully parsed Jid (#72)

xmppftw created

Change summary

tokio-xmpp/ChangeLog                    |  3 +
tokio-xmpp/examples/contact_addr.rs     |  7 ++-
tokio-xmpp/examples/download_avatars.rs |  8 ++--
tokio-xmpp/examples/echo_bot.rs         |  7 ++-
tokio-xmpp/src/client/async_client.rs   | 11 ++----
xmpp/ChangeLog                          |  3 +
xmpp/examples/hello_bot.rs              |  9 +++--
xmpp/src/lib.rs                         | 44 +++++++++++++++++---------
8 files changed, 56 insertions(+), 36 deletions(-)

Detailed changes

tokio-xmpp/ChangeLog 🔗

@@ -0,0 +1,3 @@
+xxxxxxxxxx
+    * Breaking changes:
+          - AsyncClient::new takes a parsed Jid instead of string (#72)

tokio-xmpp/examples/contact_addr.rs 🔗

@@ -2,13 +2,14 @@ use futures::stream::StreamExt;
 use std::convert::TryFrom;
 use std::env::args;
 use std::process::exit;
+use std::str::FromStr;
 use tokio_xmpp::AsyncClient as Client;
 use xmpp_parsers::{
     disco::{DiscoInfoQuery, DiscoInfoResult},
     iq::{Iq, IqType},
     ns,
     server_info::ServerInfo,
-    Element, Jid,
+    BareJid, Element, Jid,
 };
 
 #[tokio::main]
@@ -18,12 +19,12 @@ async fn main() {
         println!("Usage: {} <jid> <password> <target>", args[0]);
         exit(1);
     }
-    let jid = &args[1];
+    let jid = BareJid::from_str(&args[1]).expect(&format!("Invalid JID: {}", &args[1]));
     let password = args[2].clone();
     let target = &args[3];
 
     // Client instance
-    let mut client = Client::new(jid, password).unwrap();
+    let mut client = Client::new(jid, password);
 
     // Main loop, processes events
     let mut wait_for_stream_end = false;

tokio-xmpp/examples/download_avatars.rs 🔗

@@ -22,7 +22,7 @@ use xmpp_parsers::{
         NodeName,
     },
     stanza_error::{DefinedCondition, ErrorType, StanzaError},
-    Element, Jid,
+    BareJid, Element, Jid,
 };
 
 #[tokio::main]
@@ -32,11 +32,11 @@ async fn main() {
         println!("Usage: {} <jid> <password>", args[0]);
         exit(1);
     }
-    let jid = &args[1];
+    let jid = BareJid::from_str(&args[1]).expect(&format!("Invalid JID: {}", &args[1]));
     let password = args[2].clone();
 
     // Client instance
-    let mut client = Client::new(jid, password).unwrap();
+    let mut client = Client::new(jid.clone(), password);
 
     let disco_info = make_disco();
 
@@ -94,7 +94,7 @@ async fn main() {
                     } else if let IqType::Result(Some(payload)) = iq.payload {
                         if payload.is("pubsub", ns::PUBSUB) {
                             let pubsub = PubSub::try_from(payload).unwrap();
-                            let from = iq.from.clone().unwrap_or(Jid::from_str(jid).unwrap());
+                            let from = iq.from.clone().unwrap_or(jid.clone().into());
                             handle_iq_result(pubsub, &from);
                         }
                     } else if let IqType::Set(_) = iq.payload {

tokio-xmpp/examples/echo_bot.rs 🔗

@@ -2,11 +2,12 @@ use futures::stream::StreamExt;
 use std::convert::TryFrom;
 use std::env::args;
 use std::process::exit;
+use std::str::FromStr;
 use tokio;
 use tokio_xmpp::AsyncClient as Client;
 use xmpp_parsers::message::{Body, Message, MessageType};
 use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
-use xmpp_parsers::{Element, Jid};
+use xmpp_parsers::{BareJid, Element, Jid};
 
 #[tokio::main]
 async fn main() {
@@ -15,11 +16,11 @@ async fn main() {
         println!("Usage: {} <jid> <password>", args[0]);
         exit(1);
     }
-    let jid = &args[1];
+    let jid = BareJid::from_str(&args[1]).expect(&format!("Invalid JID: {}", &args[1]));
     let password = &args[2];
 
     // Client instance
-    let mut client = Client::new(jid, password.to_owned()).unwrap();
+    let mut client = Client::new(jid, password.to_owned());
     client.set_reconnect(true);
 
     // Main loop, processes events

tokio-xmpp/src/client/async_client.rs 🔗

@@ -2,7 +2,6 @@ use futures::{sink::SinkExt, task::Poll, Future, Sink, Stream};
 use sasl::common::{ChannelBinding, Credentials};
 use std::mem::replace;
 use std::pin::Pin;
-use std::str::FromStr;
 use std::task::Context;
 use tokio::net::TcpStream;
 use tokio::task::JoinHandle;
@@ -10,7 +9,7 @@ use tokio::task::JoinHandle;
 use tokio_native_tls::TlsStream;
 #[cfg(feature = "tls-rust")]
 use tokio_rustls::client::TlsStream;
-use xmpp_parsers::{ns, Element, Jid, JidParseError};
+use xmpp_parsers::{ns, Element, Jid};
 
 use super::auth::auth;
 use super::bind::bind;
@@ -74,15 +73,13 @@ impl Client {
     ///
     /// Start polling the returned instance so that it will connect
     /// and yield events.
-    pub fn new<P: Into<String>>(jid: &str, password: P) -> Result<Self, JidParseError> {
-        let jid = Jid::from_str(jid)?;
+    pub fn new<J: Into<Jid>, P: Into<String>>(jid: J, password: P) -> Self {
         let config = Config {
-            jid: jid.clone(),
+            jid: jid.into(),
             password: password.into(),
             server: ServerConfig::UseSrv,
         };
-        let client = Self::new_with_config(config);
-        Ok(client)
+        Self::new_with_config(config)
     }
 
     /// Start a new client given that the JID is already parsed.

xmpp/ChangeLog 🔗

@@ -1,7 +1,10 @@
 Unreleased:
 xxxxxxxxxx
+    * Breaking changes:
+	  - ClientBuilder::new takes a parsed BareJid instead of string (#72)
     * Improvements:
       - Agent is now Send, by replacing Rc with Arc and RefCell with RwLock (#64)
+	  - ClientBuilder now has a set_resource method for manual resource management (#72)
 
 Version 0.4.0:
 2023-05-18 [ Maxime “pep” Buquet <pep@bouah.net>, Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> ]

xmpp/examples/hello_bot.rs 🔗

@@ -6,8 +6,9 @@
 
 use env_logger;
 use std::env::args;
+use std::str::FromStr;
 use xmpp::{ClientBuilder, ClientFeature, ClientType, Event};
-use xmpp_parsers::{message::MessageType, Jid};
+use xmpp_parsers::{message::MessageType, BareJid, Jid};
 
 #[tokio::main]
 async fn main() -> Result<(), Option<()>> {
@@ -18,7 +19,8 @@ async fn main() -> Result<(), Option<()>> {
         println!("Usage: {} <jid> <password>", args[0]);
         return Err(None);
     }
-    let jid = &args[1];
+
+    let jid = BareJid::from_str(&args[1]).expect(&format!("Invalid JID: {}", &args[1]));
     let password = &args[2];
 
     // Client instance
@@ -29,8 +31,7 @@ async fn main() -> Result<(), Option<()>> {
         .enable_feature(ClientFeature::Avatars)
         .enable_feature(ClientFeature::ContactList)
         .enable_feature(ClientFeature::JoinRooms)
-        .build()
-        .unwrap();
+        .build();
 
     while let Some(events) = client.wait_for_events().await {
         for event in events {

xmpp/src/lib.rs 🔗

@@ -94,19 +94,19 @@ pub enum Event {
     HttpUploadedFile(String),
 }
 
-#[derive(Default)]
 pub struct ClientBuilder<'a> {
-    jid: &'a str,
+    jid: BareJid,
     password: &'a str,
     website: String,
     default_nick: String,
     lang: Vec<String>,
     disco: (ClientType, String),
     features: Vec<ClientFeature>,
+    resource: Option<String>,
 }
 
 impl ClientBuilder<'_> {
-    pub fn new<'a>(jid: &'a str, password: &'a str) -> ClientBuilder<'a> {
+    pub fn new<'a>(jid: BareJid, password: &'a str) -> ClientBuilder<'a> {
         ClientBuilder {
             jid,
             password,
@@ -115,9 +115,16 @@ impl ClientBuilder<'_> {
             lang: vec![String::from("en")],
             disco: (ClientType::default(), String::from("tokio-xmpp")),
             features: vec![],
+            resource: None,
         }
     }
 
+    /// Optionally set a resource associated to this device on the client
+    pub fn set_resource(mut self, resource: &str) -> Self {
+        self.resource = Some(resource.to_string());
+        self
+    }
+
     pub fn set_client(mut self, type_: ClientType, name: &str) -> Self {
         self.disco = (type_, String::from(name));
         self
@@ -168,26 +175,30 @@ impl ClientBuilder<'_> {
         }
     }
 
-    pub fn build(self) -> Result<Agent, Error> {
-        let client = TokioXmppClient::new(self.jid, self.password)?;
-        Ok(self.build_impl(client)?)
+    pub fn build(self) -> Agent {
+        let jid: Jid = if let Some(resource) = &self.resource {
+            self.jid.clone().with_resource(resource.to_string()).into()
+        } else {
+            self.jid.clone().into()
+        };
+
+        let client = TokioXmppClient::new(jid, self.password);
+        self.build_impl(client)
     }
 
     // This function is meant to be used for testing build
-    pub(crate) fn build_impl(self, client: TokioXmppClient) -> Result<Agent, Error> {
+    pub(crate) fn build_impl(self, client: TokioXmppClient) -> Agent {
         let disco = self.make_disco();
         let node = self.website;
 
-        let agent = Agent {
+        Agent {
             client,
             default_nick: Arc::new(RwLock::new(self.default_nick)),
             lang: Arc::new(self.lang),
             disco,
             node,
             uploads: Vec::new(),
-        };
-
-        Ok(agent)
+        }
     }
 }
 
@@ -509,22 +520,25 @@ async fn handle_upload_result(
 
 #[cfg(test)]
 mod tests {
-    use super::{Agent, ClientBuilder, ClientFeature, ClientType, Event};
+    use super::{Agent, BareJid, ClientBuilder, ClientFeature, ClientType, Event};
+    use std::str::FromStr;
     use tokio_xmpp::AsyncClient as TokioXmppClient;
 
     #[tokio::test]
     async fn test_simple() {
-        let client = TokioXmppClient::new("foo@bar", "meh").unwrap();
+        let jid = BareJid::from_str("foo@bar").unwrap();
+
+        let client = TokioXmppClient::new(jid.clone(), "meh");
 
         // Client instance
-        let client_builder = ClientBuilder::new("foo@bar", "meh")
+        let client_builder = ClientBuilder::new(jid, "meh")
             .set_client(ClientType::Bot, "xmpp-rs")
             .set_website("https://gitlab.com/xmpp-rs/xmpp-rs")
             .set_default_nick("bot")
             .enable_feature(ClientFeature::Avatars)
             .enable_feature(ClientFeature::ContactList);
 
-        let mut agent: Agent = client_builder.build_impl(client).unwrap();
+        let mut agent: Agent = client_builder.build_impl(client);
 
         while let Some(events) = agent.wait_for_events().await {
             assert!(match events[0] {