forked from lavina/lavina
- added trait to handle incoming commands from client
- implemented handle trait for whois command
This commit is contained in:
parent
5b30312238
commit
07f2730c8f
|
@ -0,0 +1,17 @@
|
||||||
|
use lavina_core::prelude::Str;
|
||||||
|
use lavina_core::repo::Storage;
|
||||||
|
use std::future::Future;
|
||||||
|
use tokio::io::AsyncWrite;
|
||||||
|
use proto_irc::response::SendResponseBody;
|
||||||
|
|
||||||
|
pub mod whois;
|
||||||
|
|
||||||
|
pub trait Handler<Command> {
|
||||||
|
fn handle(
|
||||||
|
&self,
|
||||||
|
server_name: &Str,
|
||||||
|
client: &Str,
|
||||||
|
writer: &mut (impl AsyncWrite + Unpin),
|
||||||
|
storage: &mut Storage,
|
||||||
|
) -> impl Future<Output = anyhow::Result<()>>;
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
|
use lavina_core::prelude::Str;
|
||||||
|
use proto_irc::response::SendResponseBody;
|
||||||
|
|
||||||
|
pub struct ERR_NOSUCHNICK_401 {
|
||||||
|
client: Str,
|
||||||
|
nick: Str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ERR_NOSUCHNICK_401 {
|
||||||
|
pub fn new(client: Str, nick: Str) -> Self {
|
||||||
|
ERR_NOSUCHNICK_401 { client, nick }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ERR_NOSUCHSERVER_402 {
|
||||||
|
client: Str,
|
||||||
|
/// target parameter in WHOIS
|
||||||
|
/// example: `/whois <target> <nick>`
|
||||||
|
server_name: Str,
|
||||||
|
}
|
||||||
|
pub struct ERR_NONICKNAMEGIVEN_431 {
|
||||||
|
client: Str,
|
||||||
|
}
|
||||||
|
impl ERR_NONICKNAMEGIVEN_431 {
|
||||||
|
pub fn new(client: Str) -> Self {
|
||||||
|
ERR_NONICKNAMEGIVEN_431 { client }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendResponseBody for ERR_NOSUCHNICK_401 {
|
||||||
|
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
||||||
|
writer.write_all(b"401 ").await?;
|
||||||
|
writer.write_all(self.client.as_bytes()).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all(self.nick.as_bytes()).await?;
|
||||||
|
writer.write_all(b" :").await?;
|
||||||
|
writer.write_all("No such nick/channel".as_bytes()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendResponseBody for ERR_NONICKNAMEGIVEN_431 {
|
||||||
|
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
||||||
|
writer.write_all(b"431").await?;
|
||||||
|
writer.write_all(self.client.as_bytes()).await?;
|
||||||
|
writer.write_all(b" :").await?;
|
||||||
|
writer.write_all("No nickname given".as_bytes()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendResponseBody for ERR_NOSUCHSERVER_402 {
|
||||||
|
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
||||||
|
writer.write_all(b"402 ").await?;
|
||||||
|
writer.write_all(self.client.as_bytes()).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all(self.server_name.as_bytes()).await?;
|
||||||
|
writer.write_all(b" :").await?;
|
||||||
|
writer.write_all("No such server".as_bytes()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
use tracing::instrument::WithSubscriber;
|
||||||
|
|
||||||
|
use lavina_core::prelude::Str;
|
||||||
|
use lavina_core::repo::Storage;
|
||||||
|
use proto_irc::client::command_args::Whois;
|
||||||
|
use proto_irc::response::{IrcResponseMessage, SendResponseBody, SendResponseMessage};
|
||||||
|
|
||||||
|
use crate::commands::whois::error::{ERR_NONICKNAMEGIVEN_431, ERR_NOSUCHNICK_401};
|
||||||
|
use crate::commands::whois::response::{RplWhoIsUser311, RPL_ENDOFWHOIS_318};
|
||||||
|
use crate::commands::Handler;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
pub mod response;
|
||||||
|
|
||||||
|
impl Handler<Whois> for Whois {
|
||||||
|
async fn handle(
|
||||||
|
&self,
|
||||||
|
server_name: &Str,
|
||||||
|
client: &Str,
|
||||||
|
writer: &mut (impl AsyncWrite + Unpin),
|
||||||
|
storage: &mut Storage,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
match self {
|
||||||
|
Whois::Nick(nick) => handle_nick_target(nick, None, server_name, client, writer, storage).await?,
|
||||||
|
Whois::TargetNick(target, nick) => {
|
||||||
|
handle_nick_target(nick, Some(target), server_name, client, writer, storage).await?
|
||||||
|
}
|
||||||
|
Whois::EmptyArgs => {
|
||||||
|
IrcResponseMessage::empty_tags(
|
||||||
|
Some(server_name.clone()),
|
||||||
|
ERR_NONICKNAMEGIVEN_431::new(server_name.clone()),
|
||||||
|
)
|
||||||
|
.write_response(writer)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_nick_target(
|
||||||
|
nick: &Str,
|
||||||
|
// todo: implement logic with target
|
||||||
|
_target: Option<&Str>,
|
||||||
|
server_name: &Str,
|
||||||
|
client: &Str,
|
||||||
|
writer: &mut (impl AsyncWrite + Unpin),
|
||||||
|
storage: &mut Storage,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if let Some(user) = storage.retrieve_user_by_name(nick).await? {
|
||||||
|
IrcResponseMessage::empty_tags(
|
||||||
|
Some(server_name.clone()),
|
||||||
|
RplWhoIsUser311::new(
|
||||||
|
client.clone(),
|
||||||
|
nick.clone(),
|
||||||
|
Some(Str::from(user.name.clone())),
|
||||||
|
server_name.clone(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.write_response(writer)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
IrcResponseMessage::empty_tags(
|
||||||
|
Some(server_name.clone()),
|
||||||
|
RPL_ENDOFWHOIS_318::new(client.clone(), user.name.clone().into()),
|
||||||
|
)
|
||||||
|
.write_response(writer)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
ERR_NOSUCHNICK_401::new(client.clone(), nick.clone()).write_response(writer).await?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
|
use lavina_core::prelude::Str;
|
||||||
|
use proto_irc::response::SendResponseBody;
|
||||||
|
|
||||||
|
struct RplWhoiscertfp276;
|
||||||
|
struct RplWhoIsRegNick307;
|
||||||
|
pub struct RplWhoIsUser311 {
|
||||||
|
client: Str,
|
||||||
|
/// unique name
|
||||||
|
nick: Str,
|
||||||
|
/// username not unique
|
||||||
|
username: Option<Str>,
|
||||||
|
/// server name
|
||||||
|
host: Str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RplWhoIsUser311 {
|
||||||
|
pub fn new(client: Str, nick: Str, username: Option<Str>, host: Str) -> Self {
|
||||||
|
RplWhoIsUser311 {
|
||||||
|
client,
|
||||||
|
nick,
|
||||||
|
username,
|
||||||
|
host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _RplWhoisserver312;
|
||||||
|
struct _RplWhoisoperator313;
|
||||||
|
struct _RplWhoisidle317;
|
||||||
|
struct _RplWhoischannels319;
|
||||||
|
struct _RplWhoisspecial320;
|
||||||
|
struct _RplWhoisaccount330;
|
||||||
|
struct _RplWhoisactually338;
|
||||||
|
struct _RplWhoishost378;
|
||||||
|
struct _RplWhoismodes379;
|
||||||
|
struct _RplWhoissecure671;
|
||||||
|
struct _RplAway301;
|
||||||
|
pub struct RPL_ENDOFWHOIS_318 {
|
||||||
|
client: Str,
|
||||||
|
nick: Str,
|
||||||
|
}
|
||||||
|
impl RPL_ENDOFWHOIS_318 {
|
||||||
|
pub fn new(client: Str, nick: Str) -> Self {
|
||||||
|
RPL_ENDOFWHOIS_318 { client, nick }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendResponseBody for RplWhoIsUser311 {
|
||||||
|
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
||||||
|
writer.write_all(b"311 ").await?;
|
||||||
|
writer.write_all(self.client.as_bytes()).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all(self.nick.as_bytes()).await?;
|
||||||
|
if let Some(username) = self.username {
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all(username.as_bytes()).await?;
|
||||||
|
}
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all(self.host.as_bytes()).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all("*".as_bytes()).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all(b" :").await?;
|
||||||
|
//todo no entity in db that represents whole irc user entity
|
||||||
|
writer.write_all("<realname>".as_bytes()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendResponseBody for RPL_ENDOFWHOIS_318 {
|
||||||
|
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
||||||
|
writer.write_all(b"318 ").await?;
|
||||||
|
writer.write_all(self.client.as_bytes()).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
writer.write_all(self.nick.as_bytes()).await?;
|
||||||
|
writer.write_all(b" :").await?;
|
||||||
|
writer.write_all("End of /WHOIS list".as_bytes()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,10 +25,12 @@ use proto_irc::server::{AwayStatus, ServerMessage, ServerMessageBody};
|
||||||
use proto_irc::user::PrefixedNick;
|
use proto_irc::user::PrefixedNick;
|
||||||
use proto_irc::{Chan, Recipient};
|
use proto_irc::{Chan, Recipient};
|
||||||
use sasl::AuthBody;
|
use sasl::AuthBody;
|
||||||
|
|
||||||
mod cap;
|
mod cap;
|
||||||
|
mod commands;
|
||||||
|
|
||||||
use crate::cap::Capabilities;
|
use crate::cap::Capabilities;
|
||||||
|
use commands::Handler;
|
||||||
|
use proto_irc::response::IrcResponseMessage;
|
||||||
|
|
||||||
pub const APP_VERSION: &str = concat!("lavina", "_", env!("CARGO_PKG_VERSION"));
|
pub const APP_VERSION: &str = concat!("lavina", "_", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
|
@ -75,7 +77,7 @@ async fn handle_socket(
|
||||||
match registered_user {
|
match registered_user {
|
||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
log::debug!("User registered");
|
log::debug!("User registered");
|
||||||
handle_registered_socket(config, players, rooms, &mut reader, &mut writer, user).await?;
|
handle_registered_socket(config, players, rooms, &mut reader, &mut writer, user, &mut storage).await?;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::debug!("Registration failed: {err}");
|
log::debug!("Registration failed: {err}");
|
||||||
|
@ -387,6 +389,7 @@ async fn handle_registered_socket<'a>(
|
||||||
reader: &mut BufReader<ReadHalf<'a>>,
|
reader: &mut BufReader<ReadHalf<'a>>,
|
||||||
writer: &mut BufWriter<WriteHalf<'a>>,
|
writer: &mut BufWriter<WriteHalf<'a>>,
|
||||||
user: RegisteredUser,
|
user: RegisteredUser,
|
||||||
|
storage: &mut Storage,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
log::info!("Handling registered user: {user:?}");
|
log::info!("Handling registered user: {user:?}");
|
||||||
|
@ -466,7 +469,7 @@ async fn handle_registered_socket<'a>(
|
||||||
len
|
len
|
||||||
};
|
};
|
||||||
let incoming = std::str::from_utf8(&buffer[0..len-2])?;
|
let incoming = std::str::from_utf8(&buffer[0..len-2])?;
|
||||||
if let HandleResult::Leave = handle_incoming_message(incoming, &config, &user, &rooms, &mut connection, writer).await? {
|
if let HandleResult::Leave = handle_incoming_message(incoming, &config, &user, &rooms, &mut connection, writer, storage).await? {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
@ -615,6 +618,7 @@ async fn handle_incoming_message(
|
||||||
rooms: &RoomRegistry,
|
rooms: &RoomRegistry,
|
||||||
user_handle: &mut PlayerConnection,
|
user_handle: &mut PlayerConnection,
|
||||||
writer: &mut (impl AsyncWrite + Unpin),
|
writer: &mut (impl AsyncWrite + Unpin),
|
||||||
|
storage: &mut Storage,
|
||||||
) -> Result<HandleResult> {
|
) -> Result<HandleResult> {
|
||||||
log::debug!("Incoming raw IRC message: '{buffer}'");
|
log::debug!("Incoming raw IRC message: '{buffer}'");
|
||||||
let parsed = client_message(buffer);
|
let parsed = client_message(buffer);
|
||||||
|
@ -724,55 +728,16 @@ async fn handle_incoming_message(
|
||||||
log::warn!("Local chans not supported");
|
log::warn!("Local chans not supported");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientMessage::Whois { target, nick } => {
|
ClientMessage::Whois { arg } => {
|
||||||
// todo: finish replpies from the server to the command
|
arg.handle(
|
||||||
match (target, nick) {
|
&config.server_name,
|
||||||
(Some(target), Some(nick)) => {
|
&user.nickname,
|
||||||
ServerMessage {
|
writer,
|
||||||
tags: vec![],
|
storage,
|
||||||
sender: Some(config.server_name.clone()),
|
)
|
||||||
body: ServerMessageBody::N318EndOfWhois {
|
.await?
|
||||||
client: user.nickname.clone(),
|
|
||||||
nick: nick,
|
|
||||||
msg: "End of /WHOIS list".into(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.write_async(writer)
|
|
||||||
.await?;
|
|
||||||
writer.flush().await?
|
|
||||||
}
|
|
||||||
(Some(target), None) => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
(None, Some(nick)) => {
|
|
||||||
ServerMessage {
|
|
||||||
tags: vec![],
|
|
||||||
sender: Some(config.server_name.clone()),
|
|
||||||
body: ServerMessageBody::N318EndOfWhois {
|
|
||||||
client: user.nickname.clone(),
|
|
||||||
nick: nick,
|
|
||||||
msg: "End of /WHOIS list".into(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.write_async(writer)
|
|
||||||
.await?;
|
|
||||||
writer.flush().await?
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
ServerMessage {
|
|
||||||
tags: vec![],
|
|
||||||
sender: Some(config.server_name.clone()),
|
|
||||||
body: ServerMessageBody::N431ErrNoNicknameGiven {
|
|
||||||
client: user.nickname.clone(),
|
|
||||||
message: "No nickname given".into(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.write_async(writer)
|
|
||||||
.await?;
|
|
||||||
writer.flush().await?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientMessage::Mode { target } => {
|
ClientMessage::Mode { target } => {
|
||||||
match target {
|
match target {
|
||||||
Recipient::Nick(nickname) => {
|
Recipient::Nick(nickname) => {
|
||||||
|
|
|
@ -8,10 +8,11 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use lavina_core::repo::{Storage, StorageConfig};
|
|
||||||
use lavina_core::{player::PlayerRegistry, room::RoomRegistry};
|
use lavina_core::{player::PlayerRegistry, room::RoomRegistry};
|
||||||
use projection_irc::APP_VERSION;
|
use lavina_core::repo::{Storage, StorageConfig};
|
||||||
use projection_irc::{launch, read_irc_message, RunningServer, ServerConfig};
|
use projection_irc::{launch, read_irc_message, RunningServer, ServerConfig};
|
||||||
|
use projection_irc::APP_VERSION;
|
||||||
|
|
||||||
struct TestScope<'a> {
|
struct TestScope<'a> {
|
||||||
reader: BufReader<ReadHalf<'a>>,
|
reader: BufReader<ReadHalf<'a>>,
|
||||||
writer: WriteHalf<'a>,
|
writer: WriteHalf<'a>,
|
||||||
|
@ -193,6 +194,52 @@ async fn scenario_cap_full_negotiation() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[tokio::test]
|
||||||
|
// async fn scenario_whois_command() -> Result<()> {
|
||||||
|
// let mut server = TestServer::start().await?;
|
||||||
|
//
|
||||||
|
// server.storage.create_user("tester").await?;
|
||||||
|
// server.storage.set_password("tester", "password").await?;
|
||||||
|
// let mut stream = TcpStream::connect(server.server.addr).await?;
|
||||||
|
// let mut s = TestScope::new(&mut stream);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// s.send("PASS password").await?;
|
||||||
|
// s.send("NICK tester").await?;
|
||||||
|
// s.send("USER UserName 0 * :Real Name").await?;
|
||||||
|
//
|
||||||
|
// s.expect(":testserver 001 tester :Welcome to testserver Server").await?;
|
||||||
|
// s.expect(":testserver 002 tester :Welcome to testserver Server").await?;
|
||||||
|
// s.expect(":testserver 003 tester :Welcome to testserver Server").await?;
|
||||||
|
// s.expect(
|
||||||
|
// format!(
|
||||||
|
// ":testserver 004 tester testserver {} r CFILPQbcefgijklmnopqrstvz",
|
||||||
|
// &APP_VERSION
|
||||||
|
// )
|
||||||
|
// .as_str(),
|
||||||
|
// )
|
||||||
|
// .await?;
|
||||||
|
// s.expect(":testserver 005 tester CHANTYPES=# :are supported by this server").await?;
|
||||||
|
// s.expect_nothing().await?;
|
||||||
|
//
|
||||||
|
// s.send("WHOIS tester").await?;
|
||||||
|
// s.expect(" ").await?;
|
||||||
|
//
|
||||||
|
// s.send("QUIT :Leaving").await?;
|
||||||
|
// s.expect(":testserver ERROR :Leaving the server").await?;
|
||||||
|
// s.expect_eof().await?;
|
||||||
|
//
|
||||||
|
// stream.shutdown().await?;
|
||||||
|
//
|
||||||
|
// // wrap up
|
||||||
|
//
|
||||||
|
// server.server.terminate().await?;
|
||||||
|
//
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn scenario_cap_short_negotiation() -> Result<()> {
|
async fn scenario_cap_short_negotiation() -> Result<()> {
|
||||||
let mut server = TestServer::start().await?;
|
let mut server = TestServer::start().await?;
|
||||||
|
|
|
@ -190,7 +190,7 @@ async fn handle_socket(
|
||||||
pin!(termination);
|
pin!(termination);
|
||||||
select! {
|
select! {
|
||||||
biased;
|
biased;
|
||||||
_ = &mut termination =>{
|
_ = &mut termination => {
|
||||||
log::info!("Socket handling was terminated");
|
log::info!("Socket handling was terminated");
|
||||||
return Ok(())
|
return Ok(())
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use nom::combinator::{all_consuming, opt};
|
use nom::combinator::{all_consuming, opt};
|
||||||
use nom::error::ErrorKind;
|
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -45,8 +44,7 @@ pub enum ClientMessage {
|
||||||
},
|
},
|
||||||
/// WHOIS [<target>] <nick>
|
/// WHOIS [<target>] <nick>
|
||||||
Whois {
|
Whois {
|
||||||
target: Option<Str>, // server_name or nick_name
|
arg: command_args::Whois,
|
||||||
nick: Option<Str>,
|
|
||||||
},
|
},
|
||||||
/// `TOPIC <chan> :<topic>`
|
/// `TOPIC <chan> :<topic>`
|
||||||
Topic {
|
Topic {
|
||||||
|
@ -69,6 +67,17 @@ pub enum ClientMessage {
|
||||||
Authenticate(Str),
|
Authenticate(Str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod command_args {
|
||||||
|
use crate::prelude::Str;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Whois {
|
||||||
|
Nick(Str),
|
||||||
|
TargetNick(Str, Str),
|
||||||
|
EmptyArgs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn client_message(input: &str) -> Result<ClientMessage> {
|
pub fn client_message(input: &str) -> Result<ClientMessage> {
|
||||||
let res = all_consuming(alt((
|
let res = all_consuming(alt((
|
||||||
client_message_capability,
|
client_message_capability,
|
||||||
|
@ -191,23 +200,19 @@ fn client_message_whois(input: &str) -> IResult<&str, ClientMessage> {
|
||||||
[nick] => Ok((
|
[nick] => Ok((
|
||||||
"",
|
"",
|
||||||
ClientMessage::Whois {
|
ClientMessage::Whois {
|
||||||
target: None,
|
arg: command_args::Whois::Nick(nick.into()),
|
||||||
nick: Some(nick.into()),
|
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
[target, nick, ..] => Ok((
|
[target, nick, ..] => Ok((
|
||||||
"",
|
"",
|
||||||
ClientMessage::Whois {
|
ClientMessage::Whois {
|
||||||
target: Some(target.into()),
|
arg: command_args::Whois::TargetNick(target.into(), nick.into()),
|
||||||
nick: Some(nick.into()),
|
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
// fixme: idk how to deal with this in more elegant way
|
|
||||||
[] => Ok((
|
[] => Ok((
|
||||||
"",
|
"",
|
||||||
ClientMessage::Whois {
|
ClientMessage::Whois {
|
||||||
target: None,
|
arg: command_args::Whois::EmptyArgs,
|
||||||
nick: None,
|
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
@ -412,41 +417,33 @@ mod test {
|
||||||
let res_none_none_params = client_message(test_none_none_params);
|
let res_none_none_params = client_message(test_none_none_params);
|
||||||
|
|
||||||
let expected_arg = ClientMessage::Whois {
|
let expected_arg = ClientMessage::Whois {
|
||||||
target: None,
|
arg: command_args::Whois::Nick("val".into()),
|
||||||
nick: Some("val".into()),
|
|
||||||
};
|
};
|
||||||
let expected_user_user = ClientMessage::Whois {
|
let expected_user_user = ClientMessage::Whois {
|
||||||
target: Some("val".into()),
|
arg: command_args::Whois::TargetNick("val".into(), "val".into()),
|
||||||
nick: Some("val".into()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_server_user = ClientMessage::Whois {
|
let expected_server_user = ClientMessage::Whois {
|
||||||
target: Some("com.test.server".into()),
|
arg: command_args::Whois::TargetNick("com.test.server".into(), "user".into()),
|
||||||
nick: Some("user".into()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_user_server = ClientMessage::Whois {
|
let expected_user_server = ClientMessage::Whois {
|
||||||
target: Some("user".into()),
|
arg: command_args::Whois::TargetNick("user".into(), "com.test.server".into()),
|
||||||
nick: Some("com.test.server".into()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_user_list = ClientMessage::Whois {
|
let expected_user_list = ClientMessage::Whois {
|
||||||
target: None,
|
arg: command_args::Whois::Nick("user_1,user_2,user_3".into()),
|
||||||
nick: Some("user_1,user_2,user_3".into()),
|
|
||||||
};
|
};
|
||||||
let expected_server_user_list = ClientMessage::Whois {
|
let expected_server_user_list = ClientMessage::Whois {
|
||||||
target: Some("com.test.server".into()),
|
arg: command_args::Whois::TargetNick("com.test.server".into(), "user_1,user_2,user_3".into()),
|
||||||
nick: Some("user_1,user_2,user_3".into()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_more_than_two_params = ClientMessage::Whois {
|
let expected_more_than_two_params = ClientMessage::Whois {
|
||||||
target: Some("test.server".into()),
|
arg: command_args::Whois::TargetNick("test.server".into(), "user_1,user_2,user_3".into()),
|
||||||
nick: Some("user_1,user_2,user_3".into()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_none_none_params = ClientMessage::Whois {
|
let expected_none_none_params = ClientMessage::Whois {
|
||||||
target: None,
|
arg: command_args::Whois::EmptyArgs,
|
||||||
nick: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_matches!(res_one_arg, Ok(result) => assert_eq!(expected_arg, result));
|
assert_matches!(res_one_arg, Ok(result) => assert_eq!(expected_arg, result));
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod server;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod testkit;
|
mod testkit;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
pub mod response;
|
||||||
|
|
||||||
use crate::prelude::Str;
|
use crate::prelude::Str;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
|
use crate::prelude::Str;
|
||||||
|
use crate::Tag;
|
||||||
|
|
||||||
|
pub trait SendResponseBody {
|
||||||
|
fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> impl Future<Output = std::io::Result<()>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SendResponseMessage<Message> {
|
||||||
|
fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> impl Future<Output = std::io::Result<()>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server-to-client enum agnostic message
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct IrcResponseMessage<T: SendResponseBody> {
|
||||||
|
/// Optional tags section, prefixed with `@`
|
||||||
|
pub tags: Vec<Tag>,
|
||||||
|
/// Optional server name, prefixed with `:`.
|
||||||
|
pub sender: Option<Str>,
|
||||||
|
pub body: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SendResponseBody> IrcResponseMessage<T> {
|
||||||
|
pub fn empty_tags(sender: Option<Str>, body: T) -> Self {
|
||||||
|
IrcResponseMessage {
|
||||||
|
tags: vec![],
|
||||||
|
sender,
|
||||||
|
body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(tags: Vec<Tag>, sender: Option<Str>, body: T) -> Self {
|
||||||
|
IrcResponseMessage { tags, sender, body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SendResponseBody> SendResponseMessage<IrcResponseMessage<T>> for IrcResponseMessage<T> {
|
||||||
|
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
||||||
|
if let Some(sender) = &self.sender {
|
||||||
|
writer.write_all(b":").await?;
|
||||||
|
writer.write_all(sender.as_bytes()).await?;
|
||||||
|
writer.write_all(b" ").await?;
|
||||||
|
}
|
||||||
|
self.body.write_response(writer).await?;
|
||||||
|
writer.write_all(b"\r\n").await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -283,7 +283,7 @@ impl ServerMessageBody {
|
||||||
writer.write_all(msg.as_bytes()).await?;
|
writer.write_all(msg.as_bytes()).await?;
|
||||||
}
|
}
|
||||||
ServerMessageBody::N318EndOfWhois { client, nick, msg } => {
|
ServerMessageBody::N318EndOfWhois { client, nick, msg } => {
|
||||||
writer.write_all(b"b318 ").await?;
|
writer.write_all(b"318 ").await?;
|
||||||
writer.write_all(client.as_bytes()).await?;
|
writer.write_all(client.as_bytes()).await?;
|
||||||
writer.write_all(b" ").await?;
|
writer.write_all(b" ").await?;
|
||||||
writer.write_all(nick.as_bytes()).await?;
|
writer.write_all(nick.as_bytes()).await?;
|
||||||
|
|
Loading…
Reference in New Issue