use serde::{Deserialize, Serialize};
use crate::Cookie;
use crate::SameSite;
use crate::bidi::BiDi;
use crate::bidi::command::BidiCommand;
use crate::bidi::error::BidiError;
use crate::common::cookie::bidi::{bytes_string, same_site, string_from_bytes_value};
pub type PartitionDescriptor = serde_json::Value;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireCookie {
name: String,
value: serde_json::Value,
domain: String,
path: String,
http_only: bool,
secure: bool,
#[serde(with = "same_site")]
same_site: SameSite,
#[serde(default)]
#[allow(dead_code)] size: Option<u32>,
#[serde(default)]
expiry: Option<i64>,
}
impl From<WireCookie> for Cookie {
fn from(w: WireCookie) -> Self {
Cookie {
name: w.name,
value: string_from_bytes_value(&w.value),
path: Some(w.path),
domain: Some(w.domain),
secure: Some(w.secure),
http_only: Some(w.http_only),
expiry: w.expiry,
same_site: Some(w.same_site),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct WirePartialCookie {
name: String,
value: serde_json::Value,
domain: String,
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
http_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
secure: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", with = "same_site::option")]
same_site: Option<SameSite>,
#[serde(skip_serializing_if = "Option::is_none")]
expiry: Option<i64>,
}
impl WirePartialCookie {
fn from_cookie(c: Cookie) -> Result<Self, BidiError> {
let domain = c.domain.ok_or_else(|| BidiError {
command: "storage.setCookie".to_string(),
error: "invalid argument".to_string(),
message: "BiDi storage.setCookie requires a `domain` on the cookie".to_string(),
stacktrace: None,
})?;
Ok(Self {
name: c.name,
value: bytes_string(c.value),
domain,
path: c.path,
http_only: c.http_only,
secure: c.secure,
same_site: c.same_site,
expiry: c.expiry,
})
}
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CookieFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secure: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", with = "same_site::option")]
pub same_site: Option<SameSite>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expiry: Option<i64>,
}
impl CookieFilter {
fn by_name(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
..Self::default()
}
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct GetCookies {
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<CookieFilter>,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<PartitionDescriptor>,
}
impl BidiCommand for GetCookies {
const METHOD: &'static str = "storage.getCookies";
type Returns = WireGetCookiesResult;
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
#[doc(hidden)]
pub struct WireGetCookiesResult {
cookies: Vec<WireCookie>,
#[serde(default)]
partition_key: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct GetCookiesResult {
pub cookies: Vec<Cookie>,
pub partition_key: serde_json::Value,
}
impl From<WireGetCookiesResult> for GetCookiesResult {
fn from(w: WireGetCookiesResult) -> Self {
Self {
cookies: w.cookies.into_iter().map(Cookie::from).collect(),
partition_key: w.partition_key,
}
}
}
#[derive(Debug, Clone, Serialize)]
struct SetCookie {
cookie: WirePartialCookie,
#[serde(skip_serializing_if = "Option::is_none")]
partition: Option<PartitionDescriptor>,
}
impl BidiCommand for SetCookie {
const METHOD: &'static str = "storage.setCookie";
type Returns = SetCookieResult;
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetCookieResult {
#[serde(default)]
pub partition_key: serde_json::Value,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct DeleteCookies {
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<CookieFilter>,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<PartitionDescriptor>,
}
impl BidiCommand for DeleteCookies {
const METHOD: &'static str = "storage.deleteCookies";
type Returns = DeleteCookiesResult;
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeleteCookiesResult {
#[serde(default)]
pub partition_key: serde_json::Value,
}
#[derive(Debug)]
pub struct StorageModule<'a> {
bidi: &'a BiDi,
}
impl<'a> StorageModule<'a> {
pub(crate) fn new(bidi: &'a BiDi) -> Self {
Self {
bidi,
}
}
pub async fn get_cookies(&self) -> Result<GetCookiesResult, BidiError> {
self.get_cookies_filtered(None).await
}
pub async fn get_cookies_by_name(
&self,
name: impl Into<String>,
) -> Result<GetCookiesResult, BidiError> {
self.get_cookies_filtered(Some(CookieFilter::by_name(name))).await
}
pub async fn get_cookies_filtered(
&self,
filter: Option<CookieFilter>,
) -> Result<GetCookiesResult, BidiError> {
let raw = self
.bidi
.send(GetCookies {
filter,
partition: None,
})
.await?;
Ok(raw.into())
}
pub async fn set_cookie(&self, cookie: Cookie) -> Result<SetCookieResult, BidiError> {
let cookie = WirePartialCookie::from_cookie(cookie)?;
self.bidi
.send(SetCookie {
cookie,
partition: None,
})
.await
}
pub async fn delete_cookies_by_name(&self, name: impl Into<String>) -> Result<(), BidiError> {
self.delete_cookies_filtered(Some(CookieFilter::by_name(name))).await
}
pub async fn delete_all_cookies(&self) -> Result<(), BidiError> {
self.delete_cookies_filtered(None).await
}
pub async fn delete_cookies_filtered(
&self,
filter: Option<CookieFilter>,
) -> Result<(), BidiError> {
self.bidi
.send(DeleteCookies {
filter,
partition: None,
})
.await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_cookie_without_domain_errors_locally() {
let err = WirePartialCookie::from_cookie(Cookie::new("k", "v")).unwrap_err();
assert_eq!(err.command, "storage.setCookie");
assert!(err.message.contains("domain"));
}
}