2023-02-09 19:26:05 +00:00
|
|
|
use super::*;
|
|
|
|
|
2023-10-04 18:27:43 +00:00
|
|
|
use anyhow::{anyhow, Result};
|
|
|
|
use nom::combinator::{all_consuming, opt};
|
2023-12-08 02:47:09 +00:00
|
|
|
use nonempty::NonEmpty;
|
2023-07-03 20:26:37 +00:00
|
|
|
|
2023-02-09 19:26:05 +00:00
|
|
|
/// Client-to-server command.
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum ClientMessage {
|
|
|
|
/// CAP. Capability-related commands.
|
2023-02-14 17:53:43 +00:00
|
|
|
Capability {
|
|
|
|
subcommand: CapabilitySubcommand,
|
|
|
|
},
|
2023-02-10 10:46:10 +00:00
|
|
|
/// PING <token>
|
2023-02-14 17:53:43 +00:00
|
|
|
Ping {
|
2023-04-13 22:38:26 +00:00
|
|
|
token: Str,
|
2023-02-14 17:53:43 +00:00
|
|
|
},
|
2023-02-10 10:46:10 +00:00
|
|
|
/// PONG <token>
|
2023-02-14 17:53:43 +00:00
|
|
|
Pong {
|
2023-04-13 22:38:26 +00:00
|
|
|
token: Str,
|
2023-02-14 17:53:43 +00:00
|
|
|
},
|
2023-02-13 17:08:37 +00:00
|
|
|
/// NICK <nickname>
|
2023-02-14 17:53:43 +00:00
|
|
|
Nick {
|
2023-04-13 22:38:26 +00:00
|
|
|
nickname: Str,
|
2023-02-14 17:53:43 +00:00
|
|
|
},
|
2023-08-16 14:30:02 +00:00
|
|
|
/// PASS <password>
|
|
|
|
Pass {
|
|
|
|
password: Str,
|
|
|
|
},
|
2023-02-13 17:08:37 +00:00
|
|
|
/// USER <username> 0 * :<realname>
|
2023-02-10 10:46:10 +00:00
|
|
|
User {
|
2023-04-13 22:38:26 +00:00
|
|
|
username: Str,
|
|
|
|
realname: Str,
|
2023-02-10 10:46:10 +00:00
|
|
|
},
|
2023-02-13 17:08:37 +00:00
|
|
|
/// JOIN <chan>
|
2023-02-12 23:31:16 +00:00
|
|
|
Join(Chan),
|
2023-02-14 17:53:43 +00:00
|
|
|
/// MODE <target>
|
2023-02-16 19:37:17 +00:00
|
|
|
Mode {
|
|
|
|
target: Recipient,
|
|
|
|
},
|
2023-02-14 17:53:43 +00:00
|
|
|
/// WHO <target>
|
2023-02-16 17:39:54 +00:00
|
|
|
Who {
|
|
|
|
target: Recipient, // aka mask
|
|
|
|
},
|
2023-02-14 17:53:43 +00:00
|
|
|
/// TOPIC <chan> :<topic>
|
|
|
|
Topic {
|
|
|
|
chan: Chan,
|
2023-04-13 22:38:26 +00:00
|
|
|
topic: Str,
|
2023-02-14 17:53:43 +00:00
|
|
|
},
|
|
|
|
Part {
|
|
|
|
chan: Chan,
|
2023-04-13 22:38:26 +00:00
|
|
|
message: Str,
|
2023-02-14 17:53:43 +00:00
|
|
|
},
|
2023-02-13 17:08:37 +00:00
|
|
|
/// PRIVMSG <target> :<msg>
|
2023-02-14 17:53:43 +00:00
|
|
|
PrivateMessage {
|
|
|
|
recipient: Recipient,
|
2023-04-13 22:38:26 +00:00
|
|
|
body: Str,
|
2023-02-14 17:53:43 +00:00
|
|
|
},
|
2023-02-13 17:08:37 +00:00
|
|
|
/// QUIT :<reason>
|
2023-02-14 17:53:43 +00:00
|
|
|
Quit {
|
2023-04-13 22:38:26 +00:00
|
|
|
reason: Str,
|
2023-02-14 17:53:43 +00:00
|
|
|
},
|
2023-12-08 02:47:09 +00:00
|
|
|
Authenticate(Str),
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 18:27:43 +00:00
|
|
|
pub fn client_message(input: &str) -> Result<ClientMessage> {
|
|
|
|
let res = all_consuming(alt((
|
2023-02-09 19:26:05 +00:00
|
|
|
client_message_capability,
|
|
|
|
client_message_ping,
|
|
|
|
client_message_pong,
|
2023-02-10 10:46:10 +00:00
|
|
|
client_message_nick,
|
2023-08-16 14:30:02 +00:00
|
|
|
client_message_pass,
|
2023-02-10 10:46:10 +00:00
|
|
|
client_message_user,
|
2023-02-12 23:31:16 +00:00
|
|
|
client_message_join,
|
2023-02-14 17:53:43 +00:00
|
|
|
client_message_mode,
|
|
|
|
client_message_who,
|
|
|
|
client_message_topic,
|
|
|
|
client_message_part,
|
2023-02-13 17:08:37 +00:00
|
|
|
client_message_privmsg,
|
2023-02-10 10:46:10 +00:00
|
|
|
client_message_quit,
|
2023-12-08 02:47:09 +00:00
|
|
|
client_message_authenticate,
|
2023-10-04 18:27:43 +00:00
|
|
|
)))(input);
|
|
|
|
match res {
|
|
|
|
Ok((_, e)) => Ok(e),
|
|
|
|
Err(e) => Err(anyhow!("Parsing failed: {e}")),
|
|
|
|
}
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_capability(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("CAP ")(input)?;
|
|
|
|
let (input, subcommand) = capability_subcommand(input)?;
|
|
|
|
|
|
|
|
Ok((input, ClientMessage::Capability { subcommand }))
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_ping(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("PING ")(input)?;
|
|
|
|
let (input, token) = token(input)?;
|
|
|
|
|
2023-10-04 18:27:43 +00:00
|
|
|
Ok((input, ClientMessage::Ping { token: token.into() }))
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_pong(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("PONG ")(input)?;
|
|
|
|
let (input, token) = token(input)?;
|
|
|
|
|
2023-10-04 18:27:43 +00:00
|
|
|
Ok((input, ClientMessage::Pong { token: token.into() }))
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_nick(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-10 10:46:10 +00:00
|
|
|
let (input, _) = tag("NICK ")(input)?;
|
|
|
|
let (input, nickname) = receiver(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
ClientMessage::Nick {
|
2023-04-13 19:15:48 +00:00
|
|
|
nickname: nickname.into(),
|
2023-02-10 10:46:10 +00:00
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
2023-08-16 14:30:02 +00:00
|
|
|
fn client_message_pass(input: &str) -> IResult<&str, ClientMessage> {
|
|
|
|
let (input, _) = tag("PASS ")(input)?;
|
|
|
|
let (input, r) = opt(tag(":"))(input)?;
|
|
|
|
let (input, password) = match r {
|
|
|
|
Some(_) => token(input)?,
|
|
|
|
None => receiver(input)?,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
ClientMessage::Pass {
|
|
|
|
password: password.into(),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
2023-02-10 10:46:10 +00:00
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_user(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-10 10:46:10 +00:00
|
|
|
let (input, _) = tag("USER ")(input)?;
|
|
|
|
let (input, username) = receiver(input)?;
|
|
|
|
let (input, _) = tag(" ")(input)?;
|
2023-02-14 18:28:49 +00:00
|
|
|
let (input, _) = receiver(input)?;
|
|
|
|
let (input, _) = tag(" ")(input)?;
|
|
|
|
let (input, _) = receiver(input)?;
|
2023-07-03 20:26:37 +00:00
|
|
|
let (input, _) = tag(" ")(input)?;
|
|
|
|
let (input, r) = opt(tag(":"))(input)?;
|
|
|
|
let (input, realname) = match r {
|
|
|
|
Some(_) => token(input)?,
|
|
|
|
None => receiver(input)?,
|
|
|
|
};
|
2023-02-10 10:46:10 +00:00
|
|
|
|
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
ClientMessage::User {
|
2023-04-13 19:15:48 +00:00
|
|
|
username: username.into(),
|
|
|
|
realname: realname.into(),
|
2023-02-10 10:46:10 +00:00
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_join(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-12 23:31:16 +00:00
|
|
|
let (input, _) = tag("JOIN ")(input)?;
|
|
|
|
let (input, chan) = chan(input)?;
|
|
|
|
|
|
|
|
Ok((input, ClientMessage::Join(chan)))
|
|
|
|
}
|
2023-02-10 10:46:10 +00:00
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_mode(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-14 17:53:43 +00:00
|
|
|
let (input, _) = tag("MODE ")(input)?;
|
2023-02-16 19:37:17 +00:00
|
|
|
let (input, target) = recipient(input)?;
|
2023-02-14 17:53:43 +00:00
|
|
|
|
2023-02-16 21:49:17 +00:00
|
|
|
Ok((input, ClientMessage::Mode { target }))
|
2023-02-14 17:53:43 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_who(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-14 17:53:43 +00:00
|
|
|
let (input, _) = tag("WHO ")(input)?;
|
2023-02-16 17:39:54 +00:00
|
|
|
let (input, target) = recipient(input)?;
|
2023-02-14 17:53:43 +00:00
|
|
|
|
2023-02-16 17:39:54 +00:00
|
|
|
Ok((input, ClientMessage::Who { target }))
|
2023-02-14 17:53:43 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_topic(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-14 17:53:43 +00:00
|
|
|
let (input, _) = tag("TOPIC ")(input)?;
|
|
|
|
let (input, chan) = chan(input)?;
|
2023-07-03 20:26:37 +00:00
|
|
|
let (input, _) = tag(" ")(input)?;
|
|
|
|
let (input, r) = opt(tag(":"))(input)?;
|
|
|
|
let (input, topic) = match r {
|
|
|
|
Some(_) => token(input)?,
|
|
|
|
None => receiver(input)?,
|
|
|
|
};
|
2023-02-14 17:53:43 +00:00
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
let topic = topic.into();
|
2023-02-14 17:53:43 +00:00
|
|
|
Ok((input, ClientMessage::Topic { chan, topic }))
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_part(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-14 17:53:43 +00:00
|
|
|
let (input, _) = tag("PART ")(input)?;
|
|
|
|
let (input, chan) = chan(input)?;
|
2023-07-03 20:26:37 +00:00
|
|
|
let (input, _) = tag(" ")(input)?;
|
|
|
|
let (input, r) = opt(tag(":"))(input)?;
|
|
|
|
let (input, message) = match r {
|
|
|
|
Some(_) => token(input)?,
|
|
|
|
None => receiver(input)?,
|
|
|
|
};
|
2023-02-14 17:53:43 +00:00
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
let message = message.into();
|
2023-02-14 17:53:43 +00:00
|
|
|
Ok((input, ClientMessage::Part { chan, message }))
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_privmsg(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-13 17:08:37 +00:00
|
|
|
let (input, _) = tag("PRIVMSG ")(input)?;
|
|
|
|
let (input, recipient) = recipient(input)?;
|
2023-07-03 20:26:37 +00:00
|
|
|
let (input, _) = tag(" ")(input)?;
|
|
|
|
let (input, r) = opt(tag(":"))(input)?;
|
|
|
|
let (input, body) = match r {
|
|
|
|
Some(_) => token(input)?,
|
|
|
|
None => receiver(input)?,
|
|
|
|
};
|
2023-02-13 17:08:37 +00:00
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
let body = body.into();
|
2023-02-13 17:08:37 +00:00
|
|
|
Ok((input, ClientMessage::PrivateMessage { recipient, body }))
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn client_message_quit(input: &str) -> IResult<&str, ClientMessage> {
|
2023-02-10 10:46:10 +00:00
|
|
|
let (input, _) = tag("QUIT :")(input)?;
|
|
|
|
let (input, reason) = token(input)?;
|
|
|
|
|
2023-10-04 18:27:43 +00:00
|
|
|
Ok((input, ClientMessage::Quit { reason: reason.into() }))
|
2023-02-10 10:46:10 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 02:47:09 +00:00
|
|
|
fn client_message_authenticate(input: &str) -> IResult<&str, ClientMessage> {
|
|
|
|
let (input, _) = tag("AUTHENTICATE ")(input)?;
|
|
|
|
let (input, body) = token(input)?;
|
|
|
|
|
|
|
|
Ok((input, ClientMessage::Authenticate(body.into())))
|
|
|
|
}
|
|
|
|
|
2023-02-09 19:26:05 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum CapabilitySubcommand {
|
|
|
|
/// CAP LS {code}
|
|
|
|
List { code: [u8; 3] },
|
2023-12-08 02:47:09 +00:00
|
|
|
/// CAP REQ :...
|
|
|
|
Req(NonEmpty<CapReq>),
|
2023-02-09 19:26:05 +00:00
|
|
|
/// CAP END
|
|
|
|
End,
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn capability_subcommand(input: &str) -> IResult<&str, CapabilitySubcommand> {
|
2023-12-08 02:47:09 +00:00
|
|
|
alt((
|
|
|
|
capability_subcommand_ls,
|
|
|
|
capability_subcommand_end,
|
|
|
|
capability_subcommand_req,
|
|
|
|
))(input)
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn capability_subcommand_ls(input: &str) -> IResult<&str, CapabilitySubcommand> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("LS ")(input)?;
|
|
|
|
let (input, code) = take(3usize)(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
CapabilitySubcommand::List {
|
2023-04-13 19:15:48 +00:00
|
|
|
code: code.as_bytes().try_into().unwrap(),
|
2023-02-09 19:26:05 +00:00
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-12-08 02:47:09 +00:00
|
|
|
fn capability_subcommand_req(input: &str) -> IResult<&str, CapabilitySubcommand> {
|
|
|
|
let (input, _) = tag("REQ ")(input)?;
|
|
|
|
let (input, r) = opt(tag(":"))(input)?;
|
|
|
|
let (input, body) = match r {
|
|
|
|
Some(_) => token(input)?,
|
|
|
|
None => receiver(input)?,
|
|
|
|
};
|
|
|
|
|
|
|
|
let caps = body
|
|
|
|
.split(' ')
|
|
|
|
.map(|cap| {
|
|
|
|
let to_disable = cap.starts_with('-');
|
|
|
|
let name = if to_disable { &cap[1..] } else { &cap[..] };
|
|
|
|
CapReq {
|
|
|
|
to_disable,
|
|
|
|
name: name.into(),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let caps = NonEmpty::from_vec(caps).ok_or_else(|| todo!())?;
|
|
|
|
|
|
|
|
Ok((input, CapabilitySubcommand::Req(caps)))
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn capability_subcommand_end(input: &str) -> IResult<&str, CapabilitySubcommand> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("END")(input)?;
|
|
|
|
Ok((input, CapabilitySubcommand::End))
|
|
|
|
}
|
|
|
|
|
2023-12-08 02:47:09 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub struct CapReq {
|
|
|
|
pub to_disable: bool,
|
|
|
|
pub name: Str,
|
|
|
|
}
|
|
|
|
|
2023-02-09 19:26:05 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use assert_matches::*;
|
2023-12-08 02:47:09 +00:00
|
|
|
use nonempty::nonempty;
|
2023-02-09 19:26:05 +00:00
|
|
|
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
|
|
fn test_client_message_cap_ls() {
|
2023-04-13 19:15:48 +00:00
|
|
|
let input = "CAP LS 302";
|
2023-02-09 19:26:05 +00:00
|
|
|
let expected = ClientMessage::Capability {
|
|
|
|
subcommand: CapabilitySubcommand::List { code: *b"302" },
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = client_message(input);
|
2023-10-04 18:27:43 +00:00
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_client_message_cap_end() {
|
2023-04-13 19:15:48 +00:00
|
|
|
let input = "CAP END";
|
2023-02-09 19:26:05 +00:00
|
|
|
let expected = ClientMessage::Capability {
|
|
|
|
subcommand: CapabilitySubcommand::End,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = client_message(input);
|
2023-10-04 18:27:43 +00:00
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_client_message_ping() {
|
2023-04-13 19:15:48 +00:00
|
|
|
let input = "PING 1337";
|
2023-10-04 18:27:43 +00:00
|
|
|
let expected = ClientMessage::Ping { token: "1337".into() };
|
2023-02-09 19:26:05 +00:00
|
|
|
|
|
|
|
let result = client_message(input);
|
2023-10-04 18:27:43 +00:00
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_client_message_pong() {
|
2023-04-13 19:15:48 +00:00
|
|
|
let input = "PONG 1337";
|
2023-10-04 18:27:43 +00:00
|
|
|
let expected = ClientMessage::Pong { token: "1337".into() };
|
2023-02-09 19:26:05 +00:00
|
|
|
|
2023-02-10 10:46:10 +00:00
|
|
|
let result = client_message(input);
|
2023-10-04 18:27:43 +00:00
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
2023-02-10 10:46:10 +00:00
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_client_message_nick() {
|
2023-04-13 19:15:48 +00:00
|
|
|
let input = "NICK SomeNick";
|
2023-02-10 10:46:10 +00:00
|
|
|
let expected = ClientMessage::Nick {
|
2023-04-13 19:15:48 +00:00
|
|
|
nickname: "SomeNick".into(),
|
2023-02-10 10:46:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let result = client_message(input);
|
2023-10-04 18:27:43 +00:00
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
2023-02-10 10:46:10 +00:00
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_client_message_user() {
|
2023-04-13 19:15:48 +00:00
|
|
|
let input = "USER SomeNick 8 * :Real Name";
|
2023-02-10 10:46:10 +00:00
|
|
|
let expected = ClientMessage::User {
|
2023-04-13 19:15:48 +00:00
|
|
|
username: "SomeNick".into(),
|
|
|
|
realname: "Real Name".into(),
|
2023-02-10 10:46:10 +00:00
|
|
|
};
|
|
|
|
|
2023-02-14 17:53:43 +00:00
|
|
|
let result = client_message(input);
|
2023-10-04 18:27:43 +00:00
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
2023-02-14 17:53:43 +00:00
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_client_message_part() {
|
2023-04-13 19:15:48 +00:00
|
|
|
let input = "PART #chan :Pokasiki !!!";
|
2023-02-14 17:53:43 +00:00
|
|
|
let expected = ClientMessage::Part {
|
2023-04-13 19:15:48 +00:00
|
|
|
chan: Chan::Global("chan".into()),
|
|
|
|
message: "Pokasiki !!!".into(),
|
2023-02-14 17:53:43 +00:00
|
|
|
};
|
|
|
|
|
2023-12-08 02:47:09 +00:00
|
|
|
let result = client_message(input);
|
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_client_cap_req() {
|
|
|
|
let input = "CAP REQ :multi-prefix -sasl";
|
|
|
|
let expected = ClientMessage::Capability {
|
|
|
|
subcommand: CapabilitySubcommand::Req(nonempty![
|
|
|
|
CapReq {
|
|
|
|
to_disable: false,
|
|
|
|
name: "multi-prefix".into()
|
|
|
|
},
|
|
|
|
CapReq {
|
|
|
|
to_disable: true,
|
|
|
|
name: "sasl".into()
|
|
|
|
}
|
|
|
|
]),
|
|
|
|
};
|
|
|
|
|
2023-02-09 19:26:05 +00:00
|
|
|
let result = client_message(input);
|
2023-10-04 18:27:43 +00:00
|
|
|
assert_matches!(result, Ok(result) => assert_eq!(expected, result));
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
}
|