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;
28//! use qubit_argument::NumericArgument;
29//! use qubit_argument::StringArgument;
30//!
31//! fn validate_user(age: u8, name: String) -> ArgumentResult<(u8, String)> {
32//! let age = age.require_in_range("age", 0..=150)?;
33//! let name = name
34//! .require_non_blank("name")?
35//! .require_char_count_in("name", 3, 32)?;
36//! Ok((age, name))
37//! }
38//!
39//! let (age, name) = validate_user(42, String::from("Ada"))?;
40//! assert_eq!((age, name.as_str()), (42, "Ada"));
41//! # Ok::<(), qubit_argument::ArgumentError>(())
42//! ```
43//!
44//! # Downstream errors
45//!
46//! A downstream error can implement `From<ArgumentError>` and then use `?`
47//! without a `map_err` adapter:
48//!
49//! ```rust
50//! use qubit_argument::ArgumentError;
51//! use qubit_argument::NumericArgument;
52//!
53//! #[derive(Debug)]
54//! enum DomainError {
55//! InvalidArgument(ArgumentError),
56//! }
57//!
58//! impl From<ArgumentError> for DomainError {
59//! fn from(error: ArgumentError) -> Self {
60//! Self::InvalidArgument(error)
61//! }
62//! }
63//!
64//! fn validate_pool_size(size: u32) -> Result<u32, DomainError> {
65//! let size = size.require_positive("pool_size")?;
66//! Ok(size)
67//! }
68//!
69//! assert_eq!(validate_pool_size(4)?, 4);
70//! # Ok::<(), DomainError>(())
71//! ```
72//!
73//! Callers that are checking a genuine internal invariant may instead choose
74//! to call `expect` with a meaningful explanation. The validation APIs do not
75//! make that recovery-versus-panic decision on the caller's behalf.
76//!
77//! # Nested configuration and durations
78//!
79//! Nested validators can report local paths and add parent context only on
80//! failure. Durations retain their unit in structured comparison errors:
81//!
82//! ```rust
83//! use std::time::Duration;
84//!
85//! use qubit_argument::ArgumentResult;
86//! use qubit_argument::ArgumentResultExt;
87//! use qubit_argument::DurationArgument;
88//!
89//! fn validate_timeouts(connect: Duration) -> ArgumentResult<()> {
90//! connect.require_positive("connect")?;
91//! Ok(())
92//! }
93//!
94//! let error = validate_timeouts(Duration::ZERO)
95//! .with_path_prefix("timeouts")
96//! .expect_err("a zero connection timeout is invalid");
97//! assert_eq!(error.path().as_str(), "timeouts.connect");
98//! ```
99//!
100//! # Strings and optional regex support
101//!
102//! Byte-length methods measure UTF-8 bytes. Character-count methods measure
103//! Unicode scalar values, not grapheme clusters. String validation errors do
104//! not retain the inspected input string. Structured length errors retain a
105//! [`LengthMetric`] so byte length, Unicode scalar count, and collection
106//! element count remain distinguishable.
107//!
108//! The default feature set is empty. Enabling the `regex` feature adds
109//! `StringArgument::require_match` and `StringArgument::require_not_match`.
110//! They use `Regex::is_match` semantics and do not implicitly anchor patterns.
111
112/// Argument validation implementations re-exported at the crate root.
113mod argument;
114
115pub use argument::ArgumentBound;
116pub use argument::ArgumentError;
117pub use argument::ArgumentErrorKind;
118pub use argument::ArgumentPath;
119pub use argument::ArgumentResult;
120pub use argument::ArgumentResultExt;
121pub use argument::ArgumentValue;
122pub use argument::CollectionArgument;
123pub use argument::ComparisonConstraint;
124pub use argument::DurationArgument;
125pub use argument::FloatArgument;
126pub use argument::IndexRole;
127pub use argument::LengthConstraint;
128pub use argument::LengthMetric;
129pub use argument::NumericArgument;
130pub use argument::OptionArgument;
131pub use argument::PatternExpectation;
132pub use argument::RangeConstraint;
133pub use argument::StringArgument;
134pub use argument::check_bounds;
135pub use argument::check_element_index;
136pub use argument::check_position_index;
137pub use argument::check_position_range;
138pub use argument::require_that;