sinan 0.1.0

A Boilerplate for Rapid Axum Web Service Deployment.
Documentation
use std::io;
use std::path::PathBuf;

use axum::BoxError;
use bytes::Bytes;
use eyre::eyre;
use futures::{Stream, TryStreamExt};
use tokio::io::{AsyncReadExt, BufWriter};
use tokio_util::io::StreamReader;

use crate::error::Error;
use crate::foundation::storage_path;

pub struct File {
    name: String,
    content: Bytes,
}

impl File {
    pub fn new(name: String, content: Bytes) -> Self {
        Self { name, content }
    }

    pub fn path(&self) -> PathBuf {
        storage_path(&self.name)
    }
}

pub fn is_valid(path: &str) -> bool {
    let path = std::path::Path::new(path);
    let mut components = path.components().peekable();

    if let Some(first) = components.peek() {
        if !matches!(first, std::path::Component::Normal(_)) {
            return false;
        }
    }

    components.count() == 1
}

pub async fn store<S, E>(path: &str, stream: S) -> eyre::Result<PathBuf>
where
    S: Stream<Item = Result<Bytes, E>>,
    E: Into<BoxError>,
{
    if !is_valid(path) {
        return Err(eyre!(Error::BadRequest("Invalid file path".to_owned())));
    }

    // Convert the stream into an `AsyncRead`.
    let body_with_io_error = stream.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
    let body_reader = StreamReader::new(body_with_io_error);
    futures::pin_mut!(body_reader);

    // Create the file. `File` implements `AsyncWrite`.
    let path = storage_path(path);
    let mut file = BufWriter::new(tokio::fs::File::create(path.clone()).await?);

    // Copy the body into the file.`
    tokio::io::copy(&mut body_reader, &mut file).await?;

    Ok(path.to_owned())
}

pub async fn get(path: &PathBuf) -> Result<Bytes, Error> {
    let mut file = tokio::fs::File::open(path).await?;

    let mut contents = vec![];
    file.read_to_end(&mut contents).await?;

    Ok(Bytes::from(contents))
}