feat: split crate into library and thin CLI binary

The crypto engine was only reachable through the fcry binary; embedding
it in another Rust project meant shelling out to the CLI. Restructure
the crate so the binary sits on top of a proper library API.

- Add src/lib.rs exposing encrypt/decrypt/decrypt_range/derive_key, the
  header and policy types, and the secret-handling primitives.
- Replace the positional-argument wrapper ladder
  (encrypt_with_output_options, decrypt_with_argon_cap, ...) with
  options structs: EncryptOptions, DecryptOptions, DecryptRangeOptions
  and HeaderReadOptions. OutSinkOptions becomes the public
  OutputOptions and no longer carries the input path; the input is now
  an explicit parameter to OutSink::open_with_options so the
  same-file-aliasing guard's inputs are visible at each call site.
- File parameters take Option<PathBuf>/&Path instead of AsRef<str>, so
  non-UTF-8 paths work.
- FcryError implements Display and std::error::Error so it composes
  with anyhow/thiserror-style error handling in downstream crates.
- Move read_key_file and normalize_passphrase from main.rs into
  secrets.rs so library users get the same strict 32-byte key-file
  parsing and NFC passphrase normalization. The world-readable
  key-file warning stays in the CLI wrapper (read_key_file_cli).
- Drop now-unneeded #[allow(dead_code)] markers; ReadInfoChunk::Normal
  loses its unused byte-count payload.
- Add rustfmt.toml (StdExternalCrate grouping, crate-granularity
  imports) and reformat imports accordingly.
- Add tests/library_api.rs covering a file round-trip and a range
  decrypt through the public API with a raw key.

User-visible change: CLI behavior is unchanged except error output,
which is now human-readable Display text ("Error: wrong key or
passphrase") instead of the Rust Debug representation.

Test plan: cargo clippy (default, --tests, --benches) is clean;
cargo +nightly fmt produces no diff; cargo test passes 43 tests
including the new library_api integration tests.
This commit is contained in:
2026-06-12 22:49:23 +02:00
parent f44cfc6190
commit 2f16e735c3
13 changed files with 533 additions and 356 deletions
+30 -2
View File
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT-0
use std::{fmt, io};
use chacha20poly1305::aead;
use std::io;
#[allow(dead_code)]
#[derive(Debug)]
pub enum FcryError {
Io(io::Error),
@@ -15,6 +15,34 @@ pub enum FcryError {
WrongKey,
}
impl fmt::Display for FcryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Crypto(_) => write!(f, "cryptographic authentication failed"),
Self::Rng(e) => write!(f, "randomness error: {e}"),
Self::Format(msg) => write!(f, "format error: {msg}"),
Self::Kdf(msg) => write!(f, "KDF error: {msg}"),
Self::Passphrase(msg) => write!(f, "passphrase error: {msg}"),
Self::WrongKey => write!(f, "wrong key or passphrase"),
}
}
}
impl std::error::Error for FcryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::Rng(e) => Some(e),
Self::Crypto(_)
| Self::Format(_)
| Self::Kdf(_)
| Self::Passphrase(_)
| Self::WrongKey => None,
}
}
}
impl From<io::Error> for FcryError {
fn from(e: io::Error) -> Self {
FcryError::Io(e)