cookie-rs 0.5.0

library for working with HTTP cookies
Documentation
use cookie_rs::error::*;
use cookie_rs::prelude::*;

#[test]
fn simple_cookie() {
    let expected = Cookie::new("name", "value");
    let input = "name=value";

    assert_eq!(Cookie::parse(input), Ok(expected))
}

#[test]
fn empty_name() {
    let expected = Err(ParseError::EmptyName);
    let input = "=value";

    assert_eq!(Cookie::parse(input), expected)
}

#[test]
fn empty_value() {
    let expected = Cookie::new("key", "");
    let input = "key=";

    assert_eq!(Cookie::parse(input), Ok(expected))
}

#[test]
fn empty_input() {
    let expected = Err(ParseError::MissingPair(MissingPair::NameValue));
    let input = "";

    assert_eq!(Cookie::parse(input), expected)
}

#[test]
fn cookie_with_domain() {
    let expected = Cookie::builder("name", "value")
        .domain("example.com")
        .build();
    let input = "name=value; Domain=example.com";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_path() {
    let expected = Cookie::builder("name", "value")
        .path("/path/to/resource")
        .build();
    let input = "name=value; Path=/path/to/resource";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_secure() {
    let expected = Cookie::builder("name", "value").secure(true).build();
    let input = "name=value; Secure";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_httponly() {
    let expected = Cookie::builder("name", "value").http_only(true).build();
    let input = "name=value; HttpOnly";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_max_age() {
    let expected = Cookie::builder("name", "value")
        .max_age(std::time::Duration::from_secs(3600))
        .build();
    let input = "name=value; Max-Age=3600";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_samesite_strict() {
    let expected = Cookie::builder("name", "value")
        .same_site(SameSite::Strict)
        .build();
    let input = "name=value; SameSite=Strict";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_samesite_lax() {
    let expected = Cookie::builder("name", "value")
        .same_site(SameSite::Lax)
        .build();
    let input = "name=value; SameSite=Lax";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_unknown_attribute_strict() {
    let expected = Err(ParseError::UnknownAttribute("UnknownAttr".to_string()));
    let input = "name=value; UnknownAttr";

    assert_eq!(Cookie::parse_strict(input), expected);
}

#[test]
fn cookie_with_unknown_attribute_non_strict() {
    let expected = Cookie::builder("name", "value").build();
    let input = "name=value; UnknownAttr";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_multiple_attributes() {
    let expected = Cookie::builder("name", "value")
        .domain("example.com")
        .path("/path")
        .secure(true)
        .http_only(true)
        .same_site(SameSite::None)
        .build();
    let input = "name=value; Domain=example.com; Path=/path; Secure; HttpOnly; SameSite=None";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn malformed_cookie_missing_equals() {
    let expected = Err(ParseError::MissingPair(MissingPair::NameValue));
    let input = "namevalue";

    assert_eq!(Cookie::parse(input), expected);
}

#[test]
fn cookie_with_expires() {
    let expected = Cookie::builder("name", "value")
        .expires("Wed, 21 Oct 2025 07:28:00 GMT")
        .build();
    let input = "name=value; Expires=Wed, 21 Oct 2025 07:28:00 GMT";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_partitioned() {
    let expected = Cookie::builder("name", "value").partitioned(true).build();
    let input = "name=value; Partitioned";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_empty_attribute_value() {
    let expected = Cookie::builder("name", "value").path("").build();
    let input = "name=value; Path=";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_case_insensitive_attributes() {
    let expected = Cookie::builder("name", "value")
        .secure(true)
        .http_only(true)
        .same_site(SameSite::Lax)
        .build();
    let input = "name=value; secure; httponly; samesite=Lax";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_unexpected_whitespace() {
    let expected = Cookie::builder("name", "value")
        .domain("example.com")
        .path("/")
        .secure(true)
        .http_only(true)
        .build();
    let input = " name = value ; Domain = example.com ; Path = / ; Secure ; HttpOnly ";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_empty_pair() {
    let expected = Err(ParseError::MissingPair(MissingPair::NameValue));
    let input = ";";

    assert_eq!(Cookie::parse(input), expected);
}

#[test]
fn cookie_with_duplicate_attributes() {
    let expected = Cookie::builder("name", "value").path("/second").build();
    let input = "name=value; Path=/first; Path=/second";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_invalid_max_age() {
    let expected = Err(ParseError::ParseMaxAgeError(
        "invalid".parse::<i64>().unwrap_err(),
    ));
    let input = "name=value; Max-Age=invalid";

    assert_eq!(Cookie::parse(input), expected);
}

#[test]
fn cookie_with_negative_max_age() {
    let expected = Cookie::builder("name", "value")
        .max_age(std::time::Duration::ZERO)
        .build();
    let input = "name=value; Max-Age=-1";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_zero_max_age() {
    let expected = Cookie::builder("name", "value")
        .max_age(std::time::Duration::ZERO)
        .build();
    let input = "name=value; Max-Age=0";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_large_negative_max_age() {
    let expected = Cookie::builder("name", "value")
        .max_age(std::time::Duration::ZERO)
        .build();
    let input = "name=value; Max-Age=-99999";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_invalid_samesite_value() {
    let expected = Err(ParseError::ParseSameSiteError(
        ParseSameSiteError::UnknownValue("InvalidValue".to_string()),
    ));
    let input = "name=value; SameSite=InvalidValue";

    assert_eq!(Cookie::parse(input), expected);
}

#[test]
fn cookie_with_trailing_semicolon() {
    let expected = Cookie::builder("name", "value").build();
    let input = "name=value;";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_with_invalid_format() {
    let expected = Err(ParseError::MissingPair(MissingPair::NameValue));
    let input = "name-value";

    assert_eq!(Cookie::parse(input), expected);
}

#[test]
fn cookie_with_non_ascii_name() {
    let expected = Cookie::builder("имя", "значение").build();
    let input = "имя=значение";

    assert_eq!(Cookie::parse(input), Ok(expected));
}

#[test]
fn cookie_into_owned_preserves_fields() {
    let input = String::from("session=abc123; Path=/; Domain=example.com; Secure");
    let cookie: Cookie<'static> = Cookie::parse(input).unwrap().into_owned();

    assert_eq!(cookie.name(), "session");
    assert_eq!(cookie.value(), "abc123");
    assert_eq!(cookie.path(), Some("/"));
    assert_eq!(cookie.domain(), Some("example.com"));
    assert_eq!(cookie.secure(), Some(true));
}

#[test]
fn cookie_into_owned_from_borrowed() {
    let cookie: Cookie<'static> = Cookie::new("name", "value").into_owned();

    assert_eq!(cookie.name(), "name");
    assert_eq!(cookie.value(), "value");
}

#[test]
fn cookie_into_owned_with_all_attributes() {
    let input =
        String::from("name=value; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict");
    let cookie: Cookie<'static> = Cookie::parse(input).unwrap().into_owned();

    assert_eq!(cookie.domain(), Some("example.com"));
    assert_eq!(cookie.path(), Some("/"));
    assert_eq!(cookie.secure(), Some(true));
    assert_eq!(cookie.http_only(), Some(true));
    assert_eq!(cookie.same_site(), Some(SameSite::Strict));
}

#[cfg(feature = "percent-encoding")]
#[test]
fn cookie_value_decoded_on_parse() {
    let cookie = Cookie::parse("data=hello%20world").unwrap();
    assert_eq!(cookie.value(), "hello world");
}

#[cfg(feature = "percent-encoding")]
#[test]
fn cookie_value_decoded_utf8() {
    let cookie = Cookie::parse("data=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82").unwrap();
    assert_eq!(cookie.value(), "привет");
}

#[cfg(feature = "percent-encoding")]
#[test]
fn cookie_value_invalid_encoding_passthrough() {
    let cookie = Cookie::parse("data=%ZZ").unwrap();
    assert_eq!(cookie.value(), "%ZZ");
}

#[cfg(feature = "percent-encoding")]
#[test]
fn cookie_value_percent_sign_roundtrip() {
    let cookie = Cookie::new("data", "50% off");
    let parsed = Cookie::parse(cookie.to_string()).unwrap();
    assert_eq!(parsed.value(), "50% off");
}