base_any/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(any(feature = "std", test)), no_std)]
3
4mod decode;
5mod encode;
6mod table;
7
8pub use decode::Decode;
9pub use encode::Encode;
10pub use table::*;
11
12#[cfg(test)]
13mod tests {
14    use super::*;
15    use proptest::{prelude::TestCaseError, prop_assert, prop_assert_eq, prop_assume, proptest};
16    use std::{
17        collections::HashSet,
18        ops::{Bound, RangeBounds},
19    };
20    use tables::*;
21    use unicode_ident::{is_xid_continue, is_xid_start};
22
23    #[test]
24    fn code() {
25        for (_, table) in ALL {
26            let init_max = 1 << table.bits;
27            for code in 0..init_max {
28                let ch = table.init[code];
29                assert_eq!(table.decode(ch), Some((code as u32, false)));
30            }
31            let fin_max = if table.bits > 8 {
32                1 << (table.bits - 8)
33            } else {
34                0
35            };
36            for code in 0..fin_max {
37                let ch = table.fini[code];
38                assert_eq!(table.decode(ch), Some((code as u32, true)));
39            }
40
41            proptest!(|(code in init_max..)| {
42                prop_assert!(table.init.get(code).is_none());
43            });
44            proptest!(|(code in fin_max..)| {
45                prop_assert!(table.fini.get(code).is_none());
46            });
47
48            let chs: HashSet<_> = table.init.iter().chain(table.fini).collect();
49            proptest!(|(ch: char)| {
50                prop_assume!(!chs.contains(&ch));
51                prop_assert!(table.decode(ch).is_none());
52            });
53        }
54    }
55
56    #[test]
57    fn base8192() {
58        for c in BASE8192.init.iter().chain(BASE8192.fini) {
59            assert!(c.is_alphanumeric());
60            assert!(is_xid_continue(*c));
61        }
62    }
63
64    #[test]
65    fn base1024() {
66        for c in BASE1024.init.iter().chain(BASE1024.fini) {
67            assert!(matches!(c, '\u{4e00}'..='\u{9FFF}' | '\u{3100}'..='\u{312F}'),);
68            assert!(is_xid_start(*c));
69        }
70    }
71
72    #[test]
73    fn bits() {
74        for bits in 1..15 {
75            Table::with(bits, |table| {
76                proptest!(|(data: Vec<u8>)| {
77                    check_all(table, &data)?;
78                });
79            });
80        }
81    }
82
83    type Res = Result<(), TestCaseError>;
84
85    fn check_size(mut iter: impl Iterator) -> Res {
86        let mut size = Vec::new();
87        while {
88            let (lo, hi) = iter.size_hint();
89            size.push((
90                Bound::Included(lo),
91                hi.map_or(Bound::Unbounded, Bound::Included),
92            ));
93            iter.next().is_some()
94        } {}
95
96        for (size, range) in size.iter().rev().enumerate() {
97            prop_assert!(range.contains(&size));
98        }
99        Ok(())
100    }
101
102    fn check_fuse(mut iter: impl Iterator) -> Res {
103        iter.by_ref().count();
104        prop_assert!(iter.next().is_none());
105        Ok(())
106    }
107
108    fn check_all(table: &Table, data: &[u8]) -> Res {
109        let encode_iter = Encode::new(table, data.iter().copied());
110        let decode_iter = Decode::new(table, encode_iter.clone());
111
112        let data2: Result<Vec<_>, _> = decode_iter.clone().collect();
113        prop_assert_eq!(Ok(data), data2.as_deref());
114
115        check_size(encode_iter.clone())?;
116        check_size(decode_iter.clone())?;
117
118        check_fuse(encode_iter.clone())?;
119        check_fuse(decode_iter.clone())?;
120
121        Ok(())
122    }
123
124    proptest!(
125        #[test]
126        fn test(data: Vec<u8>) {
127            for (_, table) in ALL {
128                check_all(table, &data)?;
129            }
130        }
131
132        #[test]
133        fn decode_err(mut code: String) {
134            code.push(char::MAX);
135            for (_, table) in ALL {
136                let iter = Decode::new(table, code.chars());
137                prop_assert!(iter.clone().any(|r| r.is_err()));
138                check_fuse(iter.clone())?;
139            }
140        }
141
142        #[test]
143        fn base64(data: Vec<u8>) {
144            use base64::prelude::*;
145            for (a, b) in [
146                (BASE64, BASE64_STANDARD_NO_PAD),
147                (BASE64URL, BASE64_URL_SAFE_NO_PAD),
148            ] {
149                prop_assert_eq!(a.encode_str(data.iter().copied()), b.encode(&data));
150            }
151        }
152
153        #[test]
154        fn base32(data: Vec<u8>) {
155            use base32::Alphabet::*;
156            for (a, b) in [
157                (BASE32, Rfc4648 { padding: false }),
158                (BASE32HEX, Rfc4648Hex { padding: false }),
159            ] {
160                prop_assert_eq!(a.encode_str(data.iter().copied()), base32::encode(b, &data));
161            }
162        }
163
164        #[test]
165        fn base16(data: Vec<u8>) {
166            prop_assert_eq!(
167                HEX.encode_str(data.iter().copied()),
168                base16ct::HexDisplay(&data).to_string()
169            );
170        }
171    );
172}