From 3314c93c3e67547b170c97c8c2df762312452913 Mon Sep 17 00:00:00 2001 From: Aaron Weiss Date: Wed, 10 Feb 2016 00:15:08 -0500 Subject: [PATCH] Implemented a major redesign to simplify API and eliminate overly-complicated generics usage. --- src/client/conn.rs | 264 +++++++++++++++++++++++-------------- src/client/data/config.rs | 2 +- src/client/data/mod.rs | 1 + src/client/server/mod.rs | 109 +++++++-------- src/client/server/utils.rs | 5 +- 5 files changed, 214 insertions(+), 167 deletions(-) diff --git a/src/client/conn.rs b/src/client/conn.rs index de773d9..98bc350 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -1,35 +1,71 @@ //! Thread-safe connections on IrcStreams. #[cfg(feature = "ssl")] use std::error::Error as StdError; use std::io::prelude::*; -use std::io::{BufReader, BufWriter, Cursor, Empty, Result, Sink}; +use std::io::{BufReader, BufWriter, Cursor, Result}; use std::io::Error; use std::io::ErrorKind; use std::net::TcpStream; #[cfg(feature = "ssl")] use std::result::Result as StdResult; -use std::sync::{Mutex, MutexGuard}; +use std::sync::Mutex; #[cfg(feature = "encode")] use encoding::{DecoderTrap, EncoderTrap, Encoding}; #[cfg(feature = "encode")] use encoding::label::encoding_from_whatwg_label; -use client::data::Message; -use client::data::kinds::{IrcRead, IrcWrite}; #[cfg(feature = "ssl")] use openssl::ssl::{SslContext, SslMethod, SslStream}; #[cfg(feature = "ssl")] use openssl::ssl::error::SslError; -/// A thread-safe connection. -pub struct Connection { - reader: Mutex, - writer: Mutex, +/// A connection. +pub trait Connection { + /// Sends a message over this connection. + #[cfg(feature = "encode")] + fn send(&self, msg: &str, encoding: &str) -> Result<()>; + + /// Sends a message over this connection. + #[cfg(not(feature = "encode"))] + fn send(&self, msg: &str) -> Result<()>; + + /// Receives a single line from this connection. + #[cfg(feature = "encoding")] + fn recv(&self, encoding: &str) -> Result; + + /// Receives a single line from this connection. + #[cfg(not(feature = "encoding"))] + fn recv(&self) -> Result; + + /// Gets the full record of all sent messages if the Connection records this. + /// This is intended for use in writing tests. + fn written(&self) -> Option; + + /// Re-establishes this connection, disconnecting from the existing case if necessary. + fn reconnect(&self) -> Result<()>; } -/// A Connection over a buffered NetStream. -pub type NetConnection = Connection, BufWriter>; -/// An internal type -type NetReadWritePair = (BufReader, BufWriter); -impl Connection, BufWriter> { +/// Useful internal type definitions. +type NetReader = BufReader; +type NetWriter = BufWriter; +type NetReadWritePair = (NetReader, NetWriter); + +/// A thread-safe connection over a buffered NetStream. +pub struct NetConnection { + host: Mutex, + port: Mutex, + reader: Mutex, + writer: Mutex, +} + +impl NetConnection { + fn new(host: &str, port: u16, reader: NetReader, writer: NetWriter) -> NetConnection { + NetConnection { + host: Mutex::new(host.to_owned()), + port: Mutex::new(port), + reader: Mutex::new(reader), + writer: Mutex::new(writer), + } + } + /// Creates a thread-safe TCP connection to the specified server. pub fn connect(host: &str, port: u16) -> Result { - let (reader, writer) = try!(Connection::connect_internal(host, port)); - Ok(Connection::new(reader, writer)) + let (reader, writer) = try!(NetConnection::connect_internal(host, port)); + Ok(NetConnection::new(host, port, reader, writer)) } /// connects to the specified server and returns a reader-writer pair. @@ -42,8 +78,8 @@ impl Connection, BufWriter> { /// Creates a thread-safe TCP connection to the specified server over SSL. /// If the library is compiled without SSL support, this method panics. pub fn connect_ssl(host: &str, port: u16) -> Result { - let (reader, writer) = try!(Connection::connect_ssl_internal(host, port)); - Ok(Connection::new(reader, writer)) + let (reader, writer) = try!(NetConnection::connect_ssl_internal(host, port)); + Ok(NetConnection::new(host, port, reader, writer)) } /// Connects over SSL to the specified server and returns a reader-writer pair. @@ -61,46 +97,29 @@ impl Connection, BufWriter> { fn connect_ssl_internal(host: &str, port: u16) -> Result { panic!("Cannot connect to {}:{} over SSL without compiling with SSL support.", host, port) } - - /* - FIXME: removed until set_keepalive is stabilized. - /// Sets the keepalive for the network stream. - #[unstable = "Rust IO has not stabilized."] - pub fn set_keepalive(&self, delay_in_seconds: Option) -> Result<()> { - self.mod_stream(|tcp| tcp.set_keepalive(delay_in_seconds)) - } - - /// Modifies the internal TcpStream using a function. - fn mod_stream(&self, f: F) -> Result<()> where F: FnOnce(&mut TcpStream) -> Result<()> { - match self.reader.lock().unwrap().get_mut() { - &mut NetStream::UnsecuredTcpStream(ref mut tcp) => f(tcp), - #[cfg(feature = "ssl")] - &mut NetStream::SslTcpStream(ref mut ssl) => f(ssl.get_mut()), - } - } - */ } -impl Connection { - /// Creates a new connection from an IrcReader and an IrcWriter. - pub fn new(reader: T, writer: U) -> Connection { - Connection { - reader: Mutex::new(reader), - writer: Mutex::new(writer), - } +/// Converts a Result into an Result. +#[cfg(feature = "ssl")] +fn ssl_to_io(res: StdResult) -> Result { + match res { + Ok(x) => Ok(x), + Err(e) => Err(Error::new(ErrorKind::Other, + &format!("An SSL error occurred. ({})", e.description())[..] + )), } +} - /// Sends a Message over this connection. +impl Connection for NetConnection { #[cfg(feature = "encode")] - pub fn send>(&self, to_msg: M, encoding: &str) -> Result<()> { + fn send(&self, msg: &str, encoding: &str) -> Result<()> { let encoding = match encoding_from_whatwg_label(encoding) { Some(enc) => enc, None => return Err(Error::new( ErrorKind::InvalidInput, &format!("Failed to find encoder. ({})", encoding)[..] )) }; - let msg: Message = to_msg.into(); - let data = match encoding.encode(&msg.into_string(), EncoderTrap::Replace) { + let data = match encoding.encode(msg, EncoderTrap::Replace) { Ok(data) => data, Err(data) => return Err(Error::new(ErrorKind::InvalidInput, &format!("Failed to encode {} as {}.", data, encoding.name())[..] @@ -111,18 +130,15 @@ impl Connection { writer.flush() } - /// Sends a message over this connection. #[cfg(not(feature = "encode"))] - pub fn send>(&self, to_msg: M) -> Result<()> { + fn send(&self, msg: &str) -> Result<()> { let mut writer = self.writer.lock().unwrap(); - let msg: Message = to_msg.into(); - try!(writer.write_all(&msg.into_string().as_bytes())); + try!(writer.write_all(msg.as_bytes())); writer.flush() } - /// Receives a single line from this connection. #[cfg(feature = "encoding")] - pub fn recv(&self, encoding: &str) -> Result { + fn recv(&self, encoding: &str) -> Result { let encoding = match encoding_from_whatwg_label(encoding) { Some(enc) => enc, None => return Err(Error::new( @@ -141,9 +157,8 @@ impl Connection { ) } - /// Receives a single line from this connection. #[cfg(not(feature = "encoding"))] - pub fn recv(&self) -> Result { + fn recv(&self) -> Result { let mut ret = String::new(); try!(self.reader.lock().unwrap().read_line(&mut ret)); if ret.is_empty() { @@ -153,55 +168,22 @@ impl Connection { } } - /// Acquires the Reader lock. - pub fn reader<'a>(&'a self) -> MutexGuard<'a, T> { - self.reader.lock().unwrap() + fn written(&self) -> Option { + None } - /// Acquires the Writer lock. - pub fn writer<'a>(&'a self) -> MutexGuard<'a, U> { - self.writer.lock().unwrap() - } -} - -/// Converts a Result into an Result. -#[cfg(feature = "ssl")] -fn ssl_to_io(res: StdResult) -> Result { - match res { - Ok(x) => Ok(x), - Err(e) => Err(Error::new(ErrorKind::Other, - &format!("An SSL error occurred. ({})", e.description())[..] - )), - } -} - -/// A trait defining the ability to reconnect. -pub trait Reconnect { - /// Reconnects to the specified host and port, dropping the current connection if necessary. - fn reconnect(&self, host: &str, port: u16) -> Result<()>; -} - -macro_rules! noop_reconnect { - ($T:ty, $U:ty) => { - impl Reconnect for Connection<$T, $U> { - fn reconnect(&self, _: &str, _: u16) -> Result<()> { - Ok(()) - } - } - } -} - -impl Reconnect for NetConnection { - fn reconnect(&self, host: &str, port: u16) -> Result<()> { + fn reconnect(&self) -> Result<()> { let use_ssl = match self.reader.lock().unwrap().get_ref() { &NetStream::UnsecuredTcpStream(_) => false, #[cfg(feature = "ssl")] &NetStream::SslTcpStream(_) => true, }; + let host = self.host.lock().unwrap(); + let port = self.port.lock().unwrap(); let (reader, writer) = if use_ssl { - try!(Connection::connect_ssl_internal(host, port)) + try!(NetConnection::connect_ssl_internal(&host, *port)) } else { - try!(Connection::connect_internal(host, port)) + try!(NetConnection::connect_internal(&host, *port)) }; *self.reader.lock().unwrap() = reader; *self.writer.lock().unwrap() = writer; @@ -209,11 +191,95 @@ impl Reconnect for NetConnection { } } -// TODO: replace all this with specialization when possible. :\ -noop_reconnect!(Cursor>, Vec); -noop_reconnect!(Cursor>, Sink); -noop_reconnect!(BufReader, Vec); -noop_reconnect!(BufReader, Sink); +/// A mock connection for testing purposes. +pub struct MockConnection { + reader: Mutex>>, + writer: Mutex>, +} + +impl MockConnection { + /// Creates a new mock connection with the specified string in the read buffer. + pub fn new(input: &str) -> MockConnection { + MockConnection::from_byte_vec(input.as_bytes().to_vec()) + } + + /// Creates a new mock connection with the specified bytes in the read buffer. + pub fn from_byte_vec(input: Vec) -> MockConnection { + MockConnection { + reader: Mutex::new(Cursor::new(input)), + writer: Mutex::new(Vec::new()), + } + } +} + +impl Connection for MockConnection { + #[cfg(feature = "encode")] + fn send(&self, msg: &str, encoding: &str) -> Result<()> { + let encoding = match encoding_from_whatwg_label(encoding) { + Some(enc) => enc, + None => return Err(Error::new( + ErrorKind::InvalidInput, &format!("Failed to find encoder. ({})", encoding)[..] + )) + }; + let data = match encoding.encode(msg, EncoderTrap::Replace) { + Ok(data) => data, + Err(data) => return Err(Error::new(ErrorKind::InvalidInput, + &format!("Failed to encode {} as {}.", data, encoding.name())[..] + )) + }; + let mut writer = self.writer.lock().unwrap(); + try!(writer.write_all(&data)); + writer.flush() + } + + #[cfg(not(feature = "encode"))] + fn send(&self, msg: &str) -> Result<()> { + let mut writer = self.writer.lock().unwrap(); + try!(writer.write_all(msg.as_bytes())); + writer.flush() + } + + #[cfg(feature = "encoding")] + fn recv(&self, encoding: &str) -> Result { + let encoding = match encoding_from_whatwg_label(encoding) { + Some(enc) => enc, + None => return Err(Error::new( + ErrorKind::InvalidInput, &format!("Failed to find decoder. ({})", encoding)[..] + )) + }; + let mut buf = Vec::new(); + self.reader.lock().unwrap().read_until(b'\n', &mut buf).and_then(|_| + match encoding.decode(&buf, DecoderTrap::Replace) { + _ if buf.is_empty() => Err(Error::new(ErrorKind::Other, "EOF")), + Ok(data) => Ok(data), + Err(data) => return Err(Error::new(ErrorKind::InvalidInput, + &format!("Failed to decode {} as {}.", data, encoding.name())[..] + )) + } + ) + } + + #[cfg(not(feature = "encoding"))] + fn recv(&self) -> Result { + let mut ret = String::new(); + try!(self.reader.lock().unwrap().read_line(&mut ret)); + if ret.is_empty() { + Err(Error::new(ErrorKind::Other, "EOF")) + } else { + Ok(ret) + } + } + + fn written(&self) -> Option { + String::from_utf8(self.writer.lock().unwrap().clone()).ok() + } + + fn reconnect(&self) -> Result<()> { + Ok(()) + } +} + + /// An abstraction over different networked streams. pub enum NetStream { diff --git a/src/client/data/config.rs b/src/client/data/config.rs index d1d14f1..373967d 100644 --- a/src/client/data/config.rs +++ b/src/client/data/config.rs @@ -174,7 +174,7 @@ impl Config { self.ping_timeout.as_ref().map(|t| *t).unwrap_or(10) } - /// Gets whether or not to use NickServ GHOST + /// Gets whether or not to attempt nickname reclamation using NickServ GHOST. /// This defaults to false when not specified. pub fn should_ghost(&self) -> bool { self.should_ghost.as_ref().map(|u| *u).unwrap_or(false) diff --git a/src/client/data/mod.rs b/src/client/data/mod.rs index b499d18..acc78ec 100644 --- a/src/client/data/mod.rs +++ b/src/client/data/mod.rs @@ -14,6 +14,7 @@ pub mod kinds { /// Trait describing all possible Writers for this library. pub trait IrcWrite: Write + Sized + Send + 'static {} impl IrcWrite for T where T: Write + Sized + Send + 'static {} + /// Trait describing all possible Readers for this library. pub trait IrcRead: BufRead + Sized + Send + 'static {} impl IrcRead for T where T: BufRead + Sized + Send + 'static {} diff --git a/src/client/server/mod.rs b/src/client/server/mod.rs index b80b3cd..cd325f2 100644 --- a/src/client/server/mod.rs +++ b/src/client/server/mod.rs @@ -5,28 +5,30 @@ use std::borrow::ToOwned; use std::cell::Cell; use std::collections::HashMap; use std::error::Error as StdError; -use std::io::{BufReader, BufWriter, Error, ErrorKind, Result}; +use std::io::{Error, ErrorKind, Result}; use std::path::Path; use std::sync::{Arc, Mutex, RwLock}; use std::sync::mpsc::{Receiver, Sender, TryRecvError, channel}; use std::thread::{JoinHandle, spawn}; -use client::conn::{Connection, NetStream, Reconnect}; +use client::conn::{Connection, NetConnection}; use client::data::{Command, Config, Message, Response, User}; use client::data::Command::{JOIN, NICK, NICKSERV, PART, PING, PRIVMSG, MODE}; -use client::data::kinds::{IrcRead, IrcWrite}; use client::server::utils::ServerExt; use time::{Duration, Timespec, Tm, now}; pub mod utils; -/// Trait describing core Server functionality. -pub trait Server<'a, T: IrcRead, U: IrcWrite> { +/// An interface for interacting with an IRC server. +pub trait Server { /// Gets the configuration being used with this Server. fn config(&self) -> &Config; + /// Sends a Command to this Server. fn send>(&self, message: M) -> Result<()> where Self: Sized; - /// Gets an Iterator over Messages received by this Server. - fn iter(&'a self) -> ServerIterator<'a, T, U>; + + /// Gets an iterator over received messages. + fn iter<'a>(&'a self) -> Box> + 'a>; + /// Gets a list of Users in the specified channel. This will be none if the channel is not /// being tracked, or if tracking is not supported altogether. For best results, be sure to /// request `multi-prefix` support from the server. @@ -34,21 +36,21 @@ pub trait Server<'a, T: IrcRead, U: IrcWrite> { } /// A thread-safe implementation of an IRC Server connection. -pub struct IrcServer { +pub struct IrcServer { /// The channel for sending messages to write. tx: Sender, /// The internal, thread-safe server state. - state: Arc>, + state: Arc, /// A thread-local count of reconnection attempts used for synchronization. reconnect_count: Cell, } /// Thread-safe internal state for an IRC server connection. -struct ServerState { +struct ServerState { /// A global copy of the channel for sending messages to write. tx: Mutex>>, /// The thread-safe IRC connection. - conn: Connection, + conn: Box, /// The handle for the message sending thread. write_handle: Mutex>>, /// The configuration used with this connection. @@ -65,11 +67,11 @@ struct ServerState { last_ping_data: Mutex>, } -impl ServerState where Connection: Reconnect { - fn new(conn: Connection, config: Config) -> ServerState { +impl ServerState { + fn new(conn: C, config: Config) -> ServerState where C: Connection + Send + Sync + 'static { ServerState { tx: Mutex::new(None), - conn: conn, + conn: Box::new(conn), write_handle: Mutex::new(None), config: config, chanlists: Mutex::new(HashMap::new()), @@ -81,7 +83,7 @@ impl ServerState where Connection: Reconnec } fn reconnect(&self) -> Result<()> { - self.conn.reconnect(self.config.server(), self.config.port()) + self.conn.reconnect() } fn action_taken(&self) { @@ -108,30 +110,27 @@ impl ServerState where Connection: Reconnec } } -/// An IrcServer over a buffered NetStream. -pub type NetIrcServer = IrcServer, BufWriter>; - -impl IrcServer, BufWriter> { +impl IrcServer { /// Creates a new IRC Server connection from the configuration at the specified path, /// connecting immediately. - pub fn new>(config: P) -> Result { + pub fn new>(config: P) -> Result { IrcServer::from_config(try!(Config::load(config))) } /// Creates a new IRC server connection from the specified configuration, connecting /// immediately. - pub fn from_config(config: Config) -> Result { + pub fn from_config(config: Config) -> Result { let conn = try!(if config.use_ssl() { - Connection::connect_ssl(config.server(), config.port()) + NetConnection::connect_ssl(config.server(), config.port()) } else { - Connection::connect(config.server(), config.port()) + NetConnection::connect(config.server(), config.port()) }); Ok(IrcServer::from_connection(config, conn)) } } -impl Clone for IrcServer { - fn clone(&self) -> IrcServer { +impl Clone for IrcServer { + fn clone(&self) -> IrcServer { IrcServer { tx: self.tx.clone(), state: self.state.clone(), @@ -140,7 +139,7 @@ impl Clone for IrcServer { } } -impl Drop for ServerState { +impl Drop for ServerState { fn drop(&mut self) { let _ = self.tx.lock().unwrap().take(); let mut guard = self.write_handle.lock().unwrap(); @@ -150,7 +149,7 @@ impl Drop for ServerState { } } -impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for ServerState where Connection: Reconnect { +impl<'a> Server for ServerState { fn config(&self) -> &Config { &self.config } @@ -164,7 +163,7 @@ impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for ServerState where C } } - fn iter(&'a self) -> ServerIterator<'a, T, U> { + fn iter(&self) -> Box>> { panic!("unimplemented") } @@ -180,7 +179,7 @@ impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for ServerState where C } } -impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for IrcServer where Connection: Reconnect { +impl Server for IrcServer { fn config(&self) -> &Config { &self.state.config } @@ -189,8 +188,8 @@ impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for IrcServer where Con self.tx.send(msg.into()).map_err(|e| Error::new(ErrorKind::Other, e)) } - fn iter(&'a self) -> ServerIterator<'a, T, U> { - ServerIterator::new(self) + fn iter<'a>(&'a self) -> Box> + 'a> { + Box::new(ServerIterator::new(self)) } #[cfg(not(feature = "nochanlists"))] @@ -205,9 +204,10 @@ impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for IrcServer where Con } } -impl IrcServer where Connection: Reconnect { - /// Creates an IRC server from the specified configuration, and any arbitrary Connection. - pub fn from_connection(config: Config, conn: Connection) -> IrcServer { +impl IrcServer { + /// Creates an IRC server from the specified configuration, and any arbitrary sync connection. + pub fn from_connection(config: Config, conn: C) -> IrcServer + where C: Connection + Send + Sync + 'static { let (tx, rx): (Sender, Receiver) = channel(); let state = Arc::new(ServerState::new(conn, config)); let weak = Arc::downgrade(&state); @@ -251,11 +251,11 @@ impl IrcServer where Connection: Reconnect } /// Gets a reference to the IRC server's connection. - pub fn conn(&self) -> &Connection { + pub fn conn(&self) -> &Box { &self.state.conn } - /// Reconnects to the IRC server. + /// Reconnects to the IRC server, disconnecting if necessary. pub fn reconnect(&self) -> Result<()> { let mut reconnect_count = self.state.reconnect_count.lock().unwrap(); let res = if self.reconnect_count.get() == *reconnect_count { @@ -269,13 +269,13 @@ impl IrcServer where Connection: Reconnect } #[cfg(feature = "encode")] - fn write>(state: &Arc>, msg: M) -> Result<()> { - state.conn.send(msg, state.config.encoding()) + fn write>(state: &Arc, msg: M) -> Result<()> { + state.conn.send(&msg.into().into_string(), state.config.encoding()) } #[cfg(not(feature = "encode"))] - fn write>(state: &Arc>, msg: M) -> Result<()> { - state.conn.send(msg) + fn write>(state: &Arc, msg: M) -> Result<()> { + state.conn.send(&msg.into().into_string()) } /// Returns a reference to the server state's channel lists. @@ -415,33 +415,14 @@ impl IrcServer where Connection: Reconnect } } -impl IrcServer where Connection: Reconnect { - /// Returns a copy of the server's connection after waiting for all pending messages to be - /// written. This function may cause unusual behavior when called on a server with operations - /// being performed on other threads. This function is destructive, and is primarily intended - /// for writing unit tests. Use it with care. - pub fn extract_writer(mut self) -> U { - let _ = self.state.tx.lock().unwrap().take(); - // This is a terrible hack to get the real channel to drop. - // Otherwise, joining would never finish. - let (tx, _) = channel(); - self.tx = tx; - let mut guard = self.state.write_handle.lock().unwrap(); - if let Some(handle) = guard.take() { - handle.join().unwrap() - } - self.conn().writer().clone() - } -} - /// An Iterator over an IrcServer's incoming Messages. -pub struct ServerIterator<'a, T: IrcRead + 'a, U: IrcWrite + 'a> { - server: &'a IrcServer +pub struct ServerIterator<'a> { + server: &'a IrcServer } -impl<'a, T: IrcRead + 'a, U: IrcWrite + 'a> ServerIterator<'a, T, U> where Connection: Reconnect { +impl<'a> ServerIterator<'a> { /// Creates a new ServerIterator for the desired IrcServer. - pub fn new(server: &IrcServer) -> ServerIterator { + pub fn new(server: &'a IrcServer) -> ServerIterator { ServerIterator { server: server } } @@ -458,7 +439,7 @@ impl<'a, T: IrcRead + 'a, U: IrcWrite + 'a> ServerIterator<'a, T, U> where Conne } } -impl<'a, T: IrcRead + 'a, U: IrcWrite + 'a> Iterator for ServerIterator<'a, T, U> where Connection: Reconnect { +impl<'a> Iterator for ServerIterator<'a> { type Item = Result; fn next(&mut self) -> Option> { loop { diff --git a/src/client/server/utils.rs b/src/client/server/utils.rs index 754e83b..7c4668f 100644 --- a/src/client/server/utils.rs +++ b/src/client/server/utils.rs @@ -5,12 +5,11 @@ use client::data::{Capability, NegotiationVersion}; use client::data::Command::{AUTHENTICATE, CAP, INVITE, JOIN, KICK, KILL, MODE, NICK, NOTICE}; use client::data::Command::{OPER, PASS, PONG, PRIVMSG, QUIT, SAMODE, SANICK, TOPIC, USER}; use client::data::command::CapSubCommand::{END, LS, REQ}; -use client::data::kinds::{IrcRead, IrcWrite}; #[cfg(feature = "ctcp")] use time::get_time; use client::server::Server; /// Extensions for Server capabilities that make it easier to work directly with the protocol. -pub trait ServerExt<'a, T: IrcRead, U: IrcWrite>: Server<'a, T, U> { +pub trait ServerExt: Server { /// Sends a request for a list of server capabilities for a specific IRCv3 version. fn send_cap_ls(&self, version: NegotiationVersion) -> Result<()> where Self: Sized { self.send(CAP(None, LS, match version { @@ -219,7 +218,7 @@ pub trait ServerExt<'a, T: IrcRead, U: IrcWrite>: Server<'a, T, U> { } } -impl<'a, T: IrcRead, U: IrcWrite, K: Server<'a, T, U>> ServerExt<'a, T, U> for K {} +impl ServerExt for S where S: Server {} #[cfg(test)] mod test {