Skip to content

Commit c6c72c5

Browse files
committed
refactor: replace unwrap() with expect() in production code
- Use expect() with descriptive messages for lock poisoning - Handle SQL prepare errors gracefully with early returns - Log HTTP server errors instead of panicking
1 parent cd06f98 commit c6c72c5

File tree

4 files changed

+26
-22
lines changed

4 files changed

+26
-22
lines changed

src/http.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ pub async fn run_http_server(
7070
.fallback(not_found)
7171
.with_state(state);
7272

73-
axum::serve(listener, app)
73+
if let Err(e) = axum::serve(listener, app)
7474
.with_graceful_shutdown(async move {
7575
let _ = shutdown.recv().await;
7676
})
7777
.await
78-
.unwrap();
78+
{
79+
tracing::error!("HTTP server error: {e}");
80+
}
7981
}
8082

8183
async fn serve_index() -> Html<&'static str> {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ pub async fn start_sink(opts: SinkOptions) -> std::io::Result<RunningServers> {
176176
let smtp_store = Arc::clone(&store);
177177
let smtp_shutdown = shutdown_tx.subscribe();
178178
let smtp_handle = if opts.tls {
179-
let acceptor = tls_acceptor.clone().unwrap();
179+
let acceptor = tls_acceptor.clone().expect("TLS acceptor required when tls=true");
180180
tokio::spawn(async move {
181181
smtp::run_smtps_server(smtp_listener, smtp_store, smtp_config, acceptor, smtp_shutdown)
182182
.await;

src/sqlite_store.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ impl SqliteStore {
125125
#[allow(clippy::significant_drop_tightening)]
126126
impl EmailStorage for SqliteStore {
127127
fn push(&self, email: MailRecord) {
128-
let conn = self.conn.lock().unwrap();
128+
let conn = self.conn.lock().expect("lock poisoned");
129129

130130
let to_json = serde_json::to_string(&email.to).unwrap_or_default();
131131
let headers_json = serde_json::to_string(&email.headers).unwrap_or_default();
@@ -170,10 +170,12 @@ impl EmailStorage for SqliteStore {
170170
}
171171

172172
fn get_all(&self) -> Vec<MailRecord> {
173-
let conn = self.conn.lock().unwrap();
174-
let mut stmt = conn
173+
let conn = self.conn.lock().expect("lock poisoned");
174+
let Ok(mut stmt) = conn
175175
.prepare("SELECT id, from_addr, to_addrs, subject, text_body, html_body, date, headers, raw FROM emails ORDER BY created_at DESC")
176-
.unwrap();
176+
else {
177+
return Vec::new();
178+
};
177179

178180
let mut records: Vec<MailRecord> = stmt
179181
.query_map([], Self::row_to_record)
@@ -188,7 +190,7 @@ impl EmailStorage for SqliteStore {
188190
}
189191

190192
fn get_by_id(&self, id: &str) -> Option<MailRecord> {
191-
let conn = self.conn.lock().unwrap();
193+
let conn = self.conn.lock().expect("lock poisoned");
192194
let mut stmt = conn
193195
.prepare("SELECT id, from_addr, to_addrs, subject, text_body, html_body, date, headers, raw FROM emails WHERE id = ?")
194196
.ok()?;
@@ -199,7 +201,7 @@ impl EmailStorage for SqliteStore {
199201
}
200202

201203
fn query(&self, query: &EmailQuery) -> Vec<MailRecord> {
202-
let conn = self.conn.lock().unwrap();
204+
let conn = self.conn.lock().expect("lock poisoned");
203205

204206
let mut sql = String::from(
205207
"SELECT id, from_addr, to_addrs, subject, text_body, html_body, date, headers, raw FROM emails WHERE 1=1"
@@ -248,7 +250,7 @@ impl EmailStorage for SqliteStore {
248250
}
249251

250252
fn get_attachment(&self, email_id: &str, filename: &str) -> Option<Attachment> {
251-
let conn = self.conn.lock().unwrap();
253+
let conn = self.conn.lock().expect("lock poisoned");
252254
let mut stmt = conn
253255
.prepare("SELECT filename, content_type, content_id, data, size FROM attachments WHERE email_id = ? AND filename = ?")
254256
.ok()?;
@@ -266,7 +268,7 @@ impl EmailStorage for SqliteStore {
266268
}
267269

268270
fn get_attachment_by_cid(&self, email_id: &str, cid: &str) -> Option<Attachment> {
269-
let conn = self.conn.lock().unwrap();
271+
let conn = self.conn.lock().expect("lock poisoned");
270272
let mut stmt = conn
271273
.prepare("SELECT filename, content_type, content_id, data, size FROM attachments WHERE email_id = ? AND content_id = ?")
272274
.ok()?;
@@ -284,14 +286,14 @@ impl EmailStorage for SqliteStore {
284286
}
285287

286288
fn clear(&self) {
287-
let conn = self.conn.lock().unwrap();
289+
let conn = self.conn.lock().expect("lock poisoned");
288290
let _ = conn.execute("DELETE FROM emails", []);
289291
drop(conn);
290292
let _ = self.notify.send(());
291293
}
292294

293295
fn remove(&self, id: &str) -> bool {
294-
let conn = self.conn.lock().unwrap();
296+
let conn = self.conn.lock().expect("lock poisoned");
295297
let result = conn.execute("DELETE FROM emails WHERE id = ?", params![id]);
296298
let removed = result.map(|n| n > 0).unwrap_or(false);
297299
drop(conn);
@@ -302,7 +304,7 @@ impl EmailStorage for SqliteStore {
302304
}
303305

304306
fn close(&self) {
305-
let conn = self.conn.lock().unwrap();
307+
let conn = self.conn.lock().expect("lock poisoned");
306308
// Checkpoint WAL to ensure all data is written to the main database file
307309
let _ = conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);");
308310
tracing::info!("SQLite database checkpointed and ready for shutdown");

src/store.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl MemoryStore {
4949

5050
impl EmailStorage for MemoryStore {
5151
fn push(&self, email: MailRecord) {
52-
let mut emails = self.emails.write().unwrap();
52+
let mut emails = self.emails.write().expect("lock poisoned");
5353
emails.push(email);
5454
while emails.len() > self.max {
5555
emails.remove(0);
@@ -59,15 +59,15 @@ impl EmailStorage for MemoryStore {
5959
}
6060

6161
fn get_all(&self) -> Vec<MailRecord> {
62-
self.emails.read().unwrap().clone()
62+
self.emails.read().expect("lock poisoned").clone()
6363
}
6464

6565
fn get_by_id(&self, id: &str) -> Option<MailRecord> {
66-
self.emails.read().unwrap().iter().find(|e| e.id == id).cloned()
66+
self.emails.read().expect("lock poisoned").iter().find(|e| e.id == id).cloned()
6767
}
6868

6969
fn query(&self, query: &EmailQuery) -> Vec<MailRecord> {
70-
let emails = self.emails.read().unwrap();
70+
let emails = self.emails.read().expect("lock poisoned");
7171
emails
7272
.iter()
7373
.filter(|e| matches_query(e, query))
@@ -77,25 +77,25 @@ impl EmailStorage for MemoryStore {
7777

7878
#[allow(clippy::significant_drop_tightening)]
7979
fn get_attachment(&self, email_id: &str, filename: &str) -> Option<Attachment> {
80-
let emails = self.emails.read().unwrap();
80+
let emails = self.emails.read().expect("lock poisoned");
8181
let email = emails.iter().find(|e| e.id == email_id)?;
8282
email.attachments.iter().find(|a| a.filename == filename).cloned()
8383
}
8484

8585
#[allow(clippy::significant_drop_tightening)]
8686
fn get_attachment_by_cid(&self, email_id: &str, cid: &str) -> Option<Attachment> {
87-
let emails = self.emails.read().unwrap();
87+
let emails = self.emails.read().expect("lock poisoned");
8888
let email = emails.iter().find(|e| e.id == email_id)?;
8989
email.attachments.iter().find(|a| a.content_id.as_deref() == Some(cid)).cloned()
9090
}
9191

9292
fn clear(&self) {
93-
self.emails.write().unwrap().clear();
93+
self.emails.write().expect("lock poisoned").clear();
9494
let _ = self.notify.send(());
9595
}
9696

9797
fn remove(&self, id: &str) -> bool {
98-
let mut emails = self.emails.write().unwrap();
98+
let mut emails = self.emails.write().expect("lock poisoned");
9999
let Some(pos) = emails.iter().position(|e| e.id == id) else {
100100
return false;
101101
};

0 commit comments

Comments
 (0)