use std::fmt;
use crate::accept::{check_accept_param, q_of, read_named_accept_entry, QValue};
use crate::check::checked_token;
use crate::diagnostic::{Field, ParseWarning};
use crate::error::ParseError;
use crate::is_token;
use crate::list::CommaList;
use crate::params::HeaderParams;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct SipAcceptEncodingEntry {
encoding: String,
params: HeaderParams,
}
header_params!(SipAcceptEncodingEntry, check: check_accept_param);
params_located!(SipAcceptEncodingEntry);
impl SipAcceptEncodingEntry {
pub fn new(encoding: impl AsRef<str>) -> Result<Self, ParseError> {
checked_token(Field::Coding, encoding.as_ref()).map(Self::unchecked)
}
fn unchecked(mut encoding: String) -> Self {
encoding.make_ascii_lowercase();
SipAcceptEncodingEntry {
encoding,
params: HeaderParams::default(),
}
}
pub fn encoding(&self) -> &str {
&self.encoding
}
pub fn q(&self) -> Option<QValue> {
q_of(&self.params)
}
}
impl fmt::Display for SipAcceptEncodingEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.encoding, self.params)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct SipAcceptEncoding(Vec<SipAcceptEncodingEntry>);
list_type!(SipAcceptEncoding, SipAcceptEncodingEntry, may_be_empty);
#[cfg(feature = "serde")]
serde_parts!(SipAcceptEncodingEntry, SipAcceptEncodingEntryParts);
#[cfg(feature = "serde")]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct SipAcceptEncodingEntryParts {
#[serde(deserialize_with = "crate::serde_parts::field::encoding")]
encoding: String,
#[serde(default, deserialize_with = "crate::params::deserialize_unchecked")]
params: HeaderParams,
}
#[cfg(feature = "serde")]
impl SipAcceptEncodingEntryParts {
fn into_value(p: Self) -> Result<SipAcceptEncodingEntry, ParseError> {
let entry = SipAcceptEncodingEntry {
params: p.params,
..SipAcceptEncodingEntry::unchecked(p.encoding)
};
crate::list::entry_reads_back::<SipAcceptEncoding>(entry)
}
fn from_value(e: SipAcceptEncodingEntry) -> Self {
SipAcceptEncodingEntryParts {
encoding: e.encoding,
params: e.params,
}
}
}
fn parse_entry(
entry: &str,
warnings: &mut Vec<ParseWarning>,
) -> Result<SipAcceptEncodingEntry, ParseError> {
let (encoding, params) = read_named_accept_entry(entry, Field::Coding, is_token, warnings)?;
Ok(SipAcceptEncodingEntry {
params,
..SipAcceptEncodingEntry::unchecked(encoding)
})
}
impl CommaList for SipAcceptEncoding {
type Entry = SipAcceptEncodingEntry;
const BLANK_ENTRIES_ARE_EMPTY: bool = true;
fn parse_entry(
entry: &str,
warnings: &mut Vec<ParseWarning>,
) -> Result<SipAcceptEncodingEntry, ParseError> {
parse_entry(entry, warnings)
}
fn from_parsed(entries: Vec<SipAcceptEncodingEntry>) -> Result<Self, ParseError> {
Ok(Self::new(entries))
}
}
list_parse!(SipAcceptEncoding);
impl crate::traits::sealed::Sealed for SipAcceptEncodingEntry {}
entry_parse!(SipAcceptEncodingEntry, SipAcceptEncoding);
#[cfg(test)]
mod tests {
use crate::list::testing::{self, Seen};
use crate::{HeaderParse, ListParse};
use sip_uri::WarningKind;
use super::*;
use crate::diagnostic::WarningCode;
#[test]
fn single_encoding() {
let ae = SipAcceptEncoding::parse("gzip").unwrap();
assert_eq!(ae.len(), 1);
assert_eq!(ae.entries()[0].encoding(), "gzip");
}
#[test]
fn multiple_encodings_with_q() {
let ae = SipAcceptEncoding::parse("gzip;q=1.0, identity;q=0.5").unwrap();
assert_eq!(ae.len(), 2);
assert_eq!(ae.entries()[0].param("q"), Some(Some("1.0")));
assert_eq!(ae.entries()[1].encoding(), "identity");
}
#[test]
fn wildcard() {
let ae = SipAcceptEncoding::parse("*").unwrap();
assert_eq!(ae.entries()[0].encoding(), "*");
}
#[test]
fn empty_input_is_empty_list() {
assert!(SipAcceptEncoding::parse("")
.unwrap()
.is_empty());
assert!(SipAcceptEncoding::parse(" ")
.unwrap()
.is_empty());
}
#[test]
fn flag_param_roundtrip() {
let raw = "gzip;foo";
let ae = SipAcceptEncoding::parse(raw).unwrap();
assert_eq!(ae.entries()[0].param("foo"), Some(None));
assert_eq!(
ae.entries()[0]
.params()
.iter()
.collect::<Vec<_>>(),
[("foo", None)]
);
assert_eq!(ae.to_string(), raw);
}
#[test]
fn quoted_param_keeps_semicolon() {
let raw = r#"gzip;x="a;b";q=0.5"#;
let ae = SipAcceptEncoding::parse(raw).unwrap();
assert_eq!(ae.entries()[0].param("X"), Some(Some("a;b")));
assert_eq!(ae.entries()[0].param("q"), Some(Some("0.5")));
assert_eq!(ae.to_string(), raw);
}
#[test]
fn error_display_omits_input() {
let err = SipAcceptEncoding::parse_strict(";secretvalue").unwrap_err();
assert!(!err
.to_string()
.contains("secretvalue"));
}
#[test]
fn display_roundtrip() {
let raw = "gzip;q=0.8";
let ae = SipAcceptEncoding::parse(raw).unwrap();
assert_eq!(ae.to_string(), raw);
}
#[test]
fn from_entries_matches_parse() {
let split = SipAcceptEncoding::from_entries(["gzip;q=0.8", "identity"]).unwrap();
let joined = SipAcceptEncoding::parse("gzip;q=0.8, identity").unwrap();
assert_eq!(split, joined);
}
#[test]
fn from_entries_blank_entry_is_empty_entry() {
let parsed = SipAcceptEncoding::from_entries_with_warnings(["gzip", " "]).unwrap();
assert_eq!(parsed.value, SipAcceptEncoding::parse("gzip").unwrap());
let w = parsed.warnings[0];
assert_eq!((w.code, w.entry), (WarningCode::EmptyEntry, Some(1)));
assert_eq!(
SipAcceptEncoding::from_entries_strict(["gzip", " "]),
Err(ParseError::NonConformant(w))
);
}
#[test]
fn params_without_coding_skip_the_entry() {
let (ae, seen) = testing::lenient::<SipAcceptEncoding>(" ;q=1");
assert!(ae.is_empty());
assert_eq!(
seen,
[(
Field::Coding,
WarningCode::SkippedEntry,
WarningKind::Lost,
Some(1),
Some(0)
)]
);
}
#[test]
fn from_entries_empty_is_empty_list() {
assert!(SipAcceptEncoding::from_entries(std::iter::empty::<&str>())
.unwrap()
.is_empty());
assert!(SipAcceptEncoding::from_entries(["", " "])
.unwrap()
.is_empty());
}
fn seen(raw: &str) -> Vec<Seen> {
testing::seen::<SipAcceptEncoding>(raw)
}
fn assert_strict_refuses(raw: &str) {
let parsed = SipAcceptEncoding::parse_with_warnings(raw).unwrap();
assert_eq!(parsed.value, SipAcceptEncoding::parse(raw).unwrap());
assert_eq!(
SipAcceptEncoding::parse_strict(raw),
Err(ParseError::NonConformant(parsed.warnings[0]))
);
}
#[test]
fn invalid_coding_token_is_warned() {
let raw = "gzip, gz/ip";
assert_eq!(
SipAcceptEncoding::parse(raw)
.unwrap()
.entries()[1]
.encoding(),
"gz/ip"
);
assert_eq!(
seen(raw),
vec![(
Field::Coding,
WarningCode::InvalidToken,
WarningKind::Recovered,
raw.find("gz/"),
Some(1)
)]
);
assert_strict_refuses(raw);
}
#[test]
fn invalid_qvalue_is_warned() {
let raw = "gzip;q=0.1234";
assert_eq!(
SipAcceptEncoding::parse(raw)
.unwrap()
.entries()[0]
.param("q"),
Some(Some("0.1234"))
);
assert_eq!(
seen(raw),
vec![(
Field::Qvalue,
WarningCode::InvalidQvalue,
WarningKind::Recovered,
Some("gzip;q=".len()),
Some(0)
)]
);
assert_strict_refuses(raw);
}
#[test]
fn unterminated_param_quote_is_warned() {
let raw = r#"gzip;x="a"#;
assert_eq!(
seen(raw),
vec![(
Field::Param,
WarningCode::UnterminatedQuote,
WarningKind::Recovered,
raw.find('"'),
Some(0)
)]
);
assert_strict_refuses(raw);
}
#[test]
fn wildcard_is_conformant() {
assert!(seen("*;q=0, identity;q=1.0").is_empty());
}
#[test]
fn warnings_api_on_conformant_input() {
let raw = "gzip;q=0.8, identity";
let parsed = SipAcceptEncoding::parse_with_warnings(raw).unwrap();
assert!(!parsed.has_warnings());
assert_eq!(SipAcceptEncoding::parse_strict(raw), Ok(parsed.value));
}
}