use crate::{FixturesCommands, MailboxCommands, SmtpCommands};
use mockforge_smtp::SmtpFixture;
pub async fn handle_smtp_command(
smtp_command: SmtpCommands,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match smtp_command {
SmtpCommands::Mailbox { mailbox_command } => {
handle_mailbox_command(mailbox_command).await?;
}
SmtpCommands::Fixtures { fixtures_command } => {
handle_fixtures_command(fixtures_command).await?;
}
SmtpCommands::Send {
to,
subject,
body,
host,
port,
from,
} => {
handle_send_command(to, subject, body, host, port, from).await?;
}
}
Ok(())
}
async fn handle_mailbox_command(
mailbox_command: MailboxCommands,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match mailbox_command {
MailboxCommands::List => {
handle_mailbox_list().await?;
}
MailboxCommands::Show { email_id } => {
handle_mailbox_show(&email_id).await?;
}
MailboxCommands::Clear => {
handle_mailbox_clear().await?;
}
MailboxCommands::Export { format, output } => {
handle_mailbox_export(&format, &output).await?;
}
MailboxCommands::Search {
sender,
recipient,
subject,
body,
since,
until,
regex,
case_sensitive,
} => {
handle_mailbox_search(
sender,
recipient,
subject,
body,
since,
until,
regex,
case_sensitive,
)
.await?;
}
}
Ok(())
}
async fn handle_mailbox_list() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("📧 Listing emails in mailbox...");
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
match client.get(format!("{}/smtp/mailbox", management_url)).send().await {
Ok(response) => {
if response.status().is_success() {
let emails: Vec<serde_json::Value> = response.json().await?;
if emails.is_empty() {
println!("📭 Mailbox is empty");
} else {
println!("📬 Found {} emails:", emails.len());
println!("{:<5} {:<30} {:<50} {}", "ID", "From", "Subject", "Received");
println!("{}", "-".repeat(100));
for email in emails {
let id = email["id"].as_str().unwrap_or("N/A");
let from = email["from"].as_str().unwrap_or("N/A");
let subject = email["subject"].as_str().unwrap_or("N/A");
let received = email["received_at"].as_str().unwrap_or("N/A");
let subject_display = if subject.len() > 47 {
format!("{}...", &subject[..44])
} else {
subject.to_string()
};
println!(
"{:<5} {:<30} {:<50} {}",
&id[..std::cmp::min(id.len(), 5)],
&from[..std::cmp::min(from.len(), 30)],
subject_display,
received
);
}
}
} else {
println!("❌ Failed to access mailbox: HTTP {}", response.status());
println!("💡 Make sure MockForge server is running with SMTP enabled");
}
}
Err(e) => {
println!("❌ Failed to connect to MockForge management API: {}", e);
println!("💡 Make sure MockForge server is running at {}", management_url);
println!("💡 Or set MOCKFORGE_MANAGEMENT_URL environment variable");
}
}
Ok(())
}
async fn handle_mailbox_show(
email_id: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("📧 Showing email {}...", email_id);
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
match client.get(format!("{}/smtp/mailbox/{}", management_url, email_id)).send().await {
Ok(response) => {
if response.status().is_success() {
let email: serde_json::Value = response.json().await?;
println!("📧 Email Details:");
println!("ID: {}", email["id"].as_str().unwrap_or("N/A"));
println!("From: {}", email["from"].as_str().unwrap_or("N/A"));
println!(
"To: {}",
email["to"]
.as_array()
.map(|to| to
.iter()
.map(|t| t.as_str().unwrap_or("N/A"))
.collect::<Vec<_>>()
.join(", "))
.unwrap_or_else(|| "N/A".to_string())
);
println!("Subject: {}", email["subject"].as_str().unwrap_or("N/A"));
println!("Received: {}", email["received_at"].as_str().unwrap_or("N/A"));
println!();
println!("Headers:");
if let Some(headers) = email["headers"].as_object() {
for (key, value) in headers {
println!(" {}: {}", key, value.as_str().unwrap_or("N/A"));
}
}
println!();
println!("Body:");
println!("{}", email["body"].as_str().unwrap_or("N/A"));
} else if response.status() == reqwest::StatusCode::NOT_FOUND {
println!("❌ Email with ID '{}' not found", email_id);
} else {
println!("❌ Failed to retrieve email: HTTP {}", response.status());
}
}
Err(e) => {
println!("❌ Failed to connect to MockForge management API: {}", e);
println!("💡 Make sure MockForge server is running");
}
}
Ok(())
}
async fn handle_mailbox_clear() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("🗑️ Clearing mailbox...");
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
match client.delete(format!("{}/smtp/mailbox", management_url)).send().await {
Ok(response) => {
if response.status().is_success() {
println!("✅ Mailbox cleared successfully");
} else {
println!("❌ Failed to clear mailbox: HTTP {}", response.status());
}
}
Err(e) => {
println!("❌ Failed to connect to MockForge management API: {}", e);
println!("💡 Make sure MockForge server is running");
}
}
Ok(())
}
async fn handle_mailbox_export(
format: &str,
output: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("📤 Exporting mailbox to {} in {} format...", output.display(), format);
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
match client
.get(format!("{}/smtp/mailbox/export?format={}", management_url, format))
.send()
.await
{
Ok(response) => {
if response.status().is_success() {
let content = response.text().await?;
std::fs::write(output, content)?;
println!("✅ Mailbox exported to {}", output.display());
} else {
println!("❌ Failed to export mailbox: HTTP {}", response.status());
}
}
Err(e) => {
println!("❌ Failed to connect to MockForge management API: {}", e);
println!("💡 Make sure MockForge server is running");
}
}
Ok(())
}
async fn handle_mailbox_search(
sender: Option<String>,
recipient: Option<String>,
subject: Option<String>,
body: Option<String>,
since: Option<String>,
until: Option<String>,
regex: bool,
case_sensitive: bool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("🔍 Searching emails in mailbox...");
let mut query_params = Vec::new();
if let Some(ref s) = sender {
query_params.push(format!("sender={}", urlencoding::encode(s)));
}
if let Some(ref r) = recipient {
query_params.push(format!("recipient={}", urlencoding::encode(r)));
}
if let Some(ref s) = subject {
query_params.push(format!("subject={}", urlencoding::encode(s)));
}
if let Some(ref b) = body {
query_params.push(format!("body={}", urlencoding::encode(b)));
}
if let Some(ref s) = since {
query_params.push(format!("since={}", urlencoding::encode(s)));
}
if let Some(ref u) = until {
query_params.push(format!("until={}", urlencoding::encode(u)));
}
if regex {
query_params.push("regex=true".to_string());
}
if case_sensitive {
query_params.push("case_sensitive=true".to_string());
}
let query_string = if query_params.is_empty() {
String::new()
} else {
format!("?{}", query_params.join("&"))
};
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
match client
.get(format!("{}/smtp/mailbox/search{}", management_url, query_string))
.send()
.await
{
Ok(response) => {
if response.status().is_success() {
let emails: Vec<serde_json::Value> = response.json().await?;
if emails.is_empty() {
println!("🔍 No emails found matching the criteria");
} else {
println!("🔍 Found {} emails:", emails.len());
println!("{:<5} {:<30} {:<50} {}", "ID", "From", "Subject", "Received");
println!("{}", "-".repeat(100));
for email in emails {
let id = email["id"].as_str().unwrap_or("N/A");
let from = email["from"].as_str().unwrap_or("N/A");
let subject = email["subject"].as_str().unwrap_or("N/A");
let received = email["received_at"].as_str().unwrap_or("N/A");
let subject_display = if subject.len() > 47 {
format!("{}...", &subject[..44])
} else {
subject.to_string()
};
println!(
"{:<5} {:<30} {:<50} {}",
&id[..std::cmp::min(id.len(), 5)],
&from[..std::cmp::min(from.len(), 30)],
subject_display,
received
);
}
}
} else {
println!("❌ Failed to search mailbox: HTTP {}", response.status());
}
}
Err(e) => {
println!("❌ Failed to connect to MockForge management API: {}", e);
println!("💡 Make sure MockForge server is running");
}
}
Ok(())
}
async fn handle_fixtures_command(
fixtures_command: FixturesCommands,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match fixtures_command {
FixturesCommands::List => {
handle_fixtures_list().await?;
}
FixturesCommands::Reload => {
handle_fixtures_reload().await?;
}
FixturesCommands::Validate { file } => {
handle_fixtures_validate(&file).await?;
}
}
Ok(())
}
async fn handle_send_command(
to: String,
subject: String,
body: String,
host: String,
port: u16,
from: String,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("📤 Sending test email...");
println!(" From: {}", from);
println!(" To: {}", to);
println!(" Subject: {}", subject);
println!(" Server: {}:{}", host, port);
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
let stream = timeout(Duration::from_secs(5), TcpStream::connect(format!("{}:{}", host, port)))
.await
.map_err(|_| format!("Failed to connect to SMTP server at {}:{}", host, port))??;
let (reader, mut writer) = stream.into_split();
let mut reader = BufReader::new(reader);
let mut response = String::new();
timeout(Duration::from_secs(5), reader.read_line(&mut response))
.await
.map_err(|_| "Timeout reading SMTP greeting")??;
if !response.starts_with("220") {
return Err(format!("Unexpected SMTP greeting: {}", response.trim()).into());
}
response.clear();
writer.write_all(format!("EHLO {}\r\n", host).as_bytes()).await?;
loop {
let mut line = String::new();
timeout(Duration::from_secs(5), reader.read_line(&mut line))
.await
.map_err(|_| "Timeout reading EHLO response")??;
response.push_str(&line);
if line.starts_with("250 ") {
break;
}
}
response.clear();
writer.write_all(format!("MAIL FROM:<{}>\r\n", from).as_bytes()).await?;
timeout(Duration::from_secs(5), reader.read_line(&mut response))
.await
.map_err(|_| "Timeout reading MAIL FROM response")??;
if !response.starts_with("250") {
return Err(format!("MAIL FROM rejected: {}", response.trim()).into());
}
response.clear();
writer.write_all(format!("RCPT TO:<{}>\r\n", to).as_bytes()).await?;
timeout(Duration::from_secs(5), reader.read_line(&mut response))
.await
.map_err(|_| "Timeout reading RCPT TO response")??;
if !response.starts_with("250") {
return Err(format!("RCPT TO rejected: {}", response.trim()).into());
}
response.clear();
writer.write_all(b"DATA\r\n").await?;
timeout(Duration::from_secs(5), reader.read_line(&mut response))
.await
.map_err(|_| "Timeout reading DATA response")??;
if !response.starts_with("354") {
return Err(format!("DATA command rejected: {}", response.trim()).into());
}
response.clear();
writer.write_all(format!("From: {}\r\n", from).as_bytes()).await?;
writer.write_all(format!("To: {}\r\n", to).as_bytes()).await?;
writer.write_all(format!("Subject: {}\r\n", subject).as_bytes()).await?;
writer.write_all(b"\r\n").await?;
writer.write_all(format!("{}\r\n", body).as_bytes()).await?;
writer.write_all(b".\r\n").await?;
timeout(Duration::from_secs(5), reader.read_line(&mut response))
.await
.map_err(|_| "Timeout reading message acceptance response")??;
if !response.starts_with("250") {
return Err(format!("Message rejected: {}", response.trim()).into());
}
response.clear();
writer.write_all(b"QUIT\r\n").await?;
timeout(Duration::from_secs(5), reader.read_line(&mut response))
.await
.map_err(|_| "Timeout reading QUIT response")??;
if !response.starts_with("221") {
return Err(format!("QUIT rejected: {}", response.trim()).into());
}
println!("✅ Email sent successfully!");
Ok(())
}
async fn handle_fixtures_reload() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("🔄 Reloading SMTP fixtures from disk...");
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
match client.post(format!("{}/smtp/fixtures/reload", management_url)).send().await {
Ok(response) => {
if response.status().is_success() {
let result: serde_json::Value = response.json().await?;
let count = result["fixtures_loaded"].as_u64().unwrap_or(0);
println!("✅ Successfully reloaded {} fixtures", count);
} else {
println!("❌ Failed to reload fixtures: HTTP {}", response.status());
}
}
Err(e) => {
println!("❌ Failed to connect to MockForge management API: {}", e);
println!("💡 Make sure MockForge server is running");
}
}
Ok(())
}
async fn handle_fixtures_validate(
file: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("🔍 Validating SMTP fixture file {}...", file.display());
match std::fs::read_to_string(file) {
Ok(content) => {
let parse_result: Result<SmtpFixture, Box<dyn std::error::Error>> =
if file.extension().and_then(|s| s.to_str()) == Some("json") {
serde_json::from_str::<SmtpFixture>(&content)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
} else {
serde_yaml::from_str::<SmtpFixture>(&content)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
};
match parse_result {
Ok(fixture) => {
println!("✅ Fixture file is valid");
println!(" Identifier: {}", fixture.identifier);
println!(" Name: {}", fixture.name);
println!(" Description: {}", fixture.description);
println!(" Status Code: {}", fixture.response.status_code);
println!(" Match All: {}", fixture.match_criteria.match_all);
if let Some(pattern) = &fixture.match_criteria.recipient_pattern {
println!(" Recipient Pattern: {}", pattern);
}
if let Some(pattern) = &fixture.match_criteria.sender_pattern {
println!(" Sender Pattern: {}", pattern);
}
if let Some(pattern) = &fixture.match_criteria.subject_pattern {
println!(" Subject Pattern: {}", pattern);
}
}
Err(e) => {
println!("❌ Fixture file is invalid: {}", e);
return Ok(());
}
}
}
Err(e) => {
println!("❌ Failed to read fixture file: {}", e);
return Ok(());
}
}
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
let file_content = std::fs::read_to_string(file)?;
match client
.post(format!("{}/smtp/fixtures/validate", management_url))
.body(file_content)
.send()
.await
{
Ok(response) => {
if response.status().is_success() {
println!("✅ Server validation passed");
} else {
let error_msg =
response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
println!("⚠️ Server validation failed: {}", error_msg);
}
}
Err(_) => {
println!("💡 Server validation skipped (server not running)");
}
}
Ok(())
}
async fn handle_fixtures_list() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("📋 Listing loaded SMTP fixtures...");
let client = reqwest::Client::new();
let management_url = std::env::var("MOCKFORGE_MANAGEMENT_URL")
.unwrap_or_else(|_| "http://localhost:8080/__mockforge/api".to_string());
match client.get(format!("{}/smtp/fixtures", management_url)).send().await {
Ok(response) => {
if response.status().is_success() {
let fixtures: Vec<serde_json::Value> = response.json().await?;
if fixtures.is_empty() {
println!("📋 No fixtures loaded");
} else {
println!("📋 Found {} fixtures:", fixtures.len());
println!("{:<20} {:<50} {}", "Identifier", "Name", "Description");
println!("{}", "-".repeat(100));
for fixture in fixtures {
let identifier = fixture["identifier"].as_str().unwrap_or("N/A");
let name = fixture["name"].as_str().unwrap_or("N/A");
let description = fixture["description"].as_str().unwrap_or("");
let name_display = if name.len() > 47 {
format!("{}...", &name[..44])
} else {
name.to_string()
};
println!(
"{:<20} {:<50} {}",
&identifier[..std::cmp::min(identifier.len(), 20)],
name_display,
description
);
}
}
} else {
println!("❌ Failed to access fixtures: HTTP {}", response.status());
println!("💡 Make sure MockForge server is running with SMTP enabled");
}
}
Err(e) => {
println!("❌ Failed to connect to MockForge management API: {}", e);
println!("💡 Make sure MockForge server is running at {}", management_url);
println!("💡 Or set MOCKFORGE_MANAGEMENT_URL environment variable");
}
}
Ok(())
}