use std::cell::RefCell; use std::collections::{BTreeMap, BTreeSet}; use std::rc::Rc; use std::sync::Arc; use crate::authority::SourceAuthority; use crate::package_config::{PackageConfig, Source}; use crate::registry::{PackageStore, checksum}; use avril_core::Error; use url::Url; use crate::local_packages::{ PackageMapping, Resolution, declared_name, package_mapping, select_binding, }; /// A remote mapping step: an already-decided resolution, or an export of /// a directory by key. The adapters acquire (`export`) or reuse prepared /// metadata (`resolve_export_meta`) afterwards. enum RemoteStep { Done(Resolution), Export(Url, String), } struct Package { requested: Url, directory: Url, identity: (Url, String), config: PackageConfig, source: String, url: Url, bindings: RefCell>>, } impl Package { fn region(&self, name: &str) -> Option<&Url> { if name.starts_with(self.directory.as_str()) { Some(&self.directory) } else if name.starts_with(self.requested.as_str()) { Some(&self.requested) } else { None } } } #[derive(Clone)] struct Owner { package: Rc, region: Url, } pub struct RemotePackages { authority: SourceAuthority, store: Arc, packages: RefCell>>, owners: RefCell>>, authorities: RefCell>>, } impl RemotePackages { pub fn new(authority: SourceAuthority, store: Arc) -> Self { Self { authority, store, packages: RefCell::default(), owners: RefCell::default(), authorities: RefCell::default(), } } fn check(&self, package: &Package) -> Result<(), Error> { if let Some(urls) = self.authorities.borrow().get(&package.identity) { for url in urls { self.store .check_acquired_remote(url.as_str(), &self.authority) .map_err(|error| Error::Module(error.to_string()))?; } } self.store .check_acquired_remote(package.url.as_str(), &self.authority) .map_err(|error| Error::Module(error.to_string())) } async fn load(&self, directory: &Url) -> Result, Error> { let config_url = directory .join("avril.conf") .map_err(|error| Error::Module(error.to_string()))?; self.authority .check_remote_module(&config_url) .map_err(|error| Error::Module(error.to_string()))?; if let Some(package) = self.packages.borrow().get(directory).cloned() { self.check(&package)?; return Ok(package); } let source = self .store .remote_config(config_url.as_str()) .await .map_err(|error| Error::Module(error.to_string()))?; let base = Url::parse( &self .store .remote_base(config_url.as_str()) .map_err(|error| Error::Module(error.to_string()))?, ) .map_err(|error| Error::Module(error.to_string()))?; let config = PackageConfig::parse(&source, &base) .map_err(|error| Error::Module(error.render(config_url.as_str(), &source)))?; let package = Rc::new(Package { requested: directory.clone(), directory: base.join("./").unwrap(), identity: (base, checksum(source.as_bytes())), config, source, url: config_url, bindings: RefCell::default(), }); self.check(&package)?; Ok(self .packages .borrow_mut() .entry(directory.clone()) .or_insert(package) .clone()) } pub async fn bindings( &self, config: &PackageConfig, ) -> Result, crate::config::Error> { config .bindings(|source| async move { match source { Source::Local(directory) => declared_name(&directory, &self.authority), Source::Http(directory) => self .load(&directory) .await .map(|package| package.config.name.clone()) .map_err(|error| error.to_string()), Source::Jsr(_) => unreachable!(), Source::Npm(_) => unreachable!(), } }) .await } fn owner(&self, name: &str) -> Option { self.owners.borrow().get(name).cloned().flatten() } fn claim(&self, name: &str, owner: Option) -> Result<(), Error> { let mut owners = self.owners.borrow_mut(); if let Some(previous) = owners.get(name) { let same = match (previous, &owner) { (None, None) => true, (Some(previous), Some(owner)) => { previous.package.identity == owner.package.identity && previous.region == owner.region } _ => false, }; if !same { let label = |owner: &Option| { owner .as_ref() .map(|owner| owner.package.url.as_str()) .unwrap_or("plain HTTP module") .to_owned() }; return Err(Error::Module(format!( "Conflicting remote package ownership for {name}: {} versus {}", label(previous), label(&owner) ))); } } if let Some(owner) = &owner { self.check(&owner.package)?; self.authorities .borrow_mut() .entry(owner.package.identity.clone()) .or_default() .insert(owner.package.url.clone()); } owners.entry(name.to_owned()).or_insert(owner); Ok(()) } async fn owner_for(&self, name: &str, mut directory: Url) -> Result { let mut seen = BTreeSet::new(); loop { if !seen.insert(directory.clone()) { return Err(Error::Module(format!( "Cyclic remote package boundary for {name}: {directory}" ))); } let package = self.load(&directory).await?; let region = package .region(name) .ok_or_else(|| { Error::Module(format!("Module is outside its remote package: {name}")) })? .clone(); let length = region.as_str().len(); let child = package .config .dependencies .iter() .filter_map(|dependency| match &dependency.source { Source::Http(directory) if name.starts_with(directory.as_str()) && directory.as_str().len() > length => { Some(directory) } _ => None, }) .max_by_key(|directory| directory.as_str().len()) .cloned(); match child { Some(child) => directory = child, None => return Ok(Owner { package, region }), } } } pub async fn import(&self, name: &str, referrer: &str, local: &[Url]) -> Result<(), Error> { if name.starts_with(self.store.options().jsr_registry.as_str()) { return self.claim(name, None); } let owner = self.owner(referrer); let mut selected = owner .as_ref() .filter(|owner| name.starts_with(owner.region.as_str())) .map(|owner| (owner.region.as_str().len(), owner.package.requested.clone())); let declarations: Vec<_> = match &owner { Some(owner) => { self.check(&owner.package)?; owner .package .config .dependencies .iter() .filter_map(|dependency| match &dependency.source { Source::Http(directory) => Some(directory.clone()), _ => None, }) .collect() } None => local.to_vec(), }; for directory in declarations { let length = directory.as_str().len(); if name.starts_with(directory.as_str()) && selected .as_ref() .is_none_or(|(previous, _)| length > *previous) { selected = Some((length, directory)); } } let owner = match selected { Some((_, directory)) => Some(self.owner_for(name, directory).await?), None => None, }; self.claim(name, owner) } pub async fn export( &self, directory: &Url, key: &str, specifier: &str, ) -> Result { let package = self.load(directory).await?; let name = self.export_target(&package, key, specifier)?; let owner = self.owner_for(&name, directory.clone()).await?; self.claim(&name, Some(owner))?; Ok(Resolution::Remote(name)) } pub async fn initialize(&self, name: &str) -> Result<(), Error> { let owner = self .owners .borrow_mut() .entry(name.to_owned()) .or_default() .clone(); if let Some(owner) = owner { let package = owner.package; self.check(&package)?; if package.bindings.borrow().is_none() { let bindings = self.bindings(&package.config).await.map_err(|error| { Error::Module(error.render(package.url.as_str(), &package.source)) })?; self.check(&package)?; *package.bindings.borrow_mut() = Some(bindings); } } Ok(()) } /// Shared synchronous decision over an owned remote package: what its /// own imports/scopes select, falling back to its prepared bindings. /// `None` selects nothing: the caller resolves the raw specifier, so /// the prepared path still never initializes or loads packages. fn classify( &self, package: &Rc, specifier: &str, referrer: &str, ) -> Result, Error> { let url = Url::parse( &self .store .remote_base(referrer) .map_err(|error| Error::Module(error.to_string()))?, ) .unwrap(); match package_mapping(&package.config, specifier, &url) .map_err(|error| Error::Module(error.to_string()))? { Some(PackageMapping::Specifier(target)) => { Ok(Some(RemoteStep::Done(Resolution::Specifier(target)))) } Some(PackageMapping::Export(key)) => { Ok(Some(RemoteStep::Export(package.requested.clone(), key))) } None => match select_binding(package.bindings.borrow().as_ref(), specifier) { Some((key, Source::Http(directory))) => { Ok(Some(RemoteStep::Export(directory, key))) } _ => Ok(None), }, } } pub fn type_override(&self, specifier: &str, referrer: &str) -> Result, Error> { let Some(owner) = self.owner(referrer) else { return Ok(None); }; self.check(&owner.package)?; let referrer_url = Url::parse(referrer).map_err(|error| Error::Module(error.to_string()))?; let result = owner .package .config .types .imports .resolve(specifier, &referrer_url) .map_err(|error| Error::Module(error.to_string()))?; self.check(&owner.package)?; Ok(result) } pub fn type_globals(&self, referrer: &str) -> Result, Error> { let Some(owner) = self.owner(referrer) else { return Ok(Vec::new()); }; self.check(&owner.package)?; let globals = owner.package.config.types.globals.clone(); self.check(&owner.package)?; Ok(globals) } pub async fn resolve( &self, specifier: &str, referrer: &str, ) -> Result, Error> { let Some(owner) = self.owner(referrer) else { return Ok(None); }; let package = owner.package; self.initialize(referrer).await?; self.check(&package)?; match self.classify(&package, specifier, referrer)? { None => Ok(None), Some(RemoteStep::Done(resolution)) => Ok(Some(resolution)), Some(RemoteStep::Export(directory, key)) => { self.export(&directory, &key, specifier).await.map(Some) } } } pub fn resolve_export_meta( &self, directory: &Url, key: &str, specifier: &str, ) -> Result { let package = self .packages .borrow() .get(directory) .cloned() .ok_or_else(|| { Error::Module(format!("Package metadata is not prepared: {directory}")) })?; self.export_target(&package, key, specifier) .map(Resolution::Remote) } fn export_target( &self, package: &Package, key: &str, specifier: &str, ) -> Result { self.check(package)?; if package.config.exports.is_empty() { return Err(Error::Module(format!( "Dependency {specifier:?} has no exports: {}", package.url ))); } let export = package.config.exports.get(key).ok_or_else(|| { Error::Module(format!( "Package has no export {key:?} for {specifier:?}: {}", package.url )) })?; self.authority .check_remote_module(&export.target) .map_err(|error| Error::Module(error.to_string()))?; Ok(export.target.to_string()) } pub fn resolve_import_meta( &self, specifier: &str, referrer: &str, ) -> Result, Error> { let Some(owner) = self.owner(referrer) else { return Ok(None); }; let package = owner.package; self.check(&package)?; match self.classify(&package, specifier, referrer)? { None => Ok(None), Some(RemoteStep::Done(resolution)) => Ok(Some(resolution)), Some(RemoteStep::Export(directory, key)) => self .resolve_export_meta(&directory, &key, specifier) .map(Some), } } pub fn metadata(&self, name: &str) -> Result, Error> { let Some(owner) = self.owner(name) else { return Ok(None); }; let package = owner.package; self.check(&package)?; let exports: BTreeMap<_, _> = package .config .exports .iter() .map(|(key, export)| (key, export.target.as_str())) .collect(); let bindings = package.bindings.borrow(); let dependencies: BTreeMap<_, _> = bindings .as_ref() .into_iter() .flatten() .map(|(name, source)| { ( name, match source { Source::Http(url) => serde_json::json!(url.as_str()), Source::Jsr(request) => serde_json::json!(request.specifier), Source::Npm(request) => serde_json::json!(request.request_key()), Source::Local(directory) => serde_json::json!(directory), }, ) }) .collect(); Ok(Some(serde_json::json!({ "config": package.identity.0.as_str(), "name": package.config.name, "version": package.config.version, "description": package.config.description, "exports": exports, "dependencies": dependencies }))) } }