use crate::config::Config;
use crate::error::{AppError, PipelineError};
use crate::faiv::{parse_faiv, UnitLib};
use crate::taiv::{embedded, parse_taiv, TypeDef, TypeLib};
use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::path::PathBuf;
#[derive(Default)]
pub struct Resolver {
pub config: Config,
cache: RefCell<HashMap<String, TypeLib>>,
unit_cache: RefCell<HashMap<String, UnitLib>>,
}
impl Resolver {
pub fn new(config: Config) -> Self {
Resolver {
config,
..Resolver::default()
}
}
pub fn offline() -> Self {
Self::default()
}
fn ensure(&self, lib: &str, layer1: &[(String, String)]) -> Result<(), PipelineError> {
if embedded(lib).is_some() || self.cache.borrow().contains_key(lib) {
return Ok(());
}
let loaded = self.load(lib, layer1)?;
self.cache.borrow_mut().insert(lib.to_string(), loaded);
Ok(())
}
pub fn contains(
&self,
lib: &str,
name: &str,
layer1: &[(String, String)],
) -> Result<bool, PipelineError> {
self.ensure(lib, layer1)?;
if let Some(l) = embedded(lib) {
return Ok(l.types.contains_key(name));
}
Ok(self.cache.borrow()[lib].types.contains_key(name))
}
pub fn def(
&self,
lib: &str,
name: &str,
layer1: &[(String, String)],
) -> Result<TypeDef, PipelineError> {
self.ensure(lib, layer1)?;
let cloned = if let Some(l) = embedded(lib) {
l.types.get(name).cloned()
} else {
self.cache.borrow()[lib].types.get(name).cloned()
};
cloned.ok_or(PipelineError::App(AppError::SchemaResolution))
}
fn load(&self, lib: &str, layer1: &[(String, String)]) -> Result<TypeLib, PipelineError> {
let bytes = self.read_artifact(lib, layer1, "taiv")?;
let parsed = parse_taiv(&bytes)?;
if parsed.library != lib {
return Err(PipelineError::App(AppError::SchemaResolution));
}
Ok(parsed)
}
pub fn unit_names(
&self,
lib: &str,
layer1: &[(String, String)],
) -> Result<BTreeSet<String>, PipelineError> {
if !self.unit_cache.borrow().contains_key(lib) {
let bytes = self.read_artifact(lib, layer1, "faiv")?;
let parsed = parse_faiv(&bytes)?;
if parsed.library != lib {
return Err(PipelineError::App(AppError::SchemaResolution));
}
self.unit_cache.borrow_mut().insert(lib.to_string(), parsed);
}
Ok(self.unit_cache.borrow()[lib]
.units
.keys()
.cloned()
.collect())
}
pub fn schema_bytes(
&self,
lib: &str,
layer1: &[(String, String)],
) -> Result<Vec<u8>, PipelineError> {
self.read_artifact(lib, layer1, "saiv")
}
fn read_artifact(
&self,
lib: &str,
layer1: &[(String, String)],
ext: &str,
) -> Result<Vec<u8>, PipelineError> {
let prefix = lib.split('/').next().unwrap_or(lib);
let base = layer1
.iter()
.find(|(p, _)| p == prefix)
.map(|(_, b)| b.as_str())
.or_else(|| self.config.base_for(prefix))
.ok_or(PipelineError::App(AppError::SchemaResolution))?;
if base.starts_with("http://") || base.starts_with("https://") {
return Err(PipelineError::App(AppError::SchemaResolution));
}
let mut path = PathBuf::from(base);
if path.is_relative() {
if let Some(dir) = &self.config.base_dir {
path = dir.join(path);
}
}
path.push(format!("{lib}.{ext}"));
std::fs::read(&path).map_err(|_| PipelineError::App(AppError::SchemaResolution))
}
}
pub fn resolve_named(
name: &str,
imports: &[String],
resolver: &Resolver,
layer1: &[(String, String)],
) -> Result<String, PipelineError> {
if crate::anno::CORE_TYPES.contains(&name) {
return Ok(name.to_string()); }
let mut found: Option<&str> = None;
for lib in imports {
if resolver.contains(lib, name, layer1)? {
if let Some(prev) = found {
return Err(PipelineError::Other(format!(
"ambiguous named type &{name}: defined in {prev} and {lib}"
)));
}
found = Some(lib);
}
}
match found {
Some(lib) => Ok(format!("{lib}/{name}")),
None => Err(PipelineError::App(AppError::SchemaResolution)),
}
}