neutralts/bif/
parse_bif_obj.rs

1#![doc = include_str!("../../doc/bif-obj.md")]
2
3use crate::{
4    bif::{constants::*, Bif, BifError, PythonExecutor},
5    constants::*,
6    utils::is_empty_key,
7    Value,
8};
9use std::fs;
10use std::path::Path;
11
12impl<'a> Bif<'a> {
13    /*
14        {:obj; ...  :}
15        {:obj; {:flags; inline :} --- >>  <div>...</div>  :}
16    */
17    pub(crate) fn parse_bif_obj(&mut self) -> Result<(), BifError> {
18        if self.mod_filter || self.mod_negate {
19            return Err(self.bif_error(BIF_ERROR_MODIFIER_NOT_ALLOWED));
20        }
21
22        let mut added_bif_code = false;
23        if !self.src.contains(BIF_CODE) {
24            self.src.push_str(BIF_CODE);
25            added_bif_code = true;
26        }
27
28        self.extract_params_code(false);
29
30        if added_bif_code {
31            self.src.truncate(self.src.len() - BIF_CODE.len());
32        }
33
34        if !self.flags.is_empty() {
35            return Err(self.bif_error(BIF_ERROR_FLAGS_NOT_ALLOWED));
36        }
37
38        let obj_raw;
39        if self.params.starts_with('{')
40            && self.params.ends_with('}')
41            && self.params.chars().nth(1) != Some(':')
42            && self.params.chars().nth(self.params.len() - 2) != Some(':')
43        {
44            // json inline
45            obj_raw = self.params.clone();
46        } else {
47            if self.params.contains(BIF_OPEN) {
48                self.params = new_child_parse!(self, &self.params, false);
49            }
50
51            self.file_path = self.params.clone();
52
53            // For security requires {:allow;
54            if self.file_path.contains(BIF_OPEN) {
55                if !self.contains_allow(&self.file_path) {
56                    return Err(self.bif_error(BIF_ERROR_INSECURE_FILE_NAME));
57                }
58                self.file_path = new_child_parse!(self, &self.params, false);
59            }
60
61            if let Some(stripped) = self.file_path.strip_prefix('#') {
62                self.file_path = format!("{}{}", self.inherit.current_dir, stripped);
63            }
64
65            let path = Path::new(&self.file_path);
66            if !path.exists() {
67                return Err(self.bif_error(BIF_ERROR_FILE_NOT_FOUND));
68            }
69
70            obj_raw = fs::read_to_string(&self.file_path)
71                .map_err(|e| self.bif_error(&format!("Failed to read file: {}", e)))?;
72        }
73
74        let mut obj: Value = serde_json::from_str(obj_raw.trim())
75            .map_err(|e| self.bif_error(&format!("Failed to parse JSON: {}", e)))?;
76
77        let engine = obj["engine"].as_str().unwrap_or(DEFAULT_OBJ_ENGINE);
78        if engine.to_lowercase() != "python" {
79            // currently only Python is supported
80            return Err(self.bif_error(BIF_ERROR_ONLY_PYTHON_ENGINE));
81        }
82
83        if !self.flags.contains("|inline|") {
84            self.parse_obj_values(&mut obj, false);
85        }
86
87        let mut file_path_obj = obj["file"].as_str().unwrap_or("").to_string();
88
89        if let Some(stripped) = file_path_obj.strip_prefix('#') {
90            file_path_obj = format!("{}{}", self.inherit.current_dir, stripped);
91        }
92
93        if !Path::new(&file_path_obj).exists() {
94            return Err(self.bif_error(BIF_ERROR_OBJ_FILE_NOT_FOUND));
95        }
96
97        let mut venv_path = obj["venv"].as_str().unwrap_or("").to_string();
98
99        if !venv_path.is_empty() {
100            if let Some(stripped) = venv_path.strip_prefix('#') {
101                venv_path = format!("{}{}", self.inherit.current_dir, stripped);
102            }
103
104            if !Path::new(&venv_path).exists() {
105                return Err(self.bif_error("venv path does not exist"));
106            }
107        }
108
109        let result = PythonExecutor::exec_py(
110            &file_path_obj,
111            &obj["params"],
112            obj["callback"].as_str().unwrap_or(DEFAULT_OBJ_CALLBACK),
113            if obj.get("schema").and_then(|v| v.as_bool()).unwrap_or(false) {
114                Some(&self.shared.schema)
115            } else {
116                None
117            },
118            if venv_path.is_empty() {
119                None
120            } else {
121                Some(venv_path.as_str())
122            },
123        )?;
124
125        let mut code = String::new();
126        if !is_empty_key(&result, "data") {
127            let data = serde_json::to_string(&result).unwrap();
128            code = String::from("{:data;{:flg; inline :}>>") + &data + ":}";
129        }
130        if !is_empty_key(&obj, "template") {
131            let template = obj["template"].as_str().unwrap();
132            code = code + "{:include;" + template + ":}";
133        }
134        self.code = code + &self.code.clone();
135
136        if self.code.contains(BIF_OPEN) {
137            self.out = new_child_parse!(self, &self.code, self.mod_scope);
138        } else {
139            self.out = self.code.clone();
140        }
141
142        Ok(())
143    }
144
145    fn parse_obj_values(&mut self, value: &mut Value, is_recursive_call: bool) {
146        if let Value::Object(map) = value {
147            for (key, val) in map.iter_mut() {
148                if key == "file" || key == "template" || key == "venv" {
149                    if let Value::String(s) = val {
150                        if s.contains(BIF_OPEN) {
151                            *val = Value::String(new_child_parse!(self, s, false));
152                        }
153                    }
154                } else if key == "params" || is_recursive_call {
155                    // Only "params" needs recursion.
156                    if let Value::String(s) = val {
157                        if s.contains(BIF_OPEN) {
158                            *val = Value::String(new_child_parse!(self, s, false));
159                        }
160                    } else if let Value::Object(_) = val {
161                        self.parse_obj_values(val, true);
162                    }
163                }
164            }
165        }
166    }
167}
168
169#[cfg(test)]
170#[path = "parse_bif_obj_tests.rs"]
171mod tests;