celp_sdk/cache/redis_cache.rs
1//! Provides an interface to read from / update the CELP cache
2//!
3//! This include functionality to add / delete fields to / from the cache.
4
5use std::collections::HashMap;
6use std::os::unix::net::UnixStream;
7
8use prost::Message;
9use redis::{Client, Commands};
10use thiserror::Error;
11
12use crate::protobuf::se::AppInfo;
13use crate::warning;
14
15const CACHE_ENDPOINT: &str = "/run/celp/redis.sock";
16const CACHE_FALLBACK_ENDPOINT: &str = "redis://127.0.0.1/";
17
18const APP_INFO_HASH_KEY: &str = "celp:app_info";
19
20#[derive(Error, Debug)]
21pub enum CacheError {
22 #[error(transparent)]
23 RedisError(#[from] redis::RedisError),
24 #[error("constraint failed: {0}")]
25 ContstraintFailed(String),
26}
27
28// Define a struct to store the Redis connection
29pub struct Cache {
30 connection: redis::Connection,
31}
32
33impl Cache {
34 /// Creates a new Cache instance.
35 ///
36 /// This function establishes a connection to a Redis server and returns a Result
37 /// containing the Cache instance if successful, or a Boxed error if an error occurs.
38 ///
39 /// # Errors
40 ///
41 /// Returns a Boxed error if:
42 ///
43 /// - The URL for the Redis server is invalid or unreachable.
44 /// - The connection to the Redis server cannot be established.
45 ///
46 /// # Example
47 ///
48 /// ```rust,no_run
49 /// use celp_sdk::cache::Cache;
50 ///
51 /// let cache = Cache::new();
52 /// ```
53 pub fn new() -> Result<Self, CacheError> {
54 let client = match UnixStream::connect(CACHE_ENDPOINT) {
55 Ok(_) => Client::open(format!("unix:{}", CACHE_ENDPOINT))?,
56 Err(e) => {
57 warning!(
58 "Unable to open cache connection to {}: {e:?}. Trying fallback {}",
59 CACHE_ENDPOINT,
60 CACHE_FALLBACK_ENDPOINT
61 );
62 Client::open(CACHE_FALLBACK_ENDPOINT)?
63 }
64 };
65 let connection = client.get_connection()?;
66
67 Ok(Self { connection })
68 }
69
70 /// Adds a field and its corresponding value to a Redis hash.
71 ///
72 /// # Arguments
73 ///
74 /// * `key` - The key of the Redis hash.
75 /// * `field` - The field to set.
76 /// * `value` - The value to set for the field.
77 ///
78 /// # Example
79 ///
80 /// ```rust,no_run
81 /// use celp_sdk::cache::Cache;
82 ///
83 /// let cache = Cache::new();
84 /// if let Ok(mut c) = cache {
85 /// c.add_field("celp:app_info", "restful-svc", "serialized-version-str".as_bytes());
86 /// }
87 /// ```
88 pub fn add_field(&mut self, key: &str, field: &str, value: &[u8]) -> Result<i32, CacheError> {
89 let result: i32 = self.connection.hset(key, field, value)?;
90 // The function returns the number of added or updated fields. result is 1 for addition, 0 for an update,
91 // result>1 indicates unexpected additional fields added or updated.
92 if result > 1 {
93 return Err(CacheError::ContstraintFailed(format!(
94 "Failed to set value for key : '{}', field : '{}'",
95 key, field
96 )));
97 }
98
99 Ok(result)
100 }
101
102 /// Retrieves all fields and their corresponding values for a key from a Redis hash.
103 ///
104 /// # Arguments
105 ///
106 /// * `key` - The key of the Redis hash.
107 ///
108 /// # Example
109 ///
110 /// ```rust,no_run
111 /// use celp_sdk::cache::Cache;
112 ///
113 /// let cache = Cache::new();
114 /// if let Ok(mut c) = cache {
115 /// let data = c.get_fields("celp:app_info");
116 /// }
117 /// ```
118 pub fn get_fields(&mut self, key: &str) -> Result<HashMap<String, String>, CacheError> {
119 Ok(self.connection.hgetall(key)?)
120 }
121
122 /// Retrieves all field names from a Redis hash for a given key.
123 ///
124 /// # Arguments
125 ///
126 /// * `key` - The key of the Redis hash.
127 ///
128 /// # Example
129 ///
130 /// ```rust,no_run
131 /// use celp_sdk::cache::Cache;
132 ///
133 /// let cache = Cache::new();
134 /// if let Ok(mut c) = cache {
135 /// let field_names = c.get_field_names("celp:app_info");
136 /// }
137 /// ```
138 pub fn get_field_names(&mut self, key: &str) -> Result<Vec<String>, CacheError> {
139 Ok(self.connection.hkeys(key)?)
140 }
141
142 /// Retrieves a specific field's value from a Redis hash.
143 ///
144 /// # Arguments
145 ///
146 /// * `key` - The key of the Redis hash.
147 /// * `field` - The field whose value you want to retrieve.
148 ///
149 /// # Example
150 ///
151 /// ```rust,no_run
152 /// use celp_sdk::cache::Cache;
153 ///
154 /// let cache = Cache::new();
155 /// if let Ok(mut c) = cache {
156 /// let value = c.get_field_value("celp:app_info", "field_name");
157 /// }
158 /// ```
159 pub fn get_field_value(
160 &mut self,
161 key: &str,
162 field: &str,
163 ) -> Result<Option<String>, CacheError> {
164 let result = self.connection.hget(key, field)?;
165 Ok(result)
166 }
167
168 /// Checks if a specific field exists in a Redis hash.
169 ///
170 /// # Arguments
171 ///
172 /// * `key` - The key of the Redis hash.
173 /// * `field` - The field you want to check for existence.
174 ///
175 /// # Example
176 ///
177 /// ```rust,no_run
178 /// use celp_sdk::cache::Cache;
179 ///
180 /// let cache = Cache::new();
181 /// if let Ok(mut c) = cache {
182 /// let exists = c.field_exists("celp:app_info", "field_name");
183 /// }
184 /// ```
185 pub fn field_exists(&mut self, key: &str, field: &str) -> Result<bool, CacheError> {
186 let result: bool = self.connection.hexists(key, field)?;
187 Ok(result)
188 }
189
190 /// Deletes a specific field from a Redis hash.
191 ///
192 /// # Arguments
193 ///
194 /// * `key` - The key of the Redis hash.
195 /// * `field` - The field you want to delete.
196 ///
197 /// # Example
198 ///
199 /// ```rust,no_run
200 /// use celp_sdk::cache::Cache;
201 ///
202 /// let cache = Cache::new();
203 /// if let Ok(mut c) = cache {
204 /// let deleted = c.delete_field("celp:app_info", "field_name");
205 /// }
206 /// ```
207 pub fn delete_field(&mut self, key: &str, field: &str) -> Result<bool, CacheError> {
208 let deleted_count: i32 = self.connection.hdel(key, &[field])?;
209 Ok(deleted_count > 0)
210 }
211}
212
213/// Caches the application information, returns an error on failure
214/// # Arguments
215///
216/// * `app_info` - The application info
217///
218/// # Examples
219/// ```rust,no_run
220/// use celp_sdk::util::celp_app::build_app_info;
221/// use celp_sdk::cache::publish_app_info;
222///
223/// let app_info = build_app_info("1.0.0").unwrap();
224/// if let Err(e) = publish_app_info(app_info) {
225/// eprintln!("unable to publish or cache app info: {e:#?}");
226/// }
227/// ```
228pub fn publish_app_info(app_info: AppInfo) -> Result<(), CacheError> {
229 let buf = app_info.encode_to_vec();
230
231 // Adds a field and its corresponding value to a Redis hash
232 let mut cache = Cache::new()?;
233 cache.add_field(APP_INFO_HASH_KEY, &app_info.app_name, &buf)?;
234
235 Ok(())
236}