Skip to content

Commit

Permalink
Implement implicit cookie creation.
Browse files Browse the repository at this point in the history
This commit Implements and leverages new `T: Into<Cookie>` `impl`s to simplify
the creation of and reference to cookies across the crate. Specifically, it adds
implementations of `Into<Cookie>` for string-like types (`&str`, `String`,
`Cow<str>`), tuples of string-like types `(name string, value string)`, and
`CookieBuilder`. The former implementations create a cookie with a name
corresponding to the string and an empty value. The tuple implementation creates
a cookie with the given name and value strings. The `CookieBuilder`
implementation simply unwraps the in-process cookie.

Functions and methods that previously accepted a `Cookie` now accept any `T:
Into<Cookie>`. This particularly affects the `CookieJar` API, which now allows
simpler addition and removal of cookies:

  * `jar.add(("foo", "bar"));`
  * `jar.add(Cookie::build(("foo", "bar")).path("/"));`
  * `jar.remove("foo");`
  * `jar.remove(Cookie::build("foo").path("/"));`

Additionally, as noted above, the `Into<Cookie>` `impl` for `CookieBuilder`
obviates most explicit calls to `CookieBuilder::finish()` (now `build()`).

Resolves #199.
  • Loading branch information
SergioBenitez committed Sep 27, 2023
1 parent 8a1ec74 commit 49ff7b0
Show file tree
Hide file tree
Showing 9 changed files with 447 additions and 262 deletions.
239 changes: 176 additions & 63 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
use std::borrow::Cow;
use std::borrow::{Cow, Borrow, BorrowMut};

use crate::{Cookie, SameSite, Expiration};

