use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct SmtpResponse {
code: u16,
lines: Vec<String>,
}
impl SmtpResponse {
pub fn new(code: u16, message: impl Into<String>) -> Self {
Self {
code,
lines: vec![message.into()],
}
}
pub fn multiline(code: u16, lines: Vec<String>) -> Self {
Self { code, lines }
}
pub fn code(&self) -> u16 {
self.code
}
pub fn is_success(&self) -> bool {
self.code >= 200 && self.code < 300
}
pub fn is_permanent_error(&self) -> bool {
self.code >= 500 && self.code < 600
}
pub fn is_temporary_error(&self) -> bool {
self.code >= 400 && self.code < 500
}
pub fn format(&self) -> String {
if self.lines.len() == 1 {
format!("{} {}\r\n", self.code, self.lines[0])
} else {
let mut result = String::new();
let last_idx = self.lines.len() - 1;
for (idx, line) in self.lines.iter().enumerate() {
if idx == last_idx {
result.push_str(&format!("{} {}\r\n", self.code, line));
} else {
result.push_str(&format!("{}-{}\r\n", self.code, line));
}
}
result
}
}
pub fn service_ready(domain: &str) -> Self {
Self::new(220, format!("{} RusMES SMTP Server ready", domain))
}
pub fn closing() -> Self {
Self::new(221, "Bye")
}
pub fn ok(message: impl Into<String>) -> Self {
Self::new(250, message)
}
pub fn ok_simple() -> Self {
Self::new(250, "OK")
}
pub fn ehlo(domain: &str, extensions: Vec<String>) -> Self {
let mut lines = vec![domain.to_string()];
lines.extend(extensions);
Self::multiline(250, lines)
}
pub fn start_data() -> Self {
Self::new(354, "Start mail input; end with <CRLF>.<CRLF>")
}
pub fn service_unavailable(message: impl Into<String>) -> Self {
Self::new(421, message)
}
pub fn mailbox_unavailable(message: impl Into<String>) -> Self {
Self::new(450, message)
}
pub fn local_error(message: impl Into<String>) -> Self {
Self::new(451, message)
}
pub fn insufficient_storage() -> Self {
Self::new(452, "Insufficient system storage")
}
pub fn syntax_error(message: impl Into<String>) -> Self {
Self::new(500, message)
}
pub fn parameter_error(message: impl Into<String>) -> Self {
Self::new(501, message)
}
pub fn not_implemented(message: impl Into<String>) -> Self {
Self::new(502, message)
}
pub fn bad_sequence(message: impl Into<String>) -> Self {
Self::new(503, message)
}
pub fn parameter_not_implemented(message: impl Into<String>) -> Self {
Self::new(504, message)
}
pub fn mailbox_not_found(message: impl Into<String>) -> Self {
Self::new(550, message)
}
pub fn user_not_local(message: impl Into<String>) -> Self {
Self::new(551, message)
}
pub fn storage_exceeded(message: impl Into<String>) -> Self {
Self::new(552, message)
}
pub fn mailbox_name_invalid(message: impl Into<String>) -> Self {
Self::new(553, message)
}
pub fn transaction_failed(message: impl Into<String>) -> Self {
Self::new(554, message)
}
pub fn parameters_not_recognized(message: impl Into<String>) -> Self {
Self::new(555, message)
}
}
impl fmt::Display for SmtpResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.format().trim_end())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_line_response() {
let resp = SmtpResponse::new(250, "OK");
assert_eq!(resp.format(), "250 OK\r\n");
assert!(resp.is_success());
}
#[test]
fn test_multiline_response() {
let resp = SmtpResponse::multiline(
250,
vec![
"mail.example.com".to_string(),
"SIZE 10240000".to_string(),
"STARTTLS".to_string(),
],
);
let formatted = resp.format();
assert!(formatted.contains("250-mail.example.com\r\n"));
assert!(formatted.contains("250-SIZE 10240000\r\n"));
assert!(formatted.contains("250 STARTTLS\r\n"));
}
#[test]
fn test_ehlo_response() {
let resp = SmtpResponse::ehlo(
"mail.example.com",
vec!["SIZE 10240000".to_string(), "STARTTLS".to_string()],
);
assert_eq!(resp.code(), 250);
let formatted = resp.format();
assert!(formatted.contains("mail.example.com"));
assert!(formatted.contains("SIZE"));
assert!(formatted.contains("STARTTLS"));
}
#[test]
fn test_error_types() {
let temp_error = SmtpResponse::new(450, "Temporary error");
assert!(temp_error.is_temporary_error());
assert!(!temp_error.is_permanent_error());
assert!(!temp_error.is_success());
let perm_error = SmtpResponse::new(550, "Permanent error");
assert!(perm_error.is_permanent_error());
assert!(!perm_error.is_temporary_error());
assert!(!perm_error.is_success());
let success = SmtpResponse::new(250, "Success");
assert!(success.is_success());
assert!(!success.is_temporary_error());
assert!(!success.is_permanent_error());
}
#[test]
fn test_standard_responses() {
assert_eq!(SmtpResponse::ok_simple().code(), 250);
assert_eq!(SmtpResponse::closing().code(), 221);
assert_eq!(SmtpResponse::start_data().code(), 354);
assert_eq!(SmtpResponse::syntax_error("test").code(), 500);
assert_eq!(SmtpResponse::mailbox_not_found("test").code(), 550);
}
}