use std::io::ErrorKind;
use std::path::PathBuf;
use std::sync::Arc;
use super::loader::IncludeLoaderError;
#[cfg(feature = "async")]
use crate::prelude::parser::loader::AsyncIncludeLoader;
use crate::prelude::parser::loader::IncludeLoader;
#[derive(Debug, Default)]
pub struct LocalIncludeLoader {
root: PathBuf,
}
impl LocalIncludeLoader {
pub fn new(root: PathBuf) -> Self {
Self { root }
}
fn build_path(&self, url: &str) -> Result<PathBuf, IncludeLoaderError> {
let path = self.root.join(url.trim_start_matches("file:///"));
path.canonicalize()
.map_err(|err| IncludeLoaderError::new(url, err.kind()))
.and_then(|path| {
if !path.starts_with(&self.root) {
Err(IncludeLoaderError::new(url, ErrorKind::NotFound))
} else {
Ok(path)
}
})
.map_err(|err| err.with_message("the path should stay in the context of the loader"))
}
}
impl IncludeLoader for LocalIncludeLoader {
fn resolve(&self, url: &str) -> Result<String, IncludeLoaderError> {
let path = self.build_path(url)?;
std::fs::read_to_string(path).map_err(|err| {
IncludeLoaderError::new(url, ErrorKind::InvalidData)
.with_message("unable to load the template file")
.with_cause(Arc::new(err))
})
}
}
#[cfg(feature = "async")]
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl AsyncIncludeLoader for LocalIncludeLoader {
async fn async_resolve(&self, url: &str) -> Result<String, IncludeLoaderError> {
let path = self.build_path(url)?;
std::fs::read_to_string(path).map_err(|err| {
IncludeLoaderError::new(url, ErrorKind::InvalidData)
.with_message("unable to load the template file")
.with_cause(Arc::new(err))
})
}
}
#[cfg(test)]
mod tests {
use std::io::ErrorKind;
use std::path::PathBuf;
use super::LocalIncludeLoader;
use crate::prelude::parser::loader::IncludeLoader;
impl LocalIncludeLoader {
fn current_dir() -> Self {
Self::new(PathBuf::from(env!("CARGO_MANIFEST_DIR")))
}
}
#[test]
fn should_turn_into_path() {
let loader = LocalIncludeLoader::current_dir();
let path = loader
.build_path("file:///resources/compare/success/mj-body.mjml")
.unwrap();
assert_eq!(
path.as_os_str(),
format!(
"{}/resources/compare/success/mj-body.mjml",
loader.root.to_string_lossy()
)
.as_str()
);
}
#[test]
fn should_handle_dots_with_existing_file() {
let loader = LocalIncludeLoader::new(PathBuf::default().join("src"));
let err = loader
.build_path("file:///../resources/compare/success/mj-body.mjml")
.unwrap_err();
assert_eq!(err.reason, ErrorKind::NotFound);
}
#[test]
fn should_handle_dots_with_missing_file() {
let loader = LocalIncludeLoader::new(PathBuf::default().join("src"));
let err = loader.build_path("file:///../partial.mjml").unwrap_err();
assert_eq!(err.reason, ErrorKind::NotFound);
assert_eq!(err.to_string(), "file:///../partial.mjml entity not found (the path should stay in the context of the loader)");
}
#[test]
fn should_resolve_path() {
let loader = LocalIncludeLoader::current_dir();
let _payload = loader
.resolve("file:///resources/compare/success/mj-body.mjml")
.unwrap();
}
}