Skip to main content

qubit_argument/argument/
bounds.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//! Bounds and caller-defined argument validation.
9
10use std::ops::Range;
11
12use crate::argument::{
13    ArgumentError,
14    ArgumentErrorKind,
15    ArgumentResult,
16    IndexRole,
17};
18
19/// Validates a value with a caller-provided predicate.
20///
21/// The predicate receives a shared reference to `value`. If it returns `true`,
22/// this function returns the original value without cloning it or allocating
23/// error strings. If it returns `false`, `path`, `code`, and `message` identify
24/// the failed validation rule.
25///
26/// # Errors
27///
28/// Returns [`ArgumentErrorKind::Custom`] when `predicate` returns `false`.
29/// The error owns copies of `path`, `code`, and `message`.
30#[inline]
31pub fn require_that<T, F>(
32    value: T,
33    path: &str,
34    predicate: F,
35    code: &str,
36    message: &str,
37) -> ArgumentResult<T>
38where
39    F: FnOnce(&T) -> bool,
40{
41    if predicate(&value) {
42        Ok(value)
43    } else {
44        Err(ArgumentError::new(
45            path,
46            ArgumentErrorKind::Custom {
47                code: String::from(code),
48                message: String::from(message),
49            },
50        ))
51    }
52}
53
54/// Validates that an offset and length fit within a total length.
55///
56/// `offset` may equal `total_length` only when `length` is zero. Validation
57/// checks the offset before subtracting it from the total, so it never computes
58/// an unchecked sum of the offset and length.
59///
60/// # Errors
61///
62/// Returns [`ArgumentErrorKind::Bounds`] when `offset` exceeds `total_length`
63/// or when `length` exceeds the number of units remaining after `offset`.
64#[inline]
65pub fn check_bounds(
66    path: &str,
67    offset: usize,
68    length: usize,
69    total_length: usize,
70) -> ArgumentResult<()> {
71    if offset > total_length {
72        return Err(ArgumentError::new(
73            path,
74            ArgumentErrorKind::Bounds {
75                offset,
76                length,
77                total_length,
78            },
79        ));
80    }
81    if length > total_length - offset {
82        return Err(ArgumentError::new(
83            path,
84            ArgumentErrorKind::Bounds {
85                offset,
86                length,
87                total_length,
88            },
89        ));
90    }
91    Ok(())
92}
93
94/// Validates an index that must identify an existing element.
95///
96/// `index` is valid exactly when it is strictly less than `size`. On success,
97/// the validated index is returned unchanged.
98///
99/// # Errors
100///
101/// Returns [`ArgumentErrorKind::Index`] with [`IndexRole::Element`] when
102/// `index` is greater than or equal to `size`.
103#[inline]
104pub fn check_element_index(
105    path: &str,
106    index: usize,
107    size: usize,
108) -> ArgumentResult<usize> {
109    if index >= size {
110        return Err(ArgumentError::new(
111            path,
112            ArgumentErrorKind::Index {
113                index,
114                size,
115                role: IndexRole::Element,
116            },
117        ));
118    }
119    Ok(index)
120}
121
122/// Validates an index that identifies a boundary position.
123///
124/// `index` is valid when it is less than or equal to `size`, including the
125/// position immediately after the final element. On success, the validated
126/// index is returned unchanged.
127///
128/// # Errors
129///
130/// Returns [`ArgumentErrorKind::Index`] with [`IndexRole::Position`] when
131/// `index` exceeds `size`.
132#[inline]
133pub fn check_position_index(
134    path: &str,
135    index: usize,
136    size: usize,
137) -> ArgumentResult<usize> {
138    if index > size {
139        return Err(ArgumentError::new(
140            path,
141            ArgumentErrorKind::Index {
142                index,
143                size,
144                role: IndexRole::Position,
145            },
146        ));
147    }
148    Ok(index)
149}
150
151/// Validates a half-open range of boundary positions.
152///
153/// `start` and `end` are valid when `start <= end <= size`. Equal endpoints
154/// are accepted and produce an empty range. On success, this function returns
155/// the validated `start..end` range.
156///
157/// # Errors
158///
159/// Returns [`ArgumentErrorKind::IndexRange`] when `start` exceeds `end` or
160/// when `end` exceeds `size`.
161#[inline]
162pub fn check_position_range(
163    path: &str,
164    start: usize,
165    end: usize,
166    size: usize,
167) -> ArgumentResult<Range<usize>> {
168    if start > end {
169        return Err(ArgumentError::new(
170            path,
171            ArgumentErrorKind::IndexRange { start, end, size },
172        ));
173    }
174    if end > size {
175        return Err(ArgumentError::new(
176            path,
177            ArgumentErrorKind::IndexRange { start, end, size },
178        ));
179    }
180    Ok(start..end)
181}