1//! Functions for querying the cache.
  2
  3use pyo3::{exceptions::PyIOError, prelude::*};
  4
  5use analyzer::data_model::{self as analyze_model};
  6
  7use crate::data_model::{Crate, Enum, Function, Module, Struct};
  8
  9fn read_file(path: &std::path::Path) -> PyResult<String> {
 10    match std::fs::read_to_string(path) {
 11        Ok(contents) => Ok(contents),
 12        Err(err) => Err(PyIOError::new_err(format!(
 13            "Could not read file: {}: {}",
 14            path.to_string_lossy(),
 15            err
 16        ))),
 17    }
 18}
 19
 20/// Deserialize an object from a string.
 21fn deserialize_object<'a, T>(name: &str, content: &'a str) -> PyResult<T>
 22where
 23    T: serde::Deserialize<'a>,
 24{
 25    let obj: T = match serde_json::from_str(content) {
 26        Ok(crate_) => crate_,
 27        Err(err) => {
 28            return Err(PyIOError::new_err(format!(
 29                "Could not deserialize {}: {}",
 30                name, err
 31            )))
 32        }
 33    };
 34    Ok(obj)
 35}
 36
 37#[pyfunction]
 38/// load a crate from the cache, if it exists
 39pub fn load_crate(cache_path: &str, name: &str) -> PyResult<Option<Crate>> {
 40    let path = std::path::Path::new(cache_path)
 41        .join("crates")
 42        .join(format!("{}.json", name));
 43    if !path.exists() {
 44        return Ok(None);
 45    }
 46    let contents = read_file(&path)?;
 47    let crate_: analyze_model::Crate = deserialize_object(name, &contents)?;
 48    Ok(Some(crate_.into()))
 49}
 50
 51#[pyfunction]
 52/// load a module from the cache, if it exists
 53pub fn load_module(cache_path: &str, full_name: &str) -> PyResult<Option<Module>> {
 54    let path = std::path::Path::new(cache_path)
 55        .join("modules")
 56        .join(format!("{}.json", full_name));
 57    if !path.exists() {
 58        return Ok(None);
 59    }
 60    let contents = read_file(&path)?;
 61    let mod_: analyze_model::Module = deserialize_object(full_name, &contents)?;
 62    Ok(Some(mod_.into()))
 63}
 64
 65#[pyfunction]
 66/// load a struct from the cache, if it exists
 67pub fn load_struct(cache_path: &str, full_name: &str) -> PyResult<Option<Struct>> {
 68    let path = std::path::Path::new(cache_path)
 69        .join("structs")
 70        .join(format!("{}.json", full_name));
 71    if !path.exists() {
 72        return Ok(None);
 73    }
 74    let contents = read_file(&path)?;
 75    let struct_: analyze_model::Struct = deserialize_object(full_name, &contents)?;
 76    Ok(Some(struct_.into()))
 77}
 78
 79#[pyfunction]
 80/// load an enum from the cache, if it exists
 81pub fn load_enum(cache_path: &str, full_name: &str) -> PyResult<Option<Enum>> {
 82    let path = std::path::Path::new(cache_path)
 83        .join("enums")
 84        .join(format!("{}.json", full_name));
 85    if !path.exists() {
 86        return Ok(None);
 87    }
 88    let contents = read_file(&path)?;
 89    let enum_: analyze_model::Enum = deserialize_object(full_name, &contents)?;
 90    Ok(Some(enum_.into()))
 91}
 92
 93#[pyfunction]
 94/// load a function from the cache, if it exists
 95pub fn load_function(cache_path: &str, full_name: &str) -> PyResult<Option<Function>> {
 96    let path = std::path::Path::new(cache_path)
 97        .join("functions")
 98        .join(format!("{}.json", full_name));
 99    if !path.exists() {
100        return Ok(None);
101    }
102    let contents = read_file(&path)?;
103    let func: analyze_model::Function = deserialize_object(full_name, &contents)?;
104    Ok(Some(func.into()))
105}
106
107/// Check if a path is a child of a given parent, and return the fully qualified name of the child.
108fn is_child(path: &std::path::PathBuf, parent: &Vec<String>) -> Option<String> {
109    let name = match path.file_stem() {
110        Some(name) => name,
111        None => return None,
112    };
113    let name = match name.to_str() {
114        Some(name) => name,
115        None => return None,
116    };
117    let name_path = name.split("::").collect::<Vec<_>>();
118    if name_path.len() != parent.len() + 1 {
119        return None;
120    }
121    for (a, b) in parent.iter().zip(name_path.iter().take(parent.len())) {
122        if a != b {
123            return None;
124        }
125    }
126    Some(name.to_string())
127}
128
129#[pyfunction]
130/// load all modules from the cache that are children of the given parent
131pub fn load_child_modules(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Module>> {
132    let path = std::path::Path::new(cache_path).join("modules");
133    if !path.exists() {
134        return Ok(vec![]);
135    }
136    let mut modules = vec![];
137    for entry in std::fs::read_dir(path)? {
138        let entry = entry?;
139        let path = entry.path();
140        if path.is_file() {
141            if let Some(name) = is_child(&path, &parent) {
142                let contents = read_file(&path)?;
143                let mod_: analyze_model::Module = deserialize_object(&name, &contents)?;
144                modules.push(mod_.into());
145            }
146        }
147    }
148    Ok(modules)
149}
150
151#[pyfunction]
152/// load all structs from the cache that are children of the given parent
153pub fn load_child_structs(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Struct>> {
154    let path = std::path::Path::new(cache_path).join("structs");
155    if !path.exists() {
156        return Ok(vec![]);
157    }
158    let mut structs = vec![];
159    for entry in std::fs::read_dir(path)? {
160        let entry = entry?;
161        let path = entry.path();
162        if path.is_file() {
163            if let Some(name) = is_child(&path, &parent) {
164                let contents = read_file(&path)?;
165                let struct_: analyze_model::Struct = deserialize_object(&name, &contents)?;
166                structs.push(struct_.into());
167            }
168        }
169    }
170    Ok(structs)
171}
172
173#[pyfunction]
174/// load all enums from the cache that are children of the given parent
175pub fn load_child_enums(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Enum>> {
176    let path = std::path::Path::new(cache_path).join("enums");
177    if !path.exists() {
178        return Ok(vec![]);
179    }
180    let mut enums = vec![];
181    for entry in std::fs::read_dir(path)? {
182        let entry = entry?;
183        let path = entry.path();
184        if path.is_file() {
185            if let Some(name) = is_child(&path, &parent) {
186                let contents = read_file(&path)?;
187                let enum_: analyze_model::Enum = deserialize_object(&name, &contents)?;
188                enums.push(enum_.into());
189            }
190        }
191    }
192    Ok(enums)
193}
194
195#[pyfunction]
196/// load all function from the cache that are children of the given parent
197pub fn load_child_functions(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Function>> {
198    let path = std::path::Path::new(cache_path).join("functions");
199    if !path.exists() {
200        return Ok(vec![]);
201    }
202    let mut funcs = vec![];
203    for entry in std::fs::read_dir(path)? {
204        let entry = entry?;
205        let path = entry.path();
206        if path.is_file() {
207            if let Some(name) = is_child(&path, &parent) {
208                let contents = read_file(&path)?;
209                let func: analyze_model::Function = deserialize_object(&name, &contents)?;
210                funcs.push(func.into());
211            }
212        }
213    }
214    Ok(funcs)
215}
216
217/// Check if a path is an ancestor of a given parent, and return the fully qualified name of the child.
218fn is_ancestor(
219    path: &std::path::PathBuf,
220    parent: &Vec<String>,
221    include_self: bool,
222) -> Option<String> {
223    let name = path.file_stem()?.to_str()?;
224    let name_path = name.split("::").collect::<Vec<_>>();
225    if include_self && name_path == parent.iter().map(|s| s.as_str()).collect::<Vec<_>>() {
226        return Some(name.to_string());
227    }
228    if name_path.len() <= parent.len() {
229        return None;
230    }
231    for (a, b) in parent.iter().zip(name_path.iter().take(parent.len())) {
232        if a != b {
233            return None;
234        }
235    }
236    Some(name.to_string())
237}
238
239#[pyfunction]
240/// load all modules from the cache that have a common descendant
241pub fn load_descendant_modules(
242    cache_path: &str,
243    ancestor: Vec<String>,
244    include_self: bool,
245) -> PyResult<Vec<Module>> {
246    let path = std::path::Path::new(cache_path).join("modules");
247    if !path.exists() {
248        return Ok(vec![]);
249    }
250    let mut modules = vec![];
251    for entry in std::fs::read_dir(path)? {
252        let entry = entry?;
253        let path = entry.path();
254        if path.is_file() {
255            if let Some(name) = is_ancestor(&path, &ancestor, include_self) {
256                let contents = read_file(&path)?;
257                let mod_: analyze_model::Module = deserialize_object(&name, &contents)?;
258                modules.push(mod_.into());
259            }
260        }
261    }
262    Ok(modules)
263}
264
265#[pyfunction]
266/// load all structs from the cache that have a common ancestor
267pub fn load_descendant_structs(cache_path: &str, ancestor: Vec<String>) -> PyResult<Vec<Struct>> {
268    let path = std::path::Path::new(cache_path).join("structs");
269    if !path.exists() {
270        return Ok(vec![]);
271    }
272    let mut structs = vec![];
273    for entry in std::fs::read_dir(path)? {
274        let entry = entry?;
275        let path = entry.path();
276        if path.is_file() {
277            if let Some(name) = is_ancestor(&path, &ancestor, false) {
278                let contents = read_file(&path)?;
279                let struct_: analyze_model::Struct = deserialize_object(&name, &contents)?;
280                structs.push(struct_.into());
281            }
282        }
283    }
284    Ok(structs)
285}
286
287#[pyfunction]
288/// load all enums from the cache that that have a common ancestor
289pub fn load_descendant_enums(cache_path: &str, ancestor: Vec<String>) -> PyResult<Vec<Enum>> {
290    let path = std::path::Path::new(cache_path).join("enums");
291    if !path.exists() {
292        return Ok(vec![]);
293    }
294    let mut enums = vec![];
295    for entry in std::fs::read_dir(path)? {
296        let entry = entry?;
297        let path = entry.path();
298        if path.is_file() {
299            if let Some(name) = is_ancestor(&path, &ancestor, false) {
300                let contents = read_file(&path)?;
301                let enum_: analyze_model::Enum = deserialize_object(&name, &contents)?;
302                enums.push(enum_.into());
303            }
304        }
305    }
306    Ok(enums)
307}