tide_disco/
method.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the tide-disco library.
3
4// You should have received a copy of the MIT License
5// along with the tide-disco library. If not, see <https://mit-license.org/>.
6
7//! Interfaces for methods of accessing to state.
8//!
9//! A common pattern is for the `State` type of an [App](crate::App) to enable some form of interior
10//! mutability, with the option of read-only access, such as [RwLock]. Route handlers will then
11//! acquire either shared immutable access or exclusive mutable access, where the access mutability
12//! is linked to the HTTP method of the route -- a GET method implies immutable access, for example,
13//! while a POST method implies mutable access.
14//!
15//! [tide-disco](crate) supports this pattern ergonomically for any state type which has a notion of
16//! reading and writing. This module defines the traits [ReadState] and [WriteState] for states
17//! which support immutable and mutable access, respectively, and implements the traits for commonly
18//! used types such as [RwLock], [Mutex], and [Arc]. Of course, you are free to implement these
19//! traits yourself if your state type has some other notion of shared access or interior
20//! mutability.
21
22use crate::http;
23use async_std::sync::{Arc, Mutex, RwLock};
24use async_trait::async_trait;
25use futures::future::BoxFuture;
26use std::fmt::{self, Display, Formatter};
27use std::str::FromStr;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum Method {
31    Http(http::Method),
32    Socket,
33    Metrics,
34}
35
36impl Method {
37    /// The HTTP GET method.
38    pub fn get() -> Self {
39        Self::Http(http::Method::Get)
40    }
41
42    /// The HTTP POST method.
43    pub fn post() -> Self {
44        Self::Http(http::Method::Post)
45    }
46
47    /// The HTTP PUT method.
48    pub fn put() -> Self {
49        Self::Http(http::Method::Put)
50    }
51
52    /// The HTTP DELETE method.
53    pub fn delete() -> Self {
54        Self::Http(http::Method::Delete)
55    }
56
57    /// The Tide Disco SOCKET method.
58    pub fn socket() -> Self {
59        Self::Socket
60    }
61
62    /// The Tide Disco METRICS method.
63    pub fn metrics() -> Self {
64        Self::Metrics
65    }
66
67    /// Check if a method is a standard HTTP method.
68    pub fn is_http(&self) -> bool {
69        matches!(self, Self::Http(_))
70    }
71
72    /// Check if a request method implies mutable access to the state.
73    pub fn is_mutable(&self) -> bool {
74        match self {
75            Self::Http(m) => !m.is_safe(),
76            Self::Socket => true,
77            Self::Metrics => false,
78        }
79    }
80}
81
82impl From<http::Method> for Method {
83    fn from(m: http::Method) -> Self {
84        Self::Http(m)
85    }
86}
87
88impl Display for Method {
89    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
90        match self {
91            Self::Http(m) => write!(f, "{}", m),
92            Self::Socket => write!(f, "SOCKET"),
93            Self::Metrics => write!(f, "METRICS"),
94        }
95    }
96}
97
98pub struct ParseMethodError;
99
100impl FromStr for Method {
101    type Err = ParseMethodError;
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        match s {
105            "SOCKET" => Ok(Self::Socket),
106            "METRICS" => Ok(Self::Metrics),
107            _ => s.parse().map_err(|_| ParseMethodError).map(Self::Http),
108        }
109    }
110}
111
112/// A state which allows read access.
113///
114/// Implementations may allow _shared_ read access (for instance, [RwLock]) but some implementations
115/// do not (for instance, [Mutex]). Therefore, do not assume that [read](Self::read) is reentrant,
116/// or you may have a deadlock.
117#[async_trait]
118pub trait ReadState {
119    /// The type of state which this type allows a caller to read.
120    type State: 'static;
121
122    /// Do an operation with immutable access to the state.
123    ///
124    /// # Limitations
125    ///
126    /// The reason this function takes a visitor function to apply to the state, rather than just
127    /// returning a reference to the state, is to allow implementations that cannot provide a plain
128    /// reference directly. For example, [RwLock] can produce a smart reference, a
129    /// [RwLockReadGuard](async_std::sync::RwLockReadGuard), but not a plain reference.
130    ///
131    /// Note that GATs may allow us to make this interface more ergonomic in the future. With stable
132    /// GATs, this trait could be written like
133    ///
134    /// ```ignore
135    /// trait ReadState {
136    ///     type State: 'static;
137    ///     type Reference<'a>: 'a + Deref<Target = Self::State>;
138    ///     fn read(&self) -> Self::Reference<'_>;
139    /// }
140    /// ```
141    ///
142    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
143    /// function to apply to the state is also required to return a _boxed_ future.
144    async fn read<T>(
145        &self,
146        op: impl Send + for<'a> FnOnce(&'a Self::State) -> BoxFuture<'a, T> + 'async_trait,
147    ) -> T;
148}
149
150/// A state which allows exclusive, write access.
151#[async_trait]
152pub trait WriteState: ReadState {
153    /// Do an operation with mutable access to the state.
154    ///
155    /// # Limitations
156    ///
157    /// The reason this function takes a visitor function to apply to the state, rather than just
158    /// returning a mutable reference to the state, is to allow implementations that cannot provide
159    /// a plain mutable reference directly. For example, [RwLock] can produce a smart reference, a
160    /// [RwLockWriteGuard](async_std::sync::RwLockWriteGuard), but not a plain mutable reference.
161    ///
162    /// Note that GATs may allow us to make this interface more ergonomic in the future. With stable
163    /// GATs, this trait could be written like
164    ///
165    /// ```ignore
166    /// trait WriteState {
167    ///     type State: 'static;
168    ///     type MutReference<'a>: 'a + DerefMut<Target = Self::State>;
169    ///     fn write(&self) -> Self::MutReference<'_>;
170    /// }
171    /// ```
172    ///
173    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
174    /// function to apply to the state is also required to return a _boxed_ future.
175    async fn write<T>(
176        &self,
177        op: impl Send + for<'a> FnOnce(&'a mut Self::State) -> BoxFuture<'a, T> + 'async_trait,
178    ) -> T;
179}
180
181#[async_trait]
182impl<State: 'static + Send + Sync> ReadState for RwLock<State> {
183    type State = State;
184    async fn read<T>(
185        &self,
186        op: impl Send + for<'a> FnOnce(&'a Self::State) -> BoxFuture<'a, T> + 'async_trait,
187    ) -> T {
188        op(&*self.read().await).await
189    }
190}
191
192#[async_trait]
193impl<State: 'static + Send + Sync> WriteState for RwLock<State> {
194    async fn write<T>(
195        &self,
196        op: impl Send + for<'a> FnOnce(&'a mut Self::State) -> BoxFuture<'a, T> + 'async_trait,
197    ) -> T {
198        op(&mut *self.write().await).await
199    }
200}
201
202#[async_trait]
203impl<State: 'static + Send + Sync> ReadState for Mutex<State> {
204    type State = State;
205    async fn read<T>(
206        &self,
207        op: impl Send + for<'a> FnOnce(&'a Self::State) -> BoxFuture<'a, T> + 'async_trait,
208    ) -> T {
209        op(&*self.lock().await).await
210    }
211}
212
213#[async_trait]
214impl<State: 'static + Send + Sync> WriteState for Mutex<State> {
215    async fn write<T>(
216        &self,
217        op: impl Send + for<'a> FnOnce(&'a mut Self::State) -> BoxFuture<'a, T> + 'async_trait,
218    ) -> T {
219        op(&mut *self.lock().await).await
220    }
221}
222
223#[async_trait]
224impl<R: Send + Sync + ReadState> ReadState for Arc<R> {
225    type State = R::State;
226    async fn read<T>(
227        &self,
228        op: impl Send + for<'a> FnOnce(&'a Self::State) -> BoxFuture<'a, T> + 'async_trait,
229    ) -> T {
230        (**self).read(op).await
231    }
232}
233
234#[async_trait]
235impl<W: Send + Sync + WriteState> WriteState for Arc<W> {
236    async fn write<T>(
237        &self,
238        op: impl Send + for<'a> FnOnce(&'a mut Self::State) -> BoxFuture<'a, T> + 'async_trait,
239    ) -> T {
240        (**self).write(op).await
241    }
242}
243
244/// This allows you to do `api.get(...)` in a simple API where the state is `()`.
245#[async_trait]
246impl ReadState for () {
247    type State = ();
248    async fn read<T>(
249        &self,
250        op: impl Send + for<'a> FnOnce(&'a Self::State) -> BoxFuture<'a, T> + 'async_trait,
251    ) -> T {
252        op(&()).await
253    }
254}
255
256/// This allows you to do `api.post(...)` in a simple API where the state is `()`.
257#[async_trait]
258impl WriteState for () {
259    async fn write<T>(
260        &self,
261        op: impl Send + for<'a> FnOnce(&'a mut Self::State) -> BoxFuture<'a, T> + 'async_trait,
262    ) -> T {
263        op(&mut ()).await
264    }
265}