From c7813bb5ae43dea7ab73cd4abfa1efce20f77ecb Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sun, 31 Jan 2021 22:18:16 +0000 Subject: [PATCH] Zero-copy encoding to the wire --- Cargo.lock | 19 +++---------------- titanirc-codec/src/wire.rs | 7 ++++--- titanirc-server/Cargo.toml | 4 ++-- titanirc-server/src/entities/user/commands.rs | 63 ++++++++++++++++++++++++++++++++++++++++----------------------- titanirc-server/src/entities/user/mod.rs | 26 +++++++++++++++----------- titanirc-types/src/lib.rs | 50 +++++++++++++++++++++++++++----------------------- titanirc-types/src/primitives.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------- titanirc-types/src/replies.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------- 8 files changed, 211 insertions(+), 175 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a06c6d5..e5d7673 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,7 +3,7 @@ [[package]] name = "actix" version = "0.11.0-beta.1" -source = "git+https://github.com/actix/actix?rev=fdaa5d50e25ffc892f5c1c6fcc51097796debecf#fdaa5d50e25ffc892f5c1c6fcc51097796debecf" +source = "git+https://github.com/JordanForks/actix#6c04d4eab0b9bf883e85a9b5659ecdc00a609bc4" dependencies = [ "actix-macros 0.1.3", "actix-rt", @@ -937,30 +937,17 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] name = "tokio-util" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb971a26599ffd28066d387f109746df178eff14d5ea1e235015c5601967a4b" +checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b" dependencies = [ - "async-stream", "bytes", "futures-core", "futures-sink", "log", "pin-project-lite", "tokio", - "tokio-stream", ] [[package]] diff --git a/titanirc-codec/src/wire.rs b/titanirc-codec/src/wire.rs index 01e6da7..0db4539 100644 --- a/titanirc-codec/src/wire.rs +++ b/titanirc-codec/src/wire.rs @@ -1,4 +1,4 @@ -use bytes::{BytesMut}; +use bytes::BytesMut; use titanirc_types::Command; use tokio_util::codec::Decoder as FrameDecoder; @@ -7,7 +7,8 @@ pub const MAX_LENGTH: usize = 1024; pub struct Decoder; impl FrameDecoder for Decoder { - type Item = Command; + /// Returns `'static` since we just return `BytesCow::Owned(bytes::Bytes)` and doesn't use the lifetime. + type Item = Command<'static>; type Error = std::io::Error; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -49,7 +50,7 @@ impl FrameDecoder for Decoder { pub struct Encoder; -impl tokio_util::codec::Encoder for Encoder { +impl tokio_util::codec::Encoder> for Encoder { type Error = std::io::Error; fn encode( diff --git a/titanirc-server/Cargo.toml b/titanirc-server/Cargo.toml index 5dcc9ad..bf7b7f8 100644 --- a/titanirc-server/Cargo.toml +++ b/titanirc-server/Cargo.toml @@ -10,8 +10,8 @@ edition = "2018" titanirc-codec = { path = "../titanirc-codec" } titanirc-types = { path = "../titanirc-types" } -actix = { git = "https://github.com/actix/actix", rev = "fdaa5d50e25ffc892f5c1c6fcc51097796debecf" } # tokio 1.0 support not yet released -actix-rt = "2.0.0-beta.1" +actix = { git = "https://github.com/JordanForks/actix" } +actix-rt = "=2.0.0-beta.2" tokio = { version = "1.1", features = ["net", "signal"] } tokio-util = "0.6" async-stream = "0.3" diff --git a/titanirc-server/src/entities/user/commands.rs b/titanirc-server/src/entities/user/commands.rs index 3bab702..d8c437e 100644 --- a/titanirc-server/src/entities/user/commands.rs +++ b/titanirc-server/src/entities/user/commands.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, time::Instant}; +use std::time::Instant; use actix::{Actor, AsyncContext, StreamHandler, WrapFuture}; use titanirc_types::{ @@ -9,8 +9,8 @@ pub trait CommandHandler: Actor { fn handle_cmd(&mut self, command: T, ctx: &mut Self::Context); } -impl StreamHandler> for super::User { - fn handle(&mut self, cmd: Result, ctx: &mut Self::Context) { +impl StreamHandler, std::io::Error>> for super::User { + fn handle(&mut self, cmd: Result, std::io::Error>, ctx: &mut Self::Context) { self.last_active = Instant::now(); match cmd { @@ -30,8 +30,12 @@ impl StreamHandler> for super::User { // TODO: all the 'raw' writes using byte strings below probably need to // be wrapped in something a bit more friendly. -impl CommandHandler for super::User { - fn handle_cmd(&mut self, NickCommand { nick }: NickCommand, _ctx: &mut Self::Context) { +impl CommandHandler> for super::User { + fn handle_cmd( + &mut self, + NickCommand { nick, .. }: NickCommand<'static>, + _ctx: &mut Self::Context, + ) { self.writer.write(titanirc_types::Reply::RplWelcome.into()); self.writer.write(titanirc_types::Reply::RplYourHost.into()); self.writer.write(titanirc_types::Reply::RplCreated.into()); @@ -44,8 +48,12 @@ impl CommandHandler for super::User { } } -impl CommandHandler for super::User { - fn handle_cmd(&mut self, JoinCommand { channel }: JoinCommand, ctx: &mut Self::Context) { +impl CommandHandler> for super::User { + fn handle_cmd( + &mut self, + JoinCommand { channel, .. }: JoinCommand<'static>, + ctx: &mut Self::Context, + ) { if let Some(ref nick) = self.nick { let server_addr = self.server.clone(); let ctx_addr = ctx.address(); @@ -71,34 +79,42 @@ impl CommandHandler for super::User { } } -impl CommandHandler for super::User { - fn handle_cmd(&mut self, ModeCommand { mode, .. }: ModeCommand, _ctx: &mut Self::Context) { +impl CommandHandler> for super::User { + fn handle_cmd( + &mut self, + ModeCommand { mode, .. }: ModeCommand<'static>, + _ctx: &mut Self::Context, + ) { self.writer .write(titanirc_types::Reply::RplUmodeIs(mode).into()) } } -impl CommandHandler for super::User { - fn handle_cmd(&mut self, _command: MotdCommand, _ctx: &mut Self::Context) { +impl CommandHandler> for super::User { + fn handle_cmd(&mut self, _command: MotdCommand<'static>, _ctx: &mut Self::Context) { static SERVER_NAME: bytes::Bytes = bytes::Bytes::from_static(b"my.test.server"); static MOTD1: bytes::Bytes = bytes::Bytes::from_static(b"Hello, welcome to this server!"); static MOTD2: bytes::Bytes = bytes::Bytes::from_static(b"it's very cool!"); self.writer.write( - titanirc_types::Reply::RplMotdStart(titanirc_types::ServerName(SERVER_NAME.clone())) - .into(), + titanirc_types::Reply::RplMotdStart(titanirc_types::ServerName( + SERVER_NAME.clone().into(), + )) + .into(), + ); + self.writer.write( + titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD1.clone().into())).into(), + ); + self.writer.write( + titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD2.clone().into())).into(), ); - self.writer - .write(titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD1.clone())).into()); - self.writer - .write(titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD2.clone())).into()); self.writer .write(titanirc_types::Reply::RplEndOfMotd.into()); } } -impl CommandHandler for super::User { - fn handle_cmd(&mut self, _command: VersionCommand, _ctx: &mut Self::Context) { +impl CommandHandler> for super::User { + fn handle_cmd(&mut self, _command: VersionCommand<'static>, _ctx: &mut Self::Context) { static SERVER_NAME: bytes::Bytes = bytes::Bytes::from_static(b"my.test.server"); static INFO: bytes::Bytes = bytes::Bytes::from_static(b"https://github.com/MITBorg/titanirc"); @@ -107,21 +123,22 @@ impl CommandHandler for super::User { titanirc_types::Reply::RplVersion( clap::crate_version!().to_string(), "release".to_string(), - titanirc_types::ServerName(SERVER_NAME.clone()), - titanirc_types::FreeText(INFO.clone()), + titanirc_types::ServerName(SERVER_NAME.clone().into()), + titanirc_types::FreeText(INFO.clone().into()), ) .into(), ) } } -impl CommandHandler for super::User { +impl CommandHandler> for super::User { fn handle_cmd( &mut self, PrivmsgCommand { receiver, free_text, - }: PrivmsgCommand, + .. + }: PrivmsgCommand<'static>, ctx: &mut Self::Context, ) { if let Some(nick) = &self.nick { diff --git a/titanirc-server/src/entities/user/mod.rs b/titanirc-server/src/entities/user/mod.rs index 0912d23..0bce0fb 100644 --- a/titanirc-server/src/entities/user/mod.rs +++ b/titanirc-server/src/entities/user/mod.rs @@ -3,7 +3,7 @@ pub mod events; use crate::{entities::channel::events::JoinBroadcast, server::Server}; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use actix::{ io::{FramedWrite, WriteHandler}, @@ -11,13 +11,17 @@ use actix::{ }; use std::time::{Duration, Instant}; use titanirc_types::{ - Channel, FreeText, JoinCommand, PrivmsgCommand, Receiver, ServerMessage, Source, + Channel, FreeText, JoinCommand, Nick, PrivmsgCommand, Receiver, ServerMessage, Source, }; use tokio::{io::WriteHalf, net::TcpStream}; pub struct User { pub server: Addr, - pub writer: FramedWrite, titanirc_codec::Encoder>, + pub writer: FramedWrite< + WriteHalf, + titanirc_codec::Encoder, + >>::Error, + >, pub last_active: Instant, pub nick: Option, } @@ -27,7 +31,7 @@ pub struct User { impl User { pub fn new( server: Addr, - writer: FramedWrite, titanirc_codec::Encoder>, + writer: FramedWrite, titanirc_codec::Encoder>, ) -> Self { Self { server, @@ -70,9 +74,10 @@ impl actix::Handler> for User { fn handle(&mut self, msg: Arc, _ctx: &mut Self::Context) -> Self::Result { self.writer.write(ServerMessage::Command( - Source::User(bytes::Bytes::from(msg.nick.as_bytes().to_owned()).into()), + Source::User(Nick(msg.nick.as_bytes().into())), JoinCommand { - channel: Channel::from(bytes::Bytes::from(msg.channel_name.as_bytes().to_owned())), + _phantom: std::marker::PhantomData, + channel: Channel(msg.channel_name.as_bytes().into()), } .into(), )); @@ -88,12 +93,11 @@ impl actix::Handler> for User { _ctx: &mut Self::Context, ) -> Self::Result { self.writer.write(ServerMessage::Command( - Source::User(bytes::Bytes::from(msg.from.as_bytes().to_owned()).into()), + Source::User(Nick(msg.from.as_bytes().into())), PrivmsgCommand { - free_text: FreeText(bytes::Bytes::from(msg.message.as_bytes().to_owned())), - receiver: { - Receiver::Channel(bytes::Bytes::from(msg.to.as_bytes().to_owned()).into()) - }, + _phantom: std::marker::PhantomData, + free_text: FreeText(msg.message.as_bytes().into()), + receiver: Receiver::Channel(Channel(msg.to.as_bytes().into())), } .into(), )); diff --git a/titanirc-types/src/lib.rs b/titanirc-types/src/lib.rs index 582f5dc..044d762 100644 --- a/titanirc-types/src/lib.rs +++ b/titanirc-types/src/lib.rs @@ -24,19 +24,19 @@ macro_rules! define_commands { ( $( $name:ident$(( - $($param:ty),* + $($param:ident$(<$($gen:tt),+>)?),* ))? ),* $(,)? ) => { paste::paste! { #[derive(Debug)] - pub enum Command { - $([<$name:camel>]([<$name:camel Command>])),* + pub enum Command<'a> { + $([<$name:camel>]([<$name:camel Command>]<'a>)),* } $(const [<$name _BYTES>]: &[u8] = stringify!($name).as_bytes();)* - impl Command { + impl Command<'_> { pub fn parse(input: Bytes) -> Result, nom::Err>> { let input = BytesWrapper::from(input); @@ -56,7 +56,7 @@ macro_rules! define_commands { } } - impl std::fmt::Display for Command { + impl std::fmt::Display for Command<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { $(Self::[<$name:camel>](cmd) => cmd.fmt(fmt)),* @@ -66,11 +66,12 @@ macro_rules! define_commands { $( #[derive(Debug)] - pub struct [<$name:camel Command>] { - $($(pub [<$param:snake>]: $param),*),* + pub struct [<$name:camel Command>]<'a> { + pub _phantom: std::marker::PhantomData<&'a ()>, + $($(pub [<$param:snake>]: $param$(<$($gen),+>)?),*),* } - impl [<$name:camel Command>] { + impl [<$name:camel Command>]<'_> { #[allow(unused_variables)] pub fn parse(rest: BytesWrapper) -> Result>> { $( @@ -81,12 +82,13 @@ macro_rules! define_commands { )* Ok(Self { + _phantom: std::marker::PhantomData, $($([<$param:snake>]),*),* }) } } - impl std::fmt::Display for [<$name:camel Command>] { + impl std::fmt::Display for [<$name:camel Command>]<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt.write_str(stringify!($name))?; @@ -101,8 +103,8 @@ macro_rules! define_commands { } } - impl Into for [<$name:camel Command>] { - fn into(self) -> Command { + impl<'a> Into> for [<$name:camel Command>]<'a> { + fn into(self) -> Command<'a> { Command::[<$name:camel>](self) } } @@ -112,24 +114,24 @@ macro_rules! define_commands { } define_commands! { - USER(Username, HostName, ServerName, RealName), - NICK(Nick), + USER(Username<'a>, HostName<'a>, ServerName<'a>, RealName<'a>), + NICK(Nick<'a>), MOTD, VERSION, HELP, USERS, TIME, - PONG(ServerName), - PING(ServerName), + PONG(ServerName<'a>), + PING(ServerName<'a>), LIST, - MODE(Nick, Mode), - WHOIS(Nick), - USERHOST(Nick), - USERIP(Nick), - JOIN(Channel), + MODE(Nick<'a>, Mode<'a>), + WHOIS(Nick<'a>), + USERHOST(Nick<'a>), + USERIP(Nick<'a>), + JOIN(Channel<'a>), - PRIVMSG(Receiver, FreeText), + PRIVMSG(Receiver<'a>, FreeText<'a>), } #[cfg(test)] @@ -149,7 +151,8 @@ mod tests { Ok(Some(Command::Privmsg(super::PrivmsgCommand { receiver: super::Receiver::User(super::Nick(nick)), free_text: super::primitives::FreeText(msg), - }))) if nick == "foo" && msg == "baz" + _phantom: std::marker::PhantomData, + }))) if &*nick == b"foo" && &*msg == b"baz" )) } @@ -160,7 +163,8 @@ mod tests { Ok(Some(Command::Privmsg(super::PrivmsgCommand { receiver: super::Receiver::User(super::Nick(nick)), free_text: super::primitives::FreeText(msg), - }))) if nick == "foo" && msg == "baz" + _phantom: std::marker::PhantomData, + }))) if &*nick == b"foo" && &*msg == b"baz" )) } } diff --git a/titanirc-types/src/primitives.rs b/titanirc-types/src/primitives.rs index c5ea3fc..e4f9341 100644 --- a/titanirc-types/src/primitives.rs +++ b/titanirc-types/src/primitives.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use derive_more::{Deref, From}; use nom::{ bytes::complete::{tag, take_till}, - combinator::{iterator}, + combinator::iterator, sequence::terminated, IResult, }; @@ -18,6 +18,29 @@ pub trait PrimitiveParser { Self: Sized; } +#[derive(Debug, From)] +pub enum BytesCow<'a> { + Owned(Bytes), + Borrowed(&'a [u8]), +} + +impl From for BytesCow<'_> { + fn from(other: BytesWrapper) -> Self { + Self::Owned(other.into()) + } +} + +impl std::ops::Deref for BytesCow<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + Self::Owned(b) => &*b, + Self::Borrowed(b) => *b, + } + } +} + macro_rules! noop_validator { ($name:ty) => { impl ValidatingParser for $name { @@ -137,37 +160,37 @@ impl ValidatingParser for Special { } #[derive(Debug, Deref, From)] -pub struct Username(pub Bytes); -space_terminated_primitive!(Username); -noop_validator!(Username); +pub struct Username<'a>(pub BytesCow<'a>); +space_terminated_primitive!(Username<'_>); +noop_validator!(Username<'_>); #[derive(Debug, Deref, From)] -pub struct Mode(pub Bytes); -space_terminated_primitive!(Mode); -noop_validator!(Mode); +pub struct Mode<'a>(pub BytesCow<'a>); +space_terminated_primitive!(Mode<'_>); +noop_validator!(Mode<'_>); #[derive(Debug, Deref, From)] -pub struct HostName(pub Bytes); -space_terminated_primitive!(HostName); -noop_validator!(HostName); +pub struct HostName<'a>(pub BytesCow<'a>); +space_terminated_primitive!(HostName<'_>); +noop_validator!(HostName<'_>); #[derive(Debug, Deref, From)] -pub struct ServerName(pub Bytes); -space_terminated_primitive!(ServerName); -noop_validator!(ServerName); +pub struct ServerName<'a>(pub BytesCow<'a>); +space_terminated_primitive!(ServerName<'_>); +noop_validator!(ServerName<'_>); #[derive(Debug, Deref, From)] -pub struct RealName(pub Bytes); -space_terminated_primitive!(RealName); -noop_validator!(RealName); +pub struct RealName<'a>(pub BytesCow<'a>); +space_terminated_primitive!(RealName<'_>); +noop_validator!(RealName<'_>); #[derive(Debug, Deref, From)] -pub struct Nick(pub Bytes); -space_terminated_primitive!(Nick); +pub struct Nick<'a>(pub BytesCow<'a>); +space_terminated_primitive!(Nick<'_>); // TODO: i feel like this would be better suited as a nom chomper to stop // iterating over the string twice unnecessarily -impl ValidatingParser for Nick { +impl ValidatingParser for Nick<'_> { fn validate(bytes: &[u8]) -> bool { if bytes.is_empty() { return false; @@ -184,20 +207,20 @@ impl ValidatingParser for Nick { } #[derive(Debug, Deref, From)] -pub struct Channel(pub Bytes); -space_terminated_primitive!(Channel); -noop_validator!(Channel); +pub struct Channel<'a>(pub BytesCow<'a>); +space_terminated_primitive!(Channel<'_>); +noop_validator!(Channel<'_>); #[derive(Debug, Deref, From)] -pub struct FreeText(pub Bytes); -free_text_primitive!(FreeText); -noop_validator!(FreeText); +pub struct FreeText<'a>(pub BytesCow<'a>); +free_text_primitive!(FreeText<'_>); +noop_validator!(FreeText<'_>); #[derive(Debug, Deref, From)] -pub struct Nicks(pub Vec); -space_delimited_display!(Nicks); +pub struct Nicks<'a>(pub Vec>); +space_delimited_display!(Nicks<'_>); -impl PrimitiveParser for Nicks { +impl PrimitiveParser for Nicks<'_> { fn parse(bytes: BytesWrapper) -> IResult { let mut it = iterator( bytes, @@ -212,9 +235,9 @@ impl PrimitiveParser for Nicks { } #[derive(Debug)] -pub struct RightsPrefixedNick(pub Rights, pub Nick); +pub struct RightsPrefixedNick<'a>(pub Rights, pub Nick<'a>); -impl std::fmt::Display for RightsPrefixedNick { +impl std::fmt::Display for RightsPrefixedNick<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f)?; self.1.fmt(f) @@ -222,13 +245,13 @@ impl std::fmt::Display for RightsPrefixedNick { } #[derive(Debug, Deref, From)] -pub struct RightsPrefixedNicks(pub Vec); -space_delimited_display!(RightsPrefixedNicks); +pub struct RightsPrefixedNicks<'a>(pub Vec>); +space_delimited_display!(RightsPrefixedNicks<'_>); #[derive(Debug)] -pub struct RightsPrefixedChannel(pub Rights, pub Nick); +pub struct RightsPrefixedChannel<'a>(pub Rights, pub Nick<'a>); -impl std::fmt::Display for RightsPrefixedChannel { +impl std::fmt::Display for RightsPrefixedChannel<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f)?; self.1.fmt(f) @@ -236,8 +259,8 @@ impl std::fmt::Display for RightsPrefixedChannel { } #[derive(Debug, Deref, From)] -pub struct RightsPrefixedChannels(pub Vec); -space_delimited_display!(RightsPrefixedChannels); +pub struct RightsPrefixedChannels<'a>(pub Vec>); +space_delimited_display!(RightsPrefixedChannels<'_>); #[derive(Debug)] pub enum Rights { @@ -255,12 +278,12 @@ impl std::fmt::Display for Rights { } #[derive(Debug, From)] -pub enum Receiver { - User(Nick), - Channel(Channel), +pub enum Receiver<'a> { + User(Nick<'a>), + Channel(Channel<'a>), } -impl std::ops::Deref for Receiver { +impl std::ops::Deref for Receiver<'_> { type Target = str; fn deref(&self) -> &Self::Target { @@ -272,7 +295,7 @@ impl std::ops::Deref for Receiver { } } -impl PrimitiveParser for Receiver { +impl PrimitiveParser for Receiver<'_> { fn parse(bytes: BytesWrapper) -> IResult { if bytes.get(0) == Some(&b'#') { let (rest, channel) = Channel::parse(bytes)?; diff --git a/titanirc-types/src/replies.rs b/titanirc-types/src/replies.rs index 0e8809b..e680b67 100644 --- a/titanirc-types/src/replies.rs +++ b/titanirc-types/src/replies.rs @@ -4,20 +4,20 @@ use crate::{primitives::*, Command}; use std::fmt::Write; #[derive(Debug)] -pub enum Source { - User(Nick), // change Nick to whatever type nick!user@netmask is.. +pub enum Source<'a> { + User(Nick<'a>), // change Nick to whatever type nick!user@netmask is.. Server, } #[derive(Debug, derive_more::From)] -pub enum ServerMessage { - Reply(Reply), - Command(Source, Command), // change Nick to whatever type nick!user@netmask is.. +pub enum ServerMessage<'a> { + Reply(Reply<'a>), + Command(Source<'a>, Command<'a>), // change Nick to whatever type nick!user@netmask is.. Ping, Pong, } -impl ServerMessage { +impl ServerMessage<'_> { pub fn write(self, server_name: &str, client_username: &str, dst: &mut bytes::BytesMut) { match self { Self::Reply(reply) => write!( @@ -45,18 +45,18 @@ impl ServerMessage { macro_rules! define_replies { ( $( - $name:ident$(($($arg:ty),*))? = $num:expr $(=> $msg:expr)? + $name:ident$(($($arg:ident$(<$($gen:tt),+>)?),*))? = $num:expr $(=> $msg:expr)? ),* $(,)? ) => { #[derive(Debug)] #[allow(clippy::pub_enum_variant_names)] - pub enum Reply { + pub enum Reply<'a> { $( - $name$(($($arg),*))*, + $name$(($($arg$(<$($gen),+>)?),*))*, )* } - impl std::fmt::Display for Reply { + impl std::fmt::Display for Reply<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { paste::paste! { match self { @@ -66,7 +66,7 @@ macro_rules! define_replies { } } - impl Reply { + impl Reply<'_> { #[must_use] pub fn code(&self) -> &'static str { paste::paste! { @@ -106,14 +106,14 @@ define_replies! { RplMyInfo = 004 => ":my.test.server 0.0.1 DOQRSZaghilopsuwz CFILMPQSbcefgijklmnopqrstuvz bkloveqjfI", RplISupport = 005 => "D :are supported by this server", - RplUmodeIs(Mode) = 221 => "{}", + RplUmodeIs(Mode<'a>) = 221 => "{}", - ErrNoSuchNick(Nick) = 401 => "{} :No such nick/channel", - ErrNoSuchServer(ServerName) = 402 => "{} :No such server", - ErrNoSuchChannel(Channel) = 403 => "{} :No such channel", - ErrCannotSendToChan(Channel) = 404 => "{} :Cannot send to channel", - ErrTooManyChannels(Channel) = 405 => "{} :You have joined too many channels", - ErrWasNoSuchNick(Nick) = 406 => "{} :There was no such nickname", + ErrNoSuchNick(Nick<'a>) = 401 => "{} :No such nick/channel", + ErrNoSuchServer(ServerName<'a>) = 402 => "{} :No such server", + ErrNoSuchChannel(Channel<'a>) = 403 => "{} :No such channel", + ErrCannotSendToChan(Channel<'a>) = 404 => "{} :Cannot send to channel", + ErrTooManyChannels(Channel<'a>) = 405 => "{} :You have joined too many channels", + ErrWasNoSuchNick(Nick<'a>) = 406 => "{} :There was no such nickname", ErrTooManyTargets(Target) = 407 => "{} :Duplicate recipients. No message delivered", ErrNoOrigin = 409 => ":No origin specified", ErrNoRecipient(CommandName) = 411 => ":No recipient given ({})", @@ -122,16 +122,16 @@ define_replies! { ErrWildTopLevel(Mask) = 414 => "{} :Wildcard in toplevel domain", ErrUnknownCommand(CommandName) = 421 => "{} :Unknown command", ErrNoMotd = 422 => ":MOTD File is missing", - ErrNoAdminInfo(ServerName) = 423 => "{} :No administrative info available", + ErrNoAdminInfo(ServerName<'a>) = 423 => "{} :No administrative info available", ErrFileError(FileOp, File) = 424 => ":File error doing {} on {}", ErrNoNickGiven = 431 => ":No nickname given", - ErrErroneusNick(Nick) = 432 => "{} :Erroneus nickname", - ErrNickInUse(Nick) = 433 => "{} :Nick is already in use", - ErrNickCollision(Nick) = 436 => "{} :Nick collision KILL", - ErrUserNotInChannel(Nick, Channel) = 441 => "{} {} :They aren't on that channel", - ErrNotOnChannel(Channel) = 442 => "{} :You're not on that channel", - ErrUserOnChannel(Username, Channel) = 443 => "{} {} :is already on channel", - ErrNoLogin(Username) = 444 => "{} :User not logged in", + ErrErroneusNick(Nick<'a>) = 432 => "{} :Erroneus nickname", + ErrNickInUse(Nick<'a>) = 433 => "{} :Nick is already in use", + ErrNickCollision(Nick<'a>) = 436 => "{} :Nick collision KILL", + ErrUserNotInChannel(Nick<'a>, Channel<'a>) = 441 => "{} {} :They aren't on that channel", + ErrNotOnChannel(Channel<'a>) = 442 => "{} :You're not on that channel", + ErrUserOnChannel(Username<'a>, Channel<'a>) = 443 => "{} {} :is already on channel", + ErrNoLogin(Username<'a>) = 444 => "{} :User not logged in", ErrSummonDisabled = 445 => ":SUMMON has been disabled", ErrUsersDisabled = 446 => ":USERS has been disabled", ErrNotRegistered = 451 => ":You have not registered", @@ -140,52 +140,52 @@ define_replies! { ErrNoPermForHost = 463 => ":Your host isn't among the privileged", ErrPasswdMismatch = 464 => ":Password incorrect", ErrYoureBannedCreep = 465 => ":You are banned from this server", - ErrKeySet(Channel) = 467 => "{} :Channel key already set", - ErrChannelIsFull(Channel) = 471 => "{} :Cannot join channel (+l)", + ErrKeySet(Channel<'a>) = 467 => "{} :Channel key already set", + ErrChannelIsFull(Channel<'a>) = 471 => "{} :Cannot join channel (+l)", ErrUnknownMode(Char) = 472 => "{} :is unknown mode char to me", - ErrInviteOnlyChan(Channel) = 473 => "{} :Cannot join channel (+i)", - ErrBannedFromChan(Channel) = 474 => "{} :Cannot join channel (+b)", - ErrBadChannelKey(Channel) = 475 => "{} :Cannot join channel (+k)", + ErrInviteOnlyChan(Channel<'a>) = 473 => "{} :Cannot join channel (+i)", + ErrBannedFromChan(Channel<'a>) = 474 => "{} :Cannot join channel (+b)", + ErrBadChannelKey(Channel<'a>) = 475 => "{} :Cannot join channel (+k)", ErrNoPrivileges = 481 => ":Permission Denied- You're not an IRC operator", - ErrChanOPrivsNeeded(Channel) = 482 => "{} :You're not channel operator", + ErrChanOPrivsNeeded(Channel<'a>) = 482 => "{} :You're not channel operator", ErrCantKillServer = 483 => ":You cant kill a server!", ErrNoOperHost = 491 => ":No O-lines for your host", ErrUmodeUnknownFlag = 501 => ":Unknown MODE flag", ErrUsersDontMatch = 502 => ":Cant change mode for other users", RplNone = 300, RplUserHost(UserHost) = 302 => "{}", - RplIson(Nicks) = 303 => "{}", - RplAway(Nick, FreeText) = 301 => "{} :{}", + RplIson(Nicks<'a>) = 303 => "{}", + RplAway(Nick<'a>, FreeText<'a>) = 301 => "{} :{}", RplUnaway = 305 => ":You are no longer marked as being away", RplNowAway = 306 => ":You have been marked as being away", - RplWhoisUser(Nick, Username, HostName, RealName) = 311 => "{} {} {} * :{}", - RplWhoisServer(Nick, ServerName, ServerInfo) = 312 => "{} {} :{}", - RplWhoisOperator(Nick) = 313 => "{} :is an IRC operator", - RplWhoisIdle(Nick, Integer) = 317 => "{} {} :seconds idle", - RplEndOfWhois(Nick) = 318 => "{} :End of /WHOIS list", - RplWhoisChannels(Nick, RightsPrefixedChannels) = 319 => "{} :{}", // todo - RplWhoWasUser(Nick, Username, HostName, RealName) = 314 => "{} {} {} * :{}", - RplEndOfWhoWas(Nick) = 369 => "{} :End of WHOWAS", + RplWhoisUser(Nick<'a>, Username<'a>, HostName<'a>, RealName<'a>) = 311 => "{} {} {} * :{}", + RplWhoisServer(Nick<'a>, ServerName<'a>, ServerInfo) = 312 => "{} {} :{}", + RplWhoisOperator(Nick<'a>) = 313 => "{} :is an IRC operator", + RplWhoisIdle(Nick<'a>, Integer) = 317 => "{} {} :seconds idle", + RplEndOfWhois(Nick<'a>) = 318 => "{} :End of /WHOIS list", + RplWhoisChannels(Nick<'a>, RightsPrefixedChannels<'a>) = 319 => "{} :{}", // todo + RplWhoWasUser(Nick<'a>, Username<'a>, HostName<'a>, RealName<'a>) = 314 => "{} {} {} * :{}", + RplEndOfWhoWas(Nick<'a>) = 369 => "{} :End of WHOWAS", RplListStart = 321 => "Channel :Users RealName", - RplList(Channel, AmtVisible, FreeText) = 322 => "{} {} :{}", + RplList(Channel<'a>, AmtVisible, FreeText<'a>) = 322 => "{} {} :{}", RplListEnd = 323 => ":End of /LIST", - RplChannelModeIs(Channel, Mode, ModeParams) = 324 => "{} {} {}", - RplNoTopic(Channel) = 331 => "{} :No topic is set", - RplTopic(Channel, FreeText) = 332 => "{} :{}", - RplInviting(Channel, Nick) = 341 => "{} {}", - RplVersion(Version, Debuglevel, ServerName, FreeText) = 351 => "{}.{} {} :{}", - RplWhoReply(Channel, Username, HostName, ServerName, Nick, HG, Hopcount, RealName) = 352 => "{} {} {} {} {} {}[*][@|+] :{} {}", + RplChannelModeIs(Channel<'a>, Mode<'a>, ModeParams) = 324 => "{} {} {}", + RplNoTopic(Channel<'a>) = 331 => "{} :No topic is set", + RplTopic(Channel<'a>, FreeText<'a>) = 332 => "{} :{}", + RplInviting(Channel<'a>, Nick<'a>) = 341 => "{} {}", + RplVersion(Version, Debuglevel, ServerName<'a>, FreeText<'a>) = 351 => "{}.{} {} :{}", + RplWhoReply(Channel<'a>, Username<'a>, HostName<'a>, ServerName<'a>, Nick<'a>, HG, Hopcount, RealName<'a>) = 352 => "{} {} {} {} {} {}[*][@|+] :{} {}", RplEndOfWho(Target) = 315 => "{} :End of /WHO list", - RplNamReply(Channel, RightsPrefixedNicks) = 353 => "{} :{}", - RplEndOfNames(Channel) = 366 => "{} :End of /NAMES list", - RplLinks(Mask, ServerName, Hopcount, ServerInfo) = 364 => "{} {} :{} {}", + RplNamReply(Channel<'a>, RightsPrefixedNicks<'a>) = 353 => "{} :{}", + RplEndOfNames(Channel<'a>) = 366 => "{} :End of /NAMES list", + RplLinks(Mask, ServerName<'a>, Hopcount, ServerInfo) = 364 => "{} {} :{} {}", RplEndOfLinks(Mask) = 365 => "{} :End of /LINKS list", - RplBanList(Channel, Banid) = 367 => "{} {}", - RPLEndOfBanList(Channel) = 368 => "{} :End of channel ban list", + RplBanList(Channel<'a>, Banid) = 367 => "{} {}", + RPLEndOfBanList(Channel<'a>) = 368 => "{} :End of channel ban list", RplInfo(String) = 371 => ":{}", RplEndOfInfo = 374 => ":End of /INFO list", - RplMotdStart(ServerName) = 375 => ":- {} Message of the day -", - RplMotd(FreeText) = 372 => ":- {}", + RplMotdStart(ServerName<'a>) = 375 => ":- {} Message of the day -", + RplMotd(FreeText<'a>) = 372 => ":- {}", RplEndOfMotd = 376 => ":End of /MOTD command", RplYoureOper = 381 => ":You are now an IRC operator", RplRehashing(ConfigFile) = 382 => "{} :Rehashing", -- libgit2 1.7.2