1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use std::ops::{
    Bound::{Excluded, Included, Unbounded},
    Range, RangeBounds,
};

#[derive(Debug)]
pub enum UsizeOob {
    StartIndexLen(usize, usize, usize),
    EndIndexLen(usize, usize, usize),
    IndexOrder(usize, usize, usize),
}

impl ToString for UsizeOob {
    fn to_string(&self) -> String {
        match self {
            UsizeOob::StartIndexLen(start, _, len) => {
                format!(
                    "range start index {start} out of range for slice of length {len}"
                )
            }
            UsizeOob::EndIndexLen(_, end, len) => {
                format!(
                    "range end index {end} out of range for slice of length {len}"
                )
            }
            UsizeOob::IndexOrder(start, end, _) => {
                format!("slice index starts at {start} but ends at {end}")
            }
        }
    }
}

impl UsizeOob {
    pub fn resolve_bounds(&self) -> Range<usize> {
        let (&start, &end, &len) = match self {
            UsizeOob::StartIndexLen(start, end, len)
            | UsizeOob::EndIndexLen(start, end, len)
            | UsizeOob::IndexOrder(start, end, len) => (start, end, len),
        };
        let end = end.min(len);
        let start = start.min(end);
        start..end
    }
}

pub trait UsizeBounds {
    fn to_range(&self, len: usize) -> Range<usize>;
    fn checked_to_range(&self, len: usize) -> Result<Range<usize>, UsizeOob>;
}

impl<R: RangeBounds<usize>> UsizeBounds for R {
    fn to_range(&self, len: usize) -> Range<usize> {
        match self.checked_to_range(len) {
            Ok(o) => o,
            Err(e) => panic!("{}", e.to_string()),
        }
    }

    fn checked_to_range(&self, len: usize) -> Result<Range<usize>, UsizeOob> {
        let start = match self.start_bound() {
            Included(&s) => s,
            Excluded(&s) => s + 1,
            Unbounded => 0,
        };
        let end = match self.end_bound() {
            Included(&e) => e + 1,
            Excluded(&e) => e,
            Unbounded => len,
        };

        if start > len {
            Err(UsizeOob::StartIndexLen(start, end, len))
        } else if end > len {
            Err(UsizeOob::EndIndexLen(start, end, len))
        } else if start > end {
            Err(UsizeOob::IndexOrder(start, end, len))
        } else {
            // start <= end <= len
            Ok(start..end)
        }
    }
}