optimise quality item parsing

This commit is contained in:
Rob Ede 2021-12-05 02:57:02 +00:00
parent 2330ca60ec
commit 134b690364
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
2 changed files with 32 additions and 38 deletions

View File

@ -5,8 +5,8 @@ use std::{
use derive_more::{Display, Error}; use derive_more::{Display, Error};
pub(super) const MAX_QUALITY_INT: u16 = 1000; const MAX_QUALITY_INT: u16 = 1000;
pub(super) const MAX_QUALITY_FLOAT: f32 = 1.0; const MAX_QUALITY_FLOAT: f32 = 1.0;
/// Represents a quality used in q-factor values. /// Represents a quality used in q-factor values.
/// ///
@ -66,8 +66,6 @@ impl Default for Quality {
} }
} }
// In benchmarks this is twice as fast as a naive approach using something like
// `format!("{}").trim_end_matches('0')` for non-fast path quality values
impl fmt::Display for Quality { impl fmt::Display for Quality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 { match self.0 {
@ -78,8 +76,9 @@ impl fmt::Display for Quality {
x => { x => {
f.write_str("0.")?; f.write_str("0.")?;
// this implementation avoids string allocation otherwise required // This implementation avoids string allocation for removing trailing zeroes.
// for `.trim_end_matches('0')` // In benchmarks it is twice as fast as approach using something like
// `format!("{}").trim_end_matches('0')` for non-fast path quality values.
if x < 10 { if x < 10 {
f.write_str("00")?; f.write_str("00")?;

View File

@ -1,11 +1,8 @@
use std::{cmp, fmt, str}; use std::{cmp, convert::TryFrom as _, fmt, str};
use crate::error::ParseError; use crate::error::ParseError;
use super::{ use super::Quality;
quality::{MAX_QUALITY_FLOAT, MAX_QUALITY_INT},
Quality,
};
/// Represents an item with a quality value as defined /// Represents an item with a quality value as defined
/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). /// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).
@ -74,9 +71,9 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
match self.quality { match self.quality {
// q-factor value is implied for max value // q-factor value is implied for max value
Quality(MAX_QUALITY_INT) => Ok(()), Quality::MAX => Ok(()),
Quality(0) => f.write_str("; q=0"), Quality::MIN => f.write_str("; q=0"),
q => write!(f, "; q={}", q), q => write!(f, "; q={}", q),
} }
@ -86,57 +83,55 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
impl<T: str::FromStr> str::FromStr for QualityItem<T> { impl<T: str::FromStr> str::FromStr for QualityItem<T> {
type Err = ParseError; type Err = ParseError;
fn from_str(qitem_str: &str) -> Result<Self, Self::Err> { fn from_str(q_item_str: &str) -> Result<Self, Self::Err> {
if !qitem_str.is_ascii() { if !q_item_str.is_ascii() {
return Err(ParseError::Header); return Err(ParseError::Header);
} }
// Set defaults used if parsing fails. // set defaults used if quality-item parsing fails, i.e., item has no q-factor
let mut raw_item = qitem_str; let mut raw_item = q_item_str;
let mut quality = 1f32; let mut quality = Quality::MAX;
// TODO: MSRV(1.52): use rsplit_once let parts = q_item_str
let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect(); .rsplit_once(';')
.map(|(item, q_attr)| (item.trim(), q_attr.trim()));
if parts.len() == 2 { if let Some((val, q_attr)) = parts {
// example for item with q-factor: // example for item with q-factor:
// //
// gzip; q=0.65 // gzip;q=0.65
// ^^^^^^ parts[0] // ^^^^ val
// ^^ start // ^^^^^^ q_attr
// ^^^^ q_val // ^^ q
// ^^^^ parts[1] // ^^^^ q_val
if parts[0].len() < 2 { if q_attr.len() < 2 {
// Can't possibly be an attribute since an attribute needs at least a name followed // Can't possibly be an attribute since an attribute needs at least a name followed
// by an equals sign. And bare identifiers are forbidden. // by an equals sign. And bare identifiers are forbidden.
return Err(ParseError::Header); return Err(ParseError::Header);
} }
let start = &parts[0][0..2]; let q = &q_attr[0..2];
if start == "q=" || start == "Q=" { if q == "q=" || q == "Q=" {
let q_val = &parts[0][2..]; let q_val = &q_attr[2..];
if q_val.len() > 5 { if q_val.len() > 5 {
// longer than 5 indicates an over-precise q-factor // longer than 5 indicates an over-precise q-factor
return Err(ParseError::Header); return Err(ParseError::Header);
} }
let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?; let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?;
let q_value =
Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
if (0f32..=MAX_QUALITY_FLOAT).contains(&q_value) { quality = q_value;
quality = q_value; raw_item = val;
raw_item = parts[1];
} else {
return Err(ParseError::Header);
}
} }
} }
let item = raw_item.parse::<T>().map_err(|_| ParseError::Header)?; let item = raw_item.parse::<T>().map_err(|_| ParseError::Header)?;
// we already checked above that the quality is within range Ok(QualityItem::new(item, quality))
Ok(QualityItem::new(item, Quality::from_f32(quality)))
} }
} }