Skip to content

Commit

Permalink
refactor: Refactor the command handler & main to make ATDD eaiser
Browse files Browse the repository at this point in the history
  • Loading branch information
robinsonweng committed Dec 24, 2023
1 parent 023aa00 commit f1cf6a2
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 171 deletions.
225 changes: 210 additions & 15 deletions src/irc/command.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::irc::response::{IrcError, IrcReply};
use crate::irc::response::{IrcError, IrcErrors, IrcReply, IrcResponseToMessage};
use crate::irc::server::{Server, UserStatus};
use regex::Regex;
use std::io::Write;
use std::net::{SocketAddr, TcpStream};

#[derive(Debug)]
Expand All @@ -21,13 +23,15 @@ pub enum Command {
CommandNotFound,
}

pub struct CommandParser {
pub command: Command,
pub context: String,
#[derive(Debug)]
pub struct CommandHandler {
command: Command,
context: String,
}

impl CommandParser {
pub fn new(raw_command: &str, raw_context: &str) -> Self {
impl CommandHandler {
pub fn new(raw_message: &str) -> Self {
let (raw_command, raw_context) = Self::parse_message(raw_message);
let command = match raw_command {
"NICK" => Command::SetNickName,
"USER" => Command::User,
Expand All @@ -45,16 +49,194 @@ impl CommandParser {
&_ => Command::CommandNotFound,
};

// what if message has multiple parameter
let context = raw_context.clone().to_string().replace("\r\n", "");
let context = raw_context.replace("\r\n", "");
Self { command, context }
}
}

#[derive(Debug)]
pub struct CommandHandler {}
fn parse_message(raw_message: &str) -> (&str, &str) {
let splited = raw_message.trim_matches(char::from(0)).split_once(' ');

let command_tuple: (&str, &str);
if let Some(text) = splited {
command_tuple = text;
} else {
// not enouth args
todo!()
}
return command_tuple;
}

pub fn execute(
&self,
stream: &mut TcpStream,
server: &mut Server,
client_ip: SocketAddr,
) -> Result<(), IrcErrors> {
// if first user detected in NICK command, wait untill user occor
// unless timeout or offline
// create user here
// both username & realname should set, the user then can register as online

match self.command {
Command::SetNickName => {
println!("this is NICK command");

// do parsing
let nickname = self.context.clone().replace("\r\n", "");

let set_nickname_result = self.set_nickname(server, &nickname, client_ip);
let msg = match set_nickname_result {
Ok(()) => None,
Err(IrcError::NickCollision) => {
Some(format!("{} :Nickname collision KILL\r\n", nickname))
}
// ERR_NONICKNAMEGIVEN
_ => panic!("set nickname didn't match any rpl or err"),
};

if msg.is_some() {
// ERR_NICKCOLLISION
stream.write(msg.unwrap().as_bytes())?;
}
}

Command::User => {
println!("this is USER command");
println!("command: {:?}", self.command);
println!("context: {:?}", self.context);

let context = self.context.replace("\r\n", "");

// mabye use regex instead
let context_collection: Vec<&str> = context.split(' ').collect();
if let [username, hostname, servername, raw_realname] = &context_collection[..] {
if !raw_realname.starts_with(":") {
// ERR_NEEDMOREPARAMS
let need_more_param = IrcError::NeedMoreParams.to_message(
"USER",
"",
"Username should start with prefix ':'.",
);
stream.write(need_more_param.as_bytes())?;
return Ok(());
}

let realname = &raw_realname.replace(":", "");
println!(
"username: {}, hostname: {}, servername: {}, realname: {}",
username, hostname, servername, realname
);
self.set_username(server, client_ip, username);
self.set_realname(server, client_ip, realname);
// handle hostname & server name later
} else {
// ERR_NEEDMOREPARAMS
let need_more_param = IrcError::NeedMoreParams.to_message(
"USER",
"",
"Username should start with prefix ':'.",
);
stream.write(need_more_param.as_bytes())?;
return Ok(());
}
}

Command::Ping => {
// ERR_NOORIGIN if no server is given
println!("this is PING command");

let context = self.context.replace("\r\n", "");
if context.is_empty() {
// find nick from ip
let nickname = "";
let hostname = "localhost";
let no_origin = IrcError::NoOrigin.to_message(
hostname,
nickname,
":No origin specified\r\n",
);
stream.write(no_origin.as_bytes())?;
return Ok(());
}
}

Command::Join => {
/*
user joins a channel
if channel dont exist, create one
the channel ceases to exist if last user leave
regular channel (channel name start with '#') are all known on every server
local channel (channel name start with '&') are only known on one spcific server
*/
println!("this is JOIN command");
let context = self.context.replace("\r\n", "");

let channel_regex = Regex::new(r"^JOIN (#\w+)([,]#\w+)?( \w+)?([,]\w+)?").unwrap();
if channel_regex.is_match(&context) {
println!("correct format: {}", context);
} else {
println!("incorrect formfromat: {}", context);
}
}

Command::List => {
todo!()
}
Command::Topic => {
// ERR_NOORIGIN
// ERR_NOSUCHSERVER
println!("this is PONG command");
}
Command::Pong => {
todo!()
}
_ => {
println!("Command not support: {:?}", self.command);
}
}
Ok(())
}

pub fn user_online(
&self,
stream: &mut TcpStream,
server: &mut Server,
hostname: &str,
client_ip: SocketAddr,
) -> std::io::Result<()> {
// is username & nickname is set & status == unregister
// Start wellcome message here

let hostname = hostname.to_string().clone();

let nickname = self.is_user_ready_to_register(server, client_ip);
if nickname.is_none() {
return Ok(());
}
let nickname = nickname.unwrap();

let welcome =
IrcReply::Welcome.to_message(&hostname, &nickname, ":Welcome to the rust irc server");
stream.write(welcome.as_bytes())?;

let yourhost = IrcReply::YourHost.to_message(
&hostname,
&nickname,
":Your host is rust irc, running version 1.0.0-dev",
);
stream.write(yourhost.as_bytes())?;

let created =
IrcReply::Created.to_message(&hostname, &nickname, "Are you solid like a rock?");
stream.write(created.as_bytes())?;

let myinfo = IrcReply::MyInfo.to_message(&hostname, &nickname, "Let me see see");
stream.write(myinfo.as_bytes())?;

self.set_user_status(server, client_ip, UserStatus::Online);
Ok(())
}

impl CommandHandler {
pub fn set_nickname(
&self,
server: &mut Server,
Expand Down Expand Up @@ -88,10 +270,23 @@ impl CommandHandler {
}
Some(server.get_user_nick(source_ip))
}
}

#[cfg(test)]
mod command_tests {
use super::*;

pub fn ping(&self, server: &mut Server, source_ip: SocketAddr) -> String {
todo!()
pub fn setup(raw_message: &str) -> CommandHandler {
CommandHandler::new(raw_message)
}

pub fn wellcome(stream: TcpStream) {}
#[test]
fn test_execute_command_new_nickname() {
let raw_message = "NICK Wiz";
let command_handler = setup(raw_message);
}
fn test_execute_command_change_nickname() {
let raw_message = ":WiZ NICK Kilroy";
let command_handler = setup(raw_message);
}
}
Loading

0 comments on commit f1cf6a2

Please sign in to comment.