1#[cfg(feature = "tracing")]
2#[allow(unused_macros)]
3macro_rules! debug {
4 ($($tt:tt)*) => {
5 tracing::debug!($($tt)*)
6 }
7}
8
9#[cfg(feature = "tracing")]
10#[allow(unused_macros)]
11macro_rules! info {
12 ($($tt:tt)*) => {
13 tracing::info!($($tt)*)
14 }
15}
16
17#[cfg(feature = "tracing")]
18#[allow(unused_macros)]
19macro_rules! error {
20 ($($tt:tt)*) => {
21 tracing::error!($($tt)*)
22 };
23}
24
25#[cfg(not(feature = "tracing"))]
26#[allow(unused_macros)]
27macro_rules! debug {
28 ($($tt:tt)*) => {};
29}
30
31#[cfg(not(feature = "tracing"))]
32#[allow(unused_macros)]
33macro_rules! info {
34 ($($tt:tt)*) => {};
35}
36
37#[cfg(not(feature = "tracing"))]
38#[allow(unused_macros)]
39macro_rules! error {
40 ($($tt:tt)*) => {};
41}
42
43#[macro_export]
63macro_rules! features_table {
64 (
65 setup = $setup:literal,
66 $(
67 $feature:tt => $status:ident($description:literal $(, $include_example:tt)?)
68 ),* $(,)?
69 ) => {
70 concat!(
71 "## Features\n",
72 "| Feature | Status | Description |\n",
73 "|---------|--------|------------------------|\n",
74 $(
75 "| ",
76 features_table!(@format_feature_name $feature),
77 " | ",
78 features_table!(@status_icon $status),
79 " | ",
80 $description,
81 " |\n"
82 ),*,
83 "\n",
84 "Key:\n",
85 "✅ : Supported | ⚠️ : Not implemented | ❌ : Not Supported | ❗ Limited support\n\n",
86 "<details>\n",
87 "<summary>Tests:</summary>\n",
88 "\n",
89 $(
90 features_table!(@maybe_generate_doctest $setup, $feature, $status $(, $include_example)?)
91 ),*,
92 "</details>\n"
93 )
94 };
95
96 (@format_feature_name WebUI) => {
98 concat!("[`Web Interface`](https://docs.rs/apalis-board-api/)")
99 };
100
101 (@format_feature_name Serialization) => {
102 concat!("[`Serialization`](https://docs.rs/apalis-core/latest/apalis_core/backend/codec/index.html)")
103 };
104
105 (@format_feature_name Workflow) => {
106 concat!("[`Workflow`](https://docs.rs/apalis-workflow/)")
107 };
108
109 (@format_feature_name $feature:ident) => {
110 concat!("[`", stringify!($feature), "`](https://docs.rs/apalis-core/latest/apalis_core/backend/trait.", stringify!($feature), ".html)")
111 };
112 (@format_feature_name $feature:literal) => {
113 $feature
114 };
115
116 (@maybe_generate_doctest $setup:literal, $feature:tt, $status:ident, $include_example:tt) => {
118 features_table!(@check_should_generate $setup, $feature, $status, $include_example)
119 };
120
121 (@maybe_generate_doctest $setup:literal, $feature:tt, $status:ident) => {
123 features_table!(@check_should_generate $setup, $feature, $status, default)
124 };
125
126 (@check_should_generate $setup:literal, $feature:tt, $status:ident, true) => {
128 features_table!(@do_generate_doctest $setup, $feature, $status)
129 };
130
131 (@check_should_generate $setup:literal, $feature:tt, $status:ident, false) => {
133 ""
134 };
135
136 (@check_should_generate $setup:literal, $feature:tt, supported, default) => {
138 features_table!(@do_generate_doctest $setup, $feature, supported)
139 };
140 (@check_should_generate $setup:literal, $feature:tt, limited, default) => {
141 features_table!(@do_generate_doctest $setup, $feature, limited)
142 };
143 (@check_should_generate $setup:literal, $feature:tt, not_implemented, default) => {
144 ""
145 };
146 (@check_should_generate $setup:literal, $feature:tt, not_supported, default) => {
147 ""
148 };
149
150 (@do_generate_doctest $setup:literal, $feature:ident, $status:ident) => {
152 concat!(
153 "#### ", stringify!($feature), "\n\n",
154 "```rust\n",
155 "# use apalis_core::worker::context::WorkerContext;\n",
156 "# use apalis_core::worker::builder::WorkerBuilder;\n",
157 "#[tokio::main]\n",
158 "async fn main() {\n",
159 " // let mut backend = /* snip */;\n",
160 " # let mut backend = ", $setup, "\n",
161 features_table!(@assert_function $feature), "\n",
162 "```\n\n"
163 )
164 };
165
166 (@assert_function Backend) => { concat!(
167 " # use futures_util::StreamExt; \n",
168 " # use apalis_core::backend::Backend;\n",
169 " async fn task(task: u32, worker: WorkerContext) {\n",
170 " // Do some work \n",
171 " # worker.stop().unwrap();\n",
172 " }\n",
173 " let worker = WorkerBuilder::new(\"backend-test\")\n",
174 " .backend(backend)\n",
175 " .build(task);\n",
176 " let _ = worker.stream().take(1).collect::<Vec<_>>().await;\n",
177 "}\n"
178 ) };
179 (@assert_function TaskSink) => { concat!(
180 " # use apalis_core::backend::TaskSink;\n",
181 " backend.push(42).await.unwrap();\n\n",
182 " async fn task(task: u32, worker: WorkerContext) {\n",
183 " worker.stop().unwrap();\n",
184 " }\n",
185 " let worker = WorkerBuilder::new(\"task-sink-test\")\n",
186 " .backend(backend)\n",
187 " .build(task);\n",
188 " worker.run().await.unwrap();\n",
189 "}\n"
190 ) };
191 (@assert_function MakeShared) => { "fn assert_make_shared<T: Clone + Send + 'static>(t: T); assert_make_shared(backend);" };
192 (@assert_function Workflow) => { concat!(
194 " # use apalis_workflow::*;\n",
195 " backend.push_start(42).await.unwrap();\n\n",
196 " async fn task1(task: u32, worker: WorkerContext) -> u32 {\n",
197 " task + 99 \n",
198 " }\n",
199 " async fn task2(task: u32, worker: WorkerContext) -> u32 {\n",
200 " task + 1 \n",
201 " }\n",
202 " async fn task3(task: u32, worker: WorkerContext) {\n",
203 " assert_eq!(task, 142);\n",
204 " worker.stop().unwrap();\n",
205 " }\n",
206 " let workflow = Workflow::new(\"test-workflow\")\n",
207 " .and_then(task1)\n",
208 " .and_then(task2)\n",
209 " .and_then(task3);\n",
210 " let worker = WorkerBuilder::new(\"workflow-test\")\n",
211 " .backend(backend)\n",
212 " .build(workflow);\n",
213 " worker.run().await.unwrap();\n",
214 "}\n"
215 ) };
216 (@assert_function Serialization) => { concat!(
217 " # use apalis_core::backend::codec::Codec;\n",
218 " # use apalis_core::backend::BackendExt;\n",
219 " fn assert_codec<B: BackendExt<Args =()>>(backend: B) \n",
220 " where\n",
221 " B::Codec: Codec<(), Compact=Vec<u8>>,\n",
222 " {\n",
223 " }\n",
224 " assert_codec(backend);\n",
225 "}\n"
226 ) };
227 (@assert_function WaitForCompletion) => { concat!(
228 "# use apalis_core::backend::WaitForCompletion;\n",
229 " fn assert_wait_for_completion<B: WaitForCompletion<(), Args = u32>>(backend: B) {};\n",
230 " assert_wait_for_completion(backend);\n",
231 "}\n"
232 ) };
233 (@assert_function WebUI) => { concat!(
234 "# use apalis_core::backend::Expose;\n",
235 " fn assert_web_ui<B: Expose<u32>>(backend: B) {};\n",
236 " assert_web_ui(backend);\n",
237 "}\n"
238 ) };
239 (@status_icon supported) => { "✅" };
241 (@status_icon limited) => { "✅ ❗" };
242 (@status_icon not_implemented) => { "⚠️" };
243 (@status_icon not_supported) => { "❌" };
244}
245
246#[cfg(test)]
247mod tests {
248 #[test]
249 fn test_clean_table_structure_with_example_flags() {
250 let table = features_table! {
251 setup = "{
252 use apalis_core::backend::memory::MemoryStorage;
253 // No migrations
254 MemoryStorage::new()
255 };",
256 TaskSink => supported("Ability to push new tasks", true),
257 Serialization => limited("Serialization support for arguments. Only accepts `json`", false),
258 FetchById => not_implemented("Allow fetching a task by its ID"),
259 RegisterWorker => not_supported("Allow registering a worker with the backend"),
260 };
261
262 println!("Generated table:\n{table}");
263
264 assert!(table.contains("## Features"));
266 assert!(table.contains("| Feature | Status | Description"));
267 assert!(table.contains("|---------|--------|------------------------"));
268
269 assert!(table.contains("| ✅ | Ability to push new tasks |"));
271
272 assert!(table.contains("#### TaskSink"));
274
275 assert!(!table.contains("#### Serialization"));
277
278 assert!(!table.contains("#### FetchById"));
280 assert!(!table.contains("#### RegisterWorker"));
281
282 let lines: Vec<&str> = table.lines().collect();
284 for line in lines {
285 if line.starts_with("|") && line.contains("TaskSink") {
286 assert!(
288 !line.contains("```rust"),
289 "Table row contains code block: {line}"
290 );
291 }
292 }
293 }
294
295 #[test]
296 fn test_explicit_false_flag() {
297 let table = features_table! {
298 setup = "{ unreachable!() }",
299 TaskSink => supported("Ability to push new tasks", false),
300 };
301
302 assert!(!table.contains("#### TaskSink Example"));
304 assert!(!table.contains("```rust"));
305 }
306
307 #[test]
308 fn test_explicit_true_flag() {
309 let table = features_table! {
310 setup = "{ unreachable!() }",
311 TaskSink => supported("Ability to push new tasks", true),
312 Serialization => limited("Serialization support", true),
313 };
314
315 assert!(table.contains("#### TaskSink"));
317 assert!(table.contains("#### Serialization"));
318 }
319}