Files
warren/backend/src/lib/inbound/http/handlers/warrens/fetch_file.rs
2025-07-23 13:47:42 +02:00

103 lines
3.0 KiB
Rust

use axum::{
body::Body,
extract::{Query, State},
};
use base64::Engine as _;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio_util::io::ReaderStream;
use uuid::Uuid;
use crate::{
domain::warren::{
models::{
auth_session::AuthRequest,
file::{AbsoluteFilePathError, FilePath, FilePathError, FileStream},
warren::FetchWarrenFileRequest,
},
ports::{AuthService, WarrenService},
},
inbound::http::{AppState, handlers::extractors::SessionIdHeader, responses::ApiError},
};
#[derive(Debug, Clone, Serialize, PartialEq, Hash)]
#[serde(rename_all = "camelCase")]
pub(super) struct FetchWarrenFileHttpResponseBody {
// base64 encoded file content bytes
contents: String,
}
impl From<Vec<u8>> for FetchWarrenFileHttpResponseBody {
fn from(value: Vec<u8>) -> Self {
Self {
contents: base64::prelude::BASE64_STANDARD.encode(&value),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchWarrenFileHttpRequestBody {
warren_id: Uuid,
path: String,
}
#[derive(Debug, Clone, Error)]
pub enum ParseFetchWarrenFileHttpRequestError {
#[error(transparent)]
FilePath(#[from] FilePathError),
#[error(transparent)]
AbsoluteFilePath(#[from] AbsoluteFilePathError),
}
impl From<ParseFetchWarrenFileHttpRequestError> for ApiError {
fn from(value: ParseFetchWarrenFileHttpRequestError) -> Self {
match value {
ParseFetchWarrenFileHttpRequestError::FilePath(err) => match err {
FilePathError::InvalidPath => {
ApiError::BadRequest("The file path must be valid".to_string())
}
},
ParseFetchWarrenFileHttpRequestError::AbsoluteFilePath(err) => match err {
AbsoluteFilePathError::NotAbsolute => {
ApiError::BadRequest("The file path must be absolute".to_string())
}
},
}
}
}
impl FetchWarrenFileHttpRequestBody {
fn try_into_domain(
self,
) -> Result<FetchWarrenFileRequest, ParseFetchWarrenFileHttpRequestError> {
let path = FilePath::new(&self.path)?.try_into()?;
Ok(FetchWarrenFileRequest::new(self.warren_id, path))
}
}
impl From<FileStream> for Body {
fn from(value: FileStream) -> Self {
Body::from_stream::<ReaderStream<tokio::fs::File>>(value.into())
}
}
pub async fn fetch_file<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
Query(request): Query<FetchWarrenFileHttpRequestBody>,
) -> Result<Body, ApiError> {
let domain_request = request.try_into_domain()?;
state
.auth_service
.fetch_warren_file(
AuthRequest::new(session, domain_request),
state.warren_service.as_ref(),
)
.await
.map(|contents| contents.into())
.map_err(ApiError::from)
}