qubit-io 0.19.4

Runtime-neutral synchronous and asynchronous item-stream I/O for Rust
Documentation
// =============================================================================
//    Copyright (c) 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================

//! Allocation helpers owned by `qubit-io`.

// qubit-style: allow coverage-cfg
#[cfg(coverage)]
use std::cell::Cell;
use std::collections::TryReserveError;
use std::io::Error;
use std::io::Result as IoResult;

use qubit_utils::allocation_error as qubit_allocation_error;
use qubit_utils::try_reserve_string as qubit_try_reserve_string;
use qubit_utils::try_reserve_vec as qubit_try_reserve_vec;

#[cfg(coverage)]
thread_local! {
    static COVERAGE_RESERVE_FAIL_AFTER: Cell<usize> = const { Cell::new(usize::MAX) };
    static COVERAGE_RESERVE_MAX_ADDITIONAL: Cell<usize> = const { Cell::new(usize::MAX) };
    static COVERAGE_FAIL_NEXT_STRING_RESERVE: Cell<bool> = const { Cell::new(false) };
}

/// Converts a reserve error into an I/O allocation error.
///
/// # Parameters
///
/// - `error`: The collection reserve failure to preserve as the I/O error's
///   source.
///
/// # Returns
///
/// An I/O error with the `OutOfMemory` kind.
#[must_use]
#[inline]
pub(crate) fn allocation_error(error: TryReserveError) -> Error {
    qubit_allocation_error(error)
}

/// Creates a vector with `len` cloned elements using the local reserve path.
///
/// # Type Parameters
///
/// - `T`: Cloneable element type stored in the vector.
///
/// # Parameters
///
/// - `len`: Number of elements in the returned vector.
/// - `fill`: Value cloned into each element.
///
/// # Returns
///
/// A vector containing `len` clones of `fill`.
///
/// # Errors
///
/// Returns an I/O allocation error if reserving the requested capacity fails.
pub(crate) fn create_vec<T>(len: usize, fill: T) -> IoResult<Vec<T>>
where
    T: Clone,
{
    let mut buffer = Vec::new();
    try_reserve_vec(&mut buffer, len).map_err(allocation_error)?;
    buffer.resize(len, fill);
    Ok(buffer)
}

/// Reserves vector capacity, applying local coverage failure injection first.
///
/// # Type Parameters
///
/// - `T`: Element type stored in the vector.
///
/// # Parameters
///
/// - `output`: Vector whose capacity is increased.
/// - `additional`: Number of additional elements to reserve.
///
/// # Returns
///
/// `Ok(())` if the capacity is reserved.
///
/// # Errors
///
/// Returns the reserve error if allocation fails, capacity overflows, or a
/// coverage hook injects a failure.
pub(crate) fn try_reserve_vec<T>(output: &mut Vec<T>, additional: usize) -> Result<(), TryReserveError> {
    #[cfg(coverage)]
    if let Some(result) = coverage_maybe_fail_reserve::<()>(additional) {
        return result;
    }
    qubit_try_reserve_vec(output, additional)
}

/// Reserves string capacity, applying local coverage failure injection first.
///
/// # Parameters
///
/// - `output`: String whose capacity is increased.
/// - `additional`: Number of additional bytes to reserve.
///
/// # Returns
///
/// `Ok(())` if the capacity is reserved.
///
/// # Errors
///
/// Returns the reserve error if allocation fails, capacity overflows, or a
/// coverage hook injects a failure.
pub(crate) fn try_reserve_string(output: &mut String, additional: usize) -> Result<(), TryReserveError> {
    #[cfg(coverage)]
    if COVERAGE_FAIL_NEXT_STRING_RESERVE.with(|state| {
        let fail = state.get();
        if fail {
            state.set(false);
        }
        fail
    }) {
        return Err(coverage_reserve_error());
    }
    #[cfg(coverage)]
    if let Some(result) = coverage_maybe_fail_reserve::<()>(additional) {
        return result;
    }
    qubit_try_reserve_string(output, additional)
}

/// Makes the next vector reserve fail in coverage builds.
#[doc(hidden)]
#[cfg(coverage)]
pub fn coverage_fail_next_reserve() {
    COVERAGE_RESERVE_FAIL_AFTER.with(|state| state.set(0));
}

/// Makes a later vector reserve fail after successful attempts.
///
/// # Parameters
///
/// - `successful_attempts`: Number of vector reserves allowed to succeed before
///   the injected failure.
#[doc(hidden)]
#[cfg(coverage)]
pub fn coverage_fail_reserve_after(successful_attempts: usize) {
    COVERAGE_RESERVE_FAIL_AFTER.with(|state| state.set(successful_attempts));
}

/// Makes vector reserves above `max_additional` fail in coverage builds.
///
/// # Parameters
///
/// - `max_additional`: Largest additional capacity request allowed to succeed.
#[doc(hidden)]
#[cfg(coverage)]
pub fn coverage_fail_reserve_above(max_additional: usize) {
    COVERAGE_RESERVE_MAX_ADDITIONAL.with(|state| state.set(max_additional));
}

/// Makes the next string reserve fail in coverage builds.
#[doc(hidden)]
#[cfg(coverage)]
pub fn coverage_fail_next_string_reserve() {
    COVERAGE_FAIL_NEXT_STRING_RESERVE.with(|state| state.set(true));
}

/// Resets all local allocation coverage hooks.
#[doc(hidden)]
#[cfg(coverage)]
pub fn coverage_reset_reserve_hooks() {
    COVERAGE_RESERVE_FAIL_AFTER.with(|state| state.set(usize::MAX));
    COVERAGE_RESERVE_MAX_ADDITIONAL.with(|state| state.set(usize::MAX));
    COVERAGE_FAIL_NEXT_STRING_RESERVE.with(|state| state.set(false));
}

/// Creates a reserve error by requesting more capacity than a vector can hold.
///
/// # Returns
///
/// A synthetic capacity-overflow reserve error.
///
/// # Panics
///
/// Panics if reserving `usize::MAX` bytes unexpectedly succeeds.
#[cfg(coverage)]
fn coverage_reserve_error() -> TryReserveError {
    Vec::<u8>::new()
        .try_reserve(usize::MAX)
        .expect_err("reserving usize::MAX bytes must exceed Vec capacity")
}

/// Fails requests above the configured limit or when the successful-attempt
/// counter reaches zero; the latter failure resets that counter.
///
/// # Type Parameters
///
/// - `T`: Success type expected by the reserve operation.
///
/// # Parameters
///
/// - `additional`: Additional capacity requested by the reserve operation.
///
/// # Returns
///
/// `Some(Err(_))` when a configured hook injects a failure, including the
/// capacity-limit and remaining-attempt cases; `None` lets the real reserve
/// operation proceed.
///
/// # Panics
///
/// Panics if constructing the synthetic reserve error unexpectedly succeeds.
#[cfg(coverage)]
fn coverage_maybe_fail_reserve<T>(additional: usize) -> Option<Result<T, TryReserveError>> {
    if COVERAGE_RESERVE_MAX_ADDITIONAL.with(|state| additional > state.get()) {
        return Some(Err(coverage_reserve_error()));
    }
    COVERAGE_RESERVE_FAIL_AFTER.with(|state| {
        let remaining = state.get();
        if remaining == usize::MAX {
            return None;
        }
        if remaining == 0 {
            state.set(usize::MAX);
            return Some(Err(coverage_reserve_error()));
        }
        state.set(remaining - 1);
        None
    })
}