Skip to main content

qubit_argument/
lib.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! # Qubit Argument
9//!
10//! Ownership-preserving validation for arguments, configuration values,
11//! durations, indexes, and bounds.
12//!
13//! All public traits, functions, and error types are exported from the crate
14//! root. Validation returns [`ArgumentResult`] instead of panicking, and each
15//! failure contains an owned [`ArgumentPath`] plus an inspectable
16//! [`ArgumentErrorKind`]. Successful validation returns the original owned
17//! value or borrow without cloning it.
18//!
19//! Validation extension traits are sealed and implemented only for the types
20//! documented by this crate. [`ArgumentErrorKind`], [`ArgumentValue`], and
21//! [`LengthMetric`] are non-exhaustive; downstream matches must include a
22//! wildcard arm so the structured vocabulary can evolve compatibly.
23//!
24//! # Ownership-preserving validation
25//!
26//! ```rust
27//! use qubit_argument::{ArgumentResult, NumericArgument, StringArgument};
28//!
29//! fn validate_user(age: u8, name: String) -> ArgumentResult<(u8, String)> {
30//!     let age = age.require_in_range("age", 0..=150)?;
31//!     let name = name
32//!         .require_non_blank("name")?
33//!         .require_char_count_in("name", 3, 32)?;
34//!     Ok((age, name))
35//! }
36//!
37//! let (age, name) = validate_user(42, String::from("Ada"))?;
38//! assert_eq!((age, name.as_str()), (42, "Ada"));
39//! # Ok::<(), qubit_argument::ArgumentError>(())
40//! ```
41//!
42//! # Downstream errors
43//!
44//! A downstream error can implement `From<ArgumentError>` and then use `?`
45//! without a `map_err` adapter:
46//!
47//! ```rust
48//! use qubit_argument::{ArgumentError, NumericArgument};
49//!
50//! #[derive(Debug)]
51//! enum DomainError {
52//!     InvalidArgument(ArgumentError),
53//! }
54//!
55//! impl From<ArgumentError> for DomainError {
56//!     fn from(error: ArgumentError) -> Self {
57//!         Self::InvalidArgument(error)
58//!     }
59//! }
60//!
61//! fn validate_pool_size(size: u32) -> Result<u32, DomainError> {
62//!     let size = size.require_positive("pool_size")?;
63//!     Ok(size)
64//! }
65//!
66//! assert_eq!(validate_pool_size(4)?, 4);
67//! # Ok::<(), DomainError>(())
68//! ```
69//!
70//! Callers that are checking a genuine internal invariant may instead choose
71//! to call `expect` with a meaningful explanation. The validation APIs do not
72//! make that recovery-versus-panic decision on the caller's behalf.
73//!
74//! # Nested configuration and durations
75//!
76//! Nested validators can report local paths and add parent context only on
77//! failure. Durations retain their unit in structured comparison errors:
78//!
79//! ```rust
80//! use std::time::Duration;
81//!
82//! use qubit_argument::{
83//!     ArgumentResult,
84//!     ArgumentResultExt,
85//!     DurationArgument,
86//! };
87//!
88//! fn validate_timeouts(connect: Duration) -> ArgumentResult<()> {
89//!     connect.require_positive("connect")?;
90//!     Ok(())
91//! }
92//!
93//! let error = validate_timeouts(Duration::ZERO)
94//!     .with_path_prefix("timeouts")
95//!     .expect_err("a zero connection timeout is invalid");
96//! assert_eq!(error.path().as_str(), "timeouts.connect");
97//! ```
98//!
99//! # Strings and optional regex support
100//!
101//! Byte-length methods measure UTF-8 bytes. Character-count methods measure
102//! Unicode scalar values, not grapheme clusters. String validation errors do
103//! not retain the inspected input string. Structured length errors retain a
104//! [`LengthMetric`] so byte length, Unicode scalar count, and collection
105//! element count remain distinguishable.
106//!
107//! The default feature set is empty. Enabling the `regex` feature adds
108//! `StringArgument::require_match` and `StringArgument::require_not_match`.
109//! They use `Regex::is_match` semantics and do not implicitly anchor patterns.
110
111/// Argument validation implementations re-exported at the crate root.
112mod argument;
113
114pub use argument::{
115    ArgumentBound,
116    ArgumentError,
117    ArgumentErrorKind,
118    ArgumentPath,
119    ArgumentResult,
120    ArgumentResultExt,
121    ArgumentValue,
122    CollectionArgument,
123    ComparisonConstraint,
124    DurationArgument,
125    FloatArgument,
126    IndexRole,
127    LengthConstraint,
128    LengthMetric,
129    NumericArgument,
130    OptionArgument,
131    PatternExpectation,
132    RangeConstraint,
133    StringArgument,
134    check_bounds,
135    check_element_index,
136    check_position_index,
137    check_position_range,
138    require_that,
139};