use super::{parser::Parser, MessageError, Prefix, PrefixIndex};
use crate::{FromIrcMessage, MaybeOwned, MaybeOwnedIndex};
use std::convert::Infallible;
#[derive(Clone, PartialEq)]
pub struct IrcMessage<'a> {
pub raw: MaybeOwned<'a>,
pub tags: Option<MaybeOwnedIndex>,
pub prefix: Option<PrefixIndex>,
pub command: MaybeOwnedIndex,
pub args: Option<MaybeOwnedIndex>,
pub data: Option<MaybeOwnedIndex>,
}
impl<'a> IrcMessage<'a> {
pub(crate) fn parse(input: MaybeOwned<'a>) -> Result<Self, MessageError> {
let data = if input.ends_with("\r\n") {
&input.as_ref()[..input.len() - 2]
} else {
input.as_ref()
};
let data = data.trim();
if data.is_empty() {
return Err(super::MessageError::EmptyMessage);
}
let mut p = Parser {
input: data,
pos: 0,
};
let this = Self {
tags: p.tags(),
prefix: p.prefix(),
command: p.command(),
args: p.args(),
data: p.data(),
raw: input, };
Ok(this)
}
pub fn get_raw(&self) -> &str {
&*self.raw
}
pub fn get_tags(&self) -> Option<&str> {
self.tags.map(|index| &self.raw[index])
}
pub fn get_prefix(&self) -> Option<&str> {
self.prefix.map(|index| &self.raw[index.as_index()])
}
pub fn get_command(&self) -> &str {
&self.raw[self.command]
}
pub fn get_args(&self) -> Option<&str> {
self.args.map(|index| &self.raw[index])
}
pub fn get_data(&self) -> Option<&str> {
self.data.map(|index| &self.raw[index])
}
pub fn into_inner(self) -> MaybeOwned<'a> {
self.raw
}
pub fn nth_arg(&self, nth: usize) -> Option<&str> {
self.args
.map(|index| &self.raw[index])?
.split_ascii_whitespace()
.nth(nth)
}
pub fn nth_arg_index(&self, nth: usize) -> Option<MaybeOwnedIndex> {
let index = self.args?;
let args = &self.raw[index];
let mut seen = 0;
let (mut head, mut tail) = (index.start, index.start);
for ch in args.chars() {
if ch.is_ascii_whitespace() {
if seen == nth {
return Some(MaybeOwnedIndex::raw(head as _, tail as _));
}
head = tail + 1;
seen += 1;
}
tail += 1;
}
if seen == nth {
return Some(MaybeOwnedIndex::raw(head as _, tail as _));
}
None
}
}
impl<'a> IrcMessage<'a> {
pub const IRC_READY: &'static str = "001";
pub const READY: &'static str = "376";
pub const CAP: &'static str = "CAP";
pub const CLEAR_CHAT: &'static str = "CLEARCHAT";
pub const CLEAR_MSG: &'static str = "CLEARMSG";
pub const GLOBAL_USER_STATE: &'static str = "GLOBALUSERSTATE";
pub const HOST_TARGET: &'static str = "HOSTTARGET";
pub const JOIN: &'static str = "JOIN";
pub const NOTICE: &'static str = "NOTICE";
pub const PART: &'static str = "PART";
pub const PING: &'static str = "PING";
pub const PONG: &'static str = "PONG";
pub const PRIVMSG: &'static str = "PRIVMSG";
pub const RECONNECT: &'static str = "RECONNECT";
pub const ROOM_STATE: &'static str = "ROOMSTATE";
pub const USER_NOTICE: &'static str = "USERNOTICE";
pub const USER_STATE: &'static str = "USERSTATE";
pub const WHISPER: &'static str = "WHISPER";
}
impl<'a> FromIrcMessage<'a> for IrcMessage<'a> {
type Error = Infallible;
fn from_irc(msg: IrcMessage<'a>) -> Result<Self, Self::Error> {
Ok(msg)
}
into_inner_raw!();
}
into_owned! {
IrcMessage {
raw,
tags,
prefix,
command,
args,
data,
}
}
impl<'a> std::fmt::Debug for IrcMessage<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IrcMessage")
.field("raw", &&*self.raw)
.field("tags", &self.get_tags())
.field(
"prefix",
&self.prefix.map(|index| Prefix {
data: &self.raw,
index,
}),
)
.field("command", &self.get_command())
.field("args", &self.get_args())
.field("data", &self.get_data())
.finish()
}
}
#[cfg(feature = "serde")]
impl<'a> ::serde::Serialize for IrcMessage<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
use ::serde::ser::SerializeMap as _;
let mut s = serializer.serialize_map(Some(6))?;
s.serialize_entry("raw", &&*self.raw)?;
s.serialize_entry("tags", &self.get_tags())?;
s.serialize_entry("prefix", &self.get_prefix())?;
s.serialize_entry("command", &self.get_command())?;
s.serialize_entry("args", &self.get_args())?;
s.serialize_entry("data", &self.get_data())?;
s.end()
}
}
#[cfg(feature = "serde")]
impl<'de, 'a> ::serde::Deserialize<'de> for IrcMessage<'a> {
fn deserialize<D>(deserializer: D) -> Result<IrcMessage<'a>, D::Error>
where
D: ::serde::Deserializer<'de>,
{
deserializer.deserialize_map(crate::serde::RawVisitor::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "serde")]
fn irc_message_serde() {
let input = ":test!test@test PRIVMSG #museun :this is a test\r\n";
crate::serde::round_trip_json::<IrcMessage>(input);
crate::serde::round_trip_rmp::<IrcMessage>(input);
}
#[test]
fn parse_empty_spaces() {
for i in 0..10 {
let s: MaybeOwned<'_> = format!("{}\r\n", " ".repeat(i)).into();
let err = IrcMessage::parse(s).unwrap_err();
assert!(matches!(err, MessageError::EmptyMessage))
}
}
}