use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::{Read, BufReader};
use futures::future::{FutureExt, BoxFuture};
use iref::{Iri, IriBuf};
use json::JsonValue;
use crate::{
Error,
ErrorCode,
RemoteDocument,
context::{
self,
RemoteContext
}
};
pub trait Loader {
type Document;
fn load<'a>(&'a mut self, url: Iri<'_>) -> BoxFuture<'a, Result<RemoteDocument<Self::Document>, Error>>;
}
impl<L: Send + Sync + Loader<Document = JsonValue>> context::Loader for L {
type Output = JsonValue;
fn load_context<'a>(&'a mut self, url: Iri) -> BoxFuture<'a, Result<RemoteContext<JsonValue>, Error>> {
let url = IriBuf::from(url);
async move {
match self.load(url.as_iri()).await {
Ok(remote_doc) => {
let (doc, url) = remote_doc.into_parts();
if let JsonValue::Object(obj) = doc {
if let Some(context) = obj.get("@context") {
Ok(RemoteContext::from_parts(url, context.clone()))
} else {
Err(ErrorCode::InvalidRemoteContext.into())
}
} else {
Err(ErrorCode::InvalidRemoteContext.into())
}
},
Err(_) => {
Err(ErrorCode::LoadingRemoteContextFailed.into())
}
}
}.boxed()
}
}
pub struct NoLoader;
impl Loader for NoLoader {
type Document = JsonValue;
fn load<'a>(&'a mut self, _url: Iri<'_>) -> BoxFuture<'a, Result<RemoteDocument<Self::Document>, Error>> {
async move {
Err(ErrorCode::LoadingDocumentFailed.into())
}.boxed()
}
}
pub struct FsLoader {
cache: HashMap<IriBuf, RemoteDocument>,
mount_points: HashMap<PathBuf, IriBuf>
}
impl FsLoader {
pub fn new() -> FsLoader {
FsLoader {
cache: HashMap::new(),
mount_points: HashMap::new()
}
}
pub fn mount<P: AsRef<Path>>(&mut self, url: Iri, path: P) {
self.mount_points.insert(path.as_ref().into(), url.into());
}
}
impl Loader for FsLoader {
type Document = JsonValue;
fn load<'a>(&'a mut self, url: Iri<'_>) -> BoxFuture<'a, Result<RemoteDocument<Self::Document>, Error>> {
let url: IriBuf = url.into();
async move {
match self.cache.get(&url) {
Some(doc) => Ok(doc.clone()),
None => {
for (path, target_url) in &self.mount_points {
let url_ref = url.as_iri_ref();
match url_ref.suffix(target_url.as_iri_ref()) {
Some((suffix, _, _)) => {
let mut filepath = path.clone();
for seg in suffix.as_path().segments() {
filepath.push(seg.as_str())
}
if let Ok(file) = File::open(filepath) {
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
if buf_reader.read_to_string(&mut contents).is_ok() {
if let Ok(doc) = json::parse(contents.as_str()) {
let remote_doc = RemoteDocument::new(doc, url.as_iri());
self.cache.insert(url.clone(), remote_doc.clone());
return Ok(remote_doc)
} else {
return Err(ErrorCode::LoadingDocumentFailed.into())
}
} else {
return Err(ErrorCode::LoadingDocumentFailed.into())
}
} else {
return Err(ErrorCode::LoadingDocumentFailed.into())
}
},
None => ()
}
}
Err(ErrorCode::LoadingDocumentFailed.into())
}
}
}.boxed()
}
}