use std::{future::Future, string::FromUtf8Error}; use async_trait::async_trait; use reqwest::{Method, StatusCode}; use serde::Deserialize; use super::serde_ext::LDObject; const LD_CONTENT_TYPE: &str = "application/ld+json"; #[async_trait] pub(super) trait Resolvable: Sized { async fn resolve(&self, resolver: &Resolver) -> Result; } #[async_trait] impl Resolvable for T where T: LDObject + for<'de> Deserialize<'de> + Sync, { async fn resolve(&self, resolver: &Resolver) -> Result { resolver.resolve_into(self.get_iri()).await } } #[derive(Debug)] pub(crate) struct ResolveError(pub String); impl From for ResolveError { fn from(err: reqwest::Error) -> Self { Self(err.to_string()) } } impl From for ResolveError { fn from(e: FromUtf8Error) -> Self { Self(format!( "invalid schema format (tried utf8): {}", e.to_string() )) } } pub(super) struct Resolver { client: reqwest::Client, } impl Resolver { pub fn new() -> Self { Self { client: reqwest::ClientBuilder::new().build().unwrap(), } } async fn get(&self, iri: String, ok: F) -> Result where F: FnOnce(reqwest::Response) -> Fut, Fut: Future>, { let resp = self .client .request(Method::GET, iri) .header("Accept", LD_CONTENT_TYPE) .send() .await?; match resp.status() { StatusCode::OK => Ok(ok(resp).await?), status => Err(ResolveError(format!("non-ok status: {}", status))), } } pub async fn resolve_into<'a, T>(&self, iri: String) -> Result where T: for<'de> serde::Deserialize<'de>, { Ok(self .get(iri, |resp| async { Ok(resp.json().await?) }) .await?) } pub async fn resolve(&self, iri: String) -> Result, ResolveError> { Ok(self .get(iri, |resp| async { Ok(resp.bytes().await?.to_vec()) }) .await?) } }