Use built-in reqwest loader for example

This commit is contained in:
asonix 2022-12-11 20:05:43 -06:00
parent 85ec358ee6
commit 2a17a60bf8
2 changed files with 11 additions and 167 deletions

View file

@ -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"] }

View file

@ -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,
))
})
}
}