bastion/src/bastion/src/path.rs

724 lines
22 KiB
Rust

//!
//! A path represents a message sender's semantics and
//! later will be used to route messages to them
use crate::context::{BastionId, NIL_ID};
use std::fmt;
use std::result::Result;
#[derive(Clone)]
/// Represents a Path for a System, Supervisor, Children or Child.
///
/// BastionPath can be used to identify message senders.
/// Later it will be used to route messages to a path.
///
/// # Example
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # #[cfg(feature = "tokio-runtime")]
/// # #[tokio::main]
/// # async fn main() {
/// # run();
/// # }
/// #
/// # #[cfg(not(feature = "tokio-runtime"))]
/// # fn main() {
/// # run();
/// # }
/// #
/// # fn run() {
/// # Bastion::init();
///
/// # Bastion::children(|children| {
/// # children.with_exec(|ctx: BastionContext| {
/// # async move {
/// ctx.tell(&ctx.signature(), "Hello to myself").expect("Couldn't send a message");
/// msg! { ctx.recv().await?,
/// ref msg: &'static str => {
/// let path: &BastionPath = signature!().path();
/// assert_eq!(path.elem(), ctx.signature().path().elem());
/// };
/// // We are only sending a `&'static str` in this
/// // example, so we know that this won't happen...
/// _: _ => ();
/// }
/// # Bastion::stop();
/// # Ok(())
/// # }
/// # })
/// # }).unwrap();
/// #
/// # Bastion::start();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub struct BastionPath {
// TODO: possibly more effective collection depending on how we'll use it in routing
parent_chain: Vec<BastionId>,
this: Option<BastionPathElement>,
}
impl BastionPath {
// SYSTEM or a sender out of Bastion scope
pub(crate) fn root() -> BastionPath {
BastionPath {
parent_chain: vec![],
this: None,
}
}
/// iterates over path elements
pub(crate) fn iter(&self) -> impl Iterator<Item = &BastionId> {
let parent_iter = self.parent_chain.iter();
parent_iter.chain(self.this.iter().map(|e| e.id()))
}
/// Returns the last element's id.
/// If it's root or a dead_letters then &NIL_ID is returned.
///
/// # Example
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # #[cfg(feature = "tokio-runtime")]
/// # #[tokio::main]
/// # async fn main() {
/// # run();
/// # }
/// #
/// # #[cfg(not(feature = "tokio-runtime"))]
/// # fn main() {
/// # run();
/// # }
/// #
/// # fn run() {
/// # Bastion::init();
/// #
/// # let children_ref = Bastion::children(|children| children).unwrap();
/// let msg = "A message containing data.";
/// children_ref.broadcast(msg).expect("Couldn't send the message.");
///
/// # Bastion::children(|children| {
/// # children.with_exec(|ctx: BastionContext| {
/// # async move {
/// msg! { ctx.recv().await?,
/// ref msg: &'static str => {
/// let path = signature!().path();
/// assert_eq!(path.id(), &NIL_ID);
/// };
/// // We are only sending a `&'static str` in this
/// // example, so we know that this won't happen...
/// _: _ => ();
/// }
/// #
/// # Ok(())
/// # }
/// # })
/// # }).unwrap();
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn id(&self) -> &BastionId {
self.this.as_ref().map(|e| e.id()).unwrap_or(&NIL_ID)
}
/// Returns a path element. If the path is root then None is returned.
///
/// # Example
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # #[cfg(feature = "tokio-runtime")]
/// # #[tokio::main]
/// # async fn main() {
/// # run();
/// # }
/// #
/// # #[cfg(not(feature = "tokio-runtime"))]
/// # fn main() {
/// # run();
/// # }
/// #
/// # fn run() {
/// # Bastion::init();
/// #
/// # let children_ref = Bastion::children(|children| children).unwrap();
/// let msg = "A message containing data.";
/// children_ref.broadcast(msg).expect("Couldn't send the message.");
///
/// # Bastion::children(|children| {
/// # children.with_exec(|ctx: BastionContext| {
/// # async move {
/// msg! { ctx.recv().await?,
/// ref msg: &'static str => {
/// let path = signature!().path();
/// assert!(path.elem().as_ref().unwrap().is_children());
/// };
/// // We are only sending a `&'static str` in this
/// // example, so we know that this won't happen...
/// _: _ => ();
/// }
/// #
/// # Ok(())
/// # }
/// # })
/// # }).unwrap();
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn elem(&self) -> &Option<BastionPathElement> {
&self.this
}
/// Checks whether `BastionPath` is a dead-letters path.
///
/// # Example
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # #[cfg(feature = "tokio-runtime")]
/// # #[tokio::main]
/// # async fn main() {
/// # run();
/// # }
/// #
/// # #[cfg(not(feature = "tokio-runtime"))]
/// # fn main() {
/// # run();
/// # }
/// #
/// # fn run() {
/// # Bastion::init();
/// #
/// # let children_ref = Bastion::children(|children| children).unwrap();
/// let msg = "A message containing data.";
/// children_ref.broadcast(msg).expect("Couldn't send the message.");
///
/// # Bastion::children(|children| {
/// # children.with_exec(|ctx: BastionContext| {
/// # async move {
/// msg! { ctx.recv().await?,
/// ref msg: &'static str => {
/// let path = signature!().path();
/// assert!(path.is_dead_letters());
/// };
/// // We are only sending a `&'static str` in this
/// // example, so we know that this won't happen...
/// _: _ => ();
/// }
/// #
/// # Ok(())
/// # }
/// # })
/// # }).unwrap();
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn is_dead_letters(&self) -> bool {
self.parent_chain.len() == 2 && self.this.as_ref().map(|e| e.is_child()).unwrap_or(false)
}
}
impl fmt::Display for BastionPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"/{}",
self.iter()
.map(|id| format!("{}", id))
.collect::<Vec<String>>()
.join("/")
)
}
}
impl fmt::Debug for BastionPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.this {
Some(this @ BastionPathElement::Supervisor(_))
| Some(this @ BastionPathElement::Children(_)) => write!(
f,
"/{}",
self.parent_chain
.iter()
.map(|id| BastionPathElement::Supervisor(id.clone()))
.chain(vec![this.clone()])
.map(|el| format!("{:?}", el))
.collect::<Vec<String>>()
.join("/")
),
Some(this @ BastionPathElement::Child(_)) => {
let parent_len = self.parent_chain.len();
write!(
f,
"/{}",
self.parent_chain
.iter()
.enumerate()
.map(|(i, id)| {
if i == parent_len - 1 {
BastionPathElement::Children(id.clone())
} else {
BastionPathElement::Supervisor(id.clone())
}
})
.chain(vec![this.clone()])
.map(|el| format!("{:?}", el))
.collect::<Vec<String>>()
.join("/")
)
}
None => write!(f, "/"),
}
}
}
#[derive(Clone, PartialEq)]
/// Represents BastionPath element
///
/// # Example
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # #[cfg(feature = "tokio-runtime")]
/// # #[tokio::main]
/// # async fn main() {
/// # run();
/// # }
/// #
/// # #[cfg(not(feature = "tokio-runtime"))]
/// # fn main() {
/// # run();
/// # }
/// #
/// # fn run() {
/// # Bastion::init();
/// #
///
/// Bastion::children(|children| {
/// children.with_exec(|ctx: BastionContext| {
/// async move {
/// ctx.tell(&ctx.signature(), "Hello to myself");
///
/// let msg: SignedMessage = ctx.recv().await?;
/// let elem: &Option<BastionPathElement> = msg.signature().path().elem();
/// assert!(elem.is_some());
/// assert_eq!(elem, ctx.signature().path().elem());
///
/// # Bastion::stop();
/// Ok(())
/// }
/// })
/// }).expect("Couldn't create the children group.");
/// #
/// # Bastion::start();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub enum BastionPathElement {
#[doc(hidden)]
/// Supervisor element
Supervisor(BastionId),
#[doc(hidden)]
/// Children element
Children(BastionId),
#[doc(hidden)]
/// Child element
Child(BastionId),
}
impl fmt::Debug for BastionPathElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BastionPathElement::Supervisor(id) => write!(f, "supervisor#{}", id),
BastionPathElement::Children(id) => write!(f, "children#{}", id),
BastionPathElement::Child(id) => write!(f, "child#{}", id),
}
}
}
impl BastionPathElement {
pub(crate) fn id(&self) -> &BastionId {
match self {
BastionPathElement::Supervisor(id) => id,
BastionPathElement::Children(id) => id,
BastionPathElement::Child(id) => id,
}
}
#[doc(hidden)]
/// Checks whether the BastionPath identifies a supervisor.
pub fn is_supervisor(&self) -> bool {
matches!(self, BastionPathElement::Supervisor(_))
}
#[doc(hidden)]
/// Checks whether the BastionPath identifies children.
pub fn is_children(&self) -> bool {
matches!(self, BastionPathElement::Children(_))
}
/// Checks whether the BastionPath identifies a child.
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # #[cfg(feature = "tokio-runtime")]
/// # #[tokio::main]
/// # async fn main() {
/// # run();
/// # }
/// #
/// # #[cfg(not(feature = "tokio-runtime"))]
/// # fn main() {
/// # run();
/// # }
/// #
/// # fn run() {
/// # Bastion::init();
///
/// Bastion::children(|children| {
/// children.with_exec(|ctx: BastionContext| {
/// async move {
/// ctx.tell(&ctx.signature(), "Hello to myself");
///
/// let msg: SignedMessage = ctx.recv().await?;
/// let elem: &Option<BastionPathElement> = msg.signature().path().elem();
/// assert!(elem.is_some());
/// assert_eq!(elem, ctx.signature().path().elem());
///
/// # Bastion::stop();
/// Ok(())
/// }
/// })
/// }).expect("Couldn't create the children group.");
/// #
/// # Bastion::start();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn is_child(&self) -> bool {
matches!(self, BastionPathElement::Child(_))
}
}
#[derive(Clone)]
pub(crate) struct AppendError {
path: BastionPath,
element: BastionPathElement,
}
impl fmt::Display for AppendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.element {
BastionPathElement::Supervisor(..) => match self.path.this {
None => unreachable!(),
Some(BastionPathElement::Supervisor(..)) => unreachable!(),
Some(BastionPathElement::Children(..)) => {
write!(f, "Supervisor is not appendable to children")
}
Some(BastionPathElement::Child(..)) => {
write!(f, "Supervisor is not appendable to a child")
}
},
BastionPathElement::Children(..) => match self.path.this {
None => write!(f, "Children is not appendable to root"),
Some(BastionPathElement::Supervisor(..)) => unreachable!(),
Some(BastionPathElement::Children(..)) => {
write!(f, "Children is not appendable to children")
}
Some(BastionPathElement::Child(..)) => {
write!(f, "Children is not appendable to a child")
}
},
BastionPathElement::Child(..) => match self.path.this {
None => write!(f, "Child is not appendable to root"),
Some(BastionPathElement::Supervisor(..)) => {
write!(f, "Child is not appendable to a supervisor")
}
Some(BastionPathElement::Children(..)) => unreachable!(),
Some(BastionPathElement::Child(..)) => {
write!(f, "Child is not appendable to a child")
}
},
}
}
}
impl fmt::Debug for AppendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Can't append {:?} to {:?}", self.element, self.path)
}
}
impl BastionPath {
pub(crate) fn append(self, el: BastionPathElement) -> Result<BastionPath, AppendError> {
match el {
sv @ BastionPathElement::Supervisor(_) => match self.this {
None => Ok(BastionPath {
parent_chain: self.parent_chain,
this: Some(sv),
}),
Some(BastionPathElement::Supervisor(id)) => {
let mut path = BastionPath {
parent_chain: self.parent_chain,
this: Some(sv),
};
path.parent_chain.push(id);
Ok(path)
}
this => Err(AppendError {
path: BastionPath {
parent_chain: self.parent_chain,
this,
},
element: sv,
}),
},
children @ BastionPathElement::Children(_) => match self.this {
Some(BastionPathElement::Supervisor(id)) => {
let mut path = BastionPath {
parent_chain: self.parent_chain,
this: Some(children),
};
path.parent_chain.push(id);
Ok(path)
}
this => Err(AppendError {
path: BastionPath {
parent_chain: self.parent_chain,
this,
},
element: children,
}),
},
child @ BastionPathElement::Child(_) => match self.this {
Some(BastionPathElement::Children(id)) => {
let mut path = BastionPath {
parent_chain: self.parent_chain,
this: Some(child),
};
path.parent_chain.push(id);
Ok(path)
}
this => Err(AppendError {
path: BastionPath {
parent_chain: self.parent_chain,
this,
},
element: child,
}),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
// SYSTEM + smth
#[test]
fn append_sv_to_system() {
let sv_id = BastionId::new();
let path = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id.clone()))
.unwrap();
assert_eq!(path.iter().collect::<Vec<&BastionId>>(), vec![&sv_id]);
}
#[test]
fn append_children_to_system() {
let sv_id = BastionId::new();
let res = BastionPath::root().append(BastionPathElement::Children(sv_id));
assert_eq!(
res.unwrap_err().to_string(),
"Children is not appendable to root"
);
}
#[test]
fn append_child_to_system() {
let sv_id = BastionId::new();
let res = BastionPath::root().append(BastionPathElement::Child(sv_id));
assert_eq!(
res.unwrap_err().to_string(),
"Child is not appendable to root"
);
}
// Supervisor + smth
#[test]
fn append_sv_to_sv() {
let sv1_id = BastionId::new();
let sv2_id = BastionId::new();
let path = BastionPath::root()
.append(BastionPathElement::Supervisor(sv1_id.clone()))
.unwrap()
.append(BastionPathElement::Supervisor(sv2_id.clone()))
.unwrap();
assert_eq!(
path.iter().collect::<Vec<&BastionId>>(),
vec![&sv1_id, &sv2_id]
);
}
#[test]
fn append_children_to_sv() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let path = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id.clone()))
.unwrap()
.append(BastionPathElement::Children(children_id.clone()))
.unwrap();
assert_eq!(
path.iter().collect::<Vec<&BastionId>>(),
vec![&sv_id, &children_id]
);
}
#[test]
fn append_child_to_sv() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let res = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id))
.unwrap()
.append(BastionPathElement::Child(children_id));
assert_eq!(
res.unwrap_err().to_string(),
"Child is not appendable to a supervisor"
);
}
// children + smth
#[test]
fn append_sv_to_children() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let res = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id))
.unwrap()
.append(BastionPathElement::Children(children_id))
.unwrap()
.append(BastionPathElement::Supervisor(BastionId::new()));
assert_eq!(
res.unwrap_err().to_string(),
"Supervisor is not appendable to children"
);
}
#[test]
fn append_children_to_children() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let res = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id))
.unwrap()
.append(BastionPathElement::Children(children_id))
.unwrap()
.append(BastionPathElement::Children(BastionId::new()));
assert_eq!(
res.unwrap_err().to_string(),
"Children is not appendable to children"
);
}
#[test]
fn append_child_to_children() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let child_id = BastionId::new();
let path = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id.clone()))
.unwrap()
.append(BastionPathElement::Children(children_id.clone()))
.unwrap()
.append(BastionPathElement::Child(child_id.clone()))
.unwrap();
assert_eq!(
path.iter().collect::<Vec<&BastionId>>(),
vec![&sv_id, &children_id, &child_id]
);
}
// child + smth
#[test]
fn append_sv_to_child() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let child_id = BastionId::new();
let res = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id))
.unwrap()
.append(BastionPathElement::Children(children_id))
.unwrap()
.append(BastionPathElement::Child(child_id))
.unwrap()
.append(BastionPathElement::Supervisor(BastionId::new()));
assert_eq!(
res.unwrap_err().to_string(),
"Supervisor is not appendable to a child"
);
}
#[test]
fn append_children_to_child() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let child_id = BastionId::new();
let res = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id))
.unwrap()
.append(BastionPathElement::Children(children_id))
.unwrap()
.append(BastionPathElement::Child(child_id))
.unwrap()
.append(BastionPathElement::Children(BastionId::new()));
assert_eq!(
res.unwrap_err().to_string(),
"Children is not appendable to a child"
);
}
#[test]
fn append_child_to_child() {
let sv_id = BastionId::new();
let children_id = BastionId::new();
let child_id = BastionId::new();
let res = BastionPath::root()
.append(BastionPathElement::Supervisor(sv_id))
.unwrap()
.append(BastionPathElement::Children(children_id))
.unwrap()
.append(BastionPathElement::Child(child_id))
.unwrap()
.append(BastionPathElement::Child(BastionId::new()));
assert_eq!(
res.unwrap_err().to_string(),
"Child is not appendable to a child"
);
}
}