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}