use crate::{DynMessage, Environment, ViewId};
use alloc::{boxed::Box, vec::Vec};
use anymore::AnyDebug;
#[derive(Debug)]
pub struct MessageContext {
pub(crate) environment: Environment,
full_id_path: Vec<ViewId>,
id_path_index: usize,
message: Option<DynMessage>,
}
impl MessageContext {
pub fn take_first(&mut self) -> Option<ViewId> {
let ret = self.full_id_path.get(self.id_path_index)?;
self.id_path_index += 1;
Some(*ret)
}
pub fn remaining_path(&self) -> &[ViewId] {
&self.full_id_path[self.id_path_index..]
}
pub fn current_path(&self) -> &[ViewId] {
&self.full_id_path[..self.id_path_index]
}
#[track_caller]
pub fn take_message<T: AnyDebug>(&mut self) -> Option<Box<T>> {
self.maybe_take_message(|_| true)
}
#[track_caller]
pub fn maybe_take_message<T: AnyDebug>(
&mut self,
f: impl FnOnce(&T) -> bool,
) -> Option<Box<T>> {
debug_assert_eq!(
self.full_id_path.len(),
self.id_path_index,
"Can't take a message that has not reached its target"
);
if let Some(message) = self.message.take() {
if message.is::<T>() {
let message = message.downcast().unwrap();
if f(&*message) {
return Some(message);
} else {
self.message = Some(DynMessage(message));
}
} else {
self.message = Some(message);
}
None
} else {
panic!("The message has already been taken.");
}
}
#[track_caller]
pub fn force_take_message<T: AnyDebug>(&mut self) -> Option<DynMessage> {
self.message.take()
}
}
impl MessageContext {
pub fn new(environment: Environment, target_id_path: Vec<ViewId>, message: DynMessage) -> Self {
Self {
environment,
full_id_path: target_id_path,
id_path_index: 0,
message: Some(message),
}
}
pub fn finish(self) -> (Environment, Vec<ViewId>, Option<DynMessage>) {
let Self {
environment,
full_id_path,
message,
..
} = self;
(environment, full_id_path, message)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use crate::{DynMessage, Environment, MessageContext, ViewId};
#[test]
fn take_path_full_path() {
let env = Environment::new();
let path = [0, 4, 3, 2, 1, 0]
.into_iter()
.map(ViewId::new)
.collect::<Vec<_>>();
let mut ctx = MessageContext::new(env, path.clone(), DynMessage::new(()));
for element in &path {
let next = ctx.take_first().unwrap();
assert_eq!(next, *element);
assert!(path.starts_with(ctx.current_path()));
assert!(path.ends_with(ctx.remaining_path()));
assert_eq!(
path.len(),
ctx.current_path().len() + ctx.remaining_path().len()
);
assert_eq!(*ctx.current_path().last().unwrap(), next);
}
assert!(ctx.take_first().is_none());
}
#[test]
#[cfg_attr(
not(debug_assertions),
ignore = "This test doesn't work without debug assertions (i.e. in release mode)"
)]
#[should_panic(expected = "Can't take a message that has not reached its target")]
fn take_message_nonempty_path() {
let env = Environment::new();
let path = vec![ViewId::new(1)];
let mut ctx = MessageContext::new(env, path.clone(), DynMessage::new(()));
ctx.take_message::<()>();
}
#[test]
fn take_message_wrong_type() {
let env = Environment::new();
let path = vec![];
let mut ctx = MessageContext::new(env, path.clone(), DynMessage::new(()));
let took = ctx.take_message::<u32>();
assert!(took.is_none());
let () = *ctx.take_message::<()>().unwrap();
}
#[test]
#[should_panic(expected = "The message has already been taken.")]
fn take_message_twice() {
let env = Environment::new();
let path = vec![];
let mut ctx = MessageContext::new(env, path.clone(), DynMessage::new(()));
let () = *ctx.take_message::<()>().unwrap();
ctx.take_message::<()>();
}
#[test]
fn maybe_take_message() {
let env = Environment::new();
let path = vec![];
let mut ctx = MessageContext::new(env, path.clone(), DynMessage::new(10_u32));
ctx.maybe_take_message::<u32>(|x| {
assert_eq!(*x, 10);
false
});
let ret = ctx.take_message::<u32>().unwrap();
assert_eq!(*ret, 10);
}
}