Skip to main content

jrsonnet_evaluator/
import.rs

1use crate::{
2	error::{Error::*, Result},
3	throw,
4};
5use fs::File;
6use jrsonnet_interner::IStr;
7use std::fs;
8use std::io::Read;
9use std::{
10	any::Any,
11	cell::RefCell,
12	collections::HashMap,
13	path::{Path, PathBuf},
14	rc::Rc,
15};
16
17/// Implements file resolution logic for `import` and `importStr`
18pub trait ImportResolver {
19	/// Resolves real file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
20	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`
21	/// where `${vendor}` is a library path.
22	fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>>;
23
24	/// Reads file from filesystem, should be used only with path received from `resolve_file`
25	fn load_file_contents(&self, resolved: &Path) -> Result<IStr>;
26
27	/// # Safety
28	///
29	/// For use only in bindings, should not be used elsewhere.
30	/// Implementations which are not intended to be used in bindings
31	/// should panic on call to this method.
32	unsafe fn as_any(&self) -> &dyn Any;
33}
34
35/// Dummy resolver, can't resolve/load any file
36pub struct DummyImportResolver;
37impl ImportResolver for DummyImportResolver {
38	fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>> {
39		throw!(ImportNotSupported(from.into(), path.into()))
40	}
41
42	fn load_file_contents(&self, _resolved: &Path) -> Result<IStr> {
43		// Can be only caused by library direct consumer, not by supplied jsonnet
44		panic!("dummy resolver can't load any file")
45	}
46
47	unsafe fn as_any(&self) -> &dyn Any {
48		panic!("`as_any($self)` is not supported by dummy resolver")
49	}
50}
51#[allow(clippy::use_self)]
52impl Default for Box<dyn ImportResolver> {
53	fn default() -> Self {
54		Box::new(DummyImportResolver)
55	}
56}
57
58/// File resolver, can load file from both FS and library paths
59#[derive(Default)]
60pub struct FileImportResolver {
61	/// Library directories to search for file.
62	/// Referred to as `jpath` in original jsonnet implementation.
63	pub library_paths: Vec<PathBuf>,
64}
65impl ImportResolver for FileImportResolver {
66	fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>> {
67		let mut direct = from.to_path_buf();
68		direct.push(path);
69		if direct.exists() {
70			Ok(direct.into())
71		} else {
72			for library_path in self.library_paths.iter() {
73				let mut cloned = library_path.clone();
74				cloned.push(path);
75				if cloned.exists() {
76					return Ok(cloned.into());
77				}
78			}
79			throw!(ImportFileNotFound(from.to_owned(), path.to_owned()))
80		}
81	}
82	fn load_file_contents(&self, id: &Path) -> Result<IStr> {
83		let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.to_owned()))?;
84		let mut out = String::new();
85		file.read_to_string(&mut out)
86			.map_err(|_e| ImportBadFileUtf8(id.to_owned()))?;
87		Ok(out.into())
88	}
89	unsafe fn as_any(&self) -> &dyn Any {
90		panic!("this resolver can't be used as any")
91	}
92}
93
94type ResolutionData = (PathBuf, PathBuf);
95
96/// Caches results of the underlying resolver
97pub struct CachingImportResolver {
98	resolution_cache: RefCell<HashMap<ResolutionData, Result<Rc<Path>>>>,
99	loading_cache: RefCell<HashMap<PathBuf, Result<IStr>>>,
100	inner: Box<dyn ImportResolver>,
101}
102impl ImportResolver for CachingImportResolver {
103	fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>> {
104		self.resolution_cache
105			.borrow_mut()
106			.entry((from.to_owned(), path.to_owned()))
107			.or_insert_with(|| self.inner.resolve_file(from, path))
108			.clone()
109	}
110
111	fn load_file_contents(&self, resolved: &Path) -> Result<IStr> {
112		self.loading_cache
113			.borrow_mut()
114			.entry(resolved.to_owned())
115			.or_insert_with(|| self.inner.load_file_contents(resolved))
116			.clone()
117	}
118	unsafe fn as_any(&self) -> &dyn Any {
119		panic!("this resolver can't be used as any")
120	}
121}