use super::{Operations, Subcommand};
pub async fn dispatch(
operations: &impl Operations,
subcommand: Subcommand,
args: &[String],
) -> crate::Result<()> {
match subcommand {
Subcommand::Serve => operations.serve(args).await,
Subcommand::Migrate => operations.migrate(args).await,
Subcommand::Queue => operations.queue(args).await,
Subcommand::Schedule => operations.schedule(args).await,
Subcommand::Doctor => operations.doctor(args).await,
Subcommand::About => operations.about(args).await,
}
}
#[cfg(test)]
mod tests {
use super::dispatch;
use crate::cli::{Operations, Subcommand, SubcommandError, parse};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
struct RecordingOps {
calls: Arc<AtomicUsize>,
}
impl Operations for RecordingOps {
fn serve(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
let calls = self.calls.clone();
Box::pin(async move {
calls.store(1, Ordering::SeqCst);
Ok(())
})
}
fn migrate(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
let calls = self.calls.clone();
Box::pin(async move {
calls.store(2, Ordering::SeqCst);
Ok(())
})
}
fn queue(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
let calls = self.calls.clone();
Box::pin(async move {
calls.store(3, Ordering::SeqCst);
Ok(())
})
}
fn schedule(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
let calls = self.calls.clone();
Box::pin(async move {
calls.store(4, Ordering::SeqCst);
Ok(())
})
}
fn doctor(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
let calls = self.calls.clone();
Box::pin(async move {
calls.store(5, Ordering::SeqCst);
Ok(())
})
}
fn about(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
let calls = self.calls.clone();
Box::pin(async move {
calls.store(6, Ordering::SeqCst);
Ok(())
})
}
}
#[tokio::test]
async fn dispatch_routes_each_subcommand_to_the_right_method() {
for (sub, marker) in [
(Subcommand::Serve, 1usize),
(Subcommand::Migrate, 2),
(Subcommand::Queue, 3),
(Subcommand::Schedule, 4),
(Subcommand::Doctor, 5),
(Subcommand::About, 6),
] {
let calls = Arc::new(AtomicUsize::new(0));
let ops = RecordingOps {
calls: calls.clone(),
};
dispatch(&ops, sub.clone(), &[]).await.expect("dispatch ok");
assert_eq!(
calls.load(Ordering::SeqCst),
marker,
"dispatch routed {sub:?} to the wrong operation"
);
}
}
#[tokio::test]
async fn dispatch_forwards_trailing_args() {
struct ArgOps {
captured: Arc<Mutex<Vec<String>>>,
}
impl Operations for ArgOps {
fn serve(
&self,
args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
let captured = self.captured.clone();
let args = args.to_vec();
Box::pin(async move {
*captured.lock().expect("lock") = args;
Ok(())
})
}
fn migrate(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn queue(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn schedule(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn doctor(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn about(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
}
let captured = Arc::new(Mutex::new(Vec::new()));
let ops = ArgOps {
captured: captured.clone(),
};
let args = vec!["3001".to_string(), "--no-warmup".to_string()];
dispatch(&ops, Subcommand::Serve, &args)
.await
.expect("serve ok");
assert_eq!(
captured.lock().expect("lock").clone(),
args,
"trailing args forwarded to the operation"
);
}
#[tokio::test]
async fn dispatch_surfaces_operation_errors() {
struct FailingOps;
impl Operations for FailingOps {
fn serve(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Err(crate::EngineError::InvalidPort("not-a-port".to_owned())) })
}
fn migrate(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn queue(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn schedule(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn doctor(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
fn about(
&self,
_args: &[String],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<()>> + Send + '_>>
{
Box::pin(async { Ok(()) })
}
}
let result = dispatch(&FailingOps, Subcommand::Serve, &[]).await;
assert!(matches!(result, Err(crate::EngineError::InvalidPort(_))));
}
#[test]
fn unknown_subcommand_returns_typed_error() {
assert_eq!(
parse(["deploy"]),
Err(SubcommandError::Unknown {
name: "deploy".to_owned()
})
);
}
}