1use std::rc::Rc;2use std::{any::Any, cell::RefCell, future::Future};34use jrsonnet_gcmodule::Acyclic;5use jrsonnet_ir::visit::Visitor;6use jrsonnet_ir::{7 ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, ExprParam, ExprParams,8 FieldMember, FieldName, ForSpecData, IStr, IfElse, IfSpecData, ImportKind, ObjBody, Slice,9 SliceDesc, Source, SourcePath, Spanned,10};11use rustc_hash::FxHashMap;1213use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};1415pub struct Import {16 path: ResolvePathOwned,17 expression: bool,18}1920pub struct FoundImports(Vec<Import>);21impl Visitor for FoundImports {22 fn visit_import(&mut self, expression: bool, value: IStr) {23 self.0.push(Import {24 path: ResolvePathOwned::Str(value.to_string()),25 expression,26 })27 }28}2930pub trait AsyncImportResolver {31 type Error;32 33 34 35 36 37 38 fn resolve_from(39 &self,40 from: &SourcePath,41 path: &dyn AsPathLike,42 ) -> impl Future<Output = Result<SourcePath, Self::Error>>;43 fn resolve_from_default(44 &self,45 path: &dyn AsPathLike,46 ) -> impl Future<Output = Result<SourcePath, Self::Error>> {47 async { self.resolve_from(&SourcePath::default(), path).await }48 }4950 51 52 53 54 55 fn load_file_contents(56 &self,57 resolved: &SourcePath,58 ) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;59}6061#[derive(Acyclic)]62struct ResolvedImportResolver {63 resolved: RefCell<FxHashMap<(SourcePath, ResolvePathOwned), (SourcePath, bool)>>,64}65impl ImportResolver for ResolvedImportResolver {66 fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {67 unreachable!("all files should be loaded at this point");68 }6970 fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> crate::Result<SourcePath> {71 Ok(self72 .resolved73 .borrow()74 .get(&(from.clone(), path.as_path().to_owned()))75 .expect("all imports should be resolved at this point")76 .077 .clone())78 }7980 fn resolve_from_default(&self, path: &dyn AsPathLike) -> crate::Result<SourcePath> {81 self.resolve_from(&SourcePath::default(), path)82 }83}8485enum Job {86 LoadFile { path: SourcePath, parse: bool },87 ParseFile(SourcePath),88 ResolveImport { from: SourcePath, import: Import },89}9091#[allow(clippy::future_not_send)]92pub async fn async_import<H>(s: State, handler: H, path: &dyn AsPathLike) -> Result<(), H::Error>93where94 H: AsyncImportResolver,95{96 let resolved = (s.import_resolver() as &dyn Any)97 .downcast_ref::<ResolvedImportResolver>()98 .expect("for async imports, import_resolver should be set to ResolvedImportResolver");99100 let mut queue = vec![Job::LoadFile {101 path: handler.resolve_from_default(path).await?,102 parse: true,103 }];104 while let Some(job) = queue.pop() {105 match job {106 Job::LoadFile { path, parse } => {107 if !s.0.file_cache.borrow().contains_key(&path) {108 let data = handler.load_file_contents(&path).await?;109 s.0.file_cache110 .borrow_mut()111 .insert(path.clone(), FileData::new_bytes(data.as_slice().into()));112 }113 if parse {114 queue.push(Job::ParseFile(path));115 }116 }117 Job::ParseFile(path) => {118 if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {119 if file.parsed.is_none() {120 let Some(code) = file.get_string() else {121 continue;122 };123 let source = Source::new(path.clone(), code.clone());124 125 file.parsed = crate::parse_jsonnet(&code, source).map(Rc::new).ok();126 if let Some(parsed) = &file.parsed {127 let mut imports = FoundImports(vec![]);128 imports.visit_expr(parsed);129 for import in imports.0 {130 queue.push(Job::ResolveImport {131 from: path.clone(),132 import,133 });134 }135 }136 }137 }138 }139 Job::ResolveImport { from, import } => {140 {141 let mut resolved_map = resolved.resolved.borrow_mut();142 if let Some((resolved, expression)) =143 resolved_map.get_mut(&(from.clone(), import.path.clone()))144 {145 if import.expression && !*expression {146 *expression = true;147 queue.push(Job::ParseFile(resolved.clone()));148 }149 continue;150 }151 }152 let resolved = handler.resolve_from(&from, &import.path).await?;153 queue.push(Job::LoadFile {154 path: resolved,155 parse: import.expression,156 });157 }158 }159 }160 Ok(())161}