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