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}