#![deny(
bad_style,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true,
missing_debug_implementations,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results
)]
#![forbid(unsafe_code)]
#[cfg(feature = "rate-limiting")]
mod ratelimiting;
#[cfg(feature = "rate-limiting")]
pub use ratelimiting::RateLimitedDataSource;
mod path;
use path::PathBuilder;
pub use path::{Comparison, Direction, Format, InlineCount};
use hyper::{
body::Buf,
client::{connect::Connect, Client},
http::uri::{Authority, InvalidUri, Scheme},
Body, Response, Uri,
};
use log::debug;
use serde::{de::DeserializeOwned, Deserialize};
use std::{convert::TryFrom, io::Read};
use thiserror::Error;
pub trait Connector: Connect + Clone + Send + Sync + 'static {}
impl<T: Connect + Clone + Send + Sync + 'static> Connector for T {}
#[derive(Clone, Debug)]
pub struct DataSource<C> {
client: Client<C>,
authority: Authority,
base_path: String,
scheme: Scheme,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid URI")]
Uri(#[from] InvalidUri),
#[error("http error")]
Http(#[from] hyper::http::Error),
#[error("hyper error")]
Hyper(#[from] hyper::Error),
#[error("serde error")]
Serde(serde_json::Error, String),
#[error("io error")]
Io(#[from] std::io::Error),
}
#[derive(Debug, Deserialize)]
pub struct Page<T> {
pub value: Vec<T>,
#[serde(rename = "odata.count")]
pub count: Option<String>,
#[serde(rename = "odata.nextLink")]
pub next_link: Option<String>,
#[serde(rename = "odata.metadata")]
pub metadata: Option<String>,
}
async fn deserialize_as<T: DeserializeOwned>(response: Response<Body>) -> Result<T, Error> {
let body = hyper::body::aggregate(response).await?;
let mut content = String::new();
let _ = body.reader().read_to_string(&mut content)?;
serde_json::from_str(&content).map_err(|e| Error::Serde(e, content))
}
impl<C> DataSource<C>
where
C: Connector,
{
pub fn new<A>(
client: Client<C>,
domain: A,
base_path: Option<String>,
) -> Result<DataSource<C>, Error>
where
Authority: TryFrom<A>,
Error: From<<Authority as TryFrom<A>>::Error>,
{
Ok(DataSource {
client,
authority: Authority::try_from(domain)?,
base_path: base_path.unwrap_or_default(),
scheme: Scheme::HTTPS,
})
}
async fn execute<R>(&self, request: R) -> Result<Response<Body>, Error>
where
R: Into<PathBuilder>,
{
let builder: PathBuilder = request.into().base_path(self.base_path.clone());
let uri = Uri::builder()
.scheme(self.scheme.as_ref())
.authority(self.authority.as_ref())
.path_and_query(builder.build()?)
.build()?;
debug!("fetching {}", uri);
Ok(self.client.get(uri).await?)
}
pub async fn fetch<T>(&self, request: GetRequest) -> Result<T, Error>
where
T: DeserializeOwned,
{
let response = self
.execute(Into::<PathBuilder>::into(request).format(Format::Json))
.await?;
deserialize_as::<T>(response).await
}
pub async fn fetch_paged<T>(&self, request: ListRequest) -> Result<Page<T>, Error>
where
T: DeserializeOwned,
{
let response = self
.execute(Into::<PathBuilder>::into(request).format(Format::Json))
.await?;
deserialize_as::<Page<T>>(response).await
}
}
#[derive(Debug, Clone)]
pub struct GetRequest {
builder: PathBuilder,
}
impl GetRequest {
pub fn new(resource_type: &str, id: usize) -> Self {
GetRequest {
builder: PathBuilder::new(resource_type.to_string()).id(id),
}
}
pub fn format(mut self, format: Format) -> Self {
self.builder = self.builder.format(format);
self
}
pub fn expand<'f, F>(mut self, field: F) -> Self
where
F: IntoIterator<Item = &'f str>,
{
self.builder = self.builder.expand(field);
self
}
}
impl From<GetRequest> for PathBuilder {
fn from(request: GetRequest) -> Self {
request.builder
}
}
#[derive(Debug, Clone)]
pub struct ListRequest {
builder: PathBuilder,
}
impl ListRequest {
pub fn new(resource_type: &str) -> Self {
ListRequest {
builder: PathBuilder::new(resource_type.to_string()),
}
}
pub fn format(mut self, format: Format) -> Self {
self.builder = self.builder.format(format);
self
}
pub fn order_by(mut self, field: &str, direction: Direction) -> Self {
self.builder = self.builder.order_by(field, direction);
self
}
pub fn top(mut self, count: u32) -> Self {
self.builder = self.builder.top(count);
self
}
pub fn skip(mut self, count: u32) -> Self {
self.builder = self.builder.skip(count);
self
}
pub fn inline_count(mut self, value: InlineCount) -> Self {
self.builder = self.builder.inline_count(value);
self
}
pub fn filter(mut self, field: &str, comparison: Comparison, value: &str) -> Self {
self.builder = self.builder.filter(field, comparison, value);
self
}
pub fn expand<'f, F>(mut self, field: F) -> Self
where
F: IntoIterator<Item = &'f str>,
{
self.builder = self.builder.expand(field);
self
}
}
impl From<ListRequest> for PathBuilder {
fn from(request: ListRequest) -> Self {
request.builder
}
}