use crate::command::Command;
use std::collections::BTreeMap;
use wasm_lite_std::rwlock::RwLock;
mod build_info;
mod env;
mod help;
mod list;
mod memory;
mod panics;
mod terminate;
mod threads;
pub(crate) mod uptime;
pub(crate) static COMMANDS: RwLock<BTreeMap<&'static str, Box<dyn Command>>> =
RwLock::new(BTreeMap::new());
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RegisterError {
Duplicate {
name: &'static str,
existing_description: String,
rejected_description: String,
},
}
impl std::fmt::Display for RegisterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RegisterError::Duplicate {
name,
existing_description,
rejected_description,
} => write!(
f,
"exfiltrate: refusing to register a second command named {name:?}.\n \
keeping: {existing_description}\n \
refused: {rejected_description}\n \
Rename one of them; a dotted prefix such as `mycrate.{name}` is the convention."
),
}
}
}
impl std::error::Error for RegisterError {}
pub(crate) fn insert(command: Box<dyn Command>) -> Result<(), RegisterError> {
let name = command.name();
let mut registry = COMMANDS.lock_sync_write();
match registry.get(name) {
Some(existing) => Err(RegisterError::Duplicate {
name,
existing_description: existing.short_description().to_string(),
rejected_description: command.short_description().to_string(),
}),
None => {
registry.insert(name, command);
Ok(())
}
}
}
pub(crate) fn register_commands(config: &crate::Config) {
let mut refused = Vec::new();
let mut register = |command: Box<dyn Command>| {
if let Err(error) = insert(command) {
refused.push(error);
}
};
register(Box::new(help::Help));
register(Box::new(list::List));
register(Box::new(crate::provider::Snapshot));
#[cfg(not(target_arch = "wasm32"))]
register(Box::new(terminate::Terminate));
let batteries = config.batteries;
if batteries.build_info {
register(Box::new(build_info::BuildInfoCommand));
}
if batteries.uptime {
register(Box::new(uptime::Uptime));
}
if batteries.panics {
register(Box::new(panics::Panics));
}
#[cfg(not(target_arch = "wasm32"))]
{
if batteries.threads {
register(Box::new(threads::Threads));
}
if batteries.memory {
register(Box::new(memory::Memory));
}
if batteries.env {
register(Box::new(env::Env));
}
}
for error in refused {
crate::diagnostic(&error.to_string());
}
}
#[cfg(test)]
mod tests {
use super::*;
use exfiltrate_internal::command::Response;
struct Named(&'static str, &'static str);
impl Command for Named {
fn name(&self) -> &'static str {
self.0
}
fn short_description(&self) -> &'static str {
self.1
}
fn full_description(&self) -> &'static str {
self.1
}
fn execute(&self, _args: Vec<String>) -> Result<Response, Response> {
Ok(self.1.into())
}
}
#[test]
fn a_duplicate_registration_is_refused_and_names_both_sides() {
insert(Box::new(Named("registry_test_dup", "the first one"))).unwrap();
let error = insert(Box::new(Named("registry_test_dup", "the second one"))).unwrap_err();
let RegisterError::Duplicate {
name,
existing_description,
rejected_description,
} = &error;
assert_eq!(*name, "registry_test_dup");
assert_eq!(existing_description, "the first one");
assert_eq!(rejected_description, "the second one");
let message = error.to_string();
assert!(message.contains("the first one"), "{message}");
assert!(message.contains("the second one"), "{message}");
}
#[test]
fn the_first_registration_keeps_the_name() {
insert(Box::new(Named("registry_test_first", "original"))).unwrap();
let _ = insert(Box::new(Named("registry_test_first", "impostor")));
let registry = COMMANDS.lock_sync_read();
let found = registry.get("registry_test_first").unwrap();
assert_eq!(found.short_description(), "original");
}
#[test]
fn lookup_does_not_depend_on_registration_order() {
insert(Box::new(Named("registry_test_zzz", "last alphabetically"))).unwrap();
insert(Box::new(Named("registry_test_aaa", "first alphabetically"))).unwrap();
let registry = COMMANDS.lock_sync_read();
assert_eq!(
registry
.get("registry_test_zzz")
.unwrap()
.short_description(),
"last alphabetically"
);
assert_eq!(
registry
.get("registry_test_aaa")
.unwrap()
.short_description(),
"first alphabetically"
);
}
}