redacter 0.16.2

Copy & Redact cli tool to securely copy and redact files removing Personal Identifiable Information (PII) across various filesystems.
use crate::errors::AppError;
use crate::file_systems::gcs::GoogleCloudStorageFileSystem;
use crate::file_systems::local::LocalFileSystem;
use crate::file_systems::zip::ZipFileSystem;
use crate::AppResult;
use futures::Stream;
use gcloud_sdk::prost::bytes;
use gcloud_sdk::prost::bytes::Bytes;
use mime::Mime;
use rvstruct::ValueStruct;

mod aws_s3;
mod gcs;
mod local;
mod zip;

#[cfg(feature = "clipboard")]
mod clipboard;

mod noop;

use crate::file_systems::aws_s3::AwsS3FileSystem;
use crate::file_tools::FileMatcher;
use crate::reporter::AppReporter;

#[derive(Debug, Clone, ValueStruct)]
pub struct RelativeFilePath(pub String);

impl RelativeFilePath {
    pub fn filename(&self) -> String {
        self.value()
            .split('/')
            .next_back()
            .map(|s| s.to_string())
            .unwrap_or_default()
    }
}

#[derive(Debug, Clone)]
pub struct AbsoluteFilePath {
    pub file_path: String,
}

#[derive(Debug, Clone)]
pub struct FileSystemRef {
    pub relative_path: RelativeFilePath,
    pub media_type: Option<Mime>,
    pub file_size: Option<usize>,
}

#[derive(Debug, Clone)]
pub struct ListFilesResult {
    pub files: Vec<FileSystemRef>,
    pub skipped: usize,
}

impl ListFilesResult {
    pub const EMPTY: ListFilesResult = ListFilesResult {
        files: Vec::new(),
        skipped: 0,
    };
}

pub trait FileSystemConnection<'a> {
    async fn download(
        &mut self,
        file_ref: Option<&FileSystemRef>,
    ) -> AppResult<(
        FileSystemRef,
        Box<dyn Stream<Item = AppResult<bytes::Bytes>> + Send + Sync + Unpin + 'static>,
    )>;

    async fn upload<S: Stream<Item = AppResult<bytes::Bytes>> + Send + Unpin + Sync + 'static>(
        &mut self,
        input: S,
        file_ref: Option<&FileSystemRef>,
    ) -> AppResult<()>;

    async fn list_files(
        &mut self,
        file_matcher: Option<&FileMatcher>,
        max_files_limit: Option<usize>,
    ) -> AppResult<ListFilesResult>;

    async fn close(self) -> AppResult<()>;

    async fn has_multiple_files(&self) -> AppResult<bool>;

    async fn accepts_multiple_files(&self) -> AppResult<bool>;

    fn resolve(&self, file_ref: Option<&FileSystemRef>) -> AbsoluteFilePath;
}

