Use built-in reqwest loader for example
This commit is contained in:
parent
85ec358ee6
commit
2a17a60bf8
|
@ -27,6 +27,7 @@ openssl = { version = "0.10.44", optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
iref = "2.2.0"
|
||||
json-ld = { version = "0.9.1", features = ["reqwest"] }
|
||||
reqwest = "0.11.13"
|
||||
static-iref = "2.0.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
use contextual::WithContext;
|
||||
use iref::{Iri, IriBuf};
|
||||
use iref::Iri;
|
||||
use json_ld::{
|
||||
syntax::{parse::MetaError, Parse, Value},
|
||||
Flatten, JsonLdProcessor, Loader, RemoteDocument,
|
||||
syntax::{Parse, Value},
|
||||
Flatten, JsonLdProcessor, RemoteDocument, ReqwestLoader,
|
||||
};
|
||||
use locspan::{Location, Meta, Span};
|
||||
use rdf_types::{generator::Blank, vocabulary::Index, IriVocabulary, IriVocabularyMut};
|
||||
use locspan::{Location, Span};
|
||||
use rdf_types::{generator::Blank, IriVocabularyMut};
|
||||
use reqwest::Client;
|
||||
use static_iref::iri;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
type AnyError = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), AnyError> {
|
||||
let cache = Cache::new();
|
||||
let client = Client::builder()
|
||||
.user_agent("json-ld-playground")
|
||||
.build()
|
||||
|
@ -41,18 +36,13 @@ async fn main() -> Result<(), AnyError> {
|
|||
.text()
|
||||
.await?;
|
||||
|
||||
normalize_document(cache.clone(), client.clone(), iri, &document).await?;
|
||||
normalize_document(iri, &document).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn normalize_document(
|
||||
cache: Cache,
|
||||
client: Client,
|
||||
iri: Iri<'static>,
|
||||
document: &str,
|
||||
) -> Result<(), AnyError> {
|
||||
async fn normalize_document(iri: Iri<'static>, document: &str) -> Result<(), AnyError> {
|
||||
let mut vocabulary: rdf_types::IndexVocabulary = rdf_types::IndexVocabulary::new();
|
||||
|
||||
let iri_index = vocabulary.insert(iri);
|
||||
|
@ -64,7 +54,7 @@ async fn normalize_document(
|
|||
.expect("Failed to parse"),
|
||||
);
|
||||
|
||||
let mut loader = ReqwestLoader::with_default_parser(cache, client);
|
||||
let mut loader = ReqwestLoader::default();
|
||||
|
||||
let expanded = input
|
||||
.expand_with(&mut vocabulary, &mut loader)
|
||||
|
@ -88,150 +78,3 @@ async fn normalize_document(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const ACTIVITYSTREAMS: &'static str = "https://www.w3.org/ns/activitystreams";
|
||||
const SECURITY: &'static str = "https://w3id.org/security/v1";
|
||||
|
||||
const PERMITTED_CONTEXTS: [&'static str; 2] = [ACTIVITYSTREAMS, SECURITY];
|
||||
|
||||
type DynParser<I, M, T, E> = dyn 'static
|
||||
+ Send
|
||||
+ Sync
|
||||
+ FnMut(&dyn IriVocabulary<Iri = I>, &I, &str) -> Result<Meta<T, M>, E>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct Cache {
|
||||
inner: Arc<RwLock<HashMap<IriBuf, String>>>,
|
||||
}
|
||||
|
||||
struct ReqwestLoader<I = Index, M = Location<I>, T = Value<M>, E = MetaError<M>> {
|
||||
cache: Cache,
|
||||
client: Client,
|
||||
parser: Box<DynParser<I, M, T, E>>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn get(&self, url: &IriBuf) -> Option<String> {
|
||||
let guard = self.inner.read().unwrap();
|
||||
|
||||
guard.get(url).map(String::from)
|
||||
}
|
||||
|
||||
fn store(&self, url: IriBuf, body: String) {
|
||||
self.inner.write().unwrap().insert(url, body);
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, M, T, E> ReqwestLoader<I, M, T, E> {
|
||||
fn new(
|
||||
cache: Cache,
|
||||
client: Client,
|
||||
parser: impl 'static
|
||||
+ Send
|
||||
+ Sync
|
||||
+ FnMut(&dyn IriVocabulary<Iri = I>, &I, &str) -> Result<Meta<T, M>, E>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cache,
|
||||
client,
|
||||
parser: Box::new(parser),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Clone> ReqwestLoader<I, Location<I>, Value<Location<I>>, MetaError<Location<I>>> {
|
||||
fn with_default_parser(cache: Cache, client: Client) -> Self {
|
||||
Self::new(cache, client, |_, file: &I, s| {
|
||||
Value::parse_str(s, |span| Location::new(file.clone(), span))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Clone> Default
|
||||
for ReqwestLoader<I, Location<I>, Value<Location<I>>, MetaError<Location<I>>>
|
||||
{
|
||||
fn default() -> Self {
|
||||
let client = Client::builder()
|
||||
.user_agent("json-ld-playground")
|
||||
.build()
|
||||
.expect("Successful client");
|
||||
|
||||
Self::with_default_parser(Cache::default(), client)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, M, T, E> ReqwestLoader<I, M, T, E> {
|
||||
async fn resolve_context(
|
||||
&self,
|
||||
vocabulary: &impl IriVocabulary<Iri = I>,
|
||||
url: &I,
|
||||
) -> Result<String, Error<E>> {
|
||||
let url = vocabulary.iri(url).unwrap().to_owned();
|
||||
|
||||
if !PERMITTED_CONTEXTS.contains(&url.as_str()) {
|
||||
return Err(Error::NotPermitted(url));
|
||||
}
|
||||
|
||||
if let Some(cached) = self.cache.get(&url) {
|
||||
return Ok(cached);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(url.as_str())
|
||||
.header("Accept", "application/ld+json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| Error::Fetch)?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(Error::Fetch);
|
||||
}
|
||||
|
||||
let body = response.text().await.map_err(|_| Error::Fetch)?;
|
||||
|
||||
self.cache.store(url, body.clone());
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error<E> {
|
||||
NotPermitted(IriBuf),
|
||||
Fetch,
|
||||
Parse(E),
|
||||
}
|
||||
|
||||
type AnyError = Box<dyn std::error::Error + Send + Sync>;
|
||||
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
impl<I: Send + Sync, T: Send, M: Send, E> Loader<I, M> for ReqwestLoader<I, M, T, E> {
|
||||
type Output = T;
|
||||
type Error = Error<E>;
|
||||
|
||||
fn load_with<'a>(
|
||||
&'a mut self,
|
||||
vocabulary: &'a mut (impl Sync + Send + rdf_types::IriVocabularyMut<Iri = I>),
|
||||
url: I,
|
||||
) -> BoxFuture<'a, json_ld::LoadingResult<I, M, Self::Output, Self::Error>>
|
||||
where
|
||||
I: 'a,
|
||||
{
|
||||
Box::pin(async move {
|
||||
let s = self.resolve_context(vocabulary, &url).await?;
|
||||
|
||||
let doc = (*self.parser)(vocabulary, &url, &s).map_err(Error::Parse)?;
|
||||
|
||||
Ok(RemoteDocument::new(
|
||||
Some(url),
|
||||
Some("application/ld+json".parse().unwrap()),
|
||||
doc,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue