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}