#![warn(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::cast_possible_truncation)]
#![warn(clippy::cargo)]
#![allow(unknown_lints)]
#![warn(missing_copy_implementations)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused_qualifications)]
#![warn(variant_size_differences)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_alias))]
#![cfg_attr(
not(feature = "std"),
doc = "[`std`]: https://doc.rust-lang.org/stable/std/index.html"
)]
#![cfg_attr(
not(feature = "std"),
doc = "[`std::error::Error`]: https://doc.rust-lang.org/stable/std/error/trait.Error.html"
)]
#![no_std]
#![doc(html_root_url = "https://docs.rs/boba/5.0.0")]
#[cfg(doctest)]
macro_rules! readme {
($x:expr) => {
#[doc = $x]
mod readme {}
};
() => {
readme!(include_str!("../README.md"));
};
}
#[cfg(doctest)]
readme!();
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
mod decode;
mod encode;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum DecodeError {
ChecksumMismatch,
Corrupted,
ExpectedConsonant,
ExpectedVowel,
InvalidByte(usize),
MalformedHeader,
MalformedTrailer,
}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ChecksumMismatch => f.write_str("Checksum mismatch"),
Self::Corrupted => f.write_str("Corrupted input"),
Self::ExpectedConsonant => f.write_str("Expected consonant, got something else"),
Self::ExpectedVowel => f.write_str("Expected vowel, got something else"),
Self::InvalidByte(pos) => write!(
f,
"Encountered byte outside of encoding alphabet at position {}",
pos
),
Self::MalformedHeader => f.write_str("Missing required 'x' header"),
Self::MalformedTrailer => f.write_str("Missing required 'x' trailer"),
}
}
}
#[must_use]
pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
encode::inner(data.as_ref())
}
pub fn decode<T: AsRef<[u8]>>(encoded: T) -> Result<Vec<u8>, DecodeError> {
decode::inner(encoded.as_ref())
}
#[cfg(test)]
#[allow(clippy::non_ascii_literal)]
mod tests {
use alloc::string::String;
use alloc::vec;
use core::fmt::Write as _;
use crate::{decode, encode, DecodeError};
#[test]
fn encoder() {
assert_eq!(encode([]), "xexax");
assert_eq!(encode("1234567890"), "xesef-disof-gytuf-katof-movif-baxux");
assert_eq!(encode("Pineapple"), "xigak-nyryk-humil-bosek-sonax");
assert_eq!(
encode("💎🦀❤️✨💪"),
"xusan-zugom-vesin-zenom-bumun-tanav-zyvam-zomon-sapaz-bulin-dypux"
);
}
#[test]
fn decoder() {
assert_eq!(decode("xexax"), Ok(vec![]));
assert_eq!(
decode("xesef-disof-gytuf-katof-movif-baxux"),
Ok(b"1234567890".to_vec())
);
assert_eq!(
decode("xigak-nyryk-humil-bosek-sonax"),
Ok(b"Pineapple".to_vec())
);
assert_eq!(
decode("xusan-zugom-vesin-zenom-bumun-tanav-zyvam-zomon-sapaz-bulin-dypux"),
Ok(String::from("💎🦀❤️✨💪").into_bytes())
);
}
#[test]
fn decode_error_sub_dash() {
assert_eq!(
decode("xesefxdisofxgytufxkatofxmovifxbaxux"),
Err(DecodeError::ChecksumMismatch)
);
}
#[test]
fn decode_sub_vowel_to_consonant() {
assert_eq!(
decode("xssef-disof-gytuf-katof-movif-baxux"),
Err(DecodeError::ExpectedVowel),
);
}
#[test]
fn decode_sub_consonant_to_vowel() {
assert_eq!(
decode("xeeef-disof-gytuf-katof-movif-baxux"),
Err(DecodeError::ExpectedConsonant)
);
}
#[test]
fn decode_error() {
assert_eq!(decode(""), Err(DecodeError::Corrupted));
assert_eq!(decode("z"), Err(DecodeError::Corrupted));
assert_eq!(decode("xy"), Err(DecodeError::MalformedTrailer));
assert_eq!(decode("yx"), Err(DecodeError::MalformedHeader));
assert_eq!(decode("xx"), Err(DecodeError::Corrupted));
assert_eq!(decode("x💎🦀x"), Err(DecodeError::InvalidByte(1)));
assert_eq!(decode("x789x"), Err(DecodeError::InvalidByte(1)));
}
#[test]
fn decode_error_bad_alphabet() {
assert_eq!(
decode("xigak-nyryk-/umil-bosek-sonax"),
Err(DecodeError::InvalidByte(12))
);
assert_eq!(decode(b"x\xFFx"), Err(DecodeError::InvalidByte(1)));
assert_eq!(
decode("xigak-nyryk-Humil-bosek-sonax"),
Err(DecodeError::InvalidByte(12))
);
assert_eq!(
decode("XIGAK-NYRYK-HUMIL-BOSEK-SONAX"),
Err(DecodeError::Corrupted)
);
assert_eq!(
decode("xIGAK-NYRYK-HUMIL-BOSEK-SONAX"),
Err(DecodeError::MalformedTrailer)
);
assert_eq!(
decode("xIGAK-NYRYK-HUMIL-BOSEK-SONAx"),
Err(DecodeError::InvalidByte(1))
);
}
#[test]
fn error_display_is_not_empty() {
let test_cases = [
DecodeError::ChecksumMismatch,
DecodeError::Corrupted,
DecodeError::ExpectedConsonant,
DecodeError::ExpectedVowel,
DecodeError::InvalidByte(0),
DecodeError::InvalidByte(123),
DecodeError::MalformedHeader,
DecodeError::MalformedTrailer,
];
for tc in test_cases {
let mut buf = String::new();
write!(&mut buf, "{}", tc).unwrap();
assert!(!buf.is_empty());
}
}
}