1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-09-02 21:16:37 +02:00

Deploying to gh-pages from @ 1e682e7a59 🚀

This commit is contained in:
robjtede
2022-08-24 17:10:06 +00:00
parent 677c856277
commit a717748964
200 changed files with 8532 additions and 8481 deletions

View File

@@ -315,320 +315,320 @@
<span id="313">313</span>
<span id="314">314</span>
<span id="315">315</span>
</pre><pre class="rust"><code><span class="kw">use</span> <span class="ident">actix::Addr</span>;
<span class="kw">use</span> <span class="ident">actix_redis</span>::{<span class="ident">resp_array</span>, <span class="ident">Command</span>, <span class="ident">RedisActor</span>, <span class="ident">RespValue</span>};
<span class="kw">use</span> <span class="ident">actix_web::cookie::time::Duration</span>;
<span class="kw">use</span> <span class="ident">anyhow::Error</span>;
</pre><pre class="rust"><code><span class="kw">use </span>actix::Addr;
<span class="kw">use </span>actix_redis::{resp_array, Command, RedisActor, RespValue};
<span class="kw">use </span>actix_web::cookie::time::Duration;
<span class="kw">use </span>anyhow::Error;
<span class="kw">use</span> <span class="ident"><span class="kw">super</span>::SessionKey</span>;
<span class="kw">use</span> <span class="ident"><span class="kw">crate</span>::storage</span>::{
<span class="ident">interface</span>::{<span class="ident">LoadError</span>, <span class="ident">SaveError</span>, <span class="ident">SessionState</span>, <span class="ident">UpdateError</span>},
<span class="ident">utils::generate_session_key</span>,
<span class="ident">SessionStore</span>,
<span class="kw">use </span><span class="kw">super</span>::SessionKey;
<span class="kw">use </span><span class="kw">crate</span>::storage::{
interface::{LoadError, SaveError, SessionState, UpdateError},
utils::generate_session_key,
SessionStore,
};
<span class="doccomment">/// Use Redis as session storage backend.</span>
<span class="doccomment">///</span>
<span class="doccomment">/// ```no_run</span>
<span class="doccomment">/// use actix_web::{web, App, HttpServer, HttpResponse, Error};</span>
<span class="doccomment">/// use actix_session::{SessionMiddleware, storage::RedisActorSessionStore};</span>
<span class="doccomment">/// use actix_web::cookie::Key;</span>
<span class="doccomment">///</span>
<span class="doccomment">/// // The secret key would usually be read from a configuration file/environment variables.</span>
<span class="doccomment">/// fn get_secret_key() -&gt; Key {</span>
<span class="doccomment">/// # todo!()</span>
<span class="doccomment">/// // [...]</span>
<span class="doccomment">/// }</span>
<span class="doccomment">///</span>
<span class="doccomment">/// #[actix_web::main]</span>
<span class="doccomment">/// async fn main() -&gt; std::io::Result&lt;()&gt; {</span>
<span class="doccomment">/// let secret_key = get_secret_key();</span>
<span class="doccomment">/// let redis_connection_string = &quot;127.0.0.1:6379&quot;;</span>
<span class="doccomment">/// HttpServer::new(move ||</span>
<span class="doccomment">/// App::new()</span>
<span class="doccomment">/// .wrap(</span>
<span class="doccomment">/// SessionMiddleware::new(</span>
<span class="doccomment">/// RedisActorSessionStore::new(redis_connection_string),</span>
<span class="doccomment">/// secret_key.clone()</span>
<span class="doccomment">/// )</span>
<span class="doccomment">/// )</span>
<span class="doccomment">/// .default_service(web::to(|| HttpResponse::Ok())))</span>
<span class="doccomment">/// .bind((&quot;127.0.0.1&quot;, 8080))?</span>
<span class="doccomment">/// .run()</span>
<span class="doccomment">/// .await</span>
<span class="doccomment">/// }</span>
<span class="doccomment">/// ```</span>
<span class="doccomment">///</span>
<span class="doccomment">/// # Implementation notes</span>
<span class="doccomment">///</span>
<span class="doccomment">/// `RedisActorSessionStore` leverages `actix-redis`&#39;s `RedisActor` implementation - each thread</span>
<span class="doccomment">/// worker gets its own connection to Redis.</span>
<span class="doccomment">///</span>
<span class="doccomment">/// ## Limitations</span>
<span class="doccomment">///</span>
<span class="doccomment">/// `RedisActorSessionStore` does not currently support establishing authenticated connections to</span>
<span class="doccomment">/// Redis. Use [`RedisSessionStore`] if you need TLS support.</span>
<span class="doccomment">///</span>
<span class="doccomment">/// [`RedisSessionStore`]: crate::storage::RedisSessionStore</span>
<span class="attribute">#[<span class="ident">cfg_attr</span>(<span class="ident">docsrs</span>, <span class="ident">doc</span>(<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;redis-actor-session&quot;</span>)))]</span>
<span class="kw">pub</span> <span class="kw">struct</span> <span class="ident">RedisActorSessionStore</span> {
<span class="ident">configuration</span>: <span class="ident">CacheConfiguration</span>,
<span class="ident">addr</span>: <span class="ident">Addr</span><span class="op">&lt;</span><span class="ident">RedisActor</span><span class="op">&gt;</span>,
<span class="doccomment">/// Use Redis as session storage backend.
///
/// ```no_run
/// use actix_web::{web, App, HttpServer, HttpResponse, Error};
/// use actix_session::{SessionMiddleware, storage::RedisActorSessionStore};
/// use actix_web::cookie::Key;
///
/// // The secret key would usually be read from a configuration file/environment variables.
/// fn get_secret_key() -&gt; Key {
/// # todo!()
/// // [...]
/// }
///
/// #[actix_web::main]
/// async fn main() -&gt; std::io::Result&lt;()&gt; {
/// let secret_key = get_secret_key();
/// let redis_connection_string = &quot;127.0.0.1:6379&quot;;
/// HttpServer::new(move ||
/// App::new()
/// .wrap(
/// SessionMiddleware::new(
/// RedisActorSessionStore::new(redis_connection_string),
/// secret_key.clone()
/// )
/// )
/// .default_service(web::to(|| HttpResponse::Ok())))
/// .bind((&quot;127.0.0.1&quot;, 8080))?
/// .run()
/// .await
/// }
/// ```
///
/// # Implementation notes
///
/// `RedisActorSessionStore` leverages `actix-redis`&#39;s `RedisActor` implementation - each thread
/// worker gets its own connection to Redis.
///
/// ## Limitations
///
/// `RedisActorSessionStore` does not currently support establishing authenticated connections to
/// Redis. Use [`RedisSessionStore`] if you need TLS support.
///
/// [`RedisSessionStore`]: crate::storage::RedisSessionStore
</span><span class="attribute">#[cfg_attr(docsrs, doc(cfg(feature = <span class="string">&quot;redis-actor-session&quot;</span>)))]
</span><span class="kw">pub struct </span>RedisActorSessionStore {
configuration: CacheConfiguration,
addr: Addr&lt;RedisActor&gt;,
}
<span class="kw">impl</span> <span class="ident">RedisActorSessionStore</span> {
<span class="doccomment">/// A fluent API to configure [`RedisActorSessionStore`].</span>
<span class="doccomment">///</span>
<span class="doccomment">/// It takes as input the only required input to create a new instance of</span>
<span class="doccomment">/// [`RedisActorSessionStore`]—a connection string for Redis.</span>
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">builder</span><span class="op">&lt;</span><span class="ident">S</span>: <span class="ident">Into</span><span class="op">&lt;</span><span class="ident">String</span><span class="op">&gt;</span><span class="op">&gt;</span>(<span class="ident">connection_string</span>: <span class="ident">S</span>) -&gt; <span class="ident">RedisActorSessionStoreBuilder</span> {
<span class="ident">RedisActorSessionStoreBuilder</span> {
<span class="ident">configuration</span>: <span class="ident">CacheConfiguration::default</span>(),
<span class="ident">connection_string</span>: <span class="ident">connection_string</span>.<span class="ident">into</span>(),
<span class="kw">impl </span>RedisActorSessionStore {
<span class="doccomment">/// A fluent API to configure [`RedisActorSessionStore`].
///
/// It takes as input the only required input to create a new instance of
/// [`RedisActorSessionStore`]—a connection string for Redis.
</span><span class="kw">pub fn </span>builder&lt;S: Into&lt;String&gt;&gt;(connection_string: S) -&gt; RedisActorSessionStoreBuilder {
RedisActorSessionStoreBuilder {
configuration: CacheConfiguration::default(),
connection_string: connection_string.into(),
}
}
<span class="doccomment">/// Create a new instance of [`RedisActorSessionStore`] using the default configuration.</span>
<span class="doccomment">/// It takes as input the only required input to create a new instance of [`RedisActorSessionStore`] - a</span>
<span class="doccomment">/// connection string for Redis.</span>
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">new</span><span class="op">&lt;</span><span class="ident">S</span>: <span class="ident">Into</span><span class="op">&lt;</span><span class="ident">String</span><span class="op">&gt;</span><span class="op">&gt;</span>(<span class="ident">connection_string</span>: <span class="ident">S</span>) -&gt; <span class="ident">RedisActorSessionStore</span> {
<span class="ident"><span class="self">Self</span>::builder</span>(<span class="ident">connection_string</span>).<span class="ident">build</span>()
<span class="doccomment">/// Create a new instance of [`RedisActorSessionStore`] using the default configuration.
/// It takes as input the only required input to create a new instance of [`RedisActorSessionStore`] - a
/// connection string for Redis.
</span><span class="kw">pub fn </span>new&lt;S: Into&lt;String&gt;&gt;(connection_string: S) -&gt; RedisActorSessionStore {
<span class="self">Self</span>::builder(connection_string).build()
}
}
<span class="kw">struct</span> <span class="ident">CacheConfiguration</span> {
<span class="ident">cache_keygen</span>: <span class="ident">Box</span><span class="op">&lt;</span><span class="kw">dyn</span> <span class="ident">Fn</span>(<span class="kw-2">&amp;</span><span class="ident">str</span>) -&gt; <span class="ident">String</span><span class="op">&gt;</span>,
<span class="kw">struct </span>CacheConfiguration {
cache_keygen: Box&lt;<span class="kw">dyn </span>Fn(<span class="kw-2">&amp;</span>str) -&gt; String&gt;,
}
<span class="kw">impl</span> <span class="ident">Default</span> <span class="kw">for</span> <span class="ident">CacheConfiguration</span> {
<span class="kw">fn</span> <span class="ident">default</span>() -&gt; <span class="self">Self</span> {
<span class="self">Self</span> {
<span class="ident">cache_keygen</span>: <span class="ident">Box::new</span>(<span class="ident">str::to_owned</span>),
<span class="kw">impl </span>Default <span class="kw">for </span>CacheConfiguration {
<span class="kw">fn </span>default() -&gt; <span class="self">Self </span>{
<span class="self">Self </span>{
cache_keygen: Box::new(str::to_owned),
}
}
}
<span class="doccomment">/// A fluent builder to construct a [`RedisActorSessionStore`] instance with custom configuration</span>
<span class="doccomment">/// parameters.</span>
<span class="attribute">#[<span class="ident">cfg_attr</span>(<span class="ident">docsrs</span>, <span class="ident">doc</span>(<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;redis-actor-session&quot;</span>)))]</span>
<span class="attribute">#[<span class="ident">must_use</span>]</span>
<span class="kw">pub</span> <span class="kw">struct</span> <span class="ident">RedisActorSessionStoreBuilder</span> {
<span class="ident">connection_string</span>: <span class="ident">String</span>,
<span class="ident">configuration</span>: <span class="ident">CacheConfiguration</span>,
<span class="doccomment">/// A fluent builder to construct a [`RedisActorSessionStore`] instance with custom configuration
/// parameters.
</span><span class="attribute">#[cfg_attr(docsrs, doc(cfg(feature = <span class="string">&quot;redis-actor-session&quot;</span>)))]
#[must_use]
</span><span class="kw">pub struct </span>RedisActorSessionStoreBuilder {
connection_string: String,
configuration: CacheConfiguration,
}
<span class="kw">impl</span> <span class="ident">RedisActorSessionStoreBuilder</span> {
<span class="doccomment">/// Set a custom cache key generation strategy, expecting a session key as input.</span>
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">cache_keygen</span><span class="op">&lt;</span><span class="ident">F</span><span class="op">&gt;</span>(<span class="kw-2">mut</span> <span class="self">self</span>, <span class="ident">keygen</span>: <span class="ident">F</span>) -&gt; <span class="self">Self</span>
<span class="kw">where</span>
<span class="ident">F</span>: <span class="ident">Fn</span>(<span class="kw-2">&amp;</span><span class="ident">str</span>) -&gt; <span class="ident">String</span> <span class="op">+</span> <span class="lifetime">&#39;static</span>,
<span class="kw">impl </span>RedisActorSessionStoreBuilder {
<span class="doccomment">/// Set a custom cache key generation strategy, expecting a session key as input.
</span><span class="kw">pub fn </span>cache_keygen&lt;F&gt;(<span class="kw-2">mut </span><span class="self">self</span>, keygen: F) -&gt; <span class="self">Self
</span><span class="kw">where
</span>F: Fn(<span class="kw-2">&amp;</span>str) -&gt; String + <span class="lifetime">&#39;static</span>,
{
<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span> <span class="op">=</span> <span class="ident">Box::new</span>(<span class="ident">keygen</span>);
<span class="self">self</span>
}
<span class="self">self</span>.configuration.cache_keygen = Box::new(keygen);
<span class="self">self
</span>}
<span class="doccomment">/// Finalise the builder and return a [`RedisActorSessionStore`] instance.</span>
<span class="attribute">#[<span class="ident">must_use</span>]</span>
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">build</span>(<span class="self">self</span>) -&gt; <span class="ident">RedisActorSessionStore</span> {
<span class="ident">RedisActorSessionStore</span> {
<span class="ident">configuration</span>: <span class="self">self</span>.<span class="ident">configuration</span>,
<span class="ident">addr</span>: <span class="ident">RedisActor::start</span>(<span class="self">self</span>.<span class="ident">connection_string</span>),
<span class="doccomment">/// Finalise the builder and return a [`RedisActorSessionStore`] instance.
</span><span class="attribute">#[must_use]
</span><span class="kw">pub fn </span>build(<span class="self">self</span>) -&gt; RedisActorSessionStore {
RedisActorSessionStore {
configuration: <span class="self">self</span>.configuration,
addr: RedisActor::start(<span class="self">self</span>.connection_string),
}
}
}
<span class="attribute">#[<span class="ident">async_trait::async_trait</span>(<span class="question-mark">?</span><span class="ident">Send</span>)]</span>
<span class="kw">impl</span> <span class="ident">SessionStore</span> <span class="kw">for</span> <span class="ident">RedisActorSessionStore</span> {
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">load</span>(<span class="kw-2">&amp;</span><span class="self">self</span>, <span class="ident">session_key</span>: <span class="kw-2">&amp;</span><span class="ident">SessionKey</span>) -&gt; <span class="prelude-ty">Result</span><span class="op">&lt;</span><span class="prelude-ty">Option</span><span class="op">&lt;</span><span class="ident">SessionState</span><span class="op">&gt;</span>, <span class="ident">LoadError</span><span class="op">&gt;</span> {
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
<span class="kw">let</span> <span class="ident">val</span> <span class="op">=</span> <span class="self">self</span>
.<span class="ident">addr</span>
.<span class="ident">send</span>(<span class="ident">Command</span>(<span class="macro">resp_array!</span>[<span class="string">&quot;GET&quot;</span>, <span class="ident">cache_key</span>]))
.<span class="kw">await</span>
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">LoadError::Other</span>)<span class="question-mark">?</span>
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">LoadError::Other</span>)<span class="question-mark">?</span>;
<span class="attribute">#[async_trait::async_trait(<span class="question-mark">?</span>Send)]
</span><span class="kw">impl </span>SessionStore <span class="kw">for </span>RedisActorSessionStore {
<span class="kw">async fn </span>load(<span class="kw-2">&amp;</span><span class="self">self</span>, session_key: <span class="kw-2">&amp;</span>SessionKey) -&gt; <span class="prelude-ty">Result</span>&lt;<span class="prelude-ty">Option</span>&lt;SessionState&gt;, LoadError&gt; {
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
<span class="kw">let </span>val = <span class="self">self
</span>.addr
.send(Command(<span class="macro">resp_array!</span>[<span class="string">&quot;GET&quot;</span>, cache_key]))
.<span class="kw">await
</span>.map_err(Into::into)
.map_err(LoadError::Other)<span class="question-mark">?
</span>.map_err(Into::into)
.map_err(LoadError::Other)<span class="question-mark">?</span>;
<span class="kw">match</span> <span class="ident">val</span> {
<span class="ident">RespValue::Error</span>(<span class="ident">err</span>) =&gt; <span class="prelude-val">Err</span>(<span class="ident">LoadError::Other</span>(<span class="macro">anyhow::anyhow!</span>(<span class="ident">err</span>))),
<span class="kw">match </span>val {
RespValue::Error(err) =&gt; <span class="prelude-val">Err</span>(LoadError::Other(<span class="macro">anyhow::anyhow!</span>(err))),
<span class="ident">RespValue::SimpleString</span>(<span class="ident">s</span>) =&gt; <span class="prelude-val">Ok</span>(<span class="ident">serde_json::from_str</span>(<span class="kw-2">&amp;</span><span class="ident">s</span>)
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">LoadError::Deserialization</span>)<span class="question-mark">?</span>),
RespValue::SimpleString(s) =&gt; <span class="prelude-val">Ok</span>(serde_json::from_str(<span class="kw-2">&amp;</span>s)
.map_err(Into::into)
.map_err(LoadError::Deserialization)<span class="question-mark">?</span>),
<span class="ident">RespValue::BulkString</span>(<span class="ident">s</span>) =&gt; <span class="prelude-val">Ok</span>(<span class="ident">serde_json::from_slice</span>(<span class="kw-2">&amp;</span><span class="ident">s</span>)
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">LoadError::Deserialization</span>)<span class="question-mark">?</span>),
RespValue::BulkString(s) =&gt; <span class="prelude-val">Ok</span>(serde_json::from_slice(<span class="kw-2">&amp;</span>s)
.map_err(Into::into)
.map_err(LoadError::Deserialization)<span class="question-mark">?</span>),
<span class="kw">_</span> =&gt; <span class="prelude-val">Ok</span>(<span class="prelude-val">None</span>),
<span class="kw">_ </span>=&gt; <span class="prelude-val">Ok</span>(<span class="prelude-val">None</span>),
}
}
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">save</span>(
<span class="kw">async fn </span>save(
<span class="kw-2">&amp;</span><span class="self">self</span>,
<span class="ident">session_state</span>: <span class="ident">SessionState</span>,
<span class="ident">ttl</span>: <span class="kw-2">&amp;</span><span class="ident">Duration</span>,
) -&gt; <span class="prelude-ty">Result</span><span class="op">&lt;</span><span class="ident">SessionKey</span>, <span class="ident">SaveError</span><span class="op">&gt;</span> {
<span class="kw">let</span> <span class="ident">body</span> <span class="op">=</span> <span class="ident">serde_json::to_string</span>(<span class="kw-2">&amp;</span><span class="ident">session_state</span>)
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">SaveError::Serialization</span>)<span class="question-mark">?</span>;
<span class="kw">let</span> <span class="ident">session_key</span> <span class="op">=</span> <span class="ident">generate_session_key</span>();
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
session_state: SessionState,
ttl: <span class="kw-2">&amp;</span>Duration,
) -&gt; <span class="prelude-ty">Result</span>&lt;SessionKey, SaveError&gt; {
<span class="kw">let </span>body = serde_json::to_string(<span class="kw-2">&amp;</span>session_state)
.map_err(Into::into)
.map_err(SaveError::Serialization)<span class="question-mark">?</span>;
<span class="kw">let </span>session_key = generate_session_key();
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
<span class="kw">let</span> <span class="ident">cmd</span> <span class="op">=</span> <span class="ident">Command</span>(<span class="macro">resp_array!</span>[
<span class="kw">let </span>cmd = Command(<span class="macro">resp_array!</span>[
<span class="string">&quot;SET&quot;</span>,
<span class="ident">cache_key</span>,
<span class="ident">body</span>,
<span class="string">&quot;NX&quot;</span>, <span class="comment">// NX: only set the key if it does not already exist</span>
<span class="string">&quot;EX&quot;</span>, <span class="comment">// EX: set expiry</span>
<span class="macro">format!</span>(<span class="string">&quot;{}&quot;</span>, <span class="ident">ttl</span>.<span class="ident">whole_seconds</span>())
cache_key,
body,
<span class="string">&quot;NX&quot;</span>, <span class="comment">// NX: only set the key if it does not already exist
</span><span class="string">&quot;EX&quot;</span>, <span class="comment">// EX: set expiry
</span><span class="macro">format!</span>(<span class="string">&quot;{}&quot;</span>, ttl.whole_seconds())
]);
<span class="kw">let</span> <span class="ident">result</span> <span class="op">=</span> <span class="self">self</span>
.<span class="ident">addr</span>
.<span class="ident">send</span>(<span class="ident">cmd</span>)
.<span class="kw">await</span>
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">SaveError::Other</span>)<span class="question-mark">?</span>
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">SaveError::Other</span>)<span class="question-mark">?</span>;
<span class="kw">let </span>result = <span class="self">self
</span>.addr
.send(cmd)
.<span class="kw">await
</span>.map_err(Into::into)
.map_err(SaveError::Other)<span class="question-mark">?
</span>.map_err(Into::into)
.map_err(SaveError::Other)<span class="question-mark">?</span>;
<span class="kw">match</span> <span class="ident">result</span> {
<span class="ident">RespValue::SimpleString</span>(<span class="kw">_</span>) =&gt; <span class="prelude-val">Ok</span>(<span class="ident">session_key</span>),
<span class="ident">RespValue::Nil</span> =&gt; <span class="prelude-val">Err</span>(<span class="ident">SaveError::Other</span>(<span class="macro">anyhow::anyhow!</span>(
<span class="string">&quot;Failed to save session state. A record with the same key already existed in Redis&quot;</span>
))),
<span class="ident">err</span> =&gt; <span class="prelude-val">Err</span>(<span class="ident">SaveError::Other</span>(<span class="macro">anyhow::anyhow!</span>(
<span class="kw">match </span>result {
RespValue::SimpleString(<span class="kw">_</span>) =&gt; <span class="prelude-val">Ok</span>(session_key),
RespValue::Nil =&gt; <span class="prelude-val">Err</span>(SaveError::Other(<span class="macro">anyhow::anyhow!</span>(
<span class="string">&quot;Failed to save session state. A record with the same key already existed in Redis&quot;
</span>))),
err =&gt; <span class="prelude-val">Err</span>(SaveError::Other(<span class="macro">anyhow::anyhow!</span>(
<span class="string">&quot;Failed to save session state. {:?}&quot;</span>,
<span class="ident">err</span>
err
))),
}
}
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">update</span>(
<span class="kw">async fn </span>update(
<span class="kw-2">&amp;</span><span class="self">self</span>,
<span class="ident">session_key</span>: <span class="ident">SessionKey</span>,
<span class="ident">session_state</span>: <span class="ident">SessionState</span>,
<span class="ident">ttl</span>: <span class="kw-2">&amp;</span><span class="ident">Duration</span>,
) -&gt; <span class="prelude-ty">Result</span><span class="op">&lt;</span><span class="ident">SessionKey</span>, <span class="ident">UpdateError</span><span class="op">&gt;</span> {
<span class="kw">let</span> <span class="ident">body</span> <span class="op">=</span> <span class="ident">serde_json::to_string</span>(<span class="kw-2">&amp;</span><span class="ident">session_state</span>)
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">UpdateError::Serialization</span>)<span class="question-mark">?</span>;
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
session_key: SessionKey,
session_state: SessionState,
ttl: <span class="kw-2">&amp;</span>Duration,
) -&gt; <span class="prelude-ty">Result</span>&lt;SessionKey, UpdateError&gt; {
<span class="kw">let </span>body = serde_json::to_string(<span class="kw-2">&amp;</span>session_state)
.map_err(Into::into)
.map_err(UpdateError::Serialization)<span class="question-mark">?</span>;
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
<span class="kw">let</span> <span class="ident">cmd</span> <span class="op">=</span> <span class="ident">Command</span>(<span class="macro">resp_array!</span>[
<span class="kw">let </span>cmd = Command(<span class="macro">resp_array!</span>[
<span class="string">&quot;SET&quot;</span>,
<span class="ident">cache_key</span>,
<span class="ident">body</span>,
<span class="string">&quot;XX&quot;</span>, <span class="comment">// XX: Only set the key if it already exist.</span>
<span class="string">&quot;EX&quot;</span>, <span class="comment">// EX: set expiry</span>
<span class="macro">format!</span>(<span class="string">&quot;{}&quot;</span>, <span class="ident">ttl</span>.<span class="ident">whole_seconds</span>())
cache_key,
body,
<span class="string">&quot;XX&quot;</span>, <span class="comment">// XX: Only set the key if it already exist.
</span><span class="string">&quot;EX&quot;</span>, <span class="comment">// EX: set expiry
</span><span class="macro">format!</span>(<span class="string">&quot;{}&quot;</span>, ttl.whole_seconds())
]);
<span class="kw">let</span> <span class="ident">result</span> <span class="op">=</span> <span class="self">self</span>
.<span class="ident">addr</span>
.<span class="ident">send</span>(<span class="ident">cmd</span>)
.<span class="kw">await</span>
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">UpdateError::Other</span>)<span class="question-mark">?</span>
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
.<span class="ident">map_err</span>(<span class="ident">UpdateError::Other</span>)<span class="question-mark">?</span>;
<span class="kw">let </span>result = <span class="self">self
</span>.addr
.send(cmd)
.<span class="kw">await
</span>.map_err(Into::into)
.map_err(UpdateError::Other)<span class="question-mark">?
</span>.map_err(Into::into)
.map_err(UpdateError::Other)<span class="question-mark">?</span>;
<span class="kw">match</span> <span class="ident">result</span> {
<span class="ident">RespValue::Nil</span> =&gt; {
<span class="comment">// The SET operation was not performed because the XX condition was not verified.</span>
<span class="comment">// This can happen if the session state expired between the load operation and the</span>
<span class="comment">// update operation. Unlucky, to say the least. We fall back to the `save` routine</span>
<span class="comment">// to ensure that the new key is unique.</span>
<span class="self">self</span>.<span class="ident">save</span>(<span class="ident">session_state</span>, <span class="ident">ttl</span>)
.<span class="kw">await</span>
.<span class="ident">map_err</span>(<span class="op">|</span><span class="ident">err</span><span class="op">|</span> <span class="kw">match</span> <span class="ident">err</span> {
<span class="ident">SaveError::Serialization</span>(<span class="ident">err</span>) =&gt; <span class="ident">UpdateError::Serialization</span>(<span class="ident">err</span>),
<span class="ident">SaveError::Other</span>(<span class="ident">err</span>) =&gt; <span class="ident">UpdateError::Other</span>(<span class="ident">err</span>),
<span class="kw">match </span>result {
RespValue::Nil =&gt; {
<span class="comment">// The SET operation was not performed because the XX condition was not verified.
// This can happen if the session state expired between the load operation and the
// update operation. Unlucky, to say the least. We fall back to the `save` routine
// to ensure that the new key is unique.
</span><span class="self">self</span>.save(session_state, ttl)
.<span class="kw">await
</span>.map_err(|err| <span class="kw">match </span>err {
SaveError::Serialization(err) =&gt; UpdateError::Serialization(err),
SaveError::Other(err) =&gt; UpdateError::Other(err),
})
}
<span class="ident">RespValue::SimpleString</span>(<span class="kw">_</span>) =&gt; <span class="prelude-val">Ok</span>(<span class="ident">session_key</span>),
<span class="ident">val</span> =&gt; <span class="prelude-val">Err</span>(<span class="ident">UpdateError::Other</span>(<span class="macro">anyhow::anyhow!</span>(
RespValue::SimpleString(<span class="kw">_</span>) =&gt; <span class="prelude-val">Ok</span>(session_key),
val =&gt; <span class="prelude-val">Err</span>(UpdateError::Other(<span class="macro">anyhow::anyhow!</span>(
<span class="string">&quot;Failed to update session state. {:?}&quot;</span>,
<span class="ident">val</span>
val
))),
}
}
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">update_ttl</span>(<span class="kw-2">&amp;</span><span class="self">self</span>, <span class="ident">session_key</span>: <span class="kw-2">&amp;</span><span class="ident">SessionKey</span>, <span class="ident">ttl</span>: <span class="kw-2">&amp;</span><span class="ident">Duration</span>) -&gt; <span class="prelude-ty">Result</span><span class="op">&lt;</span>(), <span class="ident">Error</span><span class="op">&gt;</span> {
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
<span class="kw">async fn </span>update_ttl(<span class="kw-2">&amp;</span><span class="self">self</span>, session_key: <span class="kw-2">&amp;</span>SessionKey, ttl: <span class="kw-2">&amp;</span>Duration) -&gt; <span class="prelude-ty">Result</span>&lt;(), Error&gt; {
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
<span class="kw">let</span> <span class="ident">cmd</span> <span class="op">=</span> <span class="ident">Command</span>(<span class="macro">resp_array!</span>[
<span class="kw">let </span>cmd = Command(<span class="macro">resp_array!</span>[
<span class="string">&quot;EXPIRE&quot;</span>,
<span class="ident">cache_key</span>,
<span class="ident">ttl</span>.<span class="ident">whole_seconds</span>().<span class="ident">to_string</span>()
cache_key,
ttl.whole_seconds().to_string()
]);
<span class="kw">match</span> <span class="self">self</span>.<span class="ident">addr</span>.<span class="ident">send</span>(<span class="ident">cmd</span>).<span class="kw">await</span><span class="question-mark">?</span> {
<span class="prelude-val">Ok</span>(<span class="ident">RespValue::Integer</span>(<span class="kw">_</span>)) =&gt; <span class="prelude-val">Ok</span>(()),
<span class="ident">val</span> =&gt; <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
<span class="kw">match </span><span class="self">self</span>.addr.send(cmd).<span class="kw">await</span><span class="question-mark">? </span>{
<span class="prelude-val">Ok</span>(RespValue::Integer(<span class="kw">_</span>)) =&gt; <span class="prelude-val">Ok</span>(()),
val =&gt; <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
<span class="string">&quot;Failed to update the session state TTL: {:?}&quot;</span>,
<span class="ident">val</span>
val
)),
}
}
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">delete</span>(<span class="kw-2">&amp;</span><span class="self">self</span>, <span class="ident">session_key</span>: <span class="kw-2">&amp;</span><span class="ident">SessionKey</span>) -&gt; <span class="prelude-ty">Result</span><span class="op">&lt;</span>(), <span class="ident">anyhow::Error</span><span class="op">&gt;</span> {
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
<span class="kw">async fn </span>delete(<span class="kw-2">&amp;</span><span class="self">self</span>, session_key: <span class="kw-2">&amp;</span>SessionKey) -&gt; <span class="prelude-ty">Result</span>&lt;(), anyhow::Error&gt; {
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
<span class="kw">let</span> <span class="ident">res</span> <span class="op">=</span> <span class="self">self</span>
.<span class="ident">addr</span>
.<span class="ident">send</span>(<span class="ident">Command</span>(<span class="macro">resp_array!</span>[<span class="string">&quot;DEL&quot;</span>, <span class="ident">cache_key</span>]))
<span class="kw">let </span>res = <span class="self">self
</span>.addr
.send(Command(<span class="macro">resp_array!</span>[<span class="string">&quot;DEL&quot;</span>, cache_key]))
.<span class="kw">await</span><span class="question-mark">?</span>;
<span class="kw">match</span> <span class="ident">res</span> {
<span class="comment">// Redis returns the number of deleted records</span>
<span class="prelude-val">Ok</span>(<span class="ident">RespValue::Integer</span>(<span class="kw">_</span>)) =&gt; <span class="prelude-val">Ok</span>(()),
<span class="ident">val</span> =&gt; <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
<span class="kw">match </span>res {
<span class="comment">// Redis returns the number of deleted records
</span><span class="prelude-val">Ok</span>(RespValue::Integer(<span class="kw">_</span>)) =&gt; <span class="prelude-val">Ok</span>(()),
val =&gt; <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
<span class="string">&quot;Failed to remove session from cache. {:?}&quot;</span>,
<span class="ident">val</span>
val
)),
}
}
}
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">test</span>)]</span>
<span class="kw">mod</span> <span class="ident">tests</span> {
<span class="kw">use</span> <span class="ident">std::collections::HashMap</span>;
<span class="attribute">#[cfg(test)]
</span><span class="kw">mod </span>tests {
<span class="kw">use </span>std::collections::HashMap;
<span class="kw">use</span> <span class="ident">actix_web::cookie::time::Duration</span>;
<span class="kw">use </span>actix_web::cookie::time::Duration;
<span class="kw">use</span> <span class="kw">super</span>::<span class="kw-2">*</span>;
<span class="kw">use</span> <span class="ident"><span class="kw">crate</span>::test_helpers::acceptance_test_suite</span>;
<span class="kw">use super</span>::<span class="kw-2">*</span>;
<span class="kw">use </span><span class="kw">crate</span>::test_helpers::acceptance_test_suite;
<span class="kw">fn</span> <span class="ident">redis_actor_store</span>() -&gt; <span class="ident">RedisActorSessionStore</span> {
<span class="ident">RedisActorSessionStore::new</span>(<span class="string">&quot;127.0.0.1:6379&quot;</span>)
<span class="kw">fn </span>redis_actor_store() -&gt; RedisActorSessionStore {
RedisActorSessionStore::new(<span class="string">&quot;127.0.0.1:6379&quot;</span>)
}
<span class="attribute">#[<span class="ident">actix_web::test</span>]</span>
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">test_session_workflow</span>() {
<span class="ident">acceptance_test_suite</span>(<span class="ident">redis_actor_store</span>, <span class="bool-val">true</span>).<span class="kw">await</span>;
<span class="attribute">#[actix_web::test]
</span><span class="kw">async fn </span>test_session_workflow() {
acceptance_test_suite(redis_actor_store, <span class="bool-val">true</span>).<span class="kw">await</span>;
}
<span class="attribute">#[<span class="ident">actix_web::test</span>]</span>
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">loading_a_missing_session_returns_none</span>() {
<span class="kw">let</span> <span class="ident">store</span> <span class="op">=</span> <span class="ident">redis_actor_store</span>();
<span class="kw">let</span> <span class="ident">session_key</span> <span class="op">=</span> <span class="ident">generate_session_key</span>();
<span class="macro">assert!</span>(<span class="ident">store</span>.<span class="ident">load</span>(<span class="kw-2">&amp;</span><span class="ident">session_key</span>).<span class="kw">await</span>.<span class="ident">unwrap</span>().<span class="ident">is_none</span>());
<span class="attribute">#[actix_web::test]
</span><span class="kw">async fn </span>loading_a_missing_session_returns_none() {
<span class="kw">let </span>store = redis_actor_store();
<span class="kw">let </span>session_key = generate_session_key();
<span class="macro">assert!</span>(store.load(<span class="kw-2">&amp;</span>session_key).<span class="kw">await</span>.unwrap().is_none());
}
<span class="attribute">#[<span class="ident">actix_web::test</span>]</span>
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">updating_of_an_expired_state_is_handled_gracefully</span>() {
<span class="kw">let</span> <span class="ident">store</span> <span class="op">=</span> <span class="ident">redis_actor_store</span>();
<span class="kw">let</span> <span class="ident">session_key</span> <span class="op">=</span> <span class="ident">generate_session_key</span>();
<span class="kw">let</span> <span class="ident">initial_session_key</span> <span class="op">=</span> <span class="ident">session_key</span>.<span class="ident">as_ref</span>().<span class="ident">to_owned</span>();
<span class="kw">let</span> <span class="ident">updated_session_key</span> <span class="op">=</span> <span class="ident">store</span>
.<span class="ident">update</span>(<span class="ident">session_key</span>, <span class="ident">HashMap::new</span>(), <span class="kw-2">&amp;</span><span class="ident">Duration::seconds</span>(<span class="number">1</span>))
.<span class="kw">await</span>
.<span class="ident">unwrap</span>();
<span class="macro">assert_ne!</span>(<span class="ident">initial_session_key</span>, <span class="ident">updated_session_key</span>.<span class="ident">as_ref</span>());
<span class="attribute">#[actix_web::test]
</span><span class="kw">async fn </span>updating_of_an_expired_state_is_handled_gracefully() {
<span class="kw">let </span>store = redis_actor_store();
<span class="kw">let </span>session_key = generate_session_key();
<span class="kw">let </span>initial_session_key = session_key.as_ref().to_owned();
<span class="kw">let </span>updated_session_key = store
.update(session_key, HashMap::new(), <span class="kw-2">&amp;</span>Duration::seconds(<span class="number">1</span>))
.<span class="kw">await
</span>.unwrap();
<span class="macro">assert_ne!</span>(initial_session_key, updated_session_key.as_ref());
}
}
</code></pre></div>
</section></div></main><div id="rustdoc-vars" data-root-path="../../../" data-current-crate="actix_session" data-themes="ayu,dark,light" data-resource-suffix="" data-rustdoc-version="1.65.0-nightly (34a6cae28 2022-08-09)" ></div></body></html>
</section></div></main><div id="rustdoc-vars" data-root-path="../../../" data-current-crate="actix_session" data-themes="ayu,dark,light" data-resource-suffix="" data-rustdoc-version="1.65.0-nightly (060e47f74 2022-08-23)" ></div></body></html>