Skip to content

Commit 3c14ecc

Browse files
committed
refactor: improve error handling with anyhow
1 parent b1028ae commit 3c14ecc

File tree

2 files changed

+91
-31
lines changed

2 files changed

+91
-31
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ path = "src/main.rs"
1111
name = "coduck-backend"
1212

1313
[dependencies]
14+
anyhow = "1.0"
1415
axum = { version = "0.8.4", features = ["json", "multipart"] }
1516
chrono = { version = "0.4.38", features = ["serde"] }
1617
reqwest = { version = "0.12.19", features = ["json", "rustls-tls"] }

src/file_manager/handlers.rs

Lines changed: 90 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,49 @@ use crate::file_manager::{
22
FileMetadata, Language, UpdateFileContentRequest, UpdateFilenameRequest,
33
};
44

5+
use anyhow::{anyhow, Result};
56
use axum::{
67
extract::{Multipart, Path},
8+
http::StatusCode,
79
response::IntoResponse,
810
Json,
911
};
1012
use chrono::Utc;
11-
use reqwest::StatusCode;
1213
use std::path::PathBuf;
1314
use tokio::fs;
1415
use uuid::Uuid;
1516

1617
const UPLOAD_DIR: &str = "uploads";
1718

19+
async fn file_exists(file_path: &PathBuf) -> bool {
20+
tokio::fs::metadata(file_path).await.is_ok()
21+
}
22+
1823
pub async fn upload_file(
1924
Path((problem_id, category)): Path<(u32, String)>,
20-
mut multipart: Multipart,
25+
multipart: Multipart,
2126
) -> impl IntoResponse {
22-
let field = multipart.next_field().await.unwrap().unwrap();
23-
let file_name = field.file_name().unwrap().to_string();
24-
let data = field.bytes().await.unwrap();
27+
let (file_name, data) = match extract_multipart_data(multipart).await {
28+
Ok(data) => data,
29+
Err(e) => {
30+
return (
31+
StatusCode::BAD_REQUEST,
32+
Json(serde_json::json!({
33+
"error": e.to_string()
34+
})),
35+
)
36+
.into_response();
37+
}
38+
};
39+
2540
let file_id = Uuid::new_v4().to_string();
26-
let upload_dir = PathBuf::from(format!("{}/{}/{}", UPLOAD_DIR, problem_id, category));
41+
let upload_dir = PathBuf::from(UPLOAD_DIR)
42+
.join(problem_id.to_string())
43+
.join(&category);
2744

2845
let file_path = upload_dir.join(&file_name);
2946

30-
if file_path.exists() {
47+
if file_exists(&file_path).await {
3148
return (
3249
StatusCode::CONFLICT,
3350
Json(serde_json::json!({
@@ -51,8 +68,16 @@ pub async fn upload_file(
5168
}
5269
};
5370

54-
fs::create_dir_all(&upload_dir).await.unwrap();
55-
fs::write(&file_path, &data).await.unwrap();
71+
if let Err(e) = save_file(&upload_dir, &file_path, &data).await {
72+
return (
73+
StatusCode::INTERNAL_SERVER_ERROR,
74+
Json(serde_json::json!({
75+
"error": e.to_string()
76+
})),
77+
)
78+
.into_response();
79+
}
80+
5681
let metadata = FileMetadata {
5782
id: file_id,
5883
filename: file_name,
@@ -65,15 +90,47 @@ pub async fn upload_file(
6590
(StatusCode::CREATED, Json(metadata)).into_response()
6691
}
6792

93+
async fn save_file(
94+
upload_dir: &PathBuf,
95+
file_path: &PathBuf,
96+
data: &axum::body::Bytes,
97+
) -> Result<()> {
98+
fs::create_dir_all(upload_dir)
99+
.await
100+
.map_err(|_| anyhow!("Failed to create directory"))?;
101+
102+
fs::write(file_path, data)
103+
.await
104+
.map_err(|_| anyhow!("Failed to write file"))?;
105+
106+
Ok(())
107+
}
108+
109+
async fn extract_multipart_data(mut multipart: Multipart) -> Result<(String, axum::body::Bytes)> {
110+
let field = multipart
111+
.next_field()
112+
.await?
113+
.ok_or_else(|| anyhow!("Missing multipart field"))?;
114+
115+
let file_name = field
116+
.file_name()
117+
.ok_or_else(|| anyhow!("Missing file name in multipart field"))?
118+
.to_string();
119+
120+
let data = field.bytes().await?;
121+
122+
Ok((file_name, data))
123+
}
124+
68125
pub async fn get_file(
69126
Path((problem_id, category, filename)): Path<(u32, String, String)>,
70127
) -> impl IntoResponse {
71-
let file_path = PathBuf::from(format!(
72-
"{}/{}/{}/{}",
73-
UPLOAD_DIR, problem_id, category, filename
74-
));
128+
let file_path = PathBuf::from(UPLOAD_DIR)
129+
.join(problem_id.to_string())
130+
.join(&category)
131+
.join(&filename);
75132

76-
if !file_path.exists() {
133+
if !file_exists(&file_path).await {
77134
return (
78135
StatusCode::NOT_FOUND,
79136
Json(serde_json::json!({
@@ -107,9 +164,11 @@ pub async fn get_file(
107164
pub async fn get_files_by_category(
108165
Path((problem_id, category)): Path<(u32, String)>,
109166
) -> impl IntoResponse {
110-
let category_dir = PathBuf::from(format!("{}/{}/{}", UPLOAD_DIR, problem_id, category));
167+
let category_dir = PathBuf::from(UPLOAD_DIR)
168+
.join(problem_id.to_string())
169+
.join(&category);
111170

112-
if !category_dir.exists() {
171+
if !file_exists(&category_dir).await {
113172
return (
114173
StatusCode::NOT_FOUND,
115174
Json(serde_json::json!({
@@ -149,12 +208,12 @@ pub async fn get_files_by_category(
149208
pub async fn delete_file(
150209
Path((problem_id, category, filename)): Path<(u32, String, String)>,
151210
) -> impl IntoResponse {
152-
let file_path = PathBuf::from(format!(
153-
"{}/{}/{}/{}",
154-
UPLOAD_DIR, problem_id, category, filename
155-
));
211+
let file_path = PathBuf::from(UPLOAD_DIR)
212+
.join(problem_id.to_string())
213+
.join(&category)
214+
.join(&filename);
156215

157-
if !file_path.exists() {
216+
if !file_exists(&file_path).await {
158217
return (
159218
StatusCode::NOT_FOUND,
160219
Json(serde_json::json!({
@@ -186,12 +245,12 @@ pub async fn update_file_content(
186245
Path((problem_id, category, filename)): Path<(u32, String, String)>,
187246
Json(update_request): Json<UpdateFileContentRequest>,
188247
) -> impl IntoResponse {
189-
let file_path = PathBuf::from(format!(
190-
"{}/{}/{}/{}",
191-
UPLOAD_DIR, problem_id, category, filename
192-
));
248+
let file_path = PathBuf::from(UPLOAD_DIR)
249+
.join(problem_id.to_string())
250+
.join(&category)
251+
.join(&filename);
193252

194-
if !file_path.exists() {
253+
if !file_exists(&file_path).await {
195254
return (
196255
StatusCode::NOT_FOUND,
197256
Json(serde_json::json!({
@@ -223,12 +282,12 @@ pub async fn update_filename(
223282
Path((problem_id, category)): Path<(u32, String)>,
224283
Json(update_request): Json<UpdateFilenameRequest>,
225284
) -> impl IntoResponse {
226-
let file_path = PathBuf::from(format!(
227-
"{}/{}/{}/{}",
228-
UPLOAD_DIR, problem_id, category, update_request.old_filename
229-
));
285+
let file_path = PathBuf::from(UPLOAD_DIR)
286+
.join(problem_id.to_string())
287+
.join(&category)
288+
.join(&update_request.old_filename);
230289

231-
if !file_path.exists() {
290+
if !file_exists(&file_path).await {
232291
return (
233292
StatusCode::NOT_FOUND,
234293
Json(serde_json::json!({

0 commit comments

Comments
 (0)