#![deny(
bad_style,
bare_trait_objects,
const_err,
dead_code,
improper_ctypes,
legacy_directory_ownership,
missing_debug_implementations,
missing_docs,
no_mangle_generic_items,
non_shorthand_field_patterns,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
plugin_as_library,
private_in_public,
safe_extern_statics,
trivial_casts,
trivial_numeric_casts,
unconditional_recursion,
unions_with_drop_fields,
unsafe_code,
unused,
unused_allocation,
unused_comparisons,
unused_extern_crates,
unused_import_braces,
unused_parens,
unused_qualifications,
unused_results,
while_true
)]
mod utils;
use bytes::{Bytes, BytesMut};
use derive_more::{Display, FromStr};
use futures_util::try_stream::TryStreamExt;
use hyper::{client::HttpConnector, Client, Request, StatusCode};
pub use mime::Mime;
use serde::{de::DeserializeOwned, Serialize};
use serde_derive::{Deserialize, Serialize};
use std::error::Error;
use tokio::prelude::*;
use url::Url;
use uuid::Uuid;
#[derive(
Clone,
Copy,
Debug,
Deserialize,
Eq,
Display,
FromStr,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
#[serde(transparent)]
pub struct Atom(#[serde(with = "utils::string")] Uuid);
#[derive(Debug)]
pub struct Connection {
base_url: Url,
client: Client<HttpConnector>,
}
impl Connection {
pub fn open(base_url: &str) -> Result<Connection, OpenError> {
let base_url = Url::parse(base_url)?;
let conn = Connection::open_url(base_url);
Ok(conn)
}
pub fn open_url(base_url: Url) -> Connection {
Connection {
base_url,
client: Client::new(),
}
}
async fn query_body<T: Serialize>(
&self,
relative_url: &str,
req_body: &T,
) -> Result<impl Stream<Item = Result<Bytes, QueryError>>, QueryError> {
let url = self.base_url.join(relative_url).unwrap_or_else(|e| {
panic!("Invalid relative url in query ({:?}): {}", relative_url, e)
});
let req_body = serde_json::to_string(req_body).expect("Failed to serialize body");
let req = Request::post(url.as_ref())
.body(req_body.into())
.expect("Failed to build request");
let res = self.client.request(req).await.map_err(QueryError::Hyper)?;
if res.status() != StatusCode::OK {
return Err(QueryError::BadStatus(res.status()));
}
Ok(res
.into_body()
.map(|r| r.map(Bytes::from).map_err(QueryError::Hyper)))
}
async fn query<T: Serialize, U: DeserializeOwned>(
&self,
relative_url: &str,
req_body: &T,
) -> Result<U, QueryError> {
let body = self
.query_body(relative_url, req_body)
.await?
.map(|r| r.map(BytesMut::from))
.try_concat()
.await?;
let out = serde_json::from_slice(&body).map_err(QueryError::BadResponse)?;
Ok(out)
}
}
impl Connection {
pub async fn create_atom(&self) -> Result<Atom, QueryError> {
#[derive(Serialize)]
struct Body;
self.query("./v0/create-atom", &Body).await
}
pub async fn create_name(&self, atom: Atom, ns: &str, title: &str) -> Result<(), QueryError> {
#[derive(Serialize)]
struct Body<'ns, 'title> {
atom: Atom,
ns: &'ns str,
title: &'title str,
}
self.query("./v0/create-name", &Body { atom, ns, title })
.await
}
pub async fn delete_name(&self, ns: &str, title: &str) -> Result<bool, QueryError> {
#[derive(Serialize)]
struct Body<'ns, 'title> {
ns: &'ns str,
title: &'title str,
}
self.query("./v0/delete-name", &Body { ns, title }).await
}
pub async fn find_atom_by_name(
&self,
ns: &str,
title: &str,
) -> Result<Option<Atom>, QueryError> {
#[derive(Serialize)]
struct Body<'ns, 'title> {
ns: &'ns str,
title: &'title str,
}
self.query("./v0/find-atom-by-name", &Body { ns, title })
.await
}
pub async fn create_edge(&self, from: Atom, to: Atom, key: &str) -> Result<(), QueryError> {
#[derive(Serialize)]
struct Body<'key> {
from: Atom,
to: Atom,
key: &'key str,
}
self.query("./v0/create-edge", &Body { from, to, key })
.await
}
pub async fn delete_edge(&self, from: Atom, to: Atom, key: &str) -> Result<bool, QueryError> {
#[derive(Serialize)]
struct Body<'key> {
from: Atom,
to: Atom,
key: &'key str,
}
self.query("./v0/delete-edge", &Body { from, to, key })
.await
}
pub async fn find_edges(
&self,
from: Option<Atom>,
to: Option<Atom>,
key: Option<&str>,
) -> Result<Vec<(Atom, Atom, String)>, QueryError> {
#[derive(Serialize)]
struct Body<'key> {
from: Option<Atom>,
to: Option<Atom>,
key: Option<&'key str>,
}
self.query("./v0/find-edges", &Body { from, to, key }).await
}
pub async fn create_tag(&self, atom: Atom, kind: &str, value: &str) -> Result<(), QueryError> {
#[derive(Serialize)]
struct Body<'kind, 'value> {
atom: Atom,
kind: &'kind str,
value: &'value str,
}
self.query("./v0/create-tag", &Body { atom, kind, value })
.await
}
pub async fn find_tag(&self, atom: Atom, kind: &str) -> Result<Option<String>, QueryError> {
#[derive(Serialize)]
struct Body<'kind> {
atom: Atom,
kind: &'kind str,
}
self.query("./v0/find-tag", &Body { atom, kind }).await
}
pub async fn delete_tag(&self, atom: Atom, kind: &str) -> Result<bool, QueryError> {
#[derive(Serialize)]
struct Body<'kind> {
atom: Atom,
kind: &'kind str,
}
self.query("./v0/delete-tag", &Body { atom, kind }).await
}
pub async fn create_blob(
&self,
atom: Atom,
mime: Mime,
contents: &[u8],
) -> Result<(), QueryError> {
#[derive(Serialize)]
struct Body<'contents> {
atom: Atom,
#[serde(with = "utils::string")]
mime: Mime,
contents: &'contents [u8],
}
self.query(
"./v0/create-blob",
&Body {
atom,
mime,
contents,
},
)
.await
}
pub async fn delete_blob(&self, atom: Atom, mime: Mime) -> Result<bool, QueryError> {
#[derive(Serialize)]
struct Body {
atom: Atom,
#[serde(with = "utils::string")]
mime: Mime,
}
self.query("./v0/delete-blob", &Body { atom, mime }).await
}
pub async fn stream_blob(
&self,
atom: Atom,
mime: Mime,
) -> Result<impl Stream<Item = Result<Bytes, QueryError>>, QueryError> {
#[derive(Serialize)]
struct Body {
atom: Atom,
#[serde(with = "utils::string")]
mime: Mime,
}
self.query_body("./v0/get-blob", &Body { atom, mime }).await
}
}
#[derive(Debug, Display)]
pub enum OpenError {
UrlParseError(url::ParseError),
}
impl Error for OpenError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
OpenError::UrlParseError(err) => Some(err),
}
}
}
impl From<url::ParseError> for OpenError {
fn from(err: url::ParseError) -> OpenError {
OpenError::UrlParseError(err)
}
}
#[derive(Debug, Display)]
pub enum QueryError {
BadStatus(StatusCode),
BadResponse(serde_json::Error),
Hyper(hyper::Error),
}
impl Error for QueryError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
QueryError::BadStatus(_) => None,
QueryError::BadResponse(err) => Some(err),
QueryError::Hyper(err) => Some(err),
}
}
}