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::sync::Arc;
7
8use serde::{Deserialize, Serialize};
9use tokio::sync::RwLock;
10
11use crate::loader::{DecisionLoader, LoaderError, LoaderResponse};
12use crate::model::DecisionContent;
13
14#[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>>(&self, key: K) -> PathBuf {
43 Path::new(&self.root).join(key.as_ref())
44 }
45
46 async fn read_from_file<K>(&self, key: K) -> LoaderResponse
47 where
48 K: AsRef<str>,
49 {
50 if let Some(memory_refs) = &self.memory_refs {
51 let mref = memory_refs.read().await;
52 if let Some(decision_content) = mref.get(key.as_ref()) {
53 return Ok(decision_content.clone());
54 }
55 }
56
57 let path = self.key_to_path(key.as_ref());
58 if !Path::exists(&path) {
59 return Err(LoaderError::NotFound(String::from(key.as_ref())).into());
60 }
61
62 let file = File::open(path).map_err(|e| LoaderError::Internal {
63 key: String::from(key.as_ref()),
64 source: e.into(),
65 })?;
66
67 let reader = BufReader::new(file);
68 let result: DecisionContent =
69 serde_json::from_reader(reader).map_err(|e| LoaderError::Internal {
70 key: String::from(key.as_ref()),
71 source: e.into(),
72 })?;
73
74 let ptr = Arc::new(result);
75 if let Some(memory_refs) = &self.memory_refs {
76 let mut mref = memory_refs.write().await;
77 mref.insert(key.as_ref().to_string(), ptr.clone());
78 }
79
80 Ok(ptr)
81 }
82}
83
84impl DecisionLoader for FilesystemLoader {
85 fn load<'a>(&'a self, key: &'a str) -> impl Future<Output = LoaderResponse> + 'a {
86 async move { self.read_from_file(key).await }
87 }
88}