#![no_std]
#![forbid(unsafe_code)]
#![doc(html_root_url = "https://docs.rs/negotiator/0.1.0")]
#![allow(
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
mod charset;
mod common;
mod encoding;
mod language;
mod media_type;
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
#[must_use]
pub fn preferred_media_types(accept: Option<&str>, provided: Option<&[&str]>) -> Vec<String> {
media_type::preferred_media_types(accept, provided)
}
#[must_use]
pub fn preferred_charsets(accept: Option<&str>, provided: Option<&[&str]>) -> Vec<String> {
charset::preferred_charsets(accept, provided)
}
#[must_use]
pub fn preferred_encodings(
accept: Option<&str>,
provided: Option<&[&str]>,
preferred: Option<&[&str]>,
) -> Vec<String> {
encoding::preferred_encodings(accept, provided, preferred)
}
#[must_use]
pub fn preferred_languages(accept: Option<&str>, provided: Option<&[&str]>) -> Vec<String> {
language::preferred_languages(accept, provided)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Negotiator<'a> {
pub accept: Option<&'a str>,
pub accept_charset: Option<&'a str>,
pub accept_encoding: Option<&'a str>,
pub accept_language: Option<&'a str>,
}
impl<'a> Negotiator<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn accept(mut self, value: &'a str) -> Self {
self.accept = Some(value);
self
}
#[must_use]
pub fn accept_charset(mut self, value: &'a str) -> Self {
self.accept_charset = Some(value);
self
}
#[must_use]
pub fn accept_encoding(mut self, value: &'a str) -> Self {
self.accept_encoding = Some(value);
self
}
#[must_use]
pub fn accept_language(mut self, value: &'a str) -> Self {
self.accept_language = Some(value);
self
}
#[must_use]
pub fn media_types(&self, available: Option<&[&str]>) -> Vec<String> {
media_type::preferred_media_types(self.accept, available)
}
#[must_use]
pub fn media_type(&self, available: Option<&[&str]>) -> Option<String> {
self.media_types(available).into_iter().next()
}
#[must_use]
pub fn charsets(&self, available: Option<&[&str]>) -> Vec<String> {
charset::preferred_charsets(self.accept_charset, available)
}
#[must_use]
pub fn charset(&self, available: Option<&[&str]>) -> Option<String> {
self.charsets(available).into_iter().next()
}
#[must_use]
pub fn encodings(&self, available: Option<&[&str]>, preferred: Option<&[&str]>) -> Vec<String> {
encoding::preferred_encodings(self.accept_encoding, available, preferred)
}
#[must_use]
pub fn encoding(
&self,
available: Option<&[&str]>,
preferred: Option<&[&str]>,
) -> Option<String> {
self.encodings(available, preferred).into_iter().next()
}
#[must_use]
pub fn languages(&self, available: Option<&[&str]>) -> Vec<String> {
language::preferred_languages(self.accept_language, available)
}
#[must_use]
pub fn language(&self, available: Option<&[&str]>) -> Option<String> {
self.languages(available).into_iter().next()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn media_type_basic() {
assert_eq!(
preferred_media_types(Some("text/html, application/json;q=0.9"), None),
vec!["text/html", "application/json"]
);
assert_eq!(
preferred_media_types(
Some("text/*;q=0.5, text/html"),
Some(&["text/html", "text/plain"])
),
vec!["text/html", "text/plain"]
);
}
#[test]
fn media_type_wildcards_and_quality() {
let n = preferred_media_types(
Some("*/*;q=0.1, application/json;q=0.9, text/html"),
Some(&["image/png", "application/json", "text/html"]),
);
assert_eq!(n, vec!["text/html", "application/json", "image/png"]);
}
#[test]
fn charset_default_is_star() {
assert_eq!(
preferred_charsets(None, Some(&["utf-8", "iso-8859-1"])),
vec!["utf-8", "iso-8859-1"]
);
assert_eq!(
preferred_charsets(
Some("utf-8;q=0, iso-8859-1"),
Some(&["utf-8", "iso-8859-1"])
),
vec!["iso-8859-1"]
);
}
#[test]
fn encoding_injects_identity() {
assert_eq!(
preferred_encodings(Some("gzip"), Some(&["identity", "gzip"]), None),
vec!["gzip", "identity"]
);
assert_eq!(
preferred_encodings(Some("gzip"), Some(&["identity"]), None),
vec!["identity"]
);
}
#[test]
fn encoding_preferred_order_breaks_ties() {
assert_eq!(
preferred_encodings(
Some("gzip, br"),
Some(&["gzip", "br"]),
Some(&["br", "gzip"])
),
vec!["br", "gzip"]
);
}
#[test]
fn language_prefix_matching() {
assert_eq!(
preferred_languages(Some("en-US, en;q=0.8"), Some(&["en", "en-US", "fr"])),
vec!["en-US", "en"]
);
assert_eq!(
preferred_languages(Some("en"), Some(&["en-US"])),
vec!["en-US"]
);
}
#[test]
fn negotiator_struct() {
let n = Negotiator::new().accept("text/html").accept_language("fr");
assert_eq!(
n.media_type(Some(&["text/html", "text/plain"])).as_deref(),
Some("text/html")
);
assert_eq!(n.language(Some(&["en", "fr"])).as_deref(), Some("fr"));
assert_eq!(n.charset(Some(&["utf-8"])).as_deref(), Some("utf-8")); }
#[test]
fn empty_header_accepts_nothing() {
assert!(preferred_media_types(Some(""), Some(&["text/html"])).is_empty());
assert!(preferred_charsets(Some(""), Some(&["utf-8"])).is_empty());
}
}