--- /dev/null +++ b/bindings/jsonnet/src/import.rs @@ -0,0 +1,153 @@ +//! Import resolution manipulation utilities + +use jrsonnet_evaluator::{ + create_error, create_error_result, Error, EvaluationState, ImportResolver, Result, +}; +use std::{ + any::Any, + cell::RefCell, + collections::HashMap, + ffi::{c_void, CStr, CString}, + fs::File, + io::Read, + os::raw::{c_char, c_int}, + path::PathBuf, + ptr::null_mut, + rc::Rc, +}; + +pub type JsonnetImportCallback = unsafe extern "C" fn( + ctx: *mut c_void, + base: *const c_char, + rel: *const c_char, + found_here: *mut *const c_char, + success: &mut c_int, +) -> *const c_char; + +/// Resolves imports using callback +pub struct CallbackImportResolver { + cb: JsonnetImportCallback, + ctx: *mut c_void, + + out: RefCell>>, +} +impl ImportResolver for CallbackImportResolver { + fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result> { + let base = CString::new(from.to_str().unwrap()).unwrap().into_raw(); + let rel = CString::new(path.to_str().unwrap()).unwrap().into_raw(); + let found_here: *mut c_char = null_mut(); + let mut success: i32 = 0; + let result_ptr = unsafe { + (self.cb)( + self.ctx, + base, + rel, + &mut (found_here as *const _), + &mut success, + ) + } as *mut i8; + // Release memory occipied by arguments passed + unsafe { + CString::from_raw(base); + CString::from_raw(rel); + } + let result_raw = unsafe { CStr::from_ptr(result_ptr) }; + let result_str = result_raw.to_str().unwrap(); + assert!(success == 0 || success == 1); + if success == 0 { + let result = result_str.to_owned(); + let err = Err(create_error(Error::ImportCallbackError(result))); + unsafe { CString::from_raw(result_ptr) }; + return err; + } + + let found_here_raw = unsafe { CStr::from_ptr(found_here) }; + let found_here_buf = PathBuf::from(found_here_raw.to_str().unwrap()); + unsafe { + CString::from_raw(found_here); + } + + let mut out = self.out.borrow_mut(); + if !out.contains_key(&found_here_buf) { + out.insert(found_here_buf.clone(), result_str.into()); + unsafe { CString::from_raw(result_ptr) }; + } + + Ok(Rc::new(found_here_buf)) + } + fn load_file_contents(&self, resolved: &PathBuf) -> Result> { + Ok(self.out.borrow().get(resolved).unwrap().clone()) + } + unsafe fn as_any(&self) -> &dyn Any { + self + } +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn jsonnet_import_callback( + vm: &EvaluationState, + cb: JsonnetImportCallback, + ctx: *mut c_void, +) { + vm.set_import_resolver(Box::new(CallbackImportResolver { + cb, + ctx, + out: RefCell::new(HashMap::new()), + })) +} + +/// Standard FS import resolver +#[derive(Default)] +pub struct NativeImportResolver { + library_paths: RefCell>, +} +impl NativeImportResolver { + fn add_jpath(&self, path: PathBuf) { + self.library_paths.borrow_mut().push(path); + } +} +impl ImportResolver for NativeImportResolver { + fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result> { + let mut new_path = from.clone(); + new_path.push(path); + if new_path.exists() { + Ok(Rc::new(new_path)) + } else { + for library_path in self.library_paths.borrow().iter() { + let mut cloned = library_path.clone(); + cloned.push(path); + if cloned.exists() { + return Ok(Rc::new(cloned)); + } + } + create_error_result(Error::ImportFileNotFound(from.clone(), path.clone())) + } + } + fn load_file_contents(&self, id: &PathBuf) -> Result> { + let mut file = + File::open(id).map_err(|_e| create_error(Error::ResolvedFileNotFound(id.clone())))?; + let mut out = String::new(); + file.read_to_string(&mut out) + .map_err(|_e| create_error(Error::ImportBadFileUtf8(id.clone())))?; + Ok(out.into()) + } + unsafe fn as_any(&self) -> &dyn Any { + self + } +} + +/// # Safety +/// +/// This function is safe, if received v is a pointer to normal C string +#[no_mangle] +pub unsafe extern "C" fn jsonnet_jpath_add(vm: &EvaluationState, v: *const c_char) { + let cstr = CStr::from_ptr(v); + let path = PathBuf::from(cstr.to_str().unwrap()); + let any_resolver = &vm.settings().import_resolver; + let resolver = any_resolver + .as_any() + .downcast_ref::() + .expect("jpaths are not compatible with callback imports!"); + resolver.add_jpath(path); +}