#![warn(missing_docs)]
pub mod meta;
pub use meta::{ChainKind, ChainStep, JigDef, JigMeta};
pub mod json;
#[doc(hidden)]
pub trait __Classify {
const KIND: &'static str;
}
pub trait Request: Sized + __Classify {
type Payload;
fn payload(&self) -> &Self::Payload;
fn into_payload(self) -> Self::Payload;
fn from_payload(payload: Self::Payload) -> Self;
fn then<J, U>(self, jig: J) -> U
where
J: Jig<Self, Out = U>,
{
jig.run(self)
}
}
pub trait Response: Sized + __Classify {
type Payload;
fn ok(payload: Self::Payload) -> Self;
fn err(msg: impl Into<String>) -> Self;
fn is_ok(&self) -> bool;
fn is_err(&self) -> bool {
!self.is_ok()
}
fn into_result(self) -> Result<Self::Payload, String>;
fn from_result(result: Result<Self::Payload, String>) -> Self {
match result {
Ok(v) => Self::ok(v),
Err(e) => Self::err(e),
}
}
fn error_msg(&self) -> Option<String> {
if self.is_err() {
Some("unknown error".into())
} else {
None
}
}
fn then<J>(self, jig: J) -> J::Out
where
J: Jig<Self>,
J::Out: Response,
{
jig.run(self)
}
}
#[derive(Debug)]
pub enum Branch<Req, Resp> {
Continue(Req),
Done(Resp),
}
impl<Req: Request, Resp: Response> __Classify for Branch<Req, Resp> {
const KIND: &'static str = "Branch";
}
impl<Req, Resp> Branch<Req, Resp> {
#[must_use]
pub fn is_continue(&self) -> bool {
matches!(self, Branch::Continue(_))
}
#[must_use]
pub fn is_done(&self) -> bool {
matches!(self, Branch::Done(_))
}
}
pub trait Jig<In> {
type Out;
fn run(&self, input: In) -> Self::Out;
}
impl<In, Out, F> Jig<In> for F
where
F: Fn(In) -> Out,
{
type Out = Out;
fn run(&self, input: In) -> Out {
(self)(input)
}
}
pub struct Pending<F>(pub F);
impl<F> __Classify for Pending<F> {
const KIND: &'static str = "Pending";
}
pub trait Step {
type Out;
type Fut: core::future::Future<Output = Self::Out>;
fn into_step(self) -> Self::Fut;
}
impl<REQ, RESP> Step for Branch<REQ, RESP>
where
REQ: Request,
RESP: Response,
{
type Out = Branch<REQ, RESP>;
type Fut = core::future::Ready<Branch<REQ, RESP>>;
fn into_step(self) -> Self::Fut {
core::future::ready(self)
}
}
impl<F> Step for Pending<F>
where
F: core::future::Future,
{
type Out = F::Output;
type Fut = F;
fn into_step(self) -> Self::Fut {
self.0
}
}
impl<F> core::future::IntoFuture for Pending<F>
where
F: core::future::Future,
{
type Output = F::Output;
type IntoFuture = F;
fn into_future(self) -> F {
self.0
}
}
impl<F> Pending<F>
where
F: core::future::Future + 'static,
{
pub fn then<J, R>(self, jig: J) -> Pending<impl core::future::Future<Output = R::Out>>
where
J: Jig<F::Output, Out = R> + 'static,
R: Step + 'static,
{
Pending(async move {
let val = self.0.await;
jig.run(val).into_step().await
})
}
}
pub trait Status {
fn succeeded(&self) -> bool;
fn error(&self) -> Option<String> {
None
}
}
impl<REQ, RESP> Status for Branch<REQ, RESP>
where
REQ: Request,
RESP: Response,
{
fn succeeded(&self) -> bool {
match self {
Branch::Continue(_) => true,
Branch::Done(r) => r.is_ok(),
}
}
fn error(&self) -> Option<String> {
match self {
Branch::Continue(_) => None,
Branch::Done(r) => r.error_msg(),
}
}
}
pub trait Merge<R> {
type Merged;
fn into_continue(self) -> Self::Merged;
fn from_done(resp: R) -> Self::Merged;
}
impl<REQ, RESP> Merge<RESP> for Branch<REQ, RESP>
where
REQ: Request,
RESP: Response,
{
type Merged = Branch<REQ, RESP>;
fn into_continue(self) -> Self::Merged {
self
}
fn from_done(resp: RESP) -> Self::Merged {
Branch::Done(resp)
}
}
impl<REQ, RESP> Branch<REQ, RESP>
where
REQ: Request,
RESP: Response,
{
#[allow(clippy::needless_pass_by_value)]
pub fn then<J, Out>(self, jig: J) -> <Out as Merge<RESP>>::Merged
where
J: Jig<REQ, Out = Out>,
Out: Merge<RESP>,
{
match self {
Branch::Continue(r) => Out::into_continue(jig.run(r)),
Branch::Done(resp) => Out::from_done(resp),
}
}
}
#[macro_export]
macro_rules! impl_request {
($t:ty) => {
impl $crate::__Classify for $t {
const KIND: &'static str = "Request";
}
impl $crate::Step for $t {
type Out = $t;
type Fut = ::core::future::Ready<$t>;
fn into_step(self) -> Self::Fut {
::core::future::ready(self)
}
}
impl<R: $crate::Response> $crate::Merge<R> for $t {
type Merged = $crate::Branch<$t, R>;
fn into_continue(self) -> Self::Merged {
$crate::Branch::Continue(self)
}
fn from_done(resp: R) -> Self::Merged {
$crate::Branch::Done(resp)
}
}
impl $crate::Status for $t {
fn succeeded(&self) -> bool {
true
}
fn error(&self) -> Option<String> {
None
}
}
};
}
#[macro_export]
macro_rules! impl_response {
($t:ty) => {
impl $crate::__Classify for $t {
const KIND: &'static str = "Response";
}
impl $crate::Step for $t {
type Out = $t;
type Fut = ::core::future::Ready<$t>;
fn into_step(self) -> Self::Fut {
::core::future::ready(self)
}
}
impl $crate::Merge<$t> for $t {
type Merged = $t;
fn into_continue(self) -> Self::Merged {
self
}
fn from_done(resp: $t) -> Self::Merged {
resp
}
}
impl $crate::Status for $t {
fn succeeded(&self) -> bool {
$crate::Response::is_ok(self)
}
fn error(&self) -> Option<String> {
$crate::Response::error_msg(self)
}
}
};
}
#[macro_export]
macro_rules! fork {
($req:expr, $($pred:expr => $jig:expr,)+ _ => $default:expr $(,)?) => {{
let __req = $req;
$crate::__fork_chain!(__req, $($pred => $jig,)+ ; $default)
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __fork_chain {
($req:ident, $pred:expr => $jig:expr, $($rest_p:expr => $rest_j:expr,)* ; $default:expr) => {
if ($pred)($crate::Request::payload(&$req)) {
($jig)($req)
} else {
$crate::__fork_chain!($req, $($rest_p => $rest_j,)* ; $default)
}
};
($req:ident, ; $default:expr) => {
($default)($req)
};
}
#[cfg(test)]
mod tests;