1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Job registry for finding the code to execute based on incoming messages of
//! specified types. Allows to spawn new jobs / messages using
//! `JobRegistry::Handle.builder().spawn().await?`.

use std::{error::Error, future::Future, pin::Pin};

use crate::{spawn::JobBuilder, CurrentJob};

/// Function type of the jobs returned by the job registry.
pub type JobFunctionType = Box<
	dyn FnMut(
			CurrentJob,
		) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn Error + Send + Sync>>> + Send>>
		+ Send,
>;

/// Functions the registry exposes.
pub trait JobRegister: Sized {
	/// Spawn a new job/message using this builder.
	fn builder(self) -> JobBuilder;
	/// Return the message name of this message/job.
	fn name(&self) -> &'static str;
	/// Get the registry entry based on message name. Returns None if not found.
	fn from_name(name: &str) -> Option<Self>;
	/// Return the handler for this message/job.
	fn function(&self) -> JobFunctionType;
}

/// Creates a job registry with the given name as first parameter. The second
/// parameter is a named map of message names to functions, which are executed
/// for the message.
///
/// Example:
/// ```
/// # use bonsaimq::{job_registry, CurrentJob};
/// async fn async_message_handler_fn(_job: CurrentJob) -> color_eyre::Result<()> {
///     Ok(())
/// }
///
/// job_registry!(JobRegistry, {
///     Ident: "message_name" => async_message_handler_fn,
/// });
/// ```
#[macro_export]
macro_rules! job_registry {
	(
		$reg_name:ident,
		{$($msg_fn_name:ident: $msg_name:literal => $msg_fn:path),*$(,)?}
	) => {
		#[doc = "Job Registry"]
		#[derive(Debug, Clone, Copy, PartialEq, Eq)]
		pub enum $reg_name {
			$(
				#[doc = concat!("`", $msg_name, "` leading to `", stringify!($msg_fn), "`.")]
				#[allow(non_camel_case_types)]
				$msg_fn_name
			),*
		}

		impl $crate::JobRegister for $reg_name {
			#[inline]
			fn builder(self) -> $crate::JobBuilder {
				match self {
					$(Self::$msg_fn_name => $crate::JobBuilder::new($msg_name)),*
				}
			}

			/// Return the message name of this message/job
			#[inline]
			fn name(&self) -> &'static str {
				match *self {
					$(Self::$msg_fn_name => $msg_name),*
				}
			}

			/// Get the registry entry based on message name. Returns None if not found.
			#[inline]
			fn from_name(name: &str) -> Option<Self> {
				match name {
					$($msg_name => Some(Self::$msg_fn_name)),*,
					_ => None,
				}
			}

			/// Return the function of this message/job
			#[inline]
			fn function(&self) -> $crate::JobFunctionType {
				match *self {
					$(Self::$msg_fn_name => Box::new(|job| Box::pin(async move {
						$msg_fn(job).await.map_err(Into::into)
					}))),*
				}
			}
		}
	};
}

/// Test correct macro type referencing and implementation. See if it compiles.
#[cfg(test)]
mod tests {
	#![allow(clippy::expect_used, unused_qualifications, clippy::unused_async)]

	use color_eyre::Result;

	use super::*;
	use crate::job_registry;

	job_registry!(JobRegistry, {
		some_fn: "cats" => some_fn,
		OtherFn: "foxes" => self::some_other_fn,
	});

	async fn some_fn(_job: CurrentJob) -> Result<()> {
		Ok(())
	}
	async fn some_other_fn(_job: CurrentJob) -> Result<(), Box<dyn Error + Send + Sync>> {
		Ok(())
	}

	#[test]
	fn test_job_registry() {
		let name = JobRegistry::some_fn.name();
		assert_eq!(name, "cats");

		let _function = JobRegistry::from_name("foxes").expect("name was set").function();

		let _builder = JobRegistry::some_fn.builder();
	}
}