zen_engine/loader/
filesystem.rs

1use std::collections::HashMap;
2use std::fs::File;
3use std::future::Future;
4use std::io::BufReader;
5use std::path::{Path, PathBuf};
6use std::pin::Pin;
7use std::sync::Arc;
8
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11
12use crate::loader::{DecisionLoader, LoaderError, LoaderResponse};
13use crate::model::DecisionContent;
14
15/// Loads decisions based on filesystem root
16#[derive(Debug)]
17pub struct FilesystemLoader {
18    root: String,
19    memory_refs: Option<RwLock<HashMap<String, Arc<DecisionContent>>>>,
20}
21
22#[derive(Serialize, Deserialize)]
23pub struct FilesystemLoaderOptions<R: Into<String>> {
24    pub root: R,
25    pub keep_in_memory: bool,
26}
27
28impl FilesystemLoader {
29    pub fn new<R>(options: FilesystemLoaderOptions<R>) -> Self
30    where
31        R: Into<String>,
32    {
33        let root = options.root.into();
34        let memory_refs = if options.keep_in_memory {
35            Some(Default::default())
36        } else {
37            None
38        };
39
40        Self { root, memory_refs }
41    }
42
43    fn key_to_path<K: AsRef<str>>(&self, key: K) -> PathBuf {
44        Path::new(&self.root).join(key.as_ref())
45    }
46
47    async fn read_from_file<K>(&self, key: K) -> LoaderResponse
48    where
49        K: AsRef<str>,
50    {
51        if let Some(memory_refs) = &self.memory_refs {
52            let mref = memory_refs.read().await;
53            if let Some(decision_content) = mref.get(key.as_ref()) {
54                return Ok(decision_content.clone());
55            }
56        }
57
58        let path = self.key_to_path(key.as_ref());
59        if !Path::exists(&path) {
60            return Err(LoaderError::NotFound(String::from(key.as_ref())).into());
61        }
62
63        let file = File::open(path).map_err(|e| LoaderError::Internal {
64            key: String::from(key.as_ref()),
65            source: e.into(),
66        })?;
67
68        let reader = BufReader::new(file);
69        let result: DecisionContent =
70            serde_json::from_reader(reader).map_err(|e| LoaderError::Internal {
71                key: String::from(key.as_ref()),
72                source: e.into(),
73            })?;
74
75        let ptr = Arc::new(result);
76        if let Some(memory_refs) = &self.memory_refs {
77            let mut mref = memory_refs.write().await;
78            mref.insert(key.as_ref().to_string(), ptr.clone());
79        }
80
81        Ok(ptr)
82    }
83}
84
85impl DecisionLoader for FilesystemLoader {
86    fn load<'a>(
87        &'a self,
88        key: &'a str,
89    ) -> Pin<Box<dyn Future<Output = LoaderResponse> + 'a + Send>> {
90        Box::pin(async move { self.read_from_file(key).await })
91    }
92}