use std::{
collections::HashMap,
error::Error,
fmt::{self, Debug},
io::{Cursor, Read},
path::PathBuf,
rc::Rc,
};
use thiserror::Error;
use crate::{parser::Value, rc_world};
pub trait ImportLoader: fmt::Debug {
fn resolve(
&self,
current: Option<&str>,
path: &str,
) -> Result<String, Box<dyn Error + 'static>>;
fn load(&self, path: &str) -> Result<Box<dyn Read>, Box<dyn Error + 'static>>;
fn r#override(self, path: String, value: String) -> Override<Self>
where
Self: Sized,
{
let mut overrides = HashMap::new();
overrides.insert(path, Some(value));
Override {
loader: self,
overrides,
}
}
fn r#block(self, path: String) -> Override<Self>
where
Self: Sized,
{
let mut overrides = HashMap::new();
overrides.insert(path, None);
Override {
loader: self,
overrides,
}
}
fn r#override_many(self, overrides: HashMap<String, Option<String>>) -> Override<Self>
where
Self: Sized,
{
Override {
loader: self,
overrides,
}
}
fn filter<F>(self, filter: F) -> Filter<Self, F>
where
Self: Sized,
F: Fn(&str) -> bool,
{
Filter {
loader: self,
filter,
}
}
fn with_resolver<F, E>(self, resolver: F) -> WithResolver<Self, F>
where
Self: Sized,
F: Fn(Option<&str>, &str) -> Result<String, E>,
{
WithResolver {
loader: self,
resolver,
}
}
fn with_loader<F, R, E>(self, loader: F) -> WithLoader<Self, F>
where
Self: Sized,
F: Fn(&str) -> Result<R, E>,
R: 'static + Read,
E: 'static + Error,
{
WithLoader {
loader: self,
func: loader,
}
}
}
#[derive(Error, Debug)]
#[error("Imports are disabled")]
pub struct NoImportError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct NoImport;
impl ImportLoader for NoImport {
fn resolve(
&self,
_current: Option<&str>,
_path: &str,
) -> Result<String, Box<dyn Error + 'static>> {
Err(Box::new(NoImportError))
}
fn load(&self, _path: &str) -> Result<Box<dyn Read>, Box<dyn Error + 'static>> {
Err(Box::new(NoImportError))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct DefaultImporter;
impl ImportLoader for DefaultImporter {
fn resolve(
&self,
current: Option<&str>,
path: &str,
) -> Result<String, Box<dyn Error + 'static>> {
if path.starts_with("env:") {
Ok(path.to_owned())
} else {
let resolved = if let Some(current) = current {
if current.starts_with("env:") {
return Err(Box::new(ImportError::CannotAccessFileSystemFromEnv));
} else {
let mut resolved = PathBuf::from(current);
resolved.pop();
resolved.push(path);
resolved
}
} else {
let mut resolved = std::env::current_dir()?;
resolved.push(path);
resolved
};
Ok(resolved.to_string_lossy().into_owned())
}
}
fn load(&self, path: &str) -> Result<Box<dyn Read>, Box<dyn Error + 'static>> {
if path.starts_with("env:") {
let var = &path["env:".len()..];
Ok(Box::new(std::io::Cursor::new(std::env::var(var)?)))
} else {
Ok(Box::new(std::fs::File::open(path)?))
}
}
}
#[derive(Error, Debug)]
pub enum ImportError {
#[error("Circular import detected at {0:?}")]
CircularImportDetected(Rc<str>),
#[error("Cannot access the filesystem from the environment variable")]
CannotAccessFileSystemFromEnv,
#[error("Cannot access the filesystem from the environment variable")]
ImportPathIsOverridden(Rc<str>),
}
#[derive(Debug)]
pub(super) struct ImportState {
pub(super) import_loader: Box<dyn ImportLoader>,
pub(super) loaded: HashMap<Rc<str>, Value>,
pub(super) import_stack: Vec<Rc<str>>,
}
impl Default for ImportState {
fn default() -> Self {
ImportState {
import_loader: Box::new(DefaultImporter),
loaded: HashMap::default(),
import_stack: vec![],
}
}
}
impl ImportState {
pub(super) fn try_push_import(
&mut self,
current: Option<&str>,
path: &str,
) -> Result<Rc<str>, Box<dyn Error + 'static>> {
let path = self.import_loader.resolve(current, path)?;
let resolved = rc_world::string_to_rc(path);
if self.import_stack.iter().any(|p| p == &resolved) {
return Err(Box::new(ImportError::CircularImportDetected(resolved)));
}
self.import_stack.push(resolved.clone());
Ok(resolved)
}
}
#[derive(Debug)]
pub struct Override<L> {
loader: L,
overrides: HashMap<String, Option<String>>,
}
impl<L: ImportLoader> ImportLoader for Override<L> {
fn resolve(
&self,
current: Option<&str>,
path: &str,
) -> Result<String, Box<dyn Error + 'static>> {
if self.overrides.contains_key(path) {
Ok(path.to_string())
} else {
self.loader.resolve(current, path)
}
}
fn load(&self, path: &str) -> Result<Box<dyn Read>, Box<dyn Error + 'static>> {
match self.overrides.get(path) {
Some(Some(overridden)) => Ok(Box::new(Cursor::new(overridden.clone()))),
Some(None) => Err(Box::new(ImportError::ImportPathIsOverridden(
rc_world::str_to_rc(path),
))),
None => self.load(path),
}
}
}
pub struct Filter<L, F> {
loader: L,
filter: F,
}
impl<L: Debug, F> Debug for Filter<L, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Filter {{ loader: {:?} }}", self.loader)
}
}
impl<L: ImportLoader, F> ImportLoader for Filter<L, F>
where
F: Fn(&str) -> bool,
{
fn resolve(
&self,
current: Option<&str>,
path: &str,
) -> Result<String, Box<dyn Error + 'static>> {
self.loader.resolve(current, path)
}
fn load(&self, path: &str) -> Result<Box<dyn Read>, Box<dyn Error + 'static>> {
if (self.filter)(path) {
self.load(path)
} else {
return Err(Box::new(ImportError::ImportPathIsOverridden(
rc_world::str_to_rc(path),
)));
}
}
}
pub struct WithResolver<L, F> {
loader: L,
resolver: F,
}
impl<L: Debug, F> Debug for WithResolver<L, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WithResolver {{ loader: {:?} }}", self.loader)
}
}
impl<L: ImportLoader, F, E> ImportLoader for WithResolver<L, F>
where
F: Fn(Option<&str>, &str) -> Result<String, E>,
E: 'static + Error,
{
fn resolve(
&self,
current: Option<&str>,
path: &str,
) -> Result<String, Box<dyn Error + 'static>> {
(self.resolver)(current, path).map_err(|e| Box::new(e) as Box<dyn Error>)
}
fn load(&self, path: &str) -> Result<Box<dyn Read>, Box<dyn Error + 'static>> {
self.loader.load(path)
}
}
pub struct WithLoader<L, F> {
loader: L,
func: F,
}
impl<L: Debug, F> Debug for WithLoader<L, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WithLoader {{ loader: {:?} }}", self.loader)
}
}
impl<L: ImportLoader, F, R, E> ImportLoader for WithLoader<L, F>
where
F: Fn(&str) -> Result<R, E>,
R: 'static + Read,
E: 'static + Error,
{
fn resolve(
&self,
current: Option<&str>,
path: &str,
) -> Result<String, Box<dyn Error + 'static>> {
self.loader.resolve(current, path)
}
fn load(&self, path: &str) -> Result<Box<dyn Read>, Box<dyn Error + 'static>> {
(self.func)(path)
.map(|read| Box::new(read) as Box<dyn Read>)
.map_err(|e| Box::new(e) as Box<dyn Error>)
}
}