pub enum DetectFileSystem<'a> {
    Local(LocalFileSystem<'a>),
    GoogleCloudStorage(GoogleCloudStorageFileSystem<'a>),
    AwsS3(AwsS3FileSystem<'a>),
    ZipFile(ZipFileSystem<'a>),
    #[cfg(feature = "clipboard")]
    Clipboard(clipboard::ClipboardFileSystem<'a>),
}

impl<'a> DetectFileSystem<'a> {
    pub async fn open(
        file_path: &str,
        reporter: &'a AppReporter<'a>,
    ) -> AppResult<impl FileSystemConnection<'a>> {
        if file_path.starts_with("file://") || !file_path.contains("://") {
            Ok(DetectFileSystem::Local(
                LocalFileSystem::new(file_path, reporter).await?,
            ))
        } else if file_path.starts_with("gs://") {
            Ok(DetectFileSystem::GoogleCloudStorage(
                GoogleCloudStorageFileSystem::new(file_path, reporter).await?,
            ))
        } else if file_path.starts_with("s3://") {
            Ok(DetectFileSystem::AwsS3(
                AwsS3FileSystem::new(file_path, reporter).await?,
            ))
        } else if file_path.starts_with("zip://") {
            Ok(DetectFileSystem::ZipFile(
                ZipFileSystem::new(file_path, reporter).await?,
            ))
        } else if file_path.starts_with("clipboard://") {
            #[cfg(feature = "clipboard")]
            {
                Ok(DetectFileSystem::Clipboard(
                    clipboard::ClipboardFileSystem::new(file_path, reporter).await?,
                ))
            }
            #[cfg(not(feature = "clipboard"))]
            {
                return Err(AppError::UnknownFileSystem {
                    file_path: file_path.to_string(),
                });
            }
        } else {
            Err(AppError::UnknownFileSystem {
                file_path: file_path.to_string(),
            })
        }
    }
}

impl<'a> FileSystemConnection<'a> for DetectFileSystem<'a> {
    async fn download(
        &mut self,
        file_ref: Option<&FileSystemRef>,
    ) -> AppResult<(
        FileSystemRef,
        Box<dyn Stream<Item = AppResult<Bytes>> + Send + Sync + Unpin + 'static>,
    )> {
        match self {
            DetectFileSystem::Local(fs) => fs.download(file_ref).await,
            DetectFileSystem::GoogleCloudStorage(fs) => fs.download(file_ref).await,
            DetectFileSystem::AwsS3(fs) => fs.download(file_ref).await,
            DetectFileSystem::ZipFile(fs) => fs.download(file_ref).await,
            #[cfg(feature = "clipboard")]
            DetectFileSystem::Clipboard(fs) => fs.download(file_ref).await,
        }
    }

    async fn upload<S: Stream<Item = AppResult<Bytes>> + Send + Unpin + Sync + 'static>(
        &mut self,
        input: S,
        file_ref: Option<&FileSystemRef>,
    ) -> AppResult<()> {
        match self {
            DetectFileSystem::Local(fs) => fs.upload(input, file_ref).await,
            DetectFileSystem::GoogleCloudStorage(fs) => fs.upload(input, file_ref).await,
            DetectFileSystem::AwsS3(fs) => fs.upload(input, file_ref).await,
            DetectFileSystem::ZipFile(fs) => fs.upload(input, file_ref).await,
            #[cfg(feature = "clipboard")]
            DetectFileSystem::Clipboard(fs) => fs.upload(input, file_ref).await,
        }
    }

    async fn list_files(
        &mut self,
        file_matcher: Option<&FileMatcher>,
        max_files_limit: Option<usize>,
    ) -> AppResult<ListFilesResult> {
        match self {
            DetectFileSystem::Local(fs) => fs.list_files(file_matcher, max_files_limit).await,
            DetectFileSystem::GoogleCloudStorage(fs) => {
                fs.list_files(file_matcher, max_files_limit).await
            }
            DetectFileSystem::AwsS3(fs) => fs.list_files(file_matcher, max_files_limit).await,
            DetectFileSystem::ZipFile(fs) => fs.list_files(file_matcher, max_files_limit).await,
            #[cfg(feature = "clipboard")]
            DetectFileSystem::Clipboard(fs) => fs.list_files(file_matcher, max_files_limit).await,
        }
    }

    async fn close(self) -> AppResult<()> {
        match self {
            DetectFileSystem::Local(fs) => fs.close().await,
            DetectFileSystem::GoogleCloudStorage(fs) => fs.close().await,
            DetectFileSystem::AwsS3(fs) => fs.close().await,
            DetectFileSystem::ZipFile(fs) => fs.close().await,
            #[cfg(feature = "clipboard")]
            DetectFileSystem::Clipboard(fs) => fs.close().await,
        }
    }

    async fn has_multiple_files(&self) -> AppResult<bool> {
        match self {
            DetectFileSystem::Local(fs) => fs.has_multiple_files().await,
            DetectFileSystem::GoogleCloudStorage(fs) => fs.has_multiple_files().await,
            DetectFileSystem::AwsS3(fs) => fs.has_multiple_files().await,
            DetectFileSystem::ZipFile(fs) => fs.has_multiple_files().await,
            #[cfg(feature = "clipboard")]
            DetectFileSystem::Clipboard(fs) => fs.has_multiple_files().await,
        }
    }

    async fn accepts_multiple_files(&self) -> AppResult<bool> {
        match self {
            DetectFileSystem::Local(fs) => fs.accepts_multiple_files().await,
            DetectFileSystem::GoogleCloudStorage(fs) => fs.accepts_multiple_files().await,
            DetectFileSystem::AwsS3(fs) => fs.accepts_multiple_files().await,
            DetectFileSystem::ZipFile(fs) => fs.accepts_multiple_files().await,
            #[cfg(feature = "clipboard")]
            DetectFileSystem::Clipboard(fs) => fs.accepts_multiple_files().await,
        }
    }

    fn resolve(&self, file_ref: Option<&FileSystemRef>) -> AbsoluteFilePath {
        match self {
            DetectFileSystem::Local(fs) => fs.resolve(file_ref),
            DetectFileSystem::GoogleCloudStorage(fs) => fs.resolve(file_ref),
            DetectFileSystem::AwsS3(fs) => fs.resolve(file_ref),
            DetectFileSystem::ZipFile(fs) => fs.resolve(file_ref),
            #[cfg(feature = "clipboard")]
            DetectFileSystem::Clipboard(fs) => fs.resolve(file_ref),
        }
    }
}