use std::borrow::Cow;
use crate::attributes::CookieAttributes;
use crate::encoding::{ValueEncoding, encode_value};
use crate::set_cookie::SetCookie;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Cookie<'a> {
name: &'a str,
value: Cow<'a, str>,
encoding: ValueEncoding,
}
impl<'a> Cookie<'a> {
pub fn new(name: &'a str, value: impl Into<Cow<'a, str>>) -> Self {
Self {
name,
value: value.into(),
encoding: ValueEncoding::default(),
}
}
pub fn with_encoding(mut self, encoding: ValueEncoding) -> Self {
self.encoding = encoding;
self
}
pub const fn name(&self) -> &str {
self.name
}
pub fn value(&self) -> &str {
&self.value
}
pub fn into_value(self) -> Cow<'a, str> {
self.value
}
pub const fn encoding(&self) -> ValueEncoding {
self.encoding
}
pub fn to_request_pair(&self) -> String {
self.to_pair(self.encoding)
}
pub fn to_pair(&self, encoding: ValueEncoding) -> String {
let mut out = String::new();
self.write_pair_into(&mut out, encoding);
out
}
pub(crate) fn write_pair_into(&self, out: &mut String, encoding: ValueEncoding) {
let value = encode_value(&self.value, encoding);
out.reserve(self.name.len() + 1 + value.len());
out.push_str(self.name);
out.push('=');
out.push_str(&value);
}
pub fn into_set_cookie(self) -> SetCookie<'a> {
self.with_attributes(CookieAttributes::default())
}
pub fn with_attributes(self, attributes: CookieAttributes<'a>) -> SetCookie<'a> {
SetCookie::from_parts(self, attributes)
}
}
impl<'a> From<Cookie<'a>> for SetCookie<'a> {
fn from(cookie: Cookie<'a>) -> Self {
cookie.into_set_cookie()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_is_a_bare_pair_with_the_default_encoding() {
let c = Cookie::new("SID", "deadbeef");
assert_eq!(c.name(), "SID");
assert_eq!(c.value(), "deadbeef");
assert_eq!(c.encoding(), ValueEncoding::default());
}
#[test]
fn value_borrows_when_clean() {
assert!(matches!(
Cookie::new("n", "deadbeef").into_value(),
Cow::Borrowed(_)
));
}
#[test]
fn to_request_pair_escapes_per_encoding() {
assert_eq!(Cookie::new("pref", "a b").to_request_pair(), "pref=a%20b");
assert_eq!(
Cookie::new("pref", "a b")
.with_encoding(ValueEncoding::Auto)
.to_request_pair(),
"pref=\"a b\""
);
}
#[test]
fn to_pair_honors_the_passed_encoding_over_the_stored_one() {
let c = Cookie::new("pref", "a b");
assert_eq!(c.to_pair(ValueEncoding::Percent), "pref=a%20b");
assert_eq!(c.to_pair(ValueEncoding::Auto), "pref=\"a b\"");
assert_eq!(c.to_request_pair(), c.to_pair(ValueEncoding::Percent));
}
#[test]
fn into_set_cookie_starts_bare() {
let sc = Cookie::new("n", "v").into_set_cookie();
assert_eq!(sc.name(), "n");
assert_eq!(sc.value(), "v");
assert_eq!(*sc.attributes(), crate::CookieAttributes::default());
assert!(!sc.attributes().http_only);
assert!(!sc.attributes().secure);
assert!(!sc.attributes().partitioned);
assert_eq!(sc.attributes().same_site, None);
assert_eq!(sc.attributes().path, None);
assert_eq!(sc.attributes().domain, None);
assert_eq!(sc.attributes().max_age, None);
assert_eq!(
SetCookie::from(Cookie::new("n", "v")).to_set_cookie(),
"n=v"
);
}
#[test]
fn completion_then_demotion_round_trips_the_kernel() {
let c = Cookie::new("n", "v").with_encoding(ValueEncoding::Percent);
assert_eq!(c.clone().into_set_cookie().into_cookie(), c);
}
#[test]
fn completion_is_value_identity_not_wire_identity() {
let sc = Cookie::new("n", "a b").into_set_cookie();
assert_eq!(sc.value(), "a b");
assert_eq!(sc.to_set_cookie(), "n=a%20b");
}
}