nova_boot_resilience_store/
lib.rs1use std::fmt;
10
11#[derive(Debug, Clone)]
13pub struct ResilienceError {
14 message: String,
15}
16
17impl ResilienceError {
18 pub fn new(message: impl Into<String>) -> Self {
20 Self {
21 message: message.into(),
22 }
23 }
24}
25
26impl fmt::Display for ResilienceError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "{}", self.message)
29 }
30}
31
32impl std::error::Error for ResilienceError {}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum LuaValue {
41 Nil,
42 Integer(i64),
43 Bytes(Vec<u8>),
44 Array(Vec<LuaValue>),
45 Status(String),
46 Ok,
47}
48
49impl LuaValue {
50 pub fn as_i64(&self) -> Option<i64> {
52 match self {
53 Self::Integer(value) => Some(*value),
54 _ => None,
55 }
56 }
57
58 pub fn as_bytes(&self) -> Option<&[u8]> {
60 match self {
61 Self::Bytes(value) => Some(value.as_slice()),
62 _ => None,
63 }
64 }
65
66 pub fn as_str(&self) -> Option<&str> {
68 self.as_bytes()
69 .and_then(|value| std::str::from_utf8(value).ok())
70 }
71}
72
73#[async_trait::async_trait]
78pub trait ResilienceStore: Send + Sync + 'static {
79 async fn incr(&self, key: &str) -> Result<i64, ResilienceError>;
80 async fn get_i64(&self, key: &str) -> Result<Option<i64>, ResilienceError>;
81 async fn set_ex(&self, key: &str, val: i64, ttl_seconds: usize) -> Result<(), ResilienceError>;
82 async fn del(&self, key: &str) -> Result<(), ResilienceError>;
83 async fn eval_lua(
84 &self,
85 script: &str,
86 keys: &[&str],
87 args: &[&str],
88 ) -> Result<LuaValue, ResilienceError>;
89}
90
91#[cfg(feature = "redis-store")]
92pub mod redis_store {
93 use super::{LuaValue, ResilienceError, ResilienceStore};
99 use redis::AsyncCommands;
100 use redis::Client;
101
102 #[derive(Clone)]
104 pub struct RedisStore {
105 client: Client,
106 }
107
108 impl RedisStore {
109 pub fn new(url: &str) -> Result<Self, ResilienceError> {
111 let client = Client::open(url).map_err(|e| ResilienceError::new(e.to_string()))?;
112 Ok(Self { client })
113 }
114 }
115
116 impl From<redis::Value> for LuaValue {
117 fn from(value: redis::Value) -> Self {
118 match value {
119 redis::Value::Nil => Self::Nil,
120 redis::Value::Int(value) => Self::Integer(value),
121 redis::Value::Data(value) => Self::Bytes(value),
122 redis::Value::Bulk(values) => {
123 Self::Array(values.into_iter().map(Self::from).collect())
124 }
125 redis::Value::Status(value) => Self::Status(value),
126 redis::Value::Okay => Self::Ok,
127 }
128 }
129 }
130
131 #[async_trait::async_trait]
132 impl ResilienceStore for RedisStore {
133 async fn incr(&self, key: &str) -> Result<i64, ResilienceError> {
134 let mut conn = self
135 .client
136 .get_tokio_connection()
137 .await
138 .map_err(|e| ResilienceError::new(e.to_string()))?;
139 let v: i64 = conn
140 .incr(key, 1)
141 .await
142 .map_err(|e| ResilienceError::new(e.to_string()))?;
143 Ok(v)
144 }
145
146 async fn get_i64(&self, key: &str) -> Result<Option<i64>, ResilienceError> {
147 let mut conn = self
148 .client
149 .get_tokio_connection()
150 .await
151 .map_err(|e| ResilienceError::new(e.to_string()))?;
152 let v: Option<i64> = conn
153 .get(key)
154 .await
155 .map_err(|e| ResilienceError::new(e.to_string()))?;
156 Ok(v)
157 }
158
159 async fn set_ex(
160 &self,
161 key: &str,
162 val: i64,
163 ttl_seconds: usize,
164 ) -> Result<(), ResilienceError> {
165 let mut conn = self
166 .client
167 .get_tokio_connection()
168 .await
169 .map_err(|e| ResilienceError::new(e.to_string()))?;
170 let () = conn
171 .set_ex(key, val, ttl_seconds)
172 .await
173 .map_err(|e| ResilienceError::new(e.to_string()))?;
174 Ok(())
175 }
176
177 async fn del(&self, key: &str) -> Result<(), ResilienceError> {
178 let mut conn = self
179 .client
180 .get_tokio_connection()
181 .await
182 .map_err(|e| ResilienceError::new(e.to_string()))?;
183 let () = conn
184 .del(key)
185 .await
186 .map_err(|e| ResilienceError::new(e.to_string()))?;
187 Ok(())
188 }
189
190 async fn eval_lua(
191 &self,
192 script: &str,
193 keys: &[&str],
194 args: &[&str],
195 ) -> Result<LuaValue, ResilienceError> {
196 let mut conn = self
197 .client
198 .get_tokio_connection()
199 .await
200 .map_err(|e| ResilienceError::new(e.to_string()))?;
201
202 let script = redis::Script::new(script);
203 let mut invocation = script.prepare_invoke();
204 for key in keys {
205 invocation.key(*key);
206 }
207 for arg in args {
208 invocation.arg(*arg);
209 }
210
211 let value: redis::Value = invocation
212 .invoke_async(&mut conn)
213 .await
214 .map_err(|e| ResilienceError::new(e.to_string()))?;
215 Ok(value.into())
216 }
217 }
218}