use spin_sdk::http::{Response, ResponseBuilder};
pub enum SameSite {
Strict,
Lax,
None,
}
pub struct Cookie {
name: &'static str,
value: &'static str,
secure: bool,
http_only: bool,
same_site: SameSite,
}
pub trait IntoSameSite {
fn into_same_site(self) -> SameSite;
}
impl IntoSameSite for SameSite {
fn into_same_site(self) -> SameSite {
self
}
}
impl IntoSameSite for &str {
fn into_same_site(self) -> SameSite {
match self.trim().to_lowercase().as_str() {
"strict" => SameSite::Strict,
"lax" => SameSite::Lax,
_ => SameSite::None,
}
}
}
impl Cookie {
pub fn new(
name: &'static str,
value: &'static str,
secure: bool,
http_only: bool,
same_site: impl IntoSameSite,
) -> Self {
Cookie {
name,
value,
secure,
http_only,
same_site: same_site.into_same_site(),
}
}
}
impl ToString for Cookie {
fn to_string(&self) -> String {
let mut value = self.value.to_string();
let mut secure = self.secure;
if self.http_only {
value.push_str("; HttpOnly");
}
match self.same_site {
SameSite::Strict => value.push_str("; SameSite=Strict"),
SameSite::Lax => value.push_str("; SameSite=Lax"),
SameSite::None => {
secure = true;
value.push_str("; SameSite=None");
}
}
if secure {
value.push_str("; Secure");
}
format!("{}={}", self.name, value)
}
}
pub trait CookieResponseBuilder {
fn build_with_cookie(&mut self, cookie: Cookie) -> Response;
}
impl CookieResponseBuilder for ResponseBuilder {
fn build_with_cookie(&mut self, cookie: Cookie) -> Response {
self.header(http::header::SET_COOKIE.as_str(), cookie.to_string());
self.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_cookie_should_add_corresponding_header() {
let name = "a";
let value = "b";
let cookie = Cookie::new(name, value, false, false, SameSite::Strict);
let sut = ResponseBuilder::new(200).build_with_cookie(cookie);
assert_eq!(
sut.header(http::header::SET_COOKIE.as_str()).is_some(),
true
);
}
#[test]
fn add_cookie_should_set_proper_header_value() {
let name = "a";
let value = "b";
let expected = format!("{}={}; SameSite=Strict", name, value);
let cookie = Cookie::new(name, value, false, false, SameSite::Strict);
let sut = ResponseBuilder::new(200).build_with_cookie(cookie);
let actual = sut
.header(http::header::SET_COOKIE.as_str())
.unwrap()
.as_str()
.unwrap();
assert_eq!(actual, &expected);
}
#[test]
fn add_cookie_should_add_secure_flag_when_same_site_is_none() {
let name = "a";
let value = "b";
let expected = format!("{}={}; SameSite=None; Secure", name, value);
let cookie = Cookie::new(name, value, false, false, SameSite::None);
let sut = ResponseBuilder::new(200).build_with_cookie(cookie);
let actual = sut
.header(http::header::SET_COOKIE.as_str())
.unwrap()
.as_str()
.unwrap();
assert_eq!(actual, &expected);
}
}