bonsaimq/
registry.rs

1//! Job registry for finding the code to execute based on incoming messages of
2//! specified types. Allows to spawn new jobs / messages using
3//! `JobRegistry::Handle.builder().spawn().await?`.
4
5use std::{error::Error, future::Future, pin::Pin};
6
7use crate::{spawn::JobBuilder, CurrentJob};
8
9/// Function type of the jobs returned by the job registry.
10pub type JobFunctionType = Box<
11	dyn FnMut(
12			CurrentJob,
13		) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn Error + Send + Sync>>> + Send>>
14		+ Send,
15>;
16
17/// Functions the registry exposes.
18pub trait JobRegister: Sized {
19	/// Spawn a new job/message using this builder.
20	fn builder(self) -> JobBuilder;
21	/// Return the message name of this message/job.
22	fn name(&self) -> &'static str;
23	/// Get the registry entry based on message name. Returns None if not found.
24	fn from_name(name: &str) -> Option<Self>;
25	/// Return the handler for this message/job.
26	fn function(&self) -> JobFunctionType;
27}
28
29/// Creates a job registry with the given name as first parameter. The second
30/// parameter is a named map of message names to functions, which are executed
31/// for the message.
32///
33/// Example:
34/// ```
35/// # use bonsaimq::{job_registry, CurrentJob};
36/// async fn async_message_handler_fn(_job: CurrentJob) -> color_eyre::Result<()> {
37///     Ok(())
38/// }
39///
40/// job_registry!(JobRegistry, {
41///     Ident: "message_name" => async_message_handler_fn,
42/// });
43/// ```
44#[macro_export]
45macro_rules! job_registry {
46	(
47		$reg_name:ident,
48		{$($msg_fn_name:ident: $msg_name:literal => $msg_fn:path),*$(,)?}
49	) => {
50		#[doc = "Job Registry"]
51		#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52		pub enum $reg_name {
53			$(
54				#[doc = concat!("`", $msg_name, "` leading to `", stringify!($msg_fn), "`.")]
55				#[allow(non_camel_case_types)]
56				$msg_fn_name
57			),*
58		}
59
60		impl $crate::JobRegister for $reg_name {
61			#[inline]
62			fn builder(self) -> $crate::JobBuilder {
63				match self {
64					$(Self::$msg_fn_name => $crate::JobBuilder::new($msg_name)),*
65				}
66			}
67
68			/// Return the message name of this message/job
69			#[inline]
70			fn name(&self) -> &'static str {
71				match *self {
72					$(Self::$msg_fn_name => $msg_name),*
73				}
74			}
75
76			/// Get the registry entry based on message name. Returns None if not found.
77			#[inline]
78			fn from_name(name: &str) -> Option<Self> {
79				match name {
80					$($msg_name => Some(Self::$msg_fn_name)),*,
81					_ => None,
82				}
83			}
84
85			/// Return the function of this message/job
86			#[inline]
87			fn function(&self) -> $crate::JobFunctionType {
88				match *self {
89					$(Self::$msg_fn_name => Box::new(|job| Box::pin(async move {
90						$msg_fn(job).await.map_err(Into::into)
91					}))),*
92				}
93			}
94		}
95	};
96}
97
98/// Test correct macro type referencing and implementation. See if it compiles.
99#[cfg(test)]
100mod tests {
101	#![allow(clippy::expect_used, unused_qualifications, clippy::unused_async)]
102
103	use color_eyre::Result;
104
105	use super::*;
106	use crate::job_registry;
107
108	job_registry!(JobRegistry, {
109		some_fn: "cats" => some_fn,
110		OtherFn: "foxes" => self::some_other_fn,
111	});
112
113	async fn some_fn(_job: CurrentJob) -> Result<()> {
114		Ok(())
115	}
116	async fn some_other_fn(_job: CurrentJob) -> Result<(), Box<dyn Error + Send + Sync>> {
117		Ok(())
118	}
119
120	#[test]
121	fn test_job_registry() {
122		let name = JobRegistry::some_fn.name();
123		assert_eq!(name, "cats");
124
125		let _function = JobRegistry::from_name("foxes").expect("name was set").function();
126
127		let _builder = JobRegistry::some_fn.builder();
128	}
129}