Add Room, RoomNick and RoomParticipant types for more safe/correct API

This commit is contained in:
xmppftw 2023-06-24 14:30:32 +02:00
parent 76b68e932a
commit bd3dcc3532
6 changed files with 168 additions and 24 deletions

View file

@ -433,8 +433,13 @@ impl FullJid {
self.inner.domain()
}
/// The resource part of the full JID, as a [`ResourcePart`].
pub fn resource(&self) -> ResourcePart {
ResourcePart::new_unchecked(self.inner.resource().unwrap())
}
/// The optional resource of the Jabber ID. Since this is a full JID it is always present.
pub fn resource(&self) -> &str {
pub fn resource_str(&self) -> &str {
self.inner.resource().unwrap()
}

View file

@ -24,6 +24,7 @@
#![warn(missing_docs)]
pub use crate::util::error::Error;
pub use jid;
pub use jid::{BareJid, Error as JidParseError, FullJid, Jid};
pub use minidom::Element;

View file

@ -67,16 +67,16 @@ async fn main() -> Result<(), Option<()>> {
)
.await;
}
Event::LeaveRoom(jid) => {
Event::LeaveRoom(room) => {
println!("Leaving room {}", jid);
}
Event::LeaveAllRooms => {
println!("Leaving all rooms…");
}
Event::RoomJoined(jid) => {
Event::RoomJoined(room) => {
println!("Joined room {}.", jid);
client
.send_message(Jid::Bare(jid), MessageType::Groupchat, "en", "Hello world!")
.send_message(Jid::Bare(jid.bare().clone()), MessageType::Groupchat, "en", "Hello world!")
.await;
}
Event::RoomLeft(jid) => {

View file

@ -39,6 +39,8 @@ use xmpp_parsers::{
extern crate log;
mod pubsub;
mod room;
pub use room::{Room, RoomNick, RoomParticipant};
pub type Error = tokio_xmpp::Error;
@ -72,7 +74,6 @@ pub enum ClientFeature {
}
pub type Id = Option<String>;
pub type RoomNick = String;
#[derive(Debug)]
pub enum Event {
@ -84,15 +85,22 @@ pub enum Event {
#[cfg(feature = "avatars")]
AvatarRetrieved(Jid, String),
ChatMessage(Id, BareJid, Body),
JoinRoom(BareJid, Conference),
LeaveRoom(BareJid),
/// Received a bookmark from the server about a specific Room, with information
/// about autojoin settings.
JoinRoom(Room, Conference),
/// The client left a Room (TODO: only this client or any client?)
LeaveRoom(Room),
/// The client is leaving all rooms (TODO: is this received when another client asks to leave all rooms?)
LeaveAllRooms,
RoomJoined(BareJid),
RoomLeft(BareJid),
RoomMessage(Id, BareJid, RoomNick, Body),
/// You just joined a room (TODO: on this client or any client?)
RoomJoined(Room),
/// You just left a room (TODO: on this client or any client ?)
RoomLeft(Room),
/// Received a message from a Room, by a person identified by their RoomNick.
RoomMessage(Id, RoomParticipant, Body),
/// A private message received from a room, containing the message ID, the room's BareJid,
/// the sender's nickname, and the message body.
RoomPrivateMessage(Id, BareJid, RoomNick, Body),
RoomPrivateMessage(Id, Room, RoomNick, Body),
ServiceMessage(Id, BareJid, Body),
HttpUploadedFile(String),
}
@ -221,7 +229,7 @@ impl Agent {
pub async fn join_room(
&mut self,
room: BareJid,
room: Room,
nick: Option<String>,
password: Option<String>,
lang: &str,
@ -233,7 +241,7 @@ impl Agent {
}
let nick = nick.unwrap_or_else(|| self.default_nick.read().unwrap().clone());
let room_jid = room.with_resource_str(&nick).unwrap();
let room_jid = room.bare().with_resource_str(&nick).unwrap();
let mut presence = Presence::new(PresenceType::None).with_to(room_jid);
presence.add_payload(muc);
presence.set_status(String::from(lang), String::from(status));
@ -262,7 +270,7 @@ impl Agent {
lang: &str,
text: &str,
) {
let recipient: Jid = room.with_resource_str(&recipient).unwrap().into();
let recipient: Jid = room.with_resource(recipient.resource()).into();
let mut message = Message::new(recipient).with_payload(MucUser::new());
message.type_ = MessageType::Chat;
message
@ -367,8 +375,10 @@ impl Agent {
let event = match from.clone() {
Jid::Full(full) => Event::RoomMessage(
message.id.clone(),
from.to_bare(),
full.resource().to_owned(),
RoomParticipant::new(
&Room::from_bare_dangerous(&from.to_bare()),
&RoomNick::from_resource_dangerous(&full.resource()),
),
body.clone(),
),
Jid::Bare(bare) => {
@ -390,8 +400,8 @@ impl Agent {
}
Jid::Full(full) => Event::RoomPrivateMessage(
message.id.clone(),
full.to_bare(),
full.resource().to_owned(),
Room::from_bare_dangerous(&full.to_bare()),
RoomNick::from_resource_dangerous(&full.resource()),
body.clone(),
),
};
@ -431,7 +441,7 @@ impl Agent {
};
for status in muc_user.status.into_iter() {
if status == Status::SelfPresence {
events.push(Event::RoomJoined(from.clone()));
events.push(Event::RoomJoined(Room::from_bare_dangerous(&from)));
break;
}
}

View file

@ -5,7 +5,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use super::Agent;
use crate::Event;
use crate::{Event, Room};
use std::convert::TryFrom;
use std::str::FromStr;
use xmpp_parsers::{
@ -41,9 +41,9 @@ pub(crate) async fn handle_event(from: &Jid, elem: Element, agent: &mut Agent) -
match Conference::try_from(payload) {
Ok(conference) => {
if conference.autojoin == Autojoin::True {
events.push(Event::JoinRoom(jid, conference));
events.push(Event::JoinRoom(Room::from_bare_dangerous(&jid), conference));
} else {
events.push(Event::LeaveRoom(jid));
events.push(Event::LeaveRoom(Room::from_bare_dangerous(&jid)));
}
}
Err(err) => println!("not bookmark: {}", err),
@ -59,7 +59,7 @@ pub(crate) async fn handle_event(from: &Jid, elem: Element, agent: &mut Agent) -
assert_eq!(items.len(), 1);
let item = items.clone().pop().unwrap();
let jid = BareJid::from_str(&item.0).unwrap();
events.push(Event::LeaveRoom(jid));
events.push(Event::LeaveRoom(Room::from_bare_dangerous(&jid)));
}
ref node => unimplemented!("node {}", node),
}
@ -98,7 +98,7 @@ pub(crate) fn handle_iq_result(from: &Jid, elem: Element) -> impl IntoIterator<I
match Conference::try_from(payload) {
Ok(conference) => {
if let Autojoin::True = conference.autojoin {
events.push(Event::JoinRoom(jid, conference));
events.push(Event::JoinRoom(Room::from_bare_dangerous(&jid), conference));
}
}
Err(err) => panic!("Wrong payload type in bookmarks 2 item: {}", err),

128
xmpp/src/room.rs Normal file
View file

@ -0,0 +1,128 @@
// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![deny(bare_trait_objects)]
use xmpp_parsers::{BareJid, FullJid};
use xmpp_parsers::jid::ResourcePart;
use std::fmt;
//use crate::Error;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Room(BareJid);
impl AsRef<BareJid> for Room {
fn as_ref(&self) -> &BareJid {
self.bare()
}
}
impl fmt::Display for Room {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl Room {
/// Builds a [`Room`] bare JID from any [`BareJid`].
/// Make sure that bare JID is actually a room before using!
pub fn from_bare_dangerous(jid: &BareJid) -> Room {
Room(jid.to_owned())
}
/// Returns a reference to the inner [`BareJid`] for this room.
pub fn bare(&self) -> &BareJid {
&self.0
}
/// Returns the full JID of a room participant as a [`RoomParticipant`] instance.
/// TODO: Dangerous? See note about RoomNick type
pub fn participant(&self, nick: &RoomNick) -> RoomParticipant {
RoomParticipant::new(&self, nick)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RoomParticipant(FullJid);
impl AsRef<FullJid> for RoomParticipant {
fn as_ref(&self) -> &FullJid {
self.full()
}
}
impl fmt::Display for RoomParticipant {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl RoomParticipant {
/// Builds a [`RoomParticipant`] from a typed [`Room`] and [`RoomNick`].
pub fn new(room: &Room, nick: &RoomNick) -> RoomParticipant {
RoomParticipant::from_full_dangerous(
&room.bare().with_resource(nick.resource())
)
}
/// Builds a [`RoomParticipant`] bare JID from any [`BareJid`].
/// Make sure that bare JID is actually a room before using!
pub fn from_full_dangerous(jid: &FullJid) -> RoomParticipant {
RoomParticipant(jid.to_owned())
}
/// Returns a reference to the inner [`FullJid`] for this room.
pub fn full(&self) -> &FullJid {
&self.0
}
/// Returns an owned [`Room`] the [`RoomParticipant`] is in.
pub fn room(&self) -> Room {
Room::from_bare_dangerous(&self.0.to_bare())
}
/// Returns an owned [`RoomNick`] for the [`RoomParticipant`].
pub fn nick(&self) -> RoomNick {
RoomNick::from_resource_dangerous(&self.0.resource())
}
}
// TODO: Do we really really want to have a RoomNick type? This makes it possible to mix nicks from different
// rooms. RoomParticipant on the other hand, by having an inner FullJid, deeply associated those two infos.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RoomNick(ResourcePart);
impl AsRef<ResourcePart> for RoomNick {
fn as_ref(&self) -> &ResourcePart {
self.resource()
}
}
impl fmt::Display for RoomNick {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl RoomNick {
/// Builds a [`RoomParticipant`] bare JID from any [`BareJid`].
/// Make sure that bare JID is actually a room before using!
pub fn from_resource_dangerous(resource: &ResourcePart) -> RoomNick {
RoomNick(resource.to_owned())
}
/// Returns a reference to the inner [`ResourcePart`] for this nick.
pub fn resource(&self) -> &ResourcePart {
&self.0
}
/// Returns an owned [`RoomParticipant`] for this nick in a specific [`Room`].
/// TODO: Dangerous? See note about RoomNick type
pub fn in_room(&self, room: &Room) -> RoomParticipant {
room.participant(self)
}
}