use camel_core::{JournalInspectFilter, RedbRuntimeEventJournal};
#[derive(clap::Args)]
pub struct JournalInspectArgs {
pub path: std::path::PathBuf,
#[arg(long, default_value = "100")]
pub limit: usize,
#[arg(long)]
pub route: Option<String>,
#[arg(long, default_value = "table")]
pub format: OutputFormat,
}
#[derive(Clone, clap::ValueEnum)]
pub enum OutputFormat {
Table,
Json,
}
pub async fn run_inspect(args: JournalInspectArgs) {
let filter = JournalInspectFilter {
route_id: args.route.clone(),
limit: args.limit,
};
let entries = match RedbRuntimeEventJournal::inspect(args.path.clone(), filter).await {
Ok(e) => e,
Err(err) => {
eprintln!("error: {err}");
std::process::exit(1);
}
};
match args.format {
OutputFormat::Table => {
println!(
"{:<8} {:<26} {:<24} ROUTE_ID",
"SEQ", "TIMESTAMP", "EVENT"
);
println!("{}", "-".repeat(80));
if entries.is_empty() {
println!("(no events)");
return;
}
for entry in &entries {
let ts = chrono::DateTime::from_timestamp_millis(entry.timestamp_ms)
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string())
.unwrap_or_else(|| "?".to_string());
let (event_name, route_id) = event_parts(&entry.event);
println!(
"{:>08} {:<26} {:<24} {}",
entry.seq, ts, event_name, route_id
);
}
}
OutputFormat::Json => {
let json = serde_json::to_string_pretty(&entries).unwrap_or_else(|e| {
eprintln!("error: json serialize: {e}");
std::process::exit(1);
});
println!("{json}");
}
}
}
fn event_parts(event: &camel_core::RuntimeEvent) -> (&'static str, &str) {
match event {
camel_core::RuntimeEvent::RouteRegistered { route_id } => ("RouteRegistered", route_id),
camel_core::RuntimeEvent::RouteStartRequested { route_id } => {
("RouteStartRequested", route_id)
}
camel_core::RuntimeEvent::RouteStarted { route_id } => ("RouteStarted", route_id),
camel_core::RuntimeEvent::RouteFailed { route_id, .. } => ("RouteFailed", route_id),
camel_core::RuntimeEvent::RouteStopped { route_id } => ("RouteStopped", route_id),
camel_core::RuntimeEvent::RouteSuspended { route_id } => ("RouteSuspended", route_id),
camel_core::RuntimeEvent::RouteResumed { route_id } => ("RouteResumed", route_id),
camel_core::RuntimeEvent::RouteReloaded { route_id } => ("RouteReloaded", route_id),
camel_core::RuntimeEvent::RouteRemoved { route_id } => ("RouteRemoved", route_id),
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Parser)]
struct TestCli {
#[command(flatten)]
args: JournalInspectArgs,
}
#[test]
fn journal_inspect_args_parse_defaults() {
let cli = TestCli::try_parse_from(["test", "runtime.db"]).expect("expected parse success");
assert_eq!(cli.args.path, std::path::PathBuf::from("runtime.db"));
assert_eq!(cli.args.limit, 100);
assert!(cli.args.route.is_none());
assert!(matches!(cli.args.format, OutputFormat::Table));
}
#[test]
fn journal_inspect_args_parse_all_options() {
let cli = TestCli::try_parse_from([
"test",
"runtime.db",
"--limit",
"7",
"--route",
"orders",
"--format",
"json",
])
.expect("expected parse success");
assert_eq!(cli.args.path, std::path::PathBuf::from("runtime.db"));
assert_eq!(cli.args.limit, 7);
assert_eq!(cli.args.route.as_deref(), Some("orders"));
assert!(matches!(cli.args.format, OutputFormat::Json));
}
#[test]
fn journal_inspect_args_reject_invalid_format() {
let result = TestCli::try_parse_from(["test", "runtime.db", "--format", "xml"]);
assert!(result.is_err());
}
#[test]
fn event_parts_maps_all_variants() {
let cases = vec![
(
camel_core::RuntimeEvent::RouteRegistered {
route_id: "r1".to_string(),
},
"RouteRegistered",
"r1",
),
(
camel_core::RuntimeEvent::RouteStartRequested {
route_id: "r2".to_string(),
},
"RouteStartRequested",
"r2",
),
(
camel_core::RuntimeEvent::RouteStarted {
route_id: "r3".to_string(),
},
"RouteStarted",
"r3",
),
(
camel_core::RuntimeEvent::RouteFailed {
route_id: "r4".to_string(),
error: "boom".to_string(),
},
"RouteFailed",
"r4",
),
(
camel_core::RuntimeEvent::RouteStopped {
route_id: "r5".to_string(),
},
"RouteStopped",
"r5",
),
(
camel_core::RuntimeEvent::RouteSuspended {
route_id: "r6".to_string(),
},
"RouteSuspended",
"r6",
),
(
camel_core::RuntimeEvent::RouteResumed {
route_id: "r7".to_string(),
},
"RouteResumed",
"r7",
),
(
camel_core::RuntimeEvent::RouteReloaded {
route_id: "r8".to_string(),
},
"RouteReloaded",
"r8",
),
(
camel_core::RuntimeEvent::RouteRemoved {
route_id: "r9".to_string(),
},
"RouteRemoved",
"r9",
),
];
for (event, expected_name, expected_route) in cases {
let (name, route_id) = event_parts(&event);
assert_eq!(name, expected_name);
assert_eq!(route_id, expected_route);
}
}
}