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}