use crate::RegisterExecPlugin;
use crate::Result;
use crate::plugin::{Context, ExecPlugin, Plugin};
use async_trait::async_trait;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use tracing::debug;
const PLUGIN_SLEEP_IDENTIFIER: &str = "sleep";
#[derive(RegisterExecPlugin)]
pub struct SleepPlugin {
duration: Duration,
}
impl SleepPlugin {
pub fn new(duration: Duration) -> Self {
Self { duration }
}
pub fn from_secs(secs: u64) -> Self {
Self {
duration: Duration::from_secs(secs),
}
}
pub fn from_millis(millis: u64) -> Self {
Self {
duration: Duration::from_millis(millis),
}
}
}
impl fmt::Debug for SleepPlugin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SleepPlugin")
.field("duration", &self.duration)
.finish()
}
}
#[async_trait]
impl Plugin for SleepPlugin {
fn name(&self) -> &str {
PLUGIN_SLEEP_IDENTIFIER
}
async fn execute(&self, _ctx: &mut Context) -> Result<()> {
debug!("Sleeping for {:?}", self.duration);
tokio::time::sleep(self.duration).await;
Ok(())
}
fn aliases() -> &'static [&'static str] {
&["delay"]
}
}
impl ExecPlugin for SleepPlugin {
fn quick_setup(prefix: &str, exec_str: &str) -> Result<Arc<dyn Plugin>> {
if prefix != PLUGIN_SLEEP_IDENTIFIER && !Self::aliases().contains(&prefix) {
return Err(crate::Error::Config(format!(
"ExecPlugin quick_setup: unsupported prefix '{}', expected one of {:?}",
prefix,
Self::aliases()
)));
}
let duration = if let Some(ms_str) = exec_str.strip_suffix("ms") {
if let Ok(ms) = ms_str.parse::<u64>() {
Duration::from_millis(ms)
} else {
return Err(crate::Error::Config(format!(
"Invalid milliseconds: {}",
ms_str
)));
}
} else if let Some(s_str) = exec_str.strip_suffix('s') {
if let Ok(s) = s_str.parse::<u64>() {
Duration::from_secs(s)
} else {
return Err(crate::Error::Config(format!("Invalid seconds: {}", s_str)));
}
} else {
return Err(crate::Error::Config(format!(
"Invalid duration format: '{}'. Use '100ms' or '1s'",
exec_str
)));
};
let plugin = SleepPlugin::new(duration);
Ok(Arc::new(plugin))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dns::Message;
use std::time::Instant;
#[tokio::test]
async fn test_sleep_plugin() {
let plugin = SleepPlugin::from_millis(100);
let mut ctx = Context::new(Message::new());
let start = Instant::now();
plugin.execute(&mut ctx).await.unwrap();
let elapsed = start.elapsed();
assert!(elapsed.as_millis() >= 90);
}
#[tokio::test]
async fn test_sleep_from_secs() {
let plugin = SleepPlugin::from_secs(0); let mut ctx = Context::new(Message::new());
assert!(plugin.execute(&mut ctx).await.is_ok());
}
#[test]
fn test_sleep_debug() {
let plugin = SleepPlugin::from_millis(100);
let debug_str = format!("{:?}", plugin);
assert!(debug_str.contains("SleepPlugin"));
assert!(debug_str.contains("duration"));
}
#[test]
fn test_exec_plugin_quick_setup() {
let plugin = <SleepPlugin as ExecPlugin>::quick_setup("sleep", "100ms").unwrap();
assert_eq!(plugin.name(), "sleep");
let result = <SleepPlugin as ExecPlugin>::quick_setup("invalid", "100ms");
assert!(result.is_err());
let plugin = <SleepPlugin as ExecPlugin>::quick_setup("sleep", "200ms").unwrap();
if let Some(sp) = plugin.as_any().downcast_ref::<SleepPlugin>() {
assert_eq!(sp.duration, Duration::from_millis(200));
}
let plugin = <SleepPlugin as ExecPlugin>::quick_setup("sleep", "2s").unwrap();
if let Some(sp) = plugin.as_any().downcast_ref::<SleepPlugin>() {
assert_eq!(sp.duration, Duration::from_secs(2));
}
let result = <SleepPlugin as ExecPlugin>::quick_setup("sleep", "100");
assert!(result.is_err());
}
}