use axum::{ Json, body::Bytes, extract::Path, extract::State, http::StatusCode, response::{IntoResponse, Response}, }; use serde::Serialize; use crate::{ app::AppState, model::{CompleteUploadResponse, CreateUploadRequest, CreateUploadResponse}, storage::StorageError, }; /// Creates an upload record and persists its metadata before returning. /// /// # Errors /// /// Returns an API error when request validation fails or metadata cannot be /// written to storage. pub async fn create_upload( State(state): State, Json(request): Json, ) -> Result, ApiError> { let meta = state.storage.create_upload(request).await?; Ok(Json(meta.create_response())) } /// Returns server-authoritative upload progress. /// /// # Errors /// /// Returns an API error when the upload id is invalid, unknown, or cannot be /// read from storage. pub async fn get_upload( State(state): State, Path(upload_id): Path, ) -> Result, ApiError> { Ok(Json(state.storage.progress(&upload_id).await?)) } /// Stores one raw binary chunk body. /// /// # Errors /// /// Returns an API error when the upload id is invalid or unknown, the chunk /// index is out of range, the body length is wrong, or the write fails. pub async fn put_chunk( State(state): State, Path((upload_id, index)): Path<(String, u64)>, body: Bytes, ) -> Result { state.storage.store_chunk(&upload_id, index, &body).await?; Ok(StatusCode::NO_CONTENT) } /// Assembles uploaded chunks into the final completed file. /// /// # Errors /// /// Returns an API error when the upload is unknown, incomplete, invalid, or /// cannot be assembled on disk. pub async fn complete_upload( State(state): State, Path(upload_id): Path, ) -> Result, ApiError> { Ok(Json(state.storage.complete_upload(&upload_id).await?)) } #[derive(Debug)] pub struct ApiError { status: StatusCode, message: String, } impl IntoResponse for ApiError { fn into_response(self) -> Response { ( self.status, Json(ErrorResponse { error: self.message, }), ) .into_response() } } impl From for ApiError { fn from(error: StorageError) -> Self { let status = if error.is_not_found() { StatusCode::NOT_FOUND } else if error.is_conflict() { StatusCode::CONFLICT } else if error.is_invalid_input() { StatusCode::BAD_REQUEST } else { StatusCode::INTERNAL_SERVER_ERROR }; Self { status, message: error.to_string(), } } } #[derive(Serialize)] struct ErrorResponse { error: String, }