#![allow(missing_docs)]
use crate::{ensure, Error, Unit};
#[cfg(feature = "diesel")]
use diesel::{
backend::Backend,
deserialize::{self, FromSql, FromSqlRow},
expression::AsExpression,
serialize::{self, IsNull, Output, ToSql},
sql_types::Text,
sqlite::Sqlite,
};
use enum_assoc::Assoc;
use libipld::{cid::Cid, serde::from_ipld, Ipld, Link};
use serde::{Deserialize, Serialize};
#[cfg(feature = "diesel")]
use std::str::FromStr;
use std::{borrow::Cow, collections::btree_map::BTreeMap, fmt};
pub const OK_BRANCH: &str = "await/ok";
pub const ERR_BRANCH: &str = "await/error";
pub const PTR_BRANCH: &str = "await/*";
#[derive(Clone, Debug, PartialEq, Eq, Assoc, Deserialize, Serialize)]
#[func(pub const fn branch(&self) -> &'static str)]
#[func(pub fn result(s: &str) -> Option<Self>)]
pub enum AwaitResult {
#[assoc(branch = OK_BRANCH)]
#[assoc(result = OK_BRANCH)]
Ok,
#[assoc(branch = ERR_BRANCH)]
#[assoc(result = ERR_BRANCH)]
Error,
#[assoc(branch = PTR_BRANCH)]
#[assoc(result = PTR_BRANCH)]
Ptr,
}
impl fmt::Display for AwaitResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AwaitResult::Error => write!(f, "await/error"),
AwaitResult::Ok => write!(f, "await/ok"),
AwaitResult::Ptr => write!(f, "await/*"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Await {
instruction: Pointer,
result: AwaitResult,
}
impl Await {
pub fn new(instruction: Pointer, result: AwaitResult) -> Self {
Self {
instruction,
result,
}
}
pub fn instruction_cid(&self) -> Cid {
self.instruction.cid()
}
pub fn result(&self) -> &AwaitResult {
&self.result
}
}
impl From<Await> for Ipld {
fn from(await_promise: Await) -> Self {
Ipld::Map(BTreeMap::from([(
await_promise.result.branch().to_string(),
await_promise.instruction.into(),
)]))
}
}
impl From<&Await> for Ipld {
fn from(await_promise: &Await) -> Self {
From::from(await_promise.to_owned())
}
}
impl TryFrom<Ipld> for Await {
type Error = Error<Unit>;
fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
let map = from_ipld::<BTreeMap<String, Ipld>>(ipld)?;
ensure!(
map.len() == 1,
Error::ConditionNotMet(
"await promise must jave only a single key ain a map".to_string()
)
);
let (key, value) = map.into_iter().next().unwrap();
let instruction = Pointer::try_from(value)?;
let result = match key.as_str() {
OK_BRANCH => AwaitResult::Ok,
ERR_BRANCH => AwaitResult::Error,
_ => AwaitResult::Ptr,
};
Ok(Await {
instruction,
result,
})
}
}
impl TryFrom<&Ipld> for Await {
type Error = Error<Unit>;
fn try_from(ipld: &Ipld) -> Result<Self, Self::Error> {
TryFrom::try_from(ipld.to_owned())
}
}
#[cfg(feature = "diesel")]
#[cfg_attr(docsrs, doc(cfg(feature = "diesel")))]
#[derive(
Clone,
Debug,
AsExpression,
FromSqlRow,
PartialEq,
Eq,
Serialize,
Deserialize,
Hash,
PartialOrd,
Ord,
)]
#[diesel(sql_type = Text)]
#[repr(transparent)]
pub struct Pointer(Cid);
#[cfg(not(feature = "diesel"))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Pointer(Cid);
impl fmt::Display for Pointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let cid_as_string = self.0.to_string();
write!(f, "{cid_as_string}")
}
}
impl Pointer {
pub fn cid(&self) -> Cid {
self.0
}
pub fn new(cid: Cid) -> Self {
Pointer(cid)
}
pub fn new_from_link<T>(link: Link<T>) -> Self {
Pointer(*link)
}
}
impl From<Pointer> for Ipld {
fn from(ptr: Pointer) -> Self {
Ipld::Link(ptr.cid())
}
}
impl TryFrom<Ipld> for Pointer {
type Error = Error<Unit>;
fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
let s: Cid = from_ipld(ipld)?;
Ok(Pointer(s))
}
}
impl TryFrom<&Ipld> for Pointer {
type Error = Error<Unit>;
fn try_from(ipld: &Ipld) -> Result<Self, Self::Error> {
TryFrom::try_from(ipld.to_owned())
}
}
impl<'a> From<Pointer> for Cow<'a, Pointer> {
fn from(ptr: Pointer) -> Self {
Cow::Owned(ptr)
}
}
impl<'a> From<&'a Pointer> for Cow<'a, Pointer> {
fn from(ptr: &'a Pointer) -> Self {
Cow::Borrowed(ptr)
}
}
#[cfg(feature = "diesel")]
#[cfg_attr(docsrs, doc(cfg(feature = "diesel")))]
impl ToSql<Text, Sqlite> for Pointer {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(self.cid().to_string());
Ok(IsNull::No)
}
}
#[cfg(feature = "diesel")]
#[cfg_attr(docsrs, doc(cfg(feature = "diesel")))]
impl<DB> FromSql<Text, DB> for Pointer
where
DB: Backend,
String: FromSql<Text, DB>,
{
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
let s = String::from_sql(bytes)?;
Ok(Pointer::new(Cid::from_str(&s)?))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test_utils::cid::generate_cid;
use rand::thread_rng;
#[test]
fn ser_de_pointer() {
let pointer = Pointer::new(generate_cid(&mut thread_rng()));
let ser = serde_json::to_string(&pointer).unwrap();
let de = serde_json::from_str(&ser).unwrap();
assert_eq!(pointer, de);
}
#[test]
fn ser_de_await() {
let awaited = Await::new(
Pointer::new(generate_cid(&mut thread_rng())),
AwaitResult::Ok,
);
let ser = serde_json::to_string(&awaited).unwrap();
let de = serde_json::from_str(&ser).unwrap();
assert_eq!(awaited, de);
}
}