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:
parent
c71c576c29
commit
89d40109e5
7 changed files with 107 additions and 1 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
18
src/retry.rs
18
src/retry.rs
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue