use core::str::FromStr;
num_to_enum! {
#[derive(Default)]
BrotliQuality(u8 in [0, 11]):"quality":"Q" {
0, 1, 2, #[default] 3, 4, 5, 6, 7, 8, 9, 10, 11,
}
}
num_to_enum! {
#[derive(Default)]
BrotliWindow(u8 in [10, 24]):"window":"W" {
10, 11, 12, 13, 14, 15, #[default] 16, 17, 18, 19, 20, 21, 22, 23, 24,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("({quality}, {window})")]
pub struct BrotliAlgorithm {
quality: BrotliQuality,
window: BrotliWindow,
}
impl Default for BrotliAlgorithm {
fn default() -> Self {
Self::new()
}
}
impl BrotliAlgorithm {
#[inline]
pub const fn new() -> Self {
Self {
quality: BrotliQuality::Q3,
window: BrotliWindow::W16,
}
}
#[inline]
pub const fn with_quality_and_window(quality: BrotliQuality, window: BrotliWindow) -> Self {
Self { quality, window }
}
#[inline]
pub const fn quality(&self) -> BrotliQuality {
self.quality
}
#[inline]
pub const fn window(&self) -> BrotliWindow {
self.window
}
#[inline]
pub(crate) const fn encode(&self) -> u8 {
let quality_bits = (self.quality as u8) << 4;
let window_bits = (self.window as u8).saturating_sub(10);
quality_bits | window_bits
}
#[inline]
pub(crate) const fn decode(byte: u8) -> Self {
let quality = BrotliQuality::from_u8(byte >> 4);
let window = BrotliWindow::from_u8(10 + (byte & 0x0F));
Self { quality, window }
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, thiserror::Error)]
#[error("invalid brotli: {0}")]
pub struct ParseBrotliAlgorithmError(String);
impl FromStr for BrotliAlgorithm {
type Err = ParseBrotliAlgorithmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.trim_matches(|c| c == '(' || c == ')');
if parts.is_empty() {
return Ok(Self::default());
}
let mut parts = parts.split(',');
let quality = parts
.next()
.ok_or_else(|| ParseBrotliAlgorithmError(s.to_string()))?
.trim();
let quality = super::parse_or_default::<u8, _>(quality)
.map_err(|_| ParseBrotliAlgorithmError(s.to_string()))?;
let window = parts
.next()
.ok_or_else(|| ParseBrotliAlgorithmError(s.to_string()))?
.trim();
let window = super::parse_or_default::<u8, _>(window)
.map_err(|_| ParseBrotliAlgorithmError(s.to_string()))?;
Ok(Self::with_quality_and_window(quality, window))
}
}
#[cfg(feature = "serde")]
const _: () = {
use serde::{Deserialize, Serialize};
#[derive(serde::Serialize, serde::Deserialize)]
struct BrotliAlgorithmHelper {
quality: BrotliQuality,
window: BrotliWindow,
}
impl From<BrotliAlgorithm> for BrotliAlgorithmHelper {
fn from(algo: BrotliAlgorithm) -> Self {
Self {
quality: algo.quality(),
window: algo.window(),
}
}
}
impl From<BrotliAlgorithmHelper> for BrotliAlgorithm {
fn from(helper: BrotliAlgorithmHelper) -> Self {
Self::with_quality_and_window(helper.quality, helper.window)
}
}
impl Serialize for BrotliAlgorithm {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
BrotliAlgorithmHelper::from(*self).serialize(serializer)
} else {
serializer.serialize_u8(self.encode())
}
}
}
impl<'de> Deserialize<'de> for BrotliAlgorithm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
BrotliAlgorithmHelper::deserialize(deserializer).map(Into::into)
} else {
u8::deserialize(deserializer).map(Self::decode)
}
}
}
};
#[test]
fn parse_str() {
assert_eq!("".parse::<BrotliAlgorithm>(), Ok(BrotliAlgorithm::new()));
assert_eq!("()".parse::<BrotliAlgorithm>(), Ok(BrotliAlgorithm::new()));
assert_eq!(
"(1, 11)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q1,
BrotliWindow::W11
))
);
assert_eq!(
"(11, 24)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q11,
BrotliWindow::W24
))
);
assert_eq!(
"(5, 15)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q5,
BrotliWindow::W15
))
);
assert_eq!(
"(10, 20)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q10,
BrotliWindow::W20
))
);
assert_eq!(
"(1, 11)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q1,
BrotliWindow::W11
))
);
assert_eq!(
"(2, 12)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q2,
BrotliWindow::W12
))
);
assert_eq!(
"(3, 13)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q3,
BrotliWindow::W13
))
);
assert_eq!(
"(4, 14)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::Q4,
BrotliWindow::W14
))
);
assert_eq!(
"(255, 255)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::with_quality_and_window(
BrotliQuality::max(),
BrotliWindow::max()
))
);
assert_eq!(
"(-, -)".parse::<BrotliAlgorithm>(),
Ok(BrotliAlgorithm::new())
);
}