/// Structure that follows the builder pattern for building `Cookie` structs.
///
/// To construct a cookie:
///
/// 1. Call [`Cookie::build`] to start building.
/// 1. Call [`Cookie::build()`] to start building.
/// 2. Use any of the builder methods to set fields in the cookie.
/// 3. Call [`CookieBuilder::finish()`] to retrieve the built cookie.
///
/// # Example
/// The resulting `CookieBuilder` can be passed directly into methods expecting
/// a `T: Into<Cookie>`:
///
/// ```rust
/// use cookie::{Cookie, CookieJar};
///
/// let mut jar = CookieJar::new();
/// jar.add(Cookie::build(("key", "value")).secure(true).path("/"));
/// jar.remove(Cookie::build("key").path("/"));
/// ```
///
/// You can also call [`CookieBuilder::build()`] directly to get a `Cookie`:
///
/// ```rust
/// # extern crate cookie;
/// use cookie::Cookie;
/// use cookie::time::Duration;
///
/// # fn main() {
/// let cookie: Cookie = Cookie::build("name", "value")
/// let cookie: Cookie = Cookie::build(("name", "value"))
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .max_age(Duration::days(1))
/// .finish();
/// # }
/// .build();
/// ```
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct CookieBuilder<'c> {
/// The cookie being built.
cookie: Cookie<'c>,
Expand All @@ -43,7 +50,12 @@ impl<'c> CookieBuilder<'c> {
/// ```rust
/// use cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar").finish();
/// // These two snippets are equivalent:
///
/// let c = Cookie::build(("foo", "bar"));
/// assert_eq!(c.inner().name_value(), ("foo", "bar"));
///
/// let c = Cookie::new("foo", "bar");
/// assert_eq!(c.name_value(), ("foo", "bar"));
/// ```
pub fn new<N, V>(name: N, value: V) -> Self
Expand All @@ -55,6 +67,8 @@ impl<'c> CookieBuilder<'c> {

/// Sets the `expires` field in the cookie being built.
///
/// See [`Expiration`] for conversions.
///
/// # Example
///
/// ```rust
Expand All @@ -63,17 +77,11 @@ impl<'c> CookieBuilder<'c> {
/// use cookie::time::OffsetDateTime;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .expires(OffsetDateTime::now_utc())
/// .finish();
///
/// assert!(c.expires().is_some());
/// let c = Cookie::build(("foo", "bar")).expires(OffsetDateTime::now_utc());
/// assert!(c.inner().expires().is_some());
///
/// let c = Cookie::build("foo", "bar")
/// .expires(None)
/// .finish();
///
/// assert_eq!(c.expires(), Some(Expiration::Session));
/// let c = Cookie::build(("foo", "bar")).expires(None);
/// assert_eq!(c.inner().expires(), Some(Expiration::Session));
/// # }
/// ```
#[inline]
Expand All @@ -87,17 +95,11 @@ impl<'c> CookieBuilder<'c> {
/// # Example
///
/// ```rust
/// # extern crate cookie;
/// use cookie::Cookie;
/// use cookie::time::Duration;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .max_age(Duration::minutes(30))
/// .finish();
///
/// assert_eq!(c.max_age(), Some(Duration::seconds(30 * 60)));
/// # }
/// let c = Cookie::build(("foo", "bar")).max_age(Duration::minutes(30));
/// assert_eq!(c.inner().max_age(), Some(Duration::seconds(30 * 60)));
/// ```
#[inline]
pub fn max_age(mut self, value: time::Duration) -> Self {
Expand All @@ -112,11 +114,8 @@ impl<'c> CookieBuilder<'c> {
/// ```rust
/// use cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .domain("www.rust-lang.org")
/// .finish();
///
/// assert_eq!(c.domain(), Some("www.rust-lang.org"));
/// let c = Cookie::build(("foo", "bar")).domain("www.rust-lang.org");
/// assert_eq!(c.inner().domain(), Some("www.rust-lang.org"));
/// ```
pub fn domain<D: Into<Cow<'c, str>>>(mut self, value: D) -> Self {
self.cookie.set_domain(value);
Expand All @@ -130,11 +129,8 @@ impl<'c> CookieBuilder<'c> {
/// ```rust
/// use cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .path("/")
/// .finish();
///
/// assert_eq!(c.path(), Some("/"));
/// let c = Cookie::build(("foo", "bar")).path("/");
/// assert_eq!(c.inner().path(), Some("/"));
/// ```
pub fn path<P: Into<Cow<'c, str>>>(mut self, path: P) -> Self {
self.cookie.set_path(path);
Expand All @@ -148,11 +144,8 @@ impl<'c> CookieBuilder<'c> {
/// ```rust
/// use cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .secure(true)
/// .finish();
///
/// assert_eq!(c.secure(), Some(true));
/// let c = Cookie::build(("foo", "bar")).secure(true);
/// assert_eq!(c.inner().secure(), Some(true));
/// ```
#[inline]
pub fn secure(mut self, value: bool) -> Self {
Expand All @@ -167,11 +160,8 @@ impl<'c> CookieBuilder<'c> {
/// ```rust
/// use cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .http_only(true)
/// .finish();
///
/// assert_eq!(c.http_only(), Some(true));
/// let c = Cookie::build(("foo", "bar")).http_only(true);
/// assert_eq!(c.inner().http_only(), Some(true));
/// ```
#[inline]
pub fn http_only(mut self, value: bool) -> Self {
Expand All @@ -186,11 +176,8 @@ impl<'c> CookieBuilder<'c> {
/// ```rust
/// use cookie::{Cookie, SameSite};
///
/// let c = Cookie::build("foo", "bar")
/// .same_site(SameSite::Strict)
/// .finish();
///
/// assert_eq!(c.same_site(), Some(SameSite::Strict));
/// let c = Cookie::build(("foo", "bar")).same_site(SameSite::Strict);
/// assert_eq!(c.inner().same_site(), Some(SameSite::Strict));
/// ```
#[inline]
pub fn same_site(mut self, value: SameSite) -> Self {
Expand All @@ -209,12 +196,9 @@ impl<'c> CookieBuilder<'c> {
/// use cookie::time::Duration;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .permanent()
/// .finish();
///
/// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
/// # assert!(c.expires().is_some());
/// let c = Cookie::build(("foo", "bar")).permanent();
/// assert_eq!(c.inner().max_age(), Some(Duration::days(365 * 20)));
/// # assert!(c.inner().expires().is_some());
/// # }
/// ```
#[inline]
Expand All @@ -223,24 +207,153 @@ impl<'c> CookieBuilder<'c> {
self
}

/// Finishes building and returns the built `Cookie`.
/// Returns a borrow to the cookie currently being built.
///
/// # Example
///
/// ```rust
/// use cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// let builder = Cookie::build(("name", "value"))
/// .domain("www.rust-lang.org")
/// .path("/")
/// .http_only(true);
///
/// assert_eq!(builder.inner().name_value(), ("name", "value"));
/// assert_eq!(builder.inner().domain(), Some("www.rust-lang.org"));
/// assert_eq!(builder.inner().path(), Some("/"));
/// assert_eq!(builder.inner().http_only(), Some(true));
/// assert_eq!(builder.inner().secure(), None);
/// ```
#[inline]
pub fn inner(&self) -> &Cookie<'c> {
&self.cookie
}

/// Returns a mutable borrow to the cookie currently being built.
///
/// # Example
///
/// ```rust
/// use cookie::Cookie;
///
/// let mut builder = Cookie::build(("name", "value"))
/// .domain("www.rust-lang.org")
/// .path("/")
/// .http_only(true);
///
/// assert_eq!(builder.inner().http_only(), Some(true));
///
/// builder.inner_mut().set_http_only(false);
/// assert_eq!(builder.inner().http_only(), Some(false));
/// ```
#[inline]
pub fn inner_mut(&mut self) -> &mut Cookie<'c> {
&mut self.cookie
}

/// Finishes building and returns the built `Cookie`.
///
/// This method usually does not need to be called directly. This is because
/// `CookieBuilder` implements `Into<Cookie>`, so a value of `CookieBuilder`
/// can be passed directly into any method that expects a `C: Into<Cookie>`.
///
/// # Example
///
/// ```rust
/// use cookie::{Cookie, CookieJar};
///
/// // We don't usually need to use `build()`. Inspect with `inner()`, and
/// // pass the builder directly into methods expecting `T: Into<Cookie>`.
/// let c = Cookie::build(("foo", "bar"))
/// .domain("crates.io")
/// .path("/");
///
/// // Use `inner()` and inspect the cookie.
/// assert_eq!(c.inner().name_value(), ("foo", "bar"));
/// assert_eq!(c.inner().domain(), Some("crates.io"));
/// assert_eq!(c.inner().path(), Some("/"));
///
/// // Add the cookie to a jar. Note the automatic conversion.
/// CookieJar::new().add(c);
///
/// // We could use `build()` to get a `Cookie` when needed.
/// let c = Cookie::build(("foo", "bar"))
/// .domain("crates.io")
/// .path("/")
/// .finish();
/// .build();
///
/// // Inspect the built cookie.
/// assert_eq!(c.name_value(), ("foo", "bar"));
/// assert_eq!(c.domain(), Some("crates.io"));
/// assert_eq!(c.path(), Some("/"));
///
/// // Add the cookie to a jar.
/// CookieJar::new().add(c);
/// ```
#[inline]
pub fn build(self) -> Cookie<'c> {
self.cookie
}

/// Deprecated. Convert `self` into a `Cookie`.
///
/// Instead of using this method, pass a `CookieBuilder` directly into
/// methods expecting a `T: Into<Cookie>`. For other cases, use
/// [`CookieBuilder::build()`].
#[deprecated(since="0.18.0", note="`CookieBuilder` can be passed in to methods expecting a `Cookie`; for other cases, use `CookieBuilder::build()`")]
pub fn finish(self) -> Cookie<'c> {
self.cookie
}
}

impl std::fmt::Display for CookieBuilder<'_> {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.cookie.fmt(f)
}
}

// NOTE: We don't implement `Deref` or `DerefMut` because there are tons of name
// collisions with builder methods.
impl<'a> Borrow<Cookie<'a>> for CookieBuilder<'a> {
fn borrow(&self) -> &Cookie<'a> {
&self.cookie
}
}

impl<'a> BorrowMut<Cookie<'a>> for CookieBuilder<'a> {
fn borrow_mut(&mut self) -> &mut Cookie<'a> {
&mut self.cookie
}
}

impl<'a> AsRef<Cookie<'a>> for CookieBuilder<'a> {
fn as_ref(&self) -> &Cookie<'a> {
&self.cookie
}
}

impl<'a> AsMut<Cookie<'a>> for CookieBuilder<'a> {
fn as_mut(&mut self) -> &mut Cookie<'a> {
&mut self.cookie
}
}

