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> for FetchWarrenFileHttpResponseBody { fn from(value: Vec) -> 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 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 { let path = FilePath::new(&self.path)?.try_into()?; Ok(FetchWarrenFileRequest::new(self.warren_id, path)) } } impl From for Body { fn from(value: FileStream) -> Self { Body::from_stream::>(value.into()) } } pub async fn fetch_file( State(state): State>, SessionIdHeader(session): SessionIdHeader, Query(request): Query, ) -> Result { 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) }