fcrypt 0.3.2

Cross-platform CLI for password-based file encryption and decryption
Documentation
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};

use crate::error::{AppError, Result};

pub fn encryption_output_path(input: &Path) -> Result<PathBuf> {
    let file_name = input
        .file_name()
        .ok_or_else(|| AppError::MissingFileName(input.to_path_buf()))?;

    let mut encrypted_name: OsString = file_name.to_os_string();
    encrypted_name.push(".bin");
    Ok(input.with_file_name(encrypted_name))
}

pub fn decryption_output_path(input: &Path) -> Result<PathBuf> {
    let file_name = input
        .file_name()
        .ok_or_else(|| AppError::MissingFileName(input.to_path_buf()))?;

    if has_extension(input, "bin") || has_extension(input, "enc") {
        return Ok(input.with_extension(""));
    }

    let mut decrypted_name: OsString = file_name.to_os_string();
    decrypted_name.push(".dec");
    Ok(input.with_file_name(decrypted_name))
}

pub fn asym_encryption_output_path(input: &Path) -> Result<PathBuf> {
    let file_name = input
        .file_name()
        .ok_or_else(|| AppError::MissingFileName(input.to_path_buf()))?;

    let mut encrypted_name: OsString = file_name.to_os_string();
    encrypted_name.push(".bin");
    Ok(input.with_file_name(encrypted_name))
}

pub fn asym_decryption_output_path(input: &Path) -> Result<PathBuf> {
    let file_name = input
        .file_name()
        .ok_or_else(|| AppError::MissingFileName(input.to_path_buf()))?;

    if has_extension(input, "bin") {
        return Ok(input.with_extension(""));
    }

    let mut decrypted_name: OsString = file_name.to_os_string();
    decrypted_name.push(".dec");
    Ok(input.with_file_name(decrypted_name))
}

pub fn asym_default_keys_dir_for_plain_input(input: &Path) -> Result<PathBuf> {
    default_keys_dir(input)
}

pub fn asym_default_keys_dir_for_encrypted_input(input: &Path) -> Result<PathBuf> {
    let logical_input = asym_decryption_output_path(input)?;
    default_keys_dir(&logical_input)
}

fn default_keys_dir(input: &Path) -> Result<PathBuf> {
    let file_stem = input
        .file_stem()
        .ok_or_else(|| AppError::MissingFileName(input.to_path_buf()))?;

    let mut dir_name = file_stem.to_os_string();
    dir_name.push("_keys");
    Ok(input.with_file_name(dir_name))
}

fn has_extension(path: &Path, extension: &str) -> bool {
    path.extension()
        .is_some_and(|value| value == OsStr::new(extension))
}