treewide: improve rustdoc for common functions; add must_use annotations

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I380a36e7007aa8b59cf6c730f4474cd66a6a6964
This commit is contained in:
raf 2026-03-17 21:37:07 +03:00
commit 89d40109e5
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
7 changed files with 107 additions and 1 deletions

View file

@ -4,9 +4,12 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Deserialize, Serialize)]
#[serde(transparent)]
#[must_use]
pub struct Secret(String);
impl Secret {
/// Returns the secret value.
#[must_use]
pub fn expose(&self) -> &str {
&self.0
}
@ -32,6 +35,18 @@ pub struct Config {
}
impl Config {
/// Loads configuration from a TOML file with environment variable
/// interpolation.
///
/// # Errors
///
/// Returns an error if:
///
/// - The file cannot be read
/// - Environment variable interpolation fails
/// - The TOML parsing fails
/// - Configuration validation fails (no sources, no sinks, invalid
/// `sample_ratio`)
pub fn from_file(path: impl AsRef<Path>) -> anyhow::Result<Self> {
let path = path.as_ref();
let contents = std::fs::read_to_string(path).map_err(|e| {
@ -203,6 +218,8 @@ pub enum SinkConfig {
}
impl SinkConfig {
/// Returns the sink identifier.
#[must_use]
pub fn id(&self) -> &str {
match self {
Self::Filesystem(cfg) => &cfg.id,

View file

@ -3,6 +3,10 @@ use blake3::Hasher;
use crate::core::types::{ContentHash, Manifest};
/// Converts a hex string to a 32-byte array.
///
/// # Errors
///
/// Returns an error if the hex string is invalid or not exactly 32 bytes.
fn hex_to_bytes(hex_str: &str) -> anyhow::Result<[u8; 32]> {
let bytes = hex::decode(hex_str)
.map_err(|e| anyhow::anyhow!("Invalid hex string '{hex_str}': {e}"))?;
@ -30,6 +34,10 @@ fn hex_to_bytes(hex_str: &str) -> anyhow::Result<[u8; 32]> {
/// - Deterministic root hash for the manifest
/// - Efficient inclusion proofs (log n verification)
/// - Cryptographic integrity guarantees
///
/// # Errors
///
/// Returns an error if any leaf hash is not a valid 32-byte hex string.
pub fn compute_merkle_root(
leaf_hashes: &[ContentHash],
) -> anyhow::Result<ContentHash> {
@ -75,6 +83,9 @@ pub fn compute_merkle_root(
/// The sibling hashes needed to verify the leaf is in the tree.
/// To verify: starting with the leaf hash, iteratively hash with siblings
/// and compare against the known root.
///
/// Returns `None` if `leaf_index` is out of bounds or if any hash is invalid.
#[must_use]
pub fn get_inclusion_proof(
leaf_hashes: &[ContentHash],
leaf_index: usize,
@ -142,6 +153,7 @@ pub fn get_inclusion_proof(
/// # Returns
///
/// `true` if the proof is valid for the given root and leaf hash.
#[must_use]
pub fn verify_inclusion_proof(
root: &ContentHash,
leaf_hash: &ContentHash,
@ -177,6 +189,7 @@ pub fn verify_inclusion_proof(
/// A node in a Merkle inclusion proof.
#[derive(Debug, Clone)]
#[must_use]
pub enum MerkleProofNode {
/// Sibling is to the left of the target.
Left([u8; 32]),
@ -186,12 +199,18 @@ pub enum MerkleProofNode {
/// A Merkle inclusion proof.
#[derive(Debug, Clone)]
#[must_use]
pub struct MerkleProof {
pub leaf_index: usize,
pub siblings: Vec<MerkleProofNode>,
}
/// Finalizes the manifest by computing its Merkle root hash.
///
/// # Errors
///
/// Returns an error if any artifact hash in the manifest is not a valid
/// 32-byte hex string.
pub fn finalize_manifest(manifest: &mut Manifest) -> anyhow::Result<()> {
if manifest.root_hash.is_none() {
let leaf_hashes: Vec<ContentHash> = manifest

View file

@ -5,6 +5,11 @@ use std::path::Path;
use blake3::Hasher;
use tokio::io::AsyncReadExt;
/// Computes the BLAKE3 hash of a file.
///
/// # Errors
///
/// Returns an error if the file cannot be opened or read.
pub async fn hash_file(path: &Path) -> anyhow::Result<String> {
let mut file = tokio::fs::File::open(path).await?;
let mut hasher = Hasher::new();
@ -27,6 +32,8 @@ pub struct StreamingHasher {
}
impl StreamingHasher {
/// Creates a new streaming hasher.
#[must_use]
pub fn new() -> Self {
Self {
hasher: Hasher::new(),
@ -39,6 +46,8 @@ impl StreamingHasher {
self.bytes_hashed += data.len() as u64;
}
/// Finalizes the hash and returns the hash string and byte count.
#[must_use]
pub fn finalize(self) -> (String, u64) {
let hash = self.hasher.finalize().to_hex().to_string();
(hash, self.bytes_hashed)

View file

@ -6,6 +6,7 @@ use crate::config::RetryConfig;
/// Classification of an error for retry purposes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use]
pub enum ErrorKind {
/// Transient error - safe to retry (network failure, rate limit, 5xx).
Transient,
@ -14,6 +15,13 @@ pub enum ErrorKind {
}
/// Classify a `reqwest::Error` for retry purposes.
///
/// # Errors
///
/// This function does not return a `Result`, but returns
/// `ErrorKind::Transient` for network-level errors (connection, timeout,
/// request) and HTTP 5xx/429 status codes, or `ErrorKind::Permanent` for
/// HTTP 4xx client errors.
pub fn classify_reqwest_error(err: &reqwest::Error) -> ErrorKind {
if err.is_connect() || err.is_timeout() || err.is_request() {
return ErrorKind::Transient;
@ -28,6 +36,10 @@ pub fn classify_reqwest_error(err: &reqwest::Error) -> ErrorKind {
}
/// Classify an HTTP status code for retry purposes.
///
/// Returns `ErrorKind::Transient` for status codes 429 and 5xx (server errors),
/// `ErrorKind::Permanent` for 4xx (client errors), and `ErrorKind::Transient`
/// as a safe default for all other status codes.
pub const fn classify_status_code(status: u16) -> ErrorKind {
match status {
// Rate limit and server-side errors - back off and retry.
@ -60,7 +72,11 @@ fn scale_backoff(backoff_ms: u64, multiplier: f64) -> u64 {
/// [`ErrorKind::Transient`] errors are retried. A [`ErrorKind::Permanent`]
/// error is returned immediately without further attempts.
///
/// Returns the last error after all attempts are exhausted.
/// # Errors
///
/// Returns the last error from `operation` after all retry attempts are
/// exhausted, or the first `ErrorKind::Permanent` error encountered (which is
/// not retried). The error type `E` is determined by the `operation` closure.
pub async fn execute_with_retry<F, Fut, T, E, C>(
config: &RetryConfig,
operation_name: &str,

View file

@ -11,11 +11,26 @@ pub struct FilesystemSink {
}
impl FilesystemSink {
/// Creates a new filesystem sink.
///
/// # Errors
///
/// Returns an error if the sink directory cannot be created.
pub fn new(config: FilesystemSinkConfig) -> anyhow::Result<Self> {
std::fs::create_dir_all(&config.path)?;
Ok(Self { config })
}
/// Writes an artifact to the filesystem sink.
///
/// # Errors
///
/// Returns an error if:
///
/// - The content hash is invalid (too short or non-ASCII)
/// - The target directory cannot be created
/// - The artifact cannot be copied
/// - Hash verification fails (if enabled)
pub async fn write(
&self,
artifact: &Artifact,

View file

@ -27,6 +27,12 @@ pub struct Repository {
}
impl ForgejoSource {
/// Creates a new Forgejo source client.
///
/// # Errors
///
/// Returns an error if the HTTP client cannot be built (e.g., invalid
/// headers).
pub fn new(
config: ForgejoSourceConfig,
retry: RetryConfig,
@ -51,6 +57,12 @@ impl ForgejoSource {
})
}
/// Lists all repositories from the configured organizations.
///
/// # Errors
///
/// Returns an error if the API request fails or if the response cannot be
/// parsed.
pub async fn list_repositories(&self) -> anyhow::Result<Vec<Repository>> {
let mut repos = Vec::new();
@ -142,6 +154,15 @@ impl ForgejoSource {
Ok((repos, has_more))
}
/// Downloads a repository archive and computes its content hash.
///
/// # Errors
///
/// Returns an error if:
///
/// - The download request fails
/// - The response status is not successful
/// - The temporary file cannot be created or written
pub async fn download_archive(
&self,
repo: &Repository,

View file

@ -20,7 +20,9 @@ impl Storage {
/// Creates a new storage connection and runs migrations.
///
/// # Errors
///
/// Returns an error if:
///
/// - The database connection cannot be established
/// - WAL mode cannot be enabled
/// - Migrations fail
@ -89,6 +91,7 @@ impl Storage {
/// Creates a new job in the database.
///
/// # Errors
///
/// Returns an error if the database insert fails.
pub async fn create_job(
&self,
@ -125,7 +128,9 @@ impl Storage {
/// Updates the status of an existing job.
///
/// # Errors
///
/// Returns an error if:
///
/// - The database update fails
/// - The job is not found
pub async fn update_job_status(
@ -161,6 +166,7 @@ impl Storage {
/// Lists all jobs for a given run.
///
/// # Errors
///
/// Returns an error if the database query fails or if row data is invalid.
pub async fn list_jobs_by_run(
&self,
@ -186,7 +192,9 @@ impl Storage {
/// Saves a manifest with its root hash for a backup run.
///
/// # Errors
///
/// Returns an error if:
///
/// - The artifact count exceeds i64 max
/// - The database insert fails
pub async fn save_manifest(
@ -221,6 +229,7 @@ impl Storage {
/// Retrieves the stored root hash for a backup run.
///
/// # Errors
///
/// Returns an error if the database query fails.
pub async fn get_manifest_root(
&self,