Skip to main content

cookie_scoop/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum BrowserName {
7    Chrome,
8    Edge,
9    Firefox,
10    Safari,
11}
12
13impl BrowserName {
14    pub fn from_str_loose(s: &str) -> Option<Self> {
15        match s.trim().to_lowercase().as_str() {
16            "chrome" => Some(Self::Chrome),
17            "edge" => Some(Self::Edge),
18            "firefox" => Some(Self::Firefox),
19            "safari" => Some(Self::Safari),
20            _ => None,
21        }
22    }
23}
24
25impl std::fmt::Display for BrowserName {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Self::Chrome => write!(f, "chrome"),
29            Self::Edge => write!(f, "edge"),
30            Self::Firefox => write!(f, "firefox"),
31            Self::Safari => write!(f, "safari"),
32        }
33    }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum CookieSameSite {
38    Strict,
39    Lax,
40    None,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "lowercase")]
45pub enum CookieMode {
46    Merge,
47    First,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct CookieSource {
52    pub browser: BrowserName,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub profile: Option<String>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub origin: Option<String>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub store_id: Option<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Cookie {
63    pub name: String,
64    pub value: String,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub domain: Option<String>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub path: Option<String>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub url: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub expires: Option<i64>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub secure: Option<bool>,
75    #[serde(rename = "httpOnly", skip_serializing_if = "Option::is_none")]
76    pub http_only: Option<bool>,
77    #[serde(rename = "sameSite", skip_serializing_if = "Option::is_none")]
78    pub same_site: Option<CookieSameSite>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub source: Option<CookieSource>,
81}
82
83#[derive(Debug, Clone)]
84pub struct GetCookiesOptions {
85    pub url: String,
86    pub origins: Option<Vec<String>>,
87    pub names: Option<Vec<String>>,
88    pub browsers: Option<Vec<BrowserName>>,
89    pub profile: Option<String>,
90    pub chrome_profile: Option<String>,
91    pub edge_profile: Option<String>,
92    pub firefox_profile: Option<String>,
93    pub safari_cookies_file: Option<String>,
94    pub include_expired: Option<bool>,
95    pub timeout_ms: Option<u64>,
96    pub debug: Option<bool>,
97    pub mode: Option<CookieMode>,
98    pub inline_cookies_file: Option<String>,
99    pub inline_cookies_json: Option<String>,
100    pub inline_cookies_base64: Option<String>,
101}
102
103impl GetCookiesOptions {
104    pub fn new(url: impl Into<String>) -> Self {
105        Self {
106            url: url.into(),
107            origins: None,
108            names: None,
109            browsers: None,
110            profile: None,
111            chrome_profile: None,
112            edge_profile: None,
113            firefox_profile: None,
114            safari_cookies_file: None,
115            include_expired: None,
116            timeout_ms: None,
117            debug: None,
118            mode: None,
119            inline_cookies_file: None,
120            inline_cookies_json: None,
121            inline_cookies_base64: None,
122        }
123    }
124
125    pub fn origins(mut self, origins: Vec<String>) -> Self {
126        self.origins = Some(origins);
127        self
128    }
129
130    pub fn names(mut self, names: Vec<String>) -> Self {
131        self.names = Some(names);
132        self
133    }
134
135    pub fn browsers(mut self, browsers: Vec<BrowserName>) -> Self {
136        self.browsers = Some(browsers);
137        self
138    }
139
140    pub fn chrome_profile(mut self, profile: impl Into<String>) -> Self {
141        self.chrome_profile = Some(profile.into());
142        self
143    }
144
145    pub fn edge_profile(mut self, profile: impl Into<String>) -> Self {
146        self.edge_profile = Some(profile.into());
147        self
148    }
149
150    pub fn firefox_profile(mut self, profile: impl Into<String>) -> Self {
151        self.firefox_profile = Some(profile.into());
152        self
153    }
154
155    pub fn safari_cookies_file(mut self, file: impl Into<String>) -> Self {
156        self.safari_cookies_file = Some(file.into());
157        self
158    }
159
160    pub fn include_expired(mut self, include: bool) -> Self {
161        self.include_expired = Some(include);
162        self
163    }
164
165    pub fn timeout_ms(mut self, ms: u64) -> Self {
166        self.timeout_ms = Some(ms);
167        self
168    }
169
170    pub fn debug(mut self, debug: bool) -> Self {
171        self.debug = Some(debug);
172        self
173    }
174
175    pub fn mode(mut self, mode: CookieMode) -> Self {
176        self.mode = Some(mode);
177        self
178    }
179
180    pub fn inline_cookies_file(mut self, file: impl Into<String>) -> Self {
181        self.inline_cookies_file = Some(file.into());
182        self
183    }
184
185    pub fn inline_cookies_json(mut self, json: impl Into<String>) -> Self {
186        self.inline_cookies_json = Some(json.into());
187        self
188    }
189
190    pub fn inline_cookies_base64(mut self, b64: impl Into<String>) -> Self {
191        self.inline_cookies_base64 = Some(b64.into());
192        self
193    }
194}
195
196#[derive(Debug, Clone, Serialize)]
197pub struct GetCookiesResult {
198    pub cookies: Vec<Cookie>,
199    pub warnings: Vec<String>,
200}
201
202#[derive(Debug, Clone)]
203pub struct CookieHeaderOptions {
204    pub dedupe_by_name: bool,
205    pub sort: CookieHeaderSort,
206}
207
208impl Default for CookieHeaderOptions {
209    fn default() -> Self {
210        Self {
211            dedupe_by_name: false,
212            sort: CookieHeaderSort::Name,
213        }
214    }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum CookieHeaderSort {
219    Name,
220    None,
221}
222
223pub(crate) fn normalize_names(names: &Option<Vec<String>>) -> Option<HashSet<String>> {
224    let names = names.as_ref()?;
225    let cleaned: HashSet<String> = names
226        .iter()
227        .map(|n| n.trim().to_string())
228        .filter(|n| !n.is_empty())
229        .collect();
230    if cleaned.is_empty() {
231        return None;
232    }
233    Some(cleaned)
234}
235
236pub(crate) fn dedupe_cookies(cookies: Vec<Cookie>) -> Vec<Cookie> {
237    let mut seen = HashSet::new();
238    let mut result = Vec::new();
239    for cookie in cookies {
240        let key = format!(
241            "{}|{}|{}",
242            cookie.name,
243            cookie.domain.as_deref().unwrap_or(""),
244            cookie.path.as_deref().unwrap_or("")
245        );
246        if seen.insert(key) {
247            result.push(cookie);
248        }
249    }
250    result
251}