flabk/flabk/src/astreams/resolve.rs

87 lines
2.2 KiB
Rust

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<Self, ResolveError>;
}
#[async_trait]
impl<T> Resolvable for T
where
T: LDObject + for<'de> Deserialize<'de> + Sync,
{
async fn resolve(&self, resolver: &Resolver) -> Result<Self, ResolveError> {
resolver.resolve_into(self.get_iri()).await
}
}
#[derive(Debug)]
pub(crate) struct ResolveError(pub String);
impl From<reqwest::Error> for ResolveError {
fn from(err: reqwest::Error) -> Self {
Self(err.to_string())
}
}
impl From<FromUtf8Error> 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<Out, F, Fut>(&self, iri: String, ok: F) -> Result<Out, ResolveError>
where
F: FnOnce(reqwest::Response) -> Fut,
Fut: Future<Output = Result<Out, ResolveError>>,
{
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<T, ResolveError>
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<Vec<u8>, ResolveError> {
Ok(self
.get(iri, |resp| async { Ok(resp.bytes().await?.to_vec()) })
.await?)
}
}