use crate::{Error, ErrorCode, RemoteDocument};
use futures::future::{BoxFuture, FutureExt};
use generic_json::Json;
use iref::{Iri, IriBuf};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use std::{marker::PhantomData, str::FromStr};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Id(usize);
impl Id {
pub fn new(index: usize) -> Self {
Self(index)
}
pub fn unwrap(self) -> usize {
self.0
}
}
impl From<Id> for usize {
fn from(id: Id) -> Self {
id.0
}
}
pub trait Loader {
type Document: Json;
fn id(&self, iri: Iri<'_>) -> Option<Id>;
#[inline(always)]
fn id_opt(&self, iri: Option<Iri<'_>>) -> Option<Id> {
iri.and_then(|iri| self.id(iri))
}
fn iri(&self, id: Id) -> Option<Iri<'_>>;
fn load<'a>(
&'a mut self,
url: Iri<'_>,
) -> BoxFuture<'a, Result<RemoteDocument<Self::Document>, Error>>;
}
pub struct NoLoader<J>(PhantomData<J>);
impl<J> NoLoader<J> {
#[inline(always)]
pub fn new() -> Self {
Self(PhantomData)
}
}
impl<J> Default for NoLoader<J> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl<J: Json> Loader for NoLoader<J> {
type Document = J;
#[inline(always)]
fn id(&self, _iri: Iri<'_>) -> Option<Id> {
None
}
#[inline(always)]
fn iri(&self, _id: Id) -> Option<Iri<'_>> {
None
}
#[inline(always)]
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<J> {
namespace: HashMap<IriBuf, Id>,
cache: Vec<(J, IriBuf)>,
mount_points: HashMap<PathBuf, IriBuf>,
parser: Box<dyn 'static + Send + Sync + FnMut(&str) -> Result<J, Error>>,
}
impl<J> FsLoader<J> {
pub fn new<E: 'static + std::error::Error>(
mut parser: impl 'static + Send + Sync + FnMut(&str) -> Result<J, E>,
) -> Self {
Self {
namespace: HashMap::new(),
cache: Vec::new(),
mount_points: HashMap::new(),
parser: Box::new(move |s| {
parser(s).map_err(|e| Error::with_source(ErrorCode::LoadingDocumentFailed, e))
}),
}
}
#[inline(always)]
pub fn mount<P: AsRef<Path>>(&mut self, url: Iri, path: P) {
self.mount_points.insert(path.as_ref().into(), url.into());
}
fn allocate(&mut self, iri: IriBuf, doc: J) -> Id {
let id = Id::new(self.cache.len());
self.namespace.insert(iri.clone(), id);
self.cache.push((doc, iri));
id
}
}
impl<J: FromStr> Default for FsLoader<J>
where
J::Err: 'static + std::error::Error,
{
#[inline(always)]
fn default() -> Self {
Self::new(|s| J::from_str(s))
}
}
impl<J: Json + Clone + Send> Loader for FsLoader<J> {
type Document = J;
#[inline(always)]
fn id(&self, iri: Iri<'_>) -> Option<Id> {
self.namespace.get(&IriBuf::from(iri)).cloned()
}
#[inline(always)]
fn iri(&self, id: Id) -> Option<Iri<'_>> {
self.cache.get(id.unwrap()).map(|(_, iri)| iri.as_iri())
}
fn load<'a>(&'a mut self, url: Iri<'_>) -> BoxFuture<'a, Result<RemoteDocument<J>, Error>> {
let url: IriBuf = url.into();
async move {
match self.namespace.get(&url) {
Some(id) => Ok(RemoteDocument::new(
self.cache[id.unwrap()].0.clone(),
url,
*id,
)),
None => {
for (path, target_url) in &self.mount_points {
let url_ref = url.as_iri_ref();
if let Some((suffix, _, _)) = url_ref.suffix(target_url.as_iri_ref()) {
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() {
let doc = (*self.parser)(contents.as_str())?;
let id = self.allocate(url.clone(), doc.clone());
return Ok(RemoteDocument::new(doc, url, id));
} else {
return Err(ErrorCode::LoadingDocumentFailed.into());
}
} else {
return Err(ErrorCode::LoadingDocumentFailed.into());
}
}
}
Err(ErrorCode::LoadingDocumentFailed.into())
}
}
}
.boxed()
}
}