1use std::rc::Rc;2use std::{any::Any, cell::RefCell, future::Future};34use jrsonnet_gcmodule::Acyclic;5use jrsonnet_ir::visit::Visitor;6use jrsonnet_ir::{IStr, Source, SourcePath};7use rustc_hash::FxHashMap;89use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};1011pub struct Import {12 path: ResolvePathOwned,13 expression: bool,14}1516pub struct FoundImports(Vec<Import>);17impl Visitor for FoundImports {18 fn visit_import(&mut self, expression: bool, value: IStr) {19 self.0.push(Import {20 path: ResolvePathOwned::Str(value.to_string()),21 expression,22 });23 }24}2526pub trait AsyncImportResolver {27 type Error;28 29 30 31 32 33 34 fn resolve_from(35 &self,36 from: &SourcePath,37 path: &dyn AsPathLike,38 ) -> impl Future<Output = Result<SourcePath, Self::Error>>;39 fn resolve_from_default(40 &self,41 path: &dyn AsPathLike,42 ) -> impl Future<Output = Result<SourcePath, Self::Error>> {43 async { self.resolve_from(&SourcePath::default(), path).await }44 }4546 47 48 49 50 51 fn load_file_contents(52 &self,53 resolved: &SourcePath,54 ) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;55}5657#[derive(Acyclic)]58struct ResolvedImportResolver {59 resolved: RefCell<FxHashMap<(SourcePath, ResolvePathOwned), (SourcePath, bool)>>,60}61impl ImportResolver for ResolvedImportResolver {62 fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {63 unreachable!("all files should be loaded at this point");64 }6566 fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> crate::Result<SourcePath> {67 Ok(self68 .resolved69 .borrow()70 .get(&(from.clone(), path.as_path().to_owned()))71 .expect("all imports should be resolved at this point")72 .073 .clone())74 }7576 fn resolve_from_default(&self, path: &dyn AsPathLike) -> crate::Result<SourcePath> {77 self.resolve_from(&SourcePath::default(), path)78 }79}8081enum Job {82 LoadFile { path: SourcePath, parse: bool },83 ParseFile(SourcePath),84 ResolveImport { from: SourcePath, import: Import },85}8687#[allow(clippy::future_not_send)]88pub async fn async_import<H>(s: State, handler: H, path: &dyn AsPathLike) -> Result<(), H::Error>89where90 H: AsyncImportResolver,91{92 let resolved = (s.import_resolver() as &dyn Any)93 .downcast_ref::<ResolvedImportResolver>()94 .expect("for async imports, import_resolver should be set to ResolvedImportResolver");9596 let mut queue = vec![Job::LoadFile {97 path: handler.resolve_from_default(path).await?,98 parse: true,99 }];100 while let Some(job) = queue.pop() {101 match job {102 Job::LoadFile { path, parse } => {103 if !s.0.file_cache.borrow().contains_key(&path) {104 let data = handler.load_file_contents(&path).await?;105 s.0.file_cache106 .borrow_mut()107 .insert(path.clone(), FileData::new_bytes(data.as_slice().into()));108 }109 if parse {110 queue.push(Job::ParseFile(path));111 }112 }113 Job::ParseFile(path) => {114 if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {115 if file.parsed.is_none() {116 let Some(code) = file.get_string() else {117 continue;118 };119 let source = Source::new(path.clone(), code.clone());120 121 file.parsed = crate::parse_jsonnet(&code, source).map(Rc::new).ok();122 if let Some(parsed) = &file.parsed {123 let mut imports = FoundImports(vec![]);124 imports.visit_expr(parsed);125 for import in imports.0 {126 queue.push(Job::ResolveImport {127 from: path.clone(),128 import,129 });130 }131 }132 }133 }134 }135 Job::ResolveImport { from, import } => {136 {137 let mut resolved_map = resolved.resolved.borrow_mut();138 if let Some((resolved, expression)) =139 resolved_map.get_mut(&(from.clone(), import.path.clone()))140 {141 if import.expression && !*expression {142 *expression = true;143 queue.push(Job::ParseFile(resolved.clone()));144 }145 continue;146 }147 }148 let resolved = handler.resolve_from(&from, &import.path).await?;149 queue.push(Job::LoadFile {150 path: resolved,151 parse: import.expression,152 });153 }154 }155 }156 Ok(())157}