proqnt/
lib.rs

1#![doc = include_str!("../README.md")]
2#![forbid(missing_docs)]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5use core::{
6    borrow::Borrow,
7    fmt::{Debug, Display},
8    iter::{once, Once},
9    ops::Deref,
10    str::FromStr,
11};
12
13mod proquint;
14pub use proquint::Proquint;
15
16pub mod fractional;
17use fractional::*;
18
19/// The consonants which may appear in a proquint, indexed by their 4-bit binary values
20pub const PROQUINT_CONSONANTS: [u8; 16] = [
21    b'b', b'd', b'f', b'g', b'h', b'j', b'k', b'l', b'm', b'n', b'p', b'r', b's', b't', b'v', b'z',
22];
23
24/// The vowels which may appear in a proquint, indexed by their 2-bit binary values
25pub const PROQUINT_VOWELS: [u8; 4] = [b'a', b'i', b'o', b'u'];
26
27/// The syllables which may appear in a proquint, indexed by their 6-bit binary values
28///
29/// A proquint syllable consists of a consonant (encoding 4 bits) followed by a syllable (encoding 2 bits).
30pub const PROQUINT_SYLLABLES: [[u8; 2]; 64] = [
31    *b"ba", *b"bi", *b"bo", *b"bu", *b"da", *b"di", *b"do", *b"du", *b"fa", *b"fi", *b"fo", *b"fu",
32    *b"ga", *b"gi", *b"go", *b"gu", *b"ha", *b"hi", *b"ho", *b"hu", *b"ja", *b"ji", *b"jo", *b"ju",
33    *b"ka", *b"ki", *b"ko", *b"ku", *b"la", *b"li", *b"lo", *b"lu", *b"ma", *b"mi", *b"mo", *b"mu",
34    *b"na", *b"ni", *b"no", *b"nu", *b"pa", *b"pi", *b"po", *b"pu", *b"ra", *b"ri", *b"ro", *b"ru",
35    *b"sa", *b"si", *b"so", *b"su", *b"ta", *b"ti", *b"to", *b"tu", *b"va", *b"vi", *b"vo", *b"vu",
36    *b"za", *b"zi", *b"zo", *b"zu",
37];
38
39/// A 2-bit mask for the data contained in a proquint vowel
40pub const VOWEL_MASK: u16 = 0b11;
41/// A 4-bit mask for the data contained in a proquint consonant
42pub const CONSONANT_MASK: u16 = 0b1111;
43/// A 6-bit mask for the data contained in a proquint syllable
44pub const SYLLABLE_MASK: u16 = 0b111111;
45
46/// The shift for the data contained in a proquint's first consonant
47pub const FIRST_CONSONANT_SHIFT: u32 = 12;
48/// The mask for the data contained in a proquint's first consonant
49pub const FIRST_CONSONANT_MASK: u16 = CONSONANT_MASK << FIRST_CONSONANT_SHIFT;
50/// The shift for the data contained in a proquint's first vowel
51pub const FIRST_VOWEL_SHIFT: u32 = 10;
52/// The mask for the data contained in a proquint's first vowel
53pub const FIRST_VOWEL_MASK: u16 = VOWEL_MASK << FIRST_VOWEL_SHIFT;
54/// The shift for the data contained in a proquint's second consonant
55pub const SECOND_CONSONANT_SHIFT: u32 = 6;
56/// The mask for the data contained in a proquint's second consonant
57pub const SECOND_CONSONANT_MASK: u16 = CONSONANT_MASK << SECOND_CONSONANT_SHIFT;
58/// The shift for the data contained in a proquint's second vowel
59pub const SECOND_VOWEL_SHIFT: u32 = 4;
60/// The mask for the data contained in a proquint's second vowel
61pub const SECOND_VOWEL_MASK: u16 = VOWEL_MASK << SECOND_VOWEL_SHIFT;
62/// The shift for the data contained in a proquint's final consonant
63pub const FINAL_CONSONANT_SHIFT: u32 = 0;
64/// The mask for the data contained in a proquint's final consonant
65pub const FINAL_CONSONANT_MASK: u16 = CONSONANT_MASK << FINAL_CONSONANT_SHIFT;
66/// The shift for the data contained in a proquint's first syllable
67pub const FIRST_SYLLABLE_SHIFT: u32 = FIRST_VOWEL_SHIFT;
68/// The mask for the data contained in a proquint's first syllable
69pub const FIRST_SYLLABLE_MASK: u16 = SYLLABLE_MASK << FIRST_VOWEL_SHIFT;
70/// The shift for the data contained in a proquint's second syllable
71pub const SECOND_SYLLABLE_SHIFT: u32 = SECOND_VOWEL_SHIFT;
72/// The mask for the data contained in a proquint's second syllable
73pub const SECOND_SYLLABLE_MASK: u16 = SYLLABLE_MASK << SECOND_SYLLABLE_SHIFT;
74
75impl FromStr for Proquint {
76    type Err = ProquintParseError;
77
78    #[inline]
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        let bytes: [u8; 5] = s
81            .as_bytes()
82            .try_into()
83            .map_err(|_| ProquintParseError(None))?;
84        Self::from_bytes(bytes)
85    }
86}
87
88impl From<u16> for Proquint {
89    #[cfg_attr(not(tarpaulin), inline(always))]
90    fn from(value: u16) -> Self {
91        Self::from_u16(value)
92    }
93}
94
95impl<'a> TryFrom<&'a [u8]> for Proquint {
96    type Error = ProquintParseError;
97
98    #[cfg_attr(not(tarpaulin), inline(always))]
99    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
100        let bytes: [u8; 5] = value.try_into().map_err(|_| ProquintParseError(None))?;
101        Self::from_bytes(bytes)
102    }
103}
104
105impl TryFrom<[u8; 5]> for Proquint {
106    type Error = ProquintParseError;
107
108    #[cfg_attr(not(tarpaulin), inline(always))]
109    fn try_from(value: [u8; 5]) -> Result<Self, Self::Error> {
110        Self::from_bytes(value)
111    }
112}
113
114/// An error in parsing a proquint
115#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
116pub struct ProquintParseError(Option<[u8; 5]>);
117
118impl Display for ProquintParseError {
119    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
120        if let Some(data) = self.0 {
121            write!(
122                f,
123                "{data:?} (utf-8 = {:?}) is not a valid proquint",
124                core::str::from_utf8(&data[..])
125            )
126        } else {
127            write!(f, "not enough characters to parse a proquint")
128        }
129    }
130}
131
132impl Proquint {
133    /// Get the value of this proquint as a `u16`
134    #[cfg_attr(not(tarpaulin), inline(always))]
135    pub const fn value(self) -> u16 {
136        Self::value_from_bytes_unchecked(*self.as_bytes())
137    }
138
139    /// Parse 5 bytes as a proquint value
140    ///
141    /// Return an error if the input is not a valid proquint
142    #[cfg_attr(not(tarpaulin), inline(always))]
143    pub const fn value_from_bytes(bytes: [u8; 5]) -> Result<u16, ProquintParseError> {
144        let first_consonant = Self::consonant(bytes[0]);
145        let first_vowel = Self::vowel(bytes[1]);
146        let second_consonant = Self::consonant(bytes[2]);
147        let second_vowel = Self::vowel(bytes[3]);
148        let final_consonant = Self::consonant(bytes[4]);
149        if first_consonant != u16::MAX
150            && first_vowel != u16::MAX
151            && second_consonant != u16::MAX
152            && second_vowel != u16::MAX
153            && final_consonant != u16::MAX
154        {
155            Ok(first_consonant << FIRST_CONSONANT_SHIFT
156                | first_vowel << FIRST_VOWEL_SHIFT
157                | second_consonant << SECOND_CONSONANT_SHIFT
158                | second_vowel << SECOND_VOWEL_SHIFT
159                | final_consonant << FINAL_CONSONANT_SHIFT)
160        } else {
161            Err(ProquintParseError(Some(bytes)))
162        }
163    }
164
165    /// Parse 5 bytes as a proquint value
166    ///
167    /// Return an arbitrary value or panic if the input is not a valid proquint
168    #[cfg_attr(not(tarpaulin), inline(always))]
169    pub const fn value_from_bytes_unchecked(bytes: [u8; 5]) -> u16 {
170        Self::consonant(bytes[0]) << FIRST_CONSONANT_SHIFT
171            | Self::vowel(bytes[1]) << FIRST_VOWEL_SHIFT
172            | Self::consonant(bytes[2]) << SECOND_CONSONANT_SHIFT
173            | Self::vowel(bytes[3]) << SECOND_VOWEL_SHIFT
174            | Self::consonant(bytes[4]) << FINAL_CONSONANT_SHIFT
175    }
176
177    /// Parse a proquint from a string
178    ///
179    /// Returns any unconsumed input
180    #[cfg_attr(not(tarpaulin), inline(always))]
181    pub fn parse_partial(input: &str) -> Result<(&str, Proquint), ProquintParseError> {
182        if input.len() >= 5 {
183            Ok((
184                &input[5..],
185                Self::from_bytes(input.as_bytes()[..5].try_into().unwrap())?,
186            ))
187        } else {
188            Err(ProquintParseError(None))
189        }
190    }
191
192    /// Parse a proquint from a string
193    #[cfg_attr(not(tarpaulin), inline(always))]
194    pub const fn parse_str(input: &str) -> Result<Proquint, ProquintParseError> {
195        Self::parse_bytes(input.as_bytes())
196    }
197
198    /// Parse a proquint from bytes
199    ///
200    /// Returns any unconsumed input
201    #[cfg_attr(not(tarpaulin), inline(always))]
202    pub fn parse_partial_bytes(input: &[u8]) -> Result<(&[u8], Proquint), ProquintParseError> {
203        if input.len() >= 5 {
204            Ok((
205                &input[5..],
206                Self::from_bytes([input[0], input[1], input[2], input[3], input[4]])?,
207            ))
208        } else {
209            Err(ProquintParseError(None))
210        }
211    }
212
213    /// Parse a proquint from bytes
214    #[cfg_attr(not(tarpaulin), inline(always))]
215    pub const fn parse_bytes(input: &[u8]) -> Result<Proquint, ProquintParseError> {
216        if input.len() == 5 {
217            Self::from_bytes([input[0], input[1], input[2], input[3], input[4]])
218        } else {
219            Err(ProquintParseError(None))
220        }
221    }
222
223    /// Parse a proquint value from a string
224    ///
225    /// Returns any unconsumed input
226    #[cfg_attr(not(tarpaulin), inline(always))]
227    pub fn parse_partial_value(input: &str) -> Result<(&str, u16), ProquintParseError> {
228        let (rest, result) = Self::parse_partial_value_bytes(input.as_bytes())?;
229        Ok((&input[input.len() - rest.len()..], result))
230    }
231
232    /// Parse a proquint value from a string
233    #[cfg_attr(not(tarpaulin), inline(always))]
234    pub const fn parse_value(input: &str) -> Result<u16, ProquintParseError> {
235        Self::parse_value_bytes(input.as_bytes())
236    }
237
238    /// Parse a proquint value from bytes
239    ///
240    /// Return any unconsumed input
241    #[cfg_attr(not(tarpaulin), inline(always))]
242    pub fn parse_partial_value_bytes(input: &[u8]) -> Result<(&[u8], u16), ProquintParseError> {
243        if input.len() >= 5 {
244            Ok((
245                &input[5..],
246                Self::value_from_bytes([input[0], input[1], input[2], input[3], input[4]])?,
247            ))
248        } else {
249            Err(ProquintParseError(None))
250        }
251    }
252
253    /// Parse a proquint value from bytes
254    #[cfg_attr(not(tarpaulin), inline(always))]
255    pub const fn parse_value_bytes(input: &[u8]) -> Result<u16, ProquintParseError> {
256        if input.len() == 5 {
257            Self::value_from_bytes([input[0], input[1], input[2], input[3], input[4]])
258        } else {
259            Err(ProquintParseError(None))
260        }
261    }
262
263    /// Get the value of a proquint consonant
264    ///
265    /// Returns `u16::MAX` for an invalid consonant
266    #[cfg_attr(not(tarpaulin), inline(always))]
267    pub const fn consonant(byte: u8) -> u16 {
268        match byte {
269            b'b' => 0,
270            b'd' => 1,
271            b'f' => 2,
272            b'g' => 3,
273            b'h' => 4,
274            b'j' => 5,
275            b'k' => 6,
276            b'l' => 7,
277            b'm' => 8,
278            b'n' => 9,
279            b'p' => 10,
280            b'r' => 11,
281            b's' => 12,
282            b't' => 13,
283            b'v' => 14,
284            b'z' => 15,
285            _ => u16::MAX, // fallback value for all other cases
286        }
287    }
288
289    /// Get the value of a proquint vowel
290    ///
291    /// Returns `u16::MAX` for an invalid consonant
292    #[cfg_attr(not(tarpaulin), inline(always))]
293    pub const fn vowel(byte: u8) -> u16 {
294        match byte {
295            b'a' => 0,
296            b'i' => 1,
297            b'o' => 2,
298            b'u' => 3,
299            _ => u16::MAX,
300        }
301    }
302}
303
304impl<'a> TryFrom<&'a str> for Proquint {
305    type Error = ProquintParseError;
306
307    #[inline]
308    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
309        Self::from_str(value)
310    }
311}
312
313impl From<Proquint> for u16 {
314    #[cfg_attr(not(tarpaulin), inline(always))]
315    fn from(value: Proquint) -> Self {
316        value.value()
317    }
318}
319
320impl Debug for Proquint {
321    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
322        write!(f, "{}", self.as_str())
323    }
324}
325
326impl Display for Proquint {
327    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328        write!(f, "{}", self.as_str())
329    }
330}
331
332/// Encode data as proquints
333#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
334pub struct ProquintEncode<T>(pub T);
335
336impl<T> IntoIterator for ProquintEncode<T>
337where
338    T: IntoIterator,
339    T::Item: IntoFraction<u16>,
340{
341    type Item = Proquint;
342
343    type IntoIter = ProquintEncodeIter<<FractionalDigits<T, u16> as IntoIterator>::IntoIter>;
344
345    #[cfg_attr(not(tarpaulin), inline(always))]
346    fn into_iter(self) -> Self::IntoIter {
347        ProquintEncodeIter(FractionalDigits::new(self.0).into_iter())
348    }
349}
350
351/// Iterate over some data's proquint encoding
352#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
353pub struct ProquintEncodeIter<T>(pub T);
354
355impl<T> Iterator for ProquintEncodeIter<T>
356where
357    T: Iterator,
358    T::Item: Into<Proquint>,
359{
360    type Item = Proquint;
361
362    #[inline]
363    fn next(&mut self) -> Option<Self::Item> {
364        self.0.next().map(Into::into)
365    }
366}
367
368impl<T> Display for ProquintEncode<T>
369where
370    T: Clone + IntoIterator,
371    T::Item: IntoFraction<u16>,
372{
373    #[inline]
374    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375        let mut copied = self.clone().into_iter();
376        if let Some(first) = copied.next() {
377            write!(f, "{first}")?;
378        }
379        for digit in copied {
380            write!(f, "-{digit}")?;
381        }
382        Ok(())
383    }
384}
385
386/// Parse a buffer as a series of dash-separated proquint digits
387#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
388
389pub struct ParseProquintDigits<T> {
390    /// The buffer to parse
391    pub buffer: T,
392    /// Whether to expect the data to start with a dash
393    pub expect_dash: bool,
394}
395
396impl<T> ParseProquintDigits<T> {
397    /// Create a new iterator over proquints parsed from the input buffer
398    #[cfg_attr(not(tarpaulin), inline(always))]
399    pub const fn new(buffer: T) -> ParseProquintDigits<T> {
400        ParseProquintDigits {
401            buffer,
402            expect_dash: false,
403        }
404    }
405
406    /// Create a new iterator over proquints parsed from the input buffer, which is expected to start with a dash
407    #[cfg_attr(not(tarpaulin), inline(always))]
408    pub const fn new_trailing(buffer: T) -> ParseProquintDigits<T> {
409        ParseProquintDigits {
410            buffer,
411            expect_dash: true,
412        }
413    }
414}
415
416impl<'a> Iterator for ParseProquintDigits<&'a str> {
417    type Item = u16;
418
419    fn next(&mut self) -> Option<Self::Item> {
420        let mut ptr = self.buffer;
421        if self.expect_dash && *ptr.as_bytes().get(0)? == b'-' {
422            ptr = &ptr[1..];
423        }
424        let (ptr, proquint) = Proquint::parse_partial_value(ptr).ok()?;
425        self.buffer = ptr;
426        self.expect_dash = true;
427        Some(proquint)
428    }
429}
430
431impl<'a> Iterator for ParseProquintDigits<&'a [u8]> {
432    type Item = u16;
433
434    fn next(&mut self) -> Option<Self::Item> {
435        let mut ptr = self.buffer;
436        if self.expect_dash && *ptr.get(0)? == b'-' {
437            ptr = &ptr[1..];
438        }
439        let (ptr, proquint) = Proquint::parse_partial_value_bytes(ptr).ok()?;
440        self.buffer = ptr;
441        self.expect_dash = true;
442        Some(proquint)
443    }
444}
445
446impl<T> ParseProquintDigits<T>
447where
448    ParseProquintDigits<T>: IntoIterator<Item = u16>,
449{
450    /// Parse a data type from a series of proquint digits
451    #[cfg_attr(not(tarpaulin), inline(always))]
452    pub fn parse<U: FromProquints>(self) -> Result<U, U::FromProquintsError> {
453        U::from_proquints(self)
454    }
455}
456
457/// Parse a buffer as a series of dash-separated proquints
458#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
459pub struct ParseProquints<T> {
460    /// The buffer to parse
461    pub buffer: T,
462    /// Whether to expect the data to start with a dash
463    pub expect_dash: bool,
464}
465
466impl<T> ParseProquints<T> {
467    /// Create a new iterator over proquints parsed from the input buffer
468    #[cfg_attr(not(tarpaulin), inline(always))]
469    pub fn new(buffer: T) -> ParseProquints<T> {
470        ParseProquints {
471            buffer,
472            expect_dash: false,
473        }
474    }
475
476    /// Create a new iterator over proquints parsed from the input buffer, which is expected to start with a dash
477    #[cfg_attr(not(tarpaulin), inline(always))]
478    pub fn new_trailing(buffer: T) -> ParseProquints<T> {
479        ParseProquints {
480            buffer,
481            expect_dash: true,
482        }
483    }
484}
485
486impl<'a> Iterator for ParseProquints<&'a str> {
487    type Item = Proquint;
488
489    #[inline]
490    fn next(&mut self) -> Option<Self::Item> {
491        let mut ptr = self.buffer;
492        if self.expect_dash && *ptr.as_bytes().get(0)? == b'-' {
493            ptr = &ptr[1..]
494        }
495        let proquint = Proquint::try_from(ptr.get(0..5)?).ok()?;
496        self.buffer = &ptr[5..];
497        self.expect_dash = true;
498        Some(proquint)
499    }
500}
501
502impl<'a> Iterator for ParseProquints<&'a [u8]> {
503    type Item = Proquint;
504
505    #[inline]
506    fn next(&mut self) -> Option<Self::Item> {
507        let mut ptr = self.buffer;
508        if self.expect_dash && *ptr.get(0)? == b'-' {
509            ptr = &ptr[1..]
510        }
511        let proquint = Proquint::try_from(ptr.get(0..5)?).ok()?;
512        self.buffer = &ptr[5..];
513        self.expect_dash = true;
514        Some(proquint)
515    }
516}
517
518/// Data which can be converted to a series of proquints
519pub trait IntoProquints<'a>: Sized {
520    /// An iterator over the proquint digits of this data
521    type DigitsIter: IntoIterator<Item = u16> + 'a;
522
523    /// Get the proquint digits of this data
524    fn proquint_digits(self) -> Self::DigitsIter;
525
526    /// Encode this data as a sequence of proquint digits
527    #[cfg_attr(not(tarpaulin), inline(always))]
528    fn proquint_encode(self) -> ProquintEncode<Self::DigitsIter> {
529        ProquintEncode(self.proquint_digits())
530    }
531}
532
533impl<'a, T> IntoProquints<'a> for T
534where
535    T: IntoFraction<u16> + 'a,
536{
537    type DigitsIter = <FractionalDigits<Once<T>, u16> as IntoIterator>::IntoIter;
538
539    #[cfg_attr(not(tarpaulin), inline(always))]
540    fn proquint_digits(self) -> Self::DigitsIter {
541        FractionalDigits::new(once(self)).into_iter()
542    }
543}
544
545impl<'a, const N: usize, T> IntoProquints<'a> for [T; N]
546where
547    T: IntoFraction<u16> + 'a,
548{
549    type DigitsIter =
550        <FractionalDigits<core::array::IntoIter<T, N>, u16> as IntoIterator>::IntoIter;
551
552    #[cfg_attr(not(tarpaulin), inline(always))]
553    fn proquint_digits(self) -> Self::DigitsIter {
554        FractionalDigits::new(self).into_iter()
555    }
556}
557
558impl<'a, T> IntoProquints<'a> for &'a [T]
559where
560    T: Clone + IntoFraction<u16> + 'a,
561{
562    type DigitsIter = <FractionalDigits<core::iter::Cloned<core::slice::Iter<'a, T>>, u16> as IntoIterator>::IntoIter;
563
564    #[cfg_attr(not(tarpaulin), inline(always))]
565    fn proquint_digits(self) -> Self::DigitsIter {
566        FractionalDigits::new(self.iter().cloned()).into_iter()
567    }
568}
569
570/// Data which can be constructed from a series of proquints
571pub trait FromProquints
572where
573    Self: Sized,
574{
575    /// Error returned on failure to parse
576    type FromProquintsError;
577
578    /// Attempt to parse this type from a series of proquints
579    ///
580    /// May leave unconsumed elements in the iterator
581    fn from_proquints_partial(
582        proquints: impl Iterator<Item = u16>,
583    ) -> Result<Self, Self::FromProquintsError>;
584
585    /// The error to return on failure to parse due to trailing data
586    fn trailing_error(next: Option<u16>) -> Self::FromProquintsError;
587
588    /// Attempt to parse this type from a series of proquints, fully consuming the underlying iterator
589    ///
590    /// Returns an error in the case of trailing data.
591    #[cfg_attr(not(tarpaulin), inline(always))]
592    fn from_proquints(
593        proquints: impl IntoIterator<Item = u16>,
594    ) -> Result<Self, Self::FromProquintsError> {
595        let mut proquints = proquints.into_iter();
596        let parse = Self::from_proquints_partial(&mut proquints)?;
597        if let Some(next) = proquints.next() {
598            Err(Self::trailing_error(Some(next)))
599        } else {
600            Ok(parse)
601        }
602    }
603
604    /// Attempt to parse a string consisting of a series of proquints, returning any leftover portion
605    #[cfg_attr(not(tarpaulin), inline(always))]
606    fn parse_partial_proquints(proquints: &str) -> Result<(&str, Self), Self::FromProquintsError> {
607        let mut proquints = ParseProquintDigits::new(proquints);
608        let parse = Self::from_proquints_partial(&mut proquints)?;
609        Ok((proquints.buffer, parse))
610    }
611
612    /// Attempt to parse a string consisting of a series of proquints, returning any leftover portion
613    #[cfg_attr(not(tarpaulin), inline(always))]
614    fn parse_partial_proquints_bytes(
615        proquints: &[u8],
616    ) -> Result<(&[u8], Self), Self::FromProquintsError> {
617        let mut proquints = ParseProquintDigits::new(proquints);
618        let parse = Self::from_proquints_partial(&mut proquints)?;
619        Ok((proquints.buffer, parse))
620    }
621
622    /// Attempt to parse a string consisting of a series of proquints, completely consuming the input data
623    ///
624    /// Returns an error in the case of trailing data.
625    #[cfg_attr(not(tarpaulin), inline(always))]
626    fn parse_proquints(proquints: impl AsRef<[u8]>) -> Result<Self, Self::FromProquintsError> {
627        let mut proquints = ParseProquintDigits::new(proquints.as_ref());
628        let parse = Self::from_proquints_partial(&mut proquints)?;
629        if proquints.buffer.len() > 0 {
630            Err(Self::trailing_error(None))
631        } else {
632            Ok(parse)
633        }
634    }
635}
636
637/// An error which occurs in parsing a stream of proquints
638#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
639pub enum ProquintsParseError {
640    /// Not enough proquints to parse the desired data
641    NotEnoughProquints(usize),
642    /// Trailing data leftover after parsing
643    TrailingData(Option<u16>),
644    /// The value parsed is invalid
645    InvalidValue,
646}
647
648impl Display for ProquintsParseError {
649    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
650        match self {
651            ProquintsParseError::NotEnoughProquints(p) => {
652                write!(f, "expected at least {p:?} more proquints of data")
653            }
654            ProquintsParseError::TrailingData(d) => write!(f, "trailing data: {d:?}"),
655            ProquintsParseError::InvalidValue => write!(f, "invalid value"),
656        }
657    }
658}
659
660impl<T> FromProquints for T
661where
662    T: FromFraction<u16>,
663{
664    type FromProquintsError = ProquintsParseError;
665
666    #[cfg_attr(not(tarpaulin), inline(always))]
667    fn from_proquints_partial(
668        proquints: impl Iterator<Item = u16>,
669    ) -> Result<Self, Self::FromProquintsError> {
670        FromFraction::from_pieces(proquints).map_err(|err| {
671            if err.0 == 0 {
672                ProquintsParseError::InvalidValue
673            } else {
674                ProquintsParseError::NotEnoughProquints(err.0)
675            }
676        })
677    }
678
679    #[cfg_attr(not(tarpaulin), inline(always))]
680    fn trailing_error(next: Option<u16>) -> Self::FromProquintsError {
681        ProquintsParseError::TrailingData(next)
682    }
683}
684
685#[cfg_attr(not(tarpaulin), inline(always))]
686pub(crate) fn be_encode_array_u16<const M: usize>(array: &[u8]) -> [u16; M] {
687    let mut result = [0; M];
688    for (i, w) in array.chunks(2).enumerate() {
689        result[i] = u16::from_be_bytes(w.try_into().unwrap())
690    }
691    result
692}
693
694#[cfg_attr(not(tarpaulin), inline(always))]
695pub(crate) fn be_encode_array_u32<const M: usize>(array: &[u8]) -> [u32; M] {
696    let mut result = [0; M];
697    for (i, w) in array.chunks(4).enumerate() {
698        result[i] = u32::from_be_bytes(w.try_into().unwrap())
699    }
700    result
701}
702
703#[cfg_attr(not(tarpaulin), inline(always))]
704pub(crate) fn be_encode_array_u64<const M: usize>(array: &[u8]) -> [u64; M] {
705    let mut result = [0; M];
706    for (i, w) in array.chunks(8).enumerate() {
707        result[i] = u64::from_be_bytes(w.try_into().unwrap())
708    }
709    result
710}
711
712#[cfg_attr(not(tarpaulin), inline(always))]
713pub(crate) fn be_encode_array_i16<const M: usize>(array: &[u8]) -> [i16; M] {
714    let mut result = [0; M];
715    for (i, w) in array.chunks(2).enumerate() {
716        result[i] = i16::from_be_bytes(w.try_into().unwrap())
717    }
718    result
719}
720
721#[cfg_attr(not(tarpaulin), inline(always))]
722pub(crate) fn be_encode_array_i32<const M: usize>(array: &[u8]) -> [i32; M] {
723    let mut result = [0; M];
724    for (i, w) in array.chunks(4).enumerate() {
725        result[i] = i32::from_be_bytes(w.try_into().unwrap())
726    }
727    result
728}
729
730#[cfg_attr(not(tarpaulin), inline(always))]
731pub(crate) fn be_encode_array_i64<const M: usize>(array: &[u8]) -> [i64; M] {
732    let mut result = [0; M];
733    for (i, w) in array.chunks(8).enumerate() {
734        result[i] = i64::from_be_bytes(w.try_into().unwrap())
735    }
736    result
737}
738
739#[cfg(feature = "std")]
740mod std_ {
741    use super::*;
742    use std::error::Error;
743    use std::net::{Ipv4Addr, Ipv6Addr};
744
745    impl IntoProquints<'static> for Ipv4Addr {
746        type DigitsIter = core::array::IntoIter<u16, 2>;
747
748        #[cfg_attr(not(tarpaulin), inline(always))]
749        fn proquint_digits(self) -> Self::DigitsIter {
750            be_encode_array_u16::<2>(&self.octets()).into_iter()
751        }
752    }
753
754    impl IntoProquints<'static> for Ipv6Addr {
755        type DigitsIter = core::array::IntoIter<u16, 8>;
756
757        #[cfg_attr(not(tarpaulin), inline(always))]
758        fn proquint_digits(self) -> Self::DigitsIter {
759            be_encode_array_u16::<8>(&self.octets()).into_iter()
760        }
761    }
762
763    impl Error for ProquintParseError {}
764    impl Error for ProquintsParseError {}
765}
766
767#[cfg(test)]
768mod test {
769    use super::*;
770    use proptest::prelude::*;
771
772    #[cfg(feature = "std")]
773    macro_rules! roundtrip {
774        ($t:ty, $i:expr) => {{
775            let mut encoded = format!("{}", $i.proquint_encode());
776            assert_eq!(<$t>::parse_proquints(&encoded).unwrap(), $i);
777            assert_eq!(<$t>::parse_proquints(encoded.as_bytes()).unwrap(), $i);
778            assert_eq!(<$t>::parse_partial_proquints(&encoded).unwrap(), ("", $i));
779            assert_eq!(
780                <$t>::parse_partial_proquints_bytes(encoded.as_bytes()).unwrap(),
781                (&b""[..], $i)
782            );
783            encoded.push('a');
784            assert_eq!(
785                <$t>::parse_proquints(&encoded).unwrap_err(),
786                ProquintsParseError::TrailingData(None)
787            );
788            assert_eq!(<$t>::parse_partial_proquints(&encoded).unwrap(), ("a", $i));
789        }};
790    }
791
792    proptest! {
793        #[test]
794        fn roundtrip_proquint(i: u16) {
795            let pq = Proquint::from(i);
796            assert_eq!(u16::from(pq), i);
797            let as_str = pq.as_str();
798            assert_eq!(as_str.as_bytes(), &*pq);
799            assert_eq!(as_str, <Proquint as AsRef<str>>::as_ref(&pq));
800            assert_eq!(as_str.as_bytes(), <Proquint as AsRef<[u8]>>::as_ref(&pq));
801            assert_eq!(as_str.as_bytes(), <Proquint as AsRef<[u8; 5]>>::as_ref(&pq));
802            assert_eq!(as_str, <Proquint as Borrow<str>>::borrow(&pq));
803            assert_eq!(as_str.as_bytes(), <Proquint as Borrow<[u8]>>::borrow(&pq));
804            assert_eq!(as_str.as_bytes(), <Proquint as Borrow<[u8; 5]>>::borrow(&pq));
805            #[cfg(feature = "std")]
806            {
807                assert_eq!(format!("{pq}").parse::<Proquint>().unwrap(), pq);
808                assert_eq!(format!("{pq:?}").parse::<Proquint>().unwrap(), pq);
809            }
810            assert_eq!(Proquint::try_from(*pq).unwrap(), pq);
811            assert_eq!(Proquint::try_from(&pq[..]).unwrap(), pq);
812        }
813    }
814
815    #[cfg(feature = "std")]
816    proptest! {
817        #[test]
818        fn roundtrip_u128(i: u128) {
819            roundtrip!(u128, i);
820        }
821
822        #[test]
823        fn roundtrip_u64(i: u64) {
824            roundtrip!(u64, i);
825            roundtrip!(u128, i as u128);
826            assert_eq!(
827                u128::from_proquints(i.proquint_digits()).unwrap(),
828                i as u128
829            );
830        }
831
832        #[test]
833        fn roundtrip_u32(i: u32) {
834            roundtrip!(u32, i);
835        }
836
837        #[test]
838        fn roundtrip_u16(i: u16) {
839            roundtrip!(u16, i);
840        }
841
842        #[test]
843        fn roundtrip_u8(i: u8) {
844            roundtrip!(u8, i);
845        }
846
847        #[test]
848        fn roundtrip_i128(i: i128) {
849            roundtrip!(i128, i);
850        }
851
852        #[test]
853        fn roundtrip_i64(i: i64) {
854            roundtrip!(i64, i);
855        }
856
857        #[test]
858        fn roundtrip_i32(i: i32) {
859            roundtrip!(i32, i);
860        }
861
862        #[test]
863        fn roundtrip_i16(i: i16) {
864            roundtrip!(i16, i);
865        }
866
867        #[test]
868        fn roundtrip_u8_4(i: [u8; 4]) {
869            roundtrip!([u8; 4], i);
870        }
871
872        #[test]
873        fn roundtrip_u8_3(i: [u8; 3]) {
874            roundtrip!([u8; 3], i);
875        }
876
877        #[test]
878        fn parse_proquints(v: Vec<u16>) {
879            let proquints = format!("{}", ProquintEncode(v));
880            Iterator::eq(ParseProquintDigits::new(&proquints[..]), ParseProquints::new(&proquints[..]).map(u16::from));
881            Iterator::eq(ParseProquintDigits::new(&proquints[..]).map(Proquint::from), ParseProquints::new(&proquints[..]));
882            Iterator::eq(ParseProquintDigits::new(proquints.as_bytes()), ParseProquints::new(proquints.as_bytes()).map(u16::from));
883            Iterator::eq(ParseProquintDigits::new(proquints.as_bytes()).map(Proquint::from), ParseProquints::new(proquints.as_bytes()));
884        }
885
886        #[test]
887        fn roundtrip_ipv4(i: std::net::Ipv4Addr) {
888            roundtrip!(std::net::Ipv4Addr, i);
889            assert_eq!(i.proquint_digits().count(), 2);
890            assert_eq!(i.proquint_encode().into_iter().count(), 2);
891            assert!(i.proquint_digits().eq(i.octets().proquint_digits()));
892            assert!(i.proquint_encode().into_iter().eq(i.octets().proquint_encode().into_iter()));
893            assert_eq!(format!("{}", i.proquint_encode()), format!("{}", i.octets().proquint_encode()));
894        }
895
896        #[test]
897        fn roundtrip_ipv6(i: std::net::Ipv6Addr) {
898            roundtrip!(std::net::Ipv6Addr, i);
899        }
900    }
901
902    #[cfg(feature = "std")]
903    #[test]
904    fn proquint_encode_nil() {
905        assert_eq!(
906            format!("{}", ProquintEncode(core::iter::Empty::<u16>::default())),
907            ""
908        );
909    }
910
911    #[test]
912    fn invalid_proquint() {
913        assert_eq!(
914            Proquint::try_from(*b"xxxxx"),
915            Err(ProquintParseError(Some(*b"xxxxx")))
916        );
917        assert_eq!(
918            Proquint::try_from("bxxxx"),
919            Err(ProquintParseError(Some(*b"bxxxx")))
920        );
921        assert_eq!(
922            Proquint::parse_str("bxxxx"),
923            Err(ProquintParseError(Some(*b"bxxxx")))
924        );
925        assert_eq!(
926            Proquint::parse_partial("bxxxx"),
927            Err(ProquintParseError(Some(*b"bxxxx")))
928        );
929        assert_eq!(
930            Proquint::parse_partial("bxxx"),
931            Err(ProquintParseError(None))
932        );
933        assert_eq!(
934            Proquint::parse_value_bytes(b"baxxx"),
935            Err(ProquintParseError(Some(*b"baxxx")))
936        );
937        assert_eq!(
938            Proquint::parse_value_bytes(b"baxx"),
939            Err(ProquintParseError(None))
940        );
941        assert_eq!(
942            Proquint::parse_bytes(b"baxxx"),
943            Err(ProquintParseError(Some(*b"baxxx")))
944        );
945        assert_eq!(
946            Proquint::parse_bytes(b"baxx"),
947            Err(ProquintParseError(None))
948        );
949        assert_eq!(
950            Proquint::parse_partial_bytes(b"baxxx"),
951            Err(ProquintParseError(Some(*b"baxxx")))
952        );
953        assert_eq!(
954            Proquint::parse_partial_bytes(b"baxx"),
955            Err(ProquintParseError(None))
956        );
957        assert_eq!(
958            Proquint::parse_value("babxx"),
959            Err(ProquintParseError(Some(*b"babxx")))
960        );
961        assert_eq!(
962            "12345".parse::<Proquint>(),
963            Err(ProquintParseError(Some(*b"12345")))
964        );
965        #[cfg(feature = "std")]
966        {
967            assert_eq!(
968                format!("{}", ProquintParseError(None)),
969                "not enough characters to parse a proquint"
970            );
971            assert_eq!(
972                format!("{}", ProquintParseError(Some(*b"xxxxx"))),
973                "[120, 120, 120, 120, 120] (utf-8 = Ok(\"xxxxx\")) is not a valid proquint"
974            );
975            assert_eq!(
976                format!("{}", ProquintsParseError::InvalidValue),
977                "invalid value"
978            );
979            assert_eq!(
980                format!("{}", ProquintsParseError::TrailingData(None)),
981                "trailing data: None"
982            );
983            assert_eq!(
984                format!("{}", ProquintsParseError::NotEnoughProquints(5)),
985                "expected at least 5 more proquints of data"
986            );
987        }
988    }
989
990    #[test]
991    fn u8_overflow_proquint() {
992        assert_eq!(
993            u8::from_proquints(once(0x0100)),
994            Err(ProquintsParseError::InvalidValue)
995        );
996        assert_eq!(
997            u8::from_proquints([0x0010, 0x0000]),
998            Err(ProquintsParseError::TrailingData(Some(0x0000)))
999        )
1000    }
1001
1002    #[cfg(feature = "std")]
1003    #[test]
1004    fn proquint_example_ips() {
1005        use std::net::Ipv4Addr;
1006
1007        let ips = [
1008            (Ipv4Addr::new(127, 0, 0, 1), "lusab-babad"),
1009            (Ipv4Addr::new(63, 84, 220, 193), "gutih-tugad"),
1010            (Ipv4Addr::new(63, 118, 7, 35), "gutuk-bisog"),
1011            (Ipv4Addr::new(140, 98, 193, 141), "mudof-sakat"),
1012            (Ipv4Addr::new(64, 255, 6, 200), "haguz-biram"),
1013            (Ipv4Addr::new(128, 30, 52, 45), "mabiv-gibot"),
1014            (Ipv4Addr::new(147, 67, 119, 2), "natag-lisaf"),
1015            (Ipv4Addr::new(212, 58, 253, 68), "tibup-zujah"),
1016            (Ipv4Addr::new(216, 35, 68, 215), "tobog-higil"),
1017            (Ipv4Addr::new(216, 68, 232, 21), "todah-vobij"),
1018            (Ipv4Addr::new(198, 81, 129, 136), "sinid-makam"),
1019            (Ipv4Addr::new(12, 110, 110, 204), "budov-kuras"),
1020            (Ipv4Addr::new(255, 255, 255, 255), "zuzuz-zuzuz"),
1021        ];
1022
1023        for (ip, name) in ips {
1024            assert_eq!(format!("{}", ip.proquint_encode()), name);
1025        }
1026    }
1027
1028    #[cfg(feature = "std")]
1029    #[test]
1030    fn proquint_ips_octet_encode() {
1031        use std::net::Ipv4Addr;
1032        assert_eq!(Ipv4Addr::new(127, 0, 0, 1).octets(), [127, 0, 0, 1]);
1033        assert_eq!(
1034            format!("{}", Ipv4Addr::new(127, 0, 0, 1).proquint_encode()),
1035            format!("{}", [127u8, 0, 0, 1].proquint_encode())
1036        );
1037    }
1038}