impl<'a, 'b> PartialEq<Cookie<'b>> for CookieBuilder<'a> {
fn eq(&self, other: &Cookie<'b>) -> bool {
&self.cookie == other
}
}

impl<'a, 'b> PartialEq<CookieBuilder<'b>> for Cookie<'a> {
fn eq(&self, other: &CookieBuilder<'b>) -> bool {
self == &other.cookie
}
}

impl<'c> From<Cookie<'c>> for CookieBuilder<'c> {
fn from(cookie: Cookie<'c>) -> Self {
CookieBuilder { cookie }
}
}
2 changes: 1 addition & 1 deletion src/delta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::Cookie;
/// that when sent to the client removes the named cookie on the client's
/// machine.
#[derive(Clone, Debug)]
pub struct DeltaCookie {
pub(crate) struct DeltaCookie {
pub cookie: Cookie<'static>,
pub removed: bool,
}
Expand Down
5 changes: 2 additions & 3 deletions src/expiration.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use time::OffsetDateTime;

/// A cookie's expiration: either session or a date-time.
/// A cookie's expiration: either a date-time or session.
///
/// An `Expiration` is constructible via `Expiration::from()` with an
/// `Option<OffsetDateTime>` or an `OffsetDateTime`:
/// An `Expiration` is constructible with `Expiration::from()` via any of:
///
/// * `None` -> `Expiration::Session`
/// * `Some(OffsetDateTime)` -> `Expiration::DateTime`
Expand Down
Loading

0 comments on commit 49ff7b0

Please sign in to comment.