qubit-argument 0.4.0

Structured, ownership-preserving argument validation for Rust applications
Documentation

Qubit Argument

Rust CI Coverage Crates.io Rust License 中文文档

Ownership-preserving argument validation for Rust.

Overview

Qubit Argument provides extension traits and focused checking functions for numeric values, durations, strings, collections, optional values, indexes, and bounds. Every validation failure is an ArgumentError with an owned argument path and an inspectable ArgumentErrorKind. Validation APIs return Result; callers choose whether to recover, convert the error, or explicitly treat a failure as an internal invariant violation.

Successful validation returns the original owned value or borrow without cloning it, so checks can be chained while preserving ownership.

Validation extension traits are sealed and implemented only for their documented standard-library types. ArgumentErrorKind, ArgumentValue, and LengthMetric are non-exhaustive; downstream matches must include a wildcard arm so new structured cases can be added compatibly.

Installation

The default feature set is empty (default = []), so core validation has no runtime dependency on the regex engine.

[dependencies]
qubit-argument = "0.4"

# Enable only when regex validation is needed.
qubit-argument = { version = "0.4", features = ["regex"] }

Quick Start

The traits and error types are exported directly from the crate root:

use qubit_argument::{ArgumentResult, NumericArgument, StringArgument};

fn validate_user(age: u8, name: String) -> ArgumentResult<(u8, String)> {
    let age = age.require_in_range("age", 0..=150)?;
    let name = name
        .require_non_blank("name")?
        .require_char_count_in("name", 3, 32)?;
    Ok((age, name))
}

Both age and name are returned in their original forms. In particular, the owned String is not cloned during validation.

Downstream Error Conversion

A downstream crate can preserve the structured error in its own domain error. After implementing From<ArgumentError>, the ? operator performs the conversion directly:

use qubit_argument::{ArgumentError, NumericArgument};

#[derive(Debug)]
enum DomainError {
    InvalidArgument(ArgumentError),
}

impl From<ArgumentError> for DomainError {
    fn from(error: ArgumentError) -> Self {
        Self::InvalidArgument(error)
    }
}

fn validate_pool_size(size: u32) -> Result<u32, DomainError> {
    let size = size.require_positive("pool_size")?;
    Ok(size)
}

Use ArgumentError::path() and ArgumentError::kind() for programmatic decisions. The Display text is intended for diagnostics, not parsing; caller-provided paths, patterns, custom codes, and messages are escaped so the diagnostic remains on one line without changing the structured values.

Nested validators can keep local field names and add parent context only when they fail. ArgumentResultExt::with_path_prefix leaves Ok values unchanged and turns a local path such as connect into timeouts.connect on Err.

Validation remains recoverable by default. When a value is an internal invariant rather than external input, the caller may explicitly use expect with a meaningful explanation:

use qubit_argument::NumericArgument;

fn built_in_retry_limit() -> u32 {
    3_u32
        .require_positive("retry_limit")
        .expect("the built-in retry limit is a positive internal invariant")
}

Validation APIs

Numeric values

NumericArgument supports all primitive integer types plus f32 and f64:

  • zero, non-zero, positive, non-negative, negative, and non-positive checks;
  • require_less_than, require_at_most, require_greater_than, and require_at_least comparisons;
  • require_in_range with standard inclusive, exclusive, and unbounded RangeBounds.

Floating-point values and range endpoints that are NaN are rejected. A reversed or structurally empty range is reported separately from a value that falls outside a valid range.

Durations and finite floats

DurationArgument is implemented for std::time::Duration. It provides a strictly positive check plus strict and inclusive comparisons. Structured comparison errors retain the exact Duration, including its unit, rather than converting it to an ambiguous integer.

FloatArgument::require_finite is implemented only for f32 and f64. It returns NotANumber for NaN and NotFinite { actual } for positive or negative infinity. Finite values can continue into the comparison and range methods from NumericArgument.

Strings

StringArgument is implemented for String and &str:

  • require_non_blank checks Unicode whitespace;
  • require_byte_len* methods count UTF-8 bytes;
  • require_char_count* methods count Unicode scalar values;
  • with the regex feature, require_match and require_not_match accept a compiled regex::Regex.

Byte length and Unicode scalar count are deliberately distinct. For example, "汉😀" contains seven UTF-8 bytes and two Unicode scalar values. Scalar counts are not grapheme-cluster counts.

Regex validation uses Regex::is_match semantics and is not implicitly anchored. Add ^ and $ to the pattern when the entire string must match.

Collections and optional values

CollectionArgument validates Vec<T>, &[T], and [T; N] without cloning elements. It provides non-empty, exact-length, minimum-length, maximum-length, and inclusive length-range checks.

OptionArgument::require_some moves a present value out of its option. OptionArgument::validate_if_some borrows a present value for validation, skips the validator for None, and returns the original option without cloning.

Custom rules and bounds

  • require_that applies a caller-provided predicate and reports a structured Custom error on failure.
  • check_bounds validates an offset and length without unchecked addition.
  • check_element_index and check_position_index distinguish element indexes from boundary positions.
  • check_position_range validates a half-open position range.

Error Privacy

String validation errors record the path, failure kind, observed length or count, and any required constraint. They do not store the original validated string, so Debug and Display do not reveal it. Pattern errors retain the pattern, not the input. Callers of require_that remain responsible for keeping their own custom code and message free of sensitive data.

Length and InvalidLengthConstraint errors also carry a LengthMetric: Bytes for UTF-8 byte methods, UnicodeScalars for character-count methods, and Elements for collection methods. Equal numeric lengths therefore remain structurally distinguishable by measurement unit.

Testing

# Core API with the default empty feature set
cargo test --no-default-features

# Core API plus regex validation
cargo test --all-features

# Project CI checks
./ci-check.sh

See COVERAGE.md for coverage details.

License

Copyright (c) 2025 - 2026. Haixing Hu, Qubit Co. Ltd. All rights reserved.

Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

Contributing

Contributions are welcome. Please follow the Rust API guidelines, keep public API documentation and tests current, and run ./ci-check.sh before submitting a pull request.

Author

Haixing Hu - Qubit Co. Ltd.

Repository: https://github.com/qubit-ltd/rs-argument