#[derive(Debug)]
pub struct XscpRequest<'a> {
opcode: OpCode,
nickname: &'a str,
message: &'a str,
}
impl<'a> XscpRequest<'a> {
pub fn try_new(opcode: OpCode, nickname: &'a str, message: &'a str) -> Result<Self, RequestError> {
if nickname.contains(['|', '\r', '\n']) || nickname.len() < 3 || nickname.len() > 32 {
return Err(RequestError::InvalidNickname);
}
if message.contains(['|', '\r', '\n']) || message.len() > 472 {
return Err(RequestError::InvalidMessage);
}
Ok(Self { opcode, nickname, message })
}
pub fn parse(raw_request: &'a str) -> Result<Self, RequestError> {
if !raw_request.ends_with("\r\n") {
return Err(RequestError::MissingCrlf);
}
let raw_request = raw_request.trim_end_matches("\r\n");
let raw_request: Vec<&str> = raw_request.split('|').collect();
if raw_request.len() != 3 {
return Err(RequestError::MalformedRequest);
}
let opcode = raw_request[0];
let opcode = match opcode {
"LOGN" => OpCode::Login,
"CHAT" => OpCode::Chat,
"EXIT" => OpCode::Exit,
_ => return Err(RequestError::UnknownOpcode),
};
let nickname = raw_request[1];
let message = raw_request[2];
Self::try_new(opcode, nickname, message)
}
pub fn opcode(&self) -> OpCode {
self.opcode
}
pub fn nickname(&self) -> &str {
self.nickname
}
pub fn message(&self) -> &str {
self.message
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OpCode {
Login,
Chat,
Exit,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum RequestError {
UnknownOpcode,
MalformedRequest,
InvalidNickname,
InvalidMessage,
MissingCrlf,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn correct_request() {
let request = XscpRequest::try_new(OpCode::Chat, "nickname", "message").unwrap();
assert_eq!(OpCode::Chat, request.opcode());
assert_eq!("nickname", request.nickname());
assert_eq!("message", request.message());
}
#[test]
fn nickname_with_pipe() {
let request = XscpRequest::try_new(OpCode::Chat, "nick|name", "message").unwrap_err();
assert_eq!(RequestError::InvalidNickname, request);
}
#[test]
fn nickname_with_crlf() {
let request = XscpRequest::try_new(OpCode::Chat, "nick\r\nname", "message").unwrap_err();
assert_eq!(RequestError::InvalidNickname, request);
}
#[test]
fn nickname_empty() {
let err = XscpRequest::try_new(OpCode::Chat, "", "message").unwrap_err();
assert_eq!(RequestError::InvalidNickname, err);
}
#[test]
fn nickname_below_min() {
let err = XscpRequest::try_new(OpCode::Chat, "ab", "message").unwrap_err();
assert_eq!(RequestError::InvalidNickname, err);
}
#[test]
fn nickname_above_max() {
let nickname = "a".repeat(33);
let err = XscpRequest::try_new(OpCode::Chat, &nickname, "message").unwrap_err();
assert_eq!(RequestError::InvalidNickname, err);
}
#[test]
fn message_with_crlf() {
let request = XscpRequest::try_new(OpCode::Chat, "nickname", "message with \r\n (CRLF)").unwrap_err();
assert_eq!(RequestError::InvalidMessage, request);
}
#[test]
fn message_with_pipe() {
let request = XscpRequest::try_new(OpCode::Chat, "nickname", "message with | (pipe)").unwrap_err();
assert_eq!(RequestError::InvalidMessage, request);
}
#[test]
fn message_above_max() {
let message = "a".repeat(473);
let err = XscpRequest::try_new(OpCode::Chat, "nickname", &message).unwrap_err();
assert_eq!(RequestError::InvalidMessage, err);
}
#[test]
fn correct_parsing() {
let raw_request = "CHAT|nickname|message\r\n";
let request = XscpRequest::parse(raw_request).unwrap();
assert_eq!(OpCode::Chat, request.opcode());
assert_eq!("nickname", request.nickname());
assert_eq!("message", request.message());
}
#[test]
fn invalid_opcode() {
let raw_request = "AAAA|nickname|message\r\n";
let error = XscpRequest::parse(raw_request).unwrap_err();
assert_eq!(RequestError::UnknownOpcode, error);
}
#[test]
fn invalid_format() {
let raw_request = "vfw9f8i9v\r\n";
let error = XscpRequest::parse(raw_request).unwrap_err();
assert_eq!(RequestError::MalformedRequest, error);
}
#[test]
fn missing_crlf() {
let raw_request = "CHAT|nickname|message";
let error = XscpRequest::parse(raw_request).unwrap_err();
assert_eq!(RequestError::MissingCrlf, error);
}
#[test]
fn invalid_request_with_extra_fields() {
let raw_request = "CHAT|nickname|message with | (pipe)\r\n";
let error = XscpRequest::parse(raw_request).unwrap_err();
assert_eq!(RequestError::MalformedRequest, error);
}
}