zen_engine/loader/
filesystem.rs1use 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#[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}