#[derive(Debug)]
pub struct XscpRequest<'a> {
opcode: OpCode,
source: &'a str,
message: &'a str,
}
impl<'a> XscpRequest<'a> {
pub fn try_new(opcode: OpCode, source: &'a str, message: &'a str) -> Result<Self, RequestError> {
if source.contains(['|', '\r', '\n']) || source.len() < 3 || source.len() > 32 {
return Err(RequestError::InvalidSource);
}
if message.contains(['\r', '\n']) || message.len() > 472 {
return Err(RequestError::InvalidMessage);
}
Ok(Self { opcode, source, 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 mut parts = raw_request.splitn(3, '|');
let opcode = parts.next().ok_or(RequestError::MalformedRequest)?;
let source = parts.next().ok_or(RequestError::MalformedRequest)?;
let message = parts.next().ok_or(RequestError::MalformedRequest)?;
let opcode = match opcode {
"LOGN" => OpCode::Login,
"SEND" => OpCode::Send,
"EXIT" => OpCode::Exit,
_ => return Err(RequestError::UnknownOpcode),
};
Self::try_new(opcode, source, message)
}
pub fn opcode(&self) -> OpCode {
self.opcode
}
pub fn source(&self) -> &str {
self.source
}
pub fn message(&self) -> &str {
self.message
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OpCode {
Login,
Send,
Exit,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum RequestError {
UnknownOpcode,
MalformedRequest,
InvalidSource,
InvalidMessage,
MissingCrlf,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn correct_request() {
let request = XscpRequest::try_new(OpCode::Send, "source", "message").unwrap();
assert_eq!(OpCode::Send, request.opcode());
assert_eq!("source", request.source());
assert_eq!("message", request.message());
}
#[test]
fn source_with_pipe() {
let request = XscpRequest::try_new(OpCode::Send, "source|name", "message").unwrap_err();
assert_eq!(RequestError::InvalidSource, request);
}
#[test]
fn source_with_crlf() {
let request = XscpRequest::try_new(OpCode::Send, "source\r\nname", "message").unwrap_err();
assert_eq!(RequestError::InvalidSource, request);
}
#[test]
fn source_empty() {
let err = XscpRequest::try_new(OpCode::Send, "", "message").unwrap_err();
assert_eq!(RequestError::InvalidSource, err);
}
#[test]
fn source_below_min() {
let err = XscpRequest::try_new(OpCode::Send, "ab", "message").unwrap_err();
assert_eq!(RequestError::InvalidSource, err);
}
#[test]
fn source_above_max() {
let source = "a".repeat(33);
let err = XscpRequest::try_new(OpCode::Send, &source, "message").unwrap_err();
assert_eq!(RequestError::InvalidSource, err);
}
#[test]
fn message_with_crlf() {
let request = XscpRequest::try_new(OpCode::Send, "source", "message with \r\n (CRLF)").unwrap_err();
assert_eq!(RequestError::InvalidMessage, request);
}
#[test]
fn message_with_pipe() {
let request = XscpRequest::try_new(OpCode::Send, "source", "message with | (pipe)").unwrap();
assert_eq!(OpCode::Send, request.opcode());
assert_eq!("source", request.source());
assert_eq!("message with | (pipe)", request.message());
}
#[test]
fn message_above_max() {
let message = "a".repeat(473);
let err = XscpRequest::try_new(OpCode::Send, "source", &message).unwrap_err();
assert_eq!(RequestError::InvalidMessage, err);
}
#[test]
fn correct_parsing() {
let raw_request = "SEND|source|message\r\n";
let request = XscpRequest::parse(raw_request).unwrap();
assert_eq!(OpCode::Send, request.opcode());
assert_eq!("source", request.source());
assert_eq!("message", request.message());
}
#[test]
fn invalid_opcode() {
let raw_request = "AAAA|source|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 = "SEND|source|message";
let error = XscpRequest::parse(raw_request).unwrap_err();
assert_eq!(RequestError::MissingCrlf, error);
}
#[test]
fn parse_message_with_pipe() {
let raw_request = "SEND|source|message with | (pipe)\r\n";
let request = XscpRequest::parse(raw_request).unwrap();
assert_eq!(OpCode::Send, request.opcode());
assert_eq!("source", request.source());
assert_eq!("message with | (pipe)", request.message());
}
}