artifacts_crate/
lib.rs

1use std::fmt::Debug;
2use std::path::{Path};
3use std::time::Duration;
4use once_cell::sync::{OnceCell};
5use tokio::fs::File;
6use tokio::io::AsyncReadExt;
7use tokio::sync::{RwLock, RwLockReadGuard};
8
9/*
10The artifacts module provides a generic Artifact<T> struct for managing shared read/write access to data stored in a JSON file.
11The Artifact struct supports initializing the data from a JSON file, getting a read lock on the data, updating the data, and automatically reloading of the data at a specified interval.
12
13The Artifact struct is designed to be used with a static global variable and ensures safe concurrent access to the data across multiple threads.
14The ARTIFACTS_PATH environment variable is used to locate the JSON file and defaults to "./artifacts" if not found.
15
16Usage example:
17
181. Define your struct and make sure it implements DeserializeOwned:
19
20#[derive(Deserialize, Debug)]
21struct ExampleStruct {
22    field1: String,
23    field2: u32,
24}
25
262. Initialize the Artifact with your struct:
27
28pub static EXAMPLE_LIST: Artifact<ExampleStruct> = Artifact::new();
29
303. In your main function or another appropriate place, initialize the Artifact data and spawn the watch task:
31
32#[tokio::main]
33async fn main() {
34    // Initialize the EXAMPLE_LIST data
35    EXAMPLE_LIST.init("example.json").await.unwrap();
36
37    // Spawn a background task to watch and reload the EXAMPLE_LIST data every 6 hours
38    tokio::spawn(EXAMPLE_LIST.watch("example.json".to_string(), 6 * 60 * 60));
39
40    {
41        // Example of getting the data
42        let example_data = EXAMPLE_LIST.get().await.unwrap();
43        println!("Example data: {:?}", *example_data);
44    }
45
46    {
47        // Example of updating the data (assuming you have new_data available)
48        let new_data = ExampleStruct { field1: "New field1 value".to_string(), field2: 42 };
49        EXAMPLE_LIST.update(new_data).await.unwrap();
50    }
51}
52*/
53
54// The Artifact struct wraps an object of type T, which will be loaded from a JSON file.
55// It uses OnceCell to ensure that the data is initialized only once, and RwLock to allow
56// concurrent reads while providing exclusive access for updates.
57pub struct Artifact<T> {
58    data: OnceCell<RwLock<T>>,
59}
60
61impl<T: Debug + serde::de::DeserializeOwned + Send + Sync + 'static> Artifact<T> {
62    // Creates a new, uninitialized Artifact instance.
63    pub const fn new() -> Self {
64        Artifact {
65            data: OnceCell::new(),
66        }
67    }
68
69    // Initializes the Artifact data by loading it from the specified JSON file.
70    // This method can be called multiple times, but the data will be initialized only once.
71    // If the data is already initialized, this method does nothing.
72    pub async fn init(&self, artifact_file: &str) -> Result<(), ArtifactError> {
73        if self.data.get().is_none() {
74            let artifacts_path = get_env_or_default("ARTIFACTS_PATH", "artifacts".to_string());
75            let path = Path::new(&artifacts_path).join(artifact_file);
76
77            let data = get_data::<T>(&path).await.map_err(|err| ArtifactError::InitializationError(err.to_string()))?;
78
79            self.data.set(RwLock::new(data)).unwrap();
80        }
81        Ok(())
82    }
83
84    // Provides read access to the Artifact data.
85    // This method returns a read guard, which allows multiple concurrent reads.
86    pub async fn get(&self) -> Result<RwLockReadGuard<'_, T>, ArtifactError> {
87        let data_lock = self.data.get().expect("Artifact is not initialized");
88        Ok(data_lock.read().await)
89    }
90
91    // Updates the Artifact data with the provided new_data.
92    // This method provides exclusive write access to the data, blocking other reads and writes
93    // while the update is in progress.
94    pub async fn update(&self, new_data: T) -> Result<(), ArtifactError> {
95        let data_lock = self.data.get().expect("Artifact is not initialized");
96        let mut data = data_lock.write().await;
97        *data = new_data;
98
99        Ok(())
100    }
101
102    // Starts a task that periodically reloads the Artifact data from the specified JSON file.
103    // The task runs indefinitely, reloading the data at the specified interval in seconds.
104    pub async fn watch(&self, artifact_file: String, interval_secs: u64) -> Result<(), ArtifactError> {
105        let artifacts_path = get_env_or_default("ARTIFACTS_PATH", "artifacts".to_string());
106        let path = Path::new(&artifacts_path).join(artifact_file);
107
108        loop {
109            tokio::time::sleep(Duration::from_secs(interval_secs)).await;
110
111            match get_data::<T>(&path).await {
112                Ok(new_data) => {
113                    if let Err(e) = self.update(new_data).await {
114                        return Err(ArtifactError::UpdateError(e.to_string()));
115                    }
116                }
117                Err(e) => {
118                    return Err(ArtifactError::WatchError(e.to_string()));
119                }
120            }
121        }
122    }
123}
124
125// A helper function to read and deserialize data from a JSON file.
126// The function is generic over the type T, which must implement the DeserializeOwned trait.
127async fn get_data<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T, ArtifactError> {
128    let mut file = File::open(path).await.map_err(|err| ArtifactError::IoError(err))?;
129    let mut contents = String::new();
130    file.read_to_string(&mut contents).await.map_err(|err| ArtifactError::IoError(err))?;
131    let data: T = serde_json::from_str(&contents).map_err(|err| ArtifactError::SerdeError(err))?;
132    Ok(data)
133}
134
135pub fn get_env_or_default(key: &str, default: String) -> String {
136    std::env::var(key).unwrap_or(default)
137}
138
139#[derive(Debug)]
140pub enum ArtifactError {
141    IoError(std::io::Error),
142    SerdeError(serde_json::Error),
143    InitializationError(String),
144    UpdateError(String),
145    WatchError(String),
146}
147
148impl std::fmt::Display for ArtifactError {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        match self {
151            ArtifactError::IoError(err) => write!(f, "I/O error: {}", err),
152            ArtifactError::SerdeError(err) => write!(f, "JSON serialization/deserialization error: {}", err),
153            ArtifactError::InitializationError(msg) => write!(f, "Initialization error: {}", msg),
154            ArtifactError::UpdateError(msg) => write!(f, "Update error: {}", msg),
155            ArtifactError::WatchError(msg) => write!(f, "Watch error: {}", msg),
156        }
157    }
158}
159
160impl std::error::Error for ArtifactError {}
161
162impl From<std::io::Error> for ArtifactError {
163    fn from(err: std::io::Error) -> Self {
164        ArtifactError::IoError(err)
165    }
166}
167
168impl From<serde_json::Error> for ArtifactError {
169    fn from(err: serde_json::Error) -> Self {
170        ArtifactError::SerdeError(err)
171    }
172}