#[cfg(feature = "tracing")]
#[allow(unused_macros)]
macro_rules! debug {
($($tt:tt)*) => {
tracing::debug!($($tt)*)
}
}
#[cfg(feature = "tracing")]
#[allow(unused_macros)]
macro_rules! trace {
($($tt:tt)*) => {
tracing::trace!($($tt)*)
}
}
#[cfg(feature = "tracing")]
#[allow(unused_macros)]
macro_rules! info {
($($tt:tt)*) => {
tracing::info!($($tt)*)
}
}
#[cfg(feature = "tracing")]
#[allow(unused_macros)]
macro_rules! error {
($($tt:tt)*) => {
tracing::error!($($tt)*)
};
}
#[cfg(feature = "tracing")]
#[allow(unused_macros)]
macro_rules! warn {
($($tt:tt)*) => {
tracing::warn!($($tt)*)
}
}
#[cfg(not(feature = "tracing"))]
#[allow(unused_macros)]
macro_rules! debug {
($($tt:tt)*) => {};
}
#[cfg(not(feature = "tracing"))]
#[allow(unused_macros)]
macro_rules! trace {
($($tt:tt)*) => {};
}
#[cfg(not(feature = "tracing"))]
#[allow(unused_macros)]
macro_rules! info {
($($tt:tt)*) => {};
}
#[cfg(not(feature = "tracing"))]
#[allow(unused_macros)]
macro_rules! error {
($($tt:tt)*) => {};
}
#[cfg(not(feature = "tracing"))]
#[allow(unused_macros)]
macro_rules! warn {
($($tt:tt)*) => {};
}
#[macro_export]
macro_rules! features_table {
(
setup = $setup:literal,
$(
$feature:tt => $status:ident($description:literal $(, $include_example:tt)?)
),* $(,)?
) => {
concat!(
"## Features\n",
"| Feature | Status | Description |\n",
"|---------|--------|------------------------|\n",
$(
"| ",
features_table!(@format_feature_name $feature),
" | ",
features_table!(@status_icon $status),
" | ",
$description,
" |\n"
),*,
"\n",
"Key:\n",
"✅ : Supported | ⚠️ : Not implemented | ❌ : Not Supported | ❗ Limited support\n\n",
"<details>\n",
"<summary>Tests:</summary>\n",
"\n",
$(
features_table!(@maybe_generate_doctest $setup, $feature, $status $(, $include_example)?)
),*,
"</details>\n"
)
};
(@format_feature_name WebUI) => {
concat!("[`Web Interface`](https://docs.rs/apalis-board-api/)")
};
(@format_feature_name Serialization) => {
concat!("[`Serialization`](https://docs.rs/apalis-core/latest/apalis_core/backend/codec/index.html)")
};
(@format_feature_name Workflow) => {
concat!("[`Workflow`](https://docs.rs/apalis-workflow/)")
};
(@format_feature_name $feature:ident) => {
concat!("[`", stringify!($feature), "`](https://docs.rs/apalis-core/latest/apalis_core/backend/trait.", stringify!($feature), ".html)")
};
(@format_feature_name $feature:literal) => {
$feature
};
(@maybe_generate_doctest $setup:literal, $feature:tt, $status:ident, $include_example:tt) => {
features_table!(@check_should_generate $setup, $feature, $status, $include_example)
};
(@maybe_generate_doctest $setup:literal, $feature:tt, $status:ident) => {
features_table!(@check_should_generate $setup, $feature, $status, default)
};
(@check_should_generate $setup:literal, $feature:tt, $status:ident, true) => {
features_table!(@do_generate_doctest $setup, $feature, $status)
};
(@check_should_generate $setup:literal, $feature:tt, $status:ident, false) => {
""
};
(@check_should_generate $setup:literal, $feature:tt, supported, default) => {
features_table!(@do_generate_doctest $setup, $feature, supported)
};
(@check_should_generate $setup:literal, $feature:tt, limited, default) => {
features_table!(@do_generate_doctest $setup, $feature, limited)
};
(@check_should_generate $setup:literal, $feature:tt, not_implemented, default) => {
""
};
(@check_should_generate $setup:literal, $feature:tt, not_supported, default) => {
""
};
(@do_generate_doctest $setup:literal, $feature:ident, $status:ident) => {
concat!(
"#### ", stringify!($feature), "\n\n",
"```rust\n",
"# use apalis_core::worker::context::WorkerContext;\n",
"# use apalis_core::worker::builder::WorkerBuilder;\n",
"#[tokio::main]\n",
"async fn main() {\n",
" // let mut backend = /* snip */;\n",
" # let mut backend = ", $setup, "\n",
features_table!(@assert_function $feature), "\n",
"```\n\n"
)
};
(@assert_function Backend) => { concat!(
" # use futures_util::StreamExt; \n",
" # use apalis_core::backend::Backend;\n",
" async fn task(task: u32, worker: WorkerContext) {\n",
" // Do some work \n",
" # worker.stop().unwrap();\n",
" }\n",
" let worker = WorkerBuilder::new(\"backend-test\")\n",
" .backend(backend)\n",
" .build(task);\n",
" let _ = worker.stream().take(1).collect::<Vec<_>>().await;\n",
"}\n"
) };
(@assert_function TaskSink) => { concat!(
" # use apalis_core::backend::TaskSink;\n",
" backend.push(42).await.unwrap();\n\n",
" async fn task(task: u32, worker: WorkerContext) {\n",
" worker.stop().unwrap();\n",
" }\n",
" let worker = WorkerBuilder::new(\"task-sink-test\")\n",
" .backend(backend)\n",
" .build(task);\n",
" worker.run().await.unwrap();\n",
"}\n"
) };
(@assert_function BackendFactory) => { "fn assert_make_shared<T: Clone + Send + 'static>(t: T); assert_make_shared(backend);" };
(@assert_function Workflow) => { concat!(
" # use apalis_workflow::*;\n",
" # use apalis_core::backend::TaskSink;\n",
" backend.push(42).await.unwrap();\n\n",
" async fn task1(task: u32, worker: WorkerContext) -> u32 {\n",
" task + 99 \n",
" }\n",
" async fn task2(task: u32, worker: WorkerContext) -> u32 {\n",
" task + 1 \n",
" }\n",
" async fn task3(task: u32, worker: WorkerContext) {\n",
" assert_eq!(task, 142);\n",
" worker.stop().unwrap();\n",
" }\n",
" let workflow = SteppedFlow::new(\"test-workflow\")\n",
" .and_then(task1)\n",
" .and_then(task2)\n",
" .and_then(task3);\n",
" let worker = WorkerBuilder::new(\"workflow-test\")\n",
" .backend(backend)\n",
" .build(workflow);\n",
" worker.run().await.unwrap();\n",
"}\n"
) };
(@assert_function Serialization) => { concat!(
" # use apalis_core::backend::codec::Codec;\n",
" # use apalis_core::backend::BackendConfig;\n",
" # use apalis_core::backend::WireFormatBackend;\n",
" fn assert_codec<B: BackendConfig + WireFormatBackend>(backend: B) \n",
" where\n",
" B::Codec: Codec<B::Args, Compact=Vec<u8>>,\n",
" {\n",
" }\n",
" assert_codec(backend);\n",
"}\n"
) };
(@assert_function WaitForCompletion) => { concat!(
" # use apalis_core::backend::Backend;\n",
" # use apalis_core::backend::WaitForCompletion;\n",
" fn assert_wait_for_completion<B: WaitForCompletion<u32> + Backend>(backend: B) {};\n",
" assert_wait_for_completion(backend);\n",
"}\n"
) };
(@assert_function WebUI) => { concat!(
"# use apalis_core::backend::Expose;\n",
"# use apalis_core::backend::finalize::Durable;\n",
" fn assert_web_ui<B: Expose<u32, Durable>>(backend: B) {};\n",
" assert_web_ui(backend);\n",
"}\n"
) };
(@status_icon supported) => { "✅" };
(@status_icon limited) => { "✅ ❗" };
(@status_icon not_implemented) => { "⚠️" };
(@status_icon not_supported) => { "❌" };
}
#[cfg(test)]
mod tests {
#[test]
fn test_clean_table_structure_with_example_flags() {
let table = features_table! {
setup = "{
use apalis_core::backend::memory::MemoryStorage;
// No migrations
MemoryStorage::new()
};",
TaskSink => supported("Ability to push new tasks", true),
Serialization => limited("Serialization support for arguments. Only accepts `json`", false),
FetchById => not_implemented("Allow fetching a task by its ID"),
RegisterWorker => not_supported("Allow registering a worker with the backend"),
};
println!("Generated table:\n{table}");
assert!(table.contains("## Features"));
assert!(table.contains("| Feature | Status | Description"));
assert!(table.contains("|---------|--------|------------------------"));
assert!(table.contains("| ✅ | Ability to push new tasks |"));
assert!(table.contains("#### TaskSink"));
assert!(!table.contains("#### Serialization"));
assert!(!table.contains("#### FetchById"));
assert!(!table.contains("#### RegisterWorker"));
let lines: Vec<&str> = table.lines().collect();
for line in lines {
if line.starts_with("|") && line.contains("TaskSink") {
assert!(
!line.contains("```rust"),
"Table row contains code block: {line}"
);
}
}
}
#[test]
fn test_explicit_false_flag() {
let table = features_table! {
setup = "{ unreachable!() }",
TaskSink => supported("Ability to push new tasks", false),
};
assert!(!table.contains("#### TaskSink Example"));
assert!(!table.contains("```rust"));
}
#[test]
fn test_explicit_true_flag() {
let table = features_table! {
setup = "{ unreachable!() }",
TaskSink => supported("Ability to push new tasks", true),
Serialization => limited("Serialization support", true),
};
assert!(table.contains("#### TaskSink"));
assert!(table.contains("#### Serialization"));
}
}