cookie_rs/
cookie.rs

1use std::borrow::{Borrow, Cow};
2use std::fmt;
3use std::time::Duration;
4
5pub use self::builder::CookieBuilder;
6use crate::StringPrison;
7
8pub mod builder;
9pub mod parse;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum SameSite {
13    Strict,
14    Lax,
15    None,
16}
17
18/// Represents an HTTP cookie, including attributes such as domain, path, and expiration.
19#[derive(Debug, Clone)]
20pub struct Cookie<'a> {
21    prison: Option<StringPrison<'a>>,
22    name: Cow<'a, str>,
23    value: Cow<'a, str>,
24    domain: Option<Cow<'a, str>>,
25    expires: Option<Cow<'a, str>>,
26    http_only: Option<bool>,
27    max_age: Option<Duration>,
28    partitioned: Option<bool>,
29    path: Option<Cow<'a, str>>,
30    same_site: Option<SameSite>,
31    secure: Option<bool>,
32}
33
34impl<'a> Cookie<'a> {
35    /// Creates a new `Cookie` with the specified name and value.
36    ///
37    /// # Arguments
38    /// - `name`: The name of the cookie.
39    /// - `value`: The value of the cookie.
40    ///
41    /// # Example
42    /// ```
43    /// use cookie_rs::prelude::*;
44    ///
45    /// let cookie = Cookie::new("session", "abc123");
46    /// assert_eq!(cookie.name(), "session");
47    /// assert_eq!(cookie.value(), "abc123");
48    /// ```
49    pub fn new<N, V>(name: N, value: V) -> Self
50    where
51        N: Into<Cow<'a, str>>,
52        V: Into<Cow<'a, str>>,
53    {
54        Self {
55            name: name.into(),
56            value: value.into(),
57            ..Default::default()
58        }
59    }
60
61    /// Creates a `CookieBuilder` for constructing a `Cookie` with additional attributes.
62    ///
63    /// # Arguments
64    /// - `name`: The name of the cookie.
65    /// - `value`: The value of the cookie.
66    ///
67    /// # Example
68    /// ```
69    /// use cookie_rs::prelude::*;
70    ///
71    /// let cookie = Cookie::builder("session", "abc123")
72    ///     .domain("example.com")
73    ///     .secure(true)
74    ///     .build();
75    /// ```
76    pub fn builder<N, V>(name: N, value: V) -> CookieBuilder<'a>
77    where
78        N: Into<Cow<'a, str>>,
79        V: Into<Cow<'a, str>>,
80    {
81        CookieBuilder::new(name, value)
82    }
83
84    /// Sets the domain for the cookie.
85    ///
86    /// # Arguments
87    /// - `domain`: The domain attribute of the cookie.
88    ///
89    /// # Example
90    /// ```
91    /// use cookie_rs::prelude::*;
92    ///
93    /// let mut cookie = Cookie::new("session", "abc123");
94    /// cookie.set_domain("example.com");
95    /// assert_eq!(cookie.domain(), Some("example.com"));
96    /// ```
97    pub fn set_domain<V: Into<Cow<'a, str>>>(&mut self, domain: V) {
98        self.domain = Some(domain.into())
99    }
100
101    /// Sets the expiration date for the cookie.
102    ///
103    /// # Arguments
104    /// - `expires`: The expiration date of the cookie.
105    ///
106    /// # Example
107    /// ```
108    /// use cookie_rs::prelude::*;
109    ///
110    /// let mut cookie = Cookie::new("session", "abc123");
111    /// cookie.set_expires("Wed, 21 Oct 2025 07:28:00 GMT");
112    /// assert_eq!(cookie.expires(), Some("Wed, 21 Oct 2025 07:28:00 GMT"));
113    /// ```
114    pub fn set_expires<V: Into<Cow<'a, str>>>(&mut self, expires: V) {
115        self.expires = Some(expires.into());
116    }
117
118    /// Sets the `HttpOnly` attribute for the cookie.
119    ///
120    /// # Arguments
121    /// - `http_only`: Whether the cookie is HttpOnly.
122    ///
123    /// # Example
124    /// ```
125    /// use cookie_rs::prelude::*;
126    ///
127    /// let mut cookie = Cookie::new("session", "abc123");
128    /// cookie.set_http_only(true);
129    /// assert_eq!(cookie.http_only(), Some(true));
130    /// ```
131    pub fn set_http_only(&mut self, http_only: bool) {
132        self.http_only = Some(http_only);
133    }
134
135    /// Sets the maximum age for the cookie.
136    ///
137    /// # Arguments
138    /// - `max_age`: The maximum age of the cookie as a `Duration`.
139    ///
140    /// # Example
141    /// ```
142    /// use std::time::Duration;
143    /// use cookie_rs::prelude::*;
144    ///
145    /// let mut cookie = Cookie::new("session", "abc123");
146    /// cookie.set_max_age(Duration::from_secs(3600));
147    /// assert_eq!(cookie.max_age(), Some(Duration::from_secs(3600)));
148    /// ```
149    pub fn set_max_age<V: Into<Duration>>(&mut self, max_age: V) {
150        self.max_age = Some(max_age.into());
151    }
152
153    /// Sets the partitioned attribute for the cookie.
154    ///
155    /// # Arguments
156    /// - `partitioned`: Whether the cookie is partitioned.
157    ///
158    /// # Example
159    /// ```
160    /// use cookie_rs::prelude::*;
161    ///
162    /// let mut cookie = Cookie::new("session", "abc123");
163    /// cookie.set_partitioned(true);
164    /// assert_eq!(cookie.partitioned(), Some(true));
165    /// ```
166    pub fn set_partitioned(&mut self, partitioned: bool) {
167        self.partitioned = Some(partitioned);
168    }
169
170    /// Sets the path attribute for the cookie.
171    ///
172    /// # Arguments
173    /// - `path`: The path attribute of the cookie.
174    ///
175    /// # Example
176    /// ```
177    /// use cookie_rs::prelude::*;
178    ///
179    /// let mut cookie = Cookie::new("session", "abc123");
180    /// cookie.set_path("/");
181    /// assert_eq!(cookie.path(), Some("/"));
182    /// ```
183    pub fn set_path<V: Into<Cow<'a, str>>>(&mut self, path: V) {
184        self.path = Some(path.into());
185    }
186
187    /// Sets the `SameSite` attribute for the cookie.
188    ///
189    /// # Arguments
190    /// - `same_site`: The `SameSite` attribute for the cookie.
191    ///
192    /// # Example
193    /// ```
194    /// use cookie_rs::prelude::*;
195    ///
196    /// let mut cookie = Cookie::new("session", "abc123");
197    /// cookie.set_same_site(SameSite::Lax);
198    /// assert_eq!(cookie.same_site(), Some(SameSite::Lax));
199    /// ```
200    pub fn set_same_site(&mut self, same_site: SameSite) {
201        self.same_site = Some(same_site);
202    }
203
204    /// Sets the `Secure` attribute for the cookie.
205    ///
206    /// # Arguments
207    /// - `secure`: Whether the cookie is secure.
208    ///
209    /// # Example
210    /// ```
211    /// use cookie_rs::prelude::*;
212    ///
213    /// let mut cookie = Cookie::new("session", "abc123");
214    /// cookie.set_secure(true);
215    /// assert_eq!(cookie.secure(), Some(true));
216    /// ```
217    pub fn set_secure(&mut self, secure: bool) {
218        self.secure = Some(secure);
219    }
220
221    /// Sets the domain for the cookie.
222    ///
223    /// # Arguments
224    /// - `domain`: The domain attribute of the cookie.
225    ///
226    /// # Example
227    /// ```
228    /// use cookie_rs::prelude::*;
229    ///
230    /// let cookie = Cookie::new("session", "abc123").with_domain("example.com");
231    ///
232    /// assert_eq!(cookie.domain(), Some("example.com"));
233    /// ```
234    pub fn with_domain<V: Into<Cow<'a, str>>>(mut self, domain: V) -> Self {
235        self.set_domain(domain);
236
237        self
238    }
239
240    /// Sets the expiration date for the cookie.
241    ///
242    /// # Arguments
243    /// - `expires`: The expiration date of the cookie.
244    ///
245    /// # Example
246    /// ```
247    /// use cookie_rs::prelude::*;
248    ///
249    /// let cookie = Cookie::new("session", "abc123").with_expires("Wed, 21 Oct 2025 07:28:00 GMT");
250    ///
251    /// assert_eq!(cookie.expires(), Some("Wed, 21 Oct 2025 07:28:00 GMT"));
252    /// ```
253    pub fn with_expires<V: Into<Cow<'a, str>>>(mut self, expires: V) -> Self {
254        self.set_expires(expires);
255
256        self
257    }
258
259    /// Sets the `HttpOnly` attribute for the cookie.
260    ///
261    /// # Arguments
262    /// - `http_only`: Whether the cookie is HttpOnly.
263    ///
264    /// # Example
265    /// ```
266    /// use cookie_rs::prelude::*;
267    ///
268    /// let cookie = Cookie::new("session", "abc123").with_http_only(true);
269    ///
270    /// assert_eq!(cookie.http_only(), Some(true));
271    /// ```
272    pub fn with_http_only(mut self, http_only: bool) -> Self {
273        self.set_http_only(http_only);
274
275        self
276    }
277
278    /// Sets the maximum age for the cookie.
279    ///
280    /// # Arguments
281    /// - `max_age`: The maximum age of the cookie as a `Duration`.
282    ///
283    /// # Example
284    /// ```
285    /// use std::time::Duration;
286    /// use cookie_rs::prelude::*;
287    ///
288    /// let cookie = Cookie::new("session", "abc123").with_max_age(Duration::from_secs(3600));
289    ///
290    /// assert_eq!(cookie.max_age(), Some(Duration::from_secs(3600)));
291    /// ```
292    pub fn with_max_age<V: Into<Duration>>(mut self, max_age: V) -> Self {
293        self.set_max_age(max_age);
294
295        self
296    }
297
298    /// Sets the partitioned attribute for the cookie.
299    ///
300    /// # Arguments
301    /// - `partitioned`: Whether the cookie is partitioned.
302    ///
303    /// # Example
304    /// ```
305    /// use cookie_rs::prelude::*;
306    ///
307    /// let cookie = Cookie::new("session", "abc123").with_partitioned(true);
308    ///
309    /// assert_eq!(cookie.partitioned(), Some(true));
310    /// ```
311    pub fn with_partitioned(mut self, partitioned: bool) -> Self {
312        self.set_partitioned(partitioned);
313
314        self
315    }
316
317    /// Sets the `Secure` attribute for the cookie.
318    ///
319    /// # Arguments
320    /// - `secure`: Whether the cookie is secure.
321    ///
322    /// # Example
323    /// ```
324    /// use cookie_rs::prelude::*;
325    ///
326    /// let cookie = Cookie::new("session", "abc123").with_secure(true);
327    ///
328    /// assert_eq!(cookie.secure(), Some(true));
329    /// ```
330    pub fn with_secure(mut self, secure: bool) -> Self {
331        self.set_secure(secure);
332
333        self
334    }
335
336    /// Sets the path attribute for the cookie.
337    ///
338    /// # Arguments
339    /// - `path`: The path attribute of the cookie.
340    ///
341    /// # Example
342    /// ```
343    /// use cookie_rs::prelude::*;
344    ///
345    /// let cookie = Cookie::new("session", "abc123").with_path("/");
346    ///
347    /// assert_eq!(cookie.path(), Some("/"));
348    /// ```
349    pub fn with_path<V: Into<Cow<'a, str>>>(mut self, path: V) -> Self {
350        self.set_path(path);
351
352        self
353    }
354
355    /// Sets the `SameSite` attribute for the cookie.
356    ///
357    /// # Arguments
358    /// - `same_site`: The `SameSite` attribute for the cookie.
359    ///
360    /// # Example
361    /// ```
362    /// use cookie_rs::prelude::*;
363    ///
364    /// let cookie = Cookie::new("session", "abc123").with_same_site(SameSite::Lax);
365    ///
366    /// assert_eq!(cookie.same_site(), Some(SameSite::Lax));
367    /// ```
368    pub fn with_same_site(mut self, same_site: SameSite) -> Self {
369        self.set_same_site(same_site);
370
371        self
372    }
373
374    /// Returns the name of the cookie.
375    ///
376    /// # Example
377    /// ```
378    /// use cookie_rs::prelude::*;
379    ///
380    /// let cookie = Cookie::new("session", "abc123");
381    /// assert_eq!(cookie.name(), "session");
382    /// ```
383    pub fn name(&self) -> &str {
384        self.name.as_ref()
385    }
386
387    /// Returns the value of the cookie.
388    ///
389    /// # Example
390    /// ```
391    /// use cookie_rs::prelude::*;
392    ///
393    /// let cookie = Cookie::new("session", "abc123");
394    /// assert_eq!(cookie.value(), "abc123");
395    /// ```
396    pub fn value(&self) -> &str {
397        self.value.as_ref()
398    }
399
400    /// Returns the domain of the cookie, if set.
401    ///
402    /// # Example
403    /// ```
404    /// use cookie_rs::prelude::*;
405    ///
406    /// let mut cookie = Cookie::new("session", "abc123");
407    /// cookie.set_domain("example.com");
408    /// assert_eq!(cookie.domain(), Some("example.com"));
409    /// ```
410    pub fn domain(&self) -> Option<&str> {
411        self.domain.as_deref()
412    }
413
414    /// Returns the expiration date of the cookie, if set.
415    ///
416    /// # Example
417    /// ```
418    /// use cookie_rs::prelude::*;
419    ///
420    /// let mut cookie = Cookie::new("session", "abc123");
421    /// cookie.set_expires("Wed, 21 Oct 2025 07:28:00 GMT");
422    /// assert_eq!(cookie.expires(), Some("Wed, 21 Oct 2025 07:28:00 GMT"));
423    /// ```
424    pub fn expires(&self) -> Option<&str> {
425        self.expires.as_deref()
426    }
427
428    /// Returns whether the cookie has the `HttpOnly` attribute set.
429    ///
430    /// # Example
431    /// ```
432    /// use cookie_rs::prelude::*;
433    ///
434    /// let mut cookie = Cookie::new("session", "abc123");
435    /// cookie.set_http_only(true);
436    /// assert_eq!(cookie.http_only(), Some(true));
437    /// ```
438    pub fn http_only(&self) -> Option<bool> {
439        self.http_only
440    }
441
442    /// Returns the maximum age of the cookie, if set.
443    ///
444    /// # Example
445    /// ```
446    /// use std::time::Duration;
447    /// use cookie_rs::prelude::*;
448    ///
449    /// let mut cookie = Cookie::new("session", "abc123");
450    /// cookie.set_max_age(Duration::from_secs(3600));
451    /// assert_eq!(cookie.max_age(), Some(Duration::from_secs(3600)));
452    /// ```
453    pub fn max_age(&self) -> Option<Duration> {
454        self.max_age
455    }
456
457    /// Returns whether the cookie is partitioned.
458    ///
459    /// # Example
460    /// ```
461    /// use cookie_rs::prelude::*;
462    ///
463    /// let mut cookie = Cookie::new("session", "abc123");
464    /// cookie.set_partitioned(true);
465    /// assert_eq!(cookie.partitioned(), Some(true));
466    /// ```
467    pub fn partitioned(&self) -> Option<bool> {
468        self.partitioned
469    }
470
471    /// Returns the path of the cookie, if set.
472    ///
473    /// # Example
474    /// ```
475    /// use cookie_rs::prelude::*;
476    ///
477    /// let mut cookie = Cookie::new("session", "abc123");
478    /// cookie.set_path("/");
479    /// assert_eq!(cookie.path(), Some("/"));
480    /// ```
481    pub fn path(&self) -> Option<&str> {
482        self.path.as_deref()
483    }
484
485    /// Returns the `SameSite` attribute of the cookie, if set.
486    ///
487    /// # Example
488    /// ```
489    /// use cookie_rs::prelude::*;
490    ///
491    /// let mut cookie = Cookie::new("session", "abc123");
492    /// cookie.set_same_site(SameSite::Lax);
493    /// assert_eq!(cookie.same_site(), Some(SameSite::Lax));
494    /// ```
495    pub fn same_site(&self) -> Option<SameSite> {
496        self.same_site
497    }
498
499    /// Returns whether the cookie has the `Secure` attribute set.
500    ///
501    /// # Example
502    /// ```
503    /// use cookie_rs::prelude::*;
504    ///
505    /// let mut cookie = Cookie::new("session", "abc123");
506    /// cookie.set_secure(true);
507    /// assert_eq!(cookie.secure(), Some(true));
508    /// ```
509    pub fn secure(&self) -> Option<bool> {
510        self.secure
511    }
512}
513
514impl PartialEq for Cookie<'_> {
515    fn eq(&self, other: &Self) -> bool {
516        match (self.domain.as_ref(), other.domain.as_ref()) {
517            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => (),
518            (None, None) => (),
519            _ => return false,
520        }
521
522        match (self.path.as_ref(), other.path.as_ref()) {
523            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => (),
524            (None, None) => (),
525            _ => return false,
526        }
527
528        self.name == other.name
529            && self.value == other.value
530            && self.expires == other.expires
531            && self.http_only == other.http_only
532            && self.max_age == other.max_age
533            && self.partitioned == other.partitioned
534            && self.same_site == other.same_site
535            && self.secure == other.secure
536    }
537}
538
539impl Eq for Cookie<'_> {}
540
541impl PartialOrd for Cookie<'_> {
542    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
543        Some(self.cmp(other))
544    }
545}
546
547impl Ord for Cookie<'_> {
548    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
549        self.name.cmp(&other.name)
550    }
551}
552
553impl Borrow<str> for Cookie<'_> {
554    fn borrow(&self) -> &str {
555        self.name()
556    }
557}
558
559impl<'a> From<&'a str> for Cookie<'a> {
560    fn from(value: &'a str) -> Self {
561        Cookie::new(value, "")
562    }
563}
564
565impl<'a> From<(&'a str, &'a str)> for Cookie<'a> {
566    fn from(value: (&'a str, &'a str)) -> Self {
567        Cookie::new(value.0, value.1)
568    }
569}
570
571impl std::str::FromStr for Cookie<'_> {
572    type Err = parse::ParseError;
573
574    fn from_str(s: &str) -> Result<Self, Self::Err> {
575        Cookie::parse(s.to_owned())
576    }
577}
578
579impl fmt::Display for Cookie<'_> {
580    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
581        write!(f, "{}={}", self.name, self.value)?;
582
583        if let Some(domain) = self.domain.as_ref() {
584            write!(f, "; Domain={}", domain)?;
585        }
586
587        if let Some(expires) = self.expires.as_ref() {
588            write!(f, "; Expires={}", expires)?;
589        }
590
591        if self.http_only.is_some_and(|v| v) {
592            write!(f, "; HttpOnly")?;
593        }
594
595        if let Some(max_age) = self.max_age.as_ref() {
596            write!(f, "; Max-Age={}", max_age.as_secs())?;
597        }
598
599        if self.partitioned.is_some_and(|v| v) {
600            write!(f, "; Partitioned")?;
601        }
602
603        if let Some(path) = self.path.as_ref() {
604            write!(f, "; Path={}", path)?;
605        }
606
607        if let Some(same_site) = self.same_site {
608            write!(f, "; SameSite={}", same_site)?;
609        }
610
611        if self.secure.is_some_and(|v| v) {
612            write!(f, "; Secure")?;
613        }
614
615        Ok(())
616    }
617}
618
619impl fmt::Display for SameSite {
620    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
621        match self {
622            SameSite::Strict => write!(f, "Strict"),
623            SameSite::Lax => write!(f, "Lax"),
624            SameSite::None => write!(f, "None"),
625        }
626    }
627}
628
629impl Default for Cookie<'_> {
630    fn default() -> Self {
631        Self {
632            prison: None,
633            name: Cow::Borrowed(""),
634            value: Cow::Borrowed(""),
635            domain: None,
636            expires: None,
637            http_only: None,
638            max_age: None,
639            partitioned: None,
640            path: None,
641            same_site: None,
642            secure: None,
643        }
644    }
645}