1use core::ops::{Bound, RangeBounds};
4
5#[macro_export]
34macro_rules! assert_str_range {
35    ($s:expr, $r:expr) => {{
36        let s = &$s;
37        let r = &$r;
38        if !$crate::str::is_valid_range(s, r) {
39            $crate::str::range_fail(s, r)
40        }
41    }};
42}
43
44pub fn is_valid_range<S, R>(s: S, range: &R) -> bool
49where
50    S: AsRef<str>,
51    R: RangeBounds<usize>,
52{
53    let s = s.as_ref();
54    validate_start_bound(s, range.start_bound()).is_ok()
55        && validate_end_bound(s, range.end_bound()).is_ok()
56}
57
58enum InvalidBound {
59    OutOfBuffer,
60    NotCharBoundary,
61}
62
63#[inline]
64fn validate_start_bound(
65    s: &str,
66    bound: Bound<&usize>,
67) -> Result<(), InvalidBound> {
68    use Bound::*;
69
70    match bound {
71        Unbounded => Ok(()),
72        Included(index) => validate_index(s, *index),
73        Excluded(index) => validate_next_index(s, *index),
74    }
75}
76
77#[inline]
78fn validate_end_bound(
79    s: &str,
80    bound: Bound<&usize>,
81) -> Result<(), InvalidBound> {
82    use Bound::*;
83
84    match bound {
85        Unbounded => Ok(()),
86        Excluded(index) => validate_index(s, *index),
87        Included(index) => validate_next_index(s, *index),
88    }
89}
90
91#[inline]
92fn validate_index(s: &str, index: usize) -> Result<(), InvalidBound> {
93    use InvalidBound::*;
94
95    if s.is_char_boundary(index) {
98        Ok(())
99    } else if index > s.len() {
100        Err(OutOfBuffer)
101    } else {
102        Err(NotCharBoundary)
103    }
104}
105
106#[inline]
107fn validate_next_index(s: &str, index: usize) -> Result<(), InvalidBound> {
108    use InvalidBound::*;
109
110    if index >= s.len() {
112        Err(OutOfBuffer)
113    } else if s.is_char_boundary(index + 1) {
114        Ok(())
115    } else {
116        Err(NotCharBoundary)
117    }
118}
119
120#[doc(hidden)]
121#[cold]
122pub fn range_fail<S, R>(s: S, range: &R) -> !
123where
124    S: AsRef<str>,
125    R: RangeBounds<usize>,
126{
127    range_fail_internal(s.as_ref(), range.start_bound(), range.end_bound())
128}
129
130fn range_fail_internal(
131    s: &str,
132    start_bound: Bound<&usize>,
133    end_bound: Bound<&usize>,
134) -> ! {
135    use InvalidBound::*;
136
137    let start_validity = validate_start_bound(s, start_bound);
138    let end_validity = validate_end_bound(s, end_bound);
139    let r = (start_bound, end_bound);
140    match (start_validity, end_validity) {
141        (Err(OutOfBuffer), _) | (_, Err(OutOfBuffer)) => {
142            panic!("range {:?} is out of bounds", r)
143        }
144        (Err(NotCharBoundary), _) | (_, Err(NotCharBoundary)) => {
145            panic!("range {:?} does not split on a UTF-8 boundary", r)
146        }
147        (Ok(()), Ok(())) => unreachable!("there was no problem with the range"),
148    }
149}