use crate::protocol::Message;
#[derive(Debug, Clone, PartialEq)]
pub enum StandardReplyType {
Fail,
Warn,
Note,
}
impl StandardReplyType {
pub fn as_str(&self) -> &'static str {
match self {
StandardReplyType::Fail => "FAIL",
StandardReplyType::Warn => "WARN",
StandardReplyType::Note => "NOTE",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StandardReplyCode {
AccountRequiredToConnect,
InvalidParams,
InvalidTarget,
NeedMoreParams,
MessageError,
UnknownError,
CannotSendToChan,
NoSuchChannel,
NotOnChannel,
InviteOnlyChan,
BadChannelKey,
ChannelIsFull,
BannedFromChan,
NoSuchNick,
NicknameInUse,
InvalidNickname,
SaslFail,
InvalidCredentials,
RegFail,
RegInvalidCallback,
AlreadyRegistered,
UnknownCommand,
CommandDisabled,
RateLimitExceeded,
ResourceLimit,
Custom(String),
}
impl StandardReplyCode {
pub fn as_str(&self) -> &str {
match self {
StandardReplyCode::AccountRequiredToConnect => "ACCOUNT_REQUIRED_TO_CONNECT",
StandardReplyCode::InvalidParams => "INVALID_PARAMS",
StandardReplyCode::InvalidTarget => "INVALID_TARGET",
StandardReplyCode::NeedMoreParams => "NEED_MORE_PARAMS",
StandardReplyCode::MessageError => "MESSAGE_ERROR",
StandardReplyCode::UnknownError => "UNKNOWN_ERROR",
StandardReplyCode::CannotSendToChan => "CANNOT_SEND_TO_CHAN",
StandardReplyCode::NoSuchChannel => "NO_SUCH_CHANNEL",
StandardReplyCode::NotOnChannel => "NOT_ON_CHANNEL",
StandardReplyCode::InviteOnlyChan => "INVITE_ONLY_CHAN",
StandardReplyCode::BadChannelKey => "BAD_CHANNEL_KEY",
StandardReplyCode::ChannelIsFull => "CHANNEL_IS_FULL",
StandardReplyCode::BannedFromChan => "BANNED_FROM_CHAN",
StandardReplyCode::NoSuchNick => "NO_SUCH_NICK",
StandardReplyCode::NicknameInUse => "NICKNAME_IN_USE",
StandardReplyCode::InvalidNickname => "INVALID_NICKNAME",
StandardReplyCode::SaslFail => "SASL_FAIL",
StandardReplyCode::InvalidCredentials => "INVALID_CREDENTIALS",
StandardReplyCode::RegFail => "REG_FAIL",
StandardReplyCode::RegInvalidCallback => "REG_INVALID_CALLBACK",
StandardReplyCode::AlreadyRegistered => "ALREADY_REGISTERED",
StandardReplyCode::UnknownCommand => "UNKNOWN_COMMAND",
StandardReplyCode::CommandDisabled => "COMMAND_DISABLED",
StandardReplyCode::RateLimitExceeded => "RATE_LIMIT_EXCEEDED",
StandardReplyCode::ResourceLimit => "RESOURCE_LIMIT",
StandardReplyCode::Custom(code) => code,
}
}
}
pub struct StandardReply {
reply_type: StandardReplyType,
command: String,
code: StandardReplyCode,
context: Vec<String>,
description: String,
}
impl StandardReply {
pub fn fail(command: &str, code: StandardReplyCode, description: &str) -> Self {
Self {
reply_type: StandardReplyType::Fail,
command: command.to_string(),
code,
context: Vec::new(),
description: description.to_string(),
}
}
pub fn warn(command: &str, code: StandardReplyCode, description: &str) -> Self {
Self {
reply_type: StandardReplyType::Warn,
command: command.to_string(),
code,
context: Vec::new(),
description: description.to_string(),
}
}
pub fn note(command: &str, code: StandardReplyCode, description: &str) -> Self {
Self {
reply_type: StandardReplyType::Note,
command: command.to_string(),
code,
context: Vec::new(),
description: description.to_string(),
}
}
pub fn with_context(mut self, context: Vec<String>) -> Self {
self.context = context;
self
}
pub fn add_context(mut self, context: String) -> Self {
self.context.push(context);
self
}
pub fn to_message(&self, server_name: &str) -> Message {
let mut params = vec![
self.command.clone(),
self.code.as_str().to_string(),
];
params.extend(self.context.clone());
params.push(self.description.clone());
Message::new(self.reply_type.as_str())
.with_prefix(server_name.to_string())
.with_params(params)
}
}
pub mod common {
use super::*;
pub fn invalid_params(command: &str, description: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::InvalidParams, description)
}
pub fn invalid_target(command: &str, target: &str, description: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::InvalidTarget, description)
.add_context(target.to_string())
}
pub fn no_such_channel(command: &str, channel: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::NoSuchChannel, "No such channel")
.add_context(channel.to_string())
}
pub fn no_such_nick(command: &str, nick: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::NoSuchNick, "No such nick/channel")
.add_context(nick.to_string())
}
pub fn not_on_channel(command: &str, channel: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::NotOnChannel, "You're not on that channel")
.add_context(channel.to_string())
}
pub fn cannot_send_to_chan(command: &str, channel: &str, reason: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::CannotSendToChan, reason)
.add_context(channel.to_string())
}
pub fn nickname_in_use(command: &str, nick: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::NicknameInUse, "Nickname is already in use")
.add_context(nick.to_string())
}
pub fn invalid_nickname(command: &str, nick: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::InvalidNickname, "Invalid nickname")
.add_context(nick.to_string())
}
pub fn unknown_command(command: &str) -> StandardReply {
StandardReply::fail("*", StandardReplyCode::UnknownCommand, "Unknown command")
.add_context(command.to_string())
}
pub fn rate_limit_exceeded(command: &str) -> StandardReply {
StandardReply::fail(command, StandardReplyCode::RateLimitExceeded, "Rate limit exceeded")
}
pub fn deprecated_command(command: &str, alternative: &str) -> StandardReply {
StandardReply::warn(command, StandardReplyCode::Custom("DEPRECATED".to_string()),
&format!("Command deprecated, use {} instead", alternative))
}
pub fn info_note(command: &str, message: &str) -> StandardReply {
StandardReply::note(command, StandardReplyCode::Custom("INFO".to_string()), message)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_standard_reply_creation() {
let reply = StandardReply::fail("JOIN", StandardReplyCode::NoSuchChannel, "No such channel")
.add_context("#nonexistent".to_string());
let message = reply.to_message("test.server");
assert_eq!(message.command, "FAIL");
assert_eq!(message.params, vec![
"JOIN".to_string(),
"NO_SUCH_CHANNEL".to_string(),
"#nonexistent".to_string(),
"No such channel".to_string()
]);
}
#[test]
fn test_common_replies() {
let reply = common::invalid_params("PRIVMSG", "Not enough parameters");
let message = reply.to_message("test.server");
assert_eq!(message.command, "FAIL");
assert!(message.params.contains(&"INVALID_PARAMS".to_string()));
assert!(message.params.contains(&"Not enough parameters".to_string()));
}
}