use tropel_sdk::traits::{Driver, InputAdapter, Protocol};
pub fn link_builtins() -> usize {
let adapters: Vec<Box<dyn InputAdapter>> = vec![
Box::new(tropel_input_postman::PostmanInputAdapter),
Box::new(tropel_input_har::HarInputAdapter),
Box::new(tropel_input_openapi::OpenApiInputAdapter),
Box::new(tropel_input_k6::K6ScriptAdapter),
Box::new(tropel_input_http::HttpFileAdapter),
Box::new(tropel_input_bru::BruInputAdapter),
Box::new(tropel_input_insomnia::InsomniaInputAdapter),
Box::new(tropel_input_knockport::KnockPortInputAdapter),
];
let drivers: Vec<Box<dyn Driver>> = vec![
Box::new(tropel_input_k6::driver::K6Driver),
Box::new(tropel_wasm::driver::WasmDriver::default()),
];
let protocols: Vec<Box<dyn Protocol>> = vec![
Box::new(tropel_x_grpc::GrpcProtocol::default()),
Box::new(tropel_x_websocket::WebSocketProtocol),
];
adapters.len() + drivers.len() + protocols.len()
}
pub fn register_builtins() {
let count = link_builtins();
tracing::debug!("Force-linked {count} built-in adapter/driver type(s)");
}
#[cfg(test)]
mod tests {
use super::*;
use tropel_ext::registry::ExtensionRegistry;
#[test]
fn no_crate_carries_a_private_reserved_metric_list() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.expect("workspace root")
.join("crates");
fn walk(dir: &std::path::Path, hits: &mut Vec<String>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if path.file_name().is_some_and(|n| n == "tropel-sdk") {
continue;
}
walk(&path, hits);
} else if path.extension().is_some_and(|e| e == "rs") {
let Ok(src) = std::fs::read_to_string(&path) else {
continue;
};
let name_needle = concat!("const ", "RESERVED");
let type_needle = concat!("&[&", "str]");
for (i, line) in src.lines().enumerate() {
if line.contains(name_needle) && line.contains(type_needle) {
hits.push(format!("{}:{}", path.display(), i + 1));
}
}
}
}
}
let mut hits = Vec::new();
walk(&root, &mut hits);
assert!(
hits.is_empty(),
"these files carry a private reserved-metric list; call \
tropel_sdk::is_reserved_builtin_metric instead (TR-102): {hits:#?}"
);
}
const REGISTRATION_EXEMPT: &[(&str, &str)] = &[(
"tropel-input-subprocess",
"factory-only: takes a runtime --subprocess-adapter <cmd> argument, so it cannot \
be a compile-time registration. A static placeholder would be probed on every \
auto-detect and spawn a bogus `echo` (see its lib.rs Registration section).",
)];
#[test]
fn every_adapter_in_the_workspace_is_reachable_from_the_cli() {
register_builtins();
let registry = ExtensionRegistry::new();
let inputs = registry.list_inputs();
let inputs_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.expect("workspace root")
.join("crates/inputs");
let mut unreachable = Vec::new();
let mut checked = 0usize;
for entry in std::fs::read_dir(&inputs_dir)
.expect("crates/inputs must exist")
.flatten()
{
let path = entry.path();
if !path.is_dir() {
continue;
}
let crate_name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default()
.to_string();
if REGISTRATION_EXEMPT.iter().any(|(n, _)| *n == crate_name) {
continue;
}
let Some(id) = crate_name.strip_prefix("tropel-input-") else {
continue;
};
checked += 1;
if !inputs.iter().any(|got| got == id) {
unreachable.push(id.to_string());
}
}
assert!(
checked >= 7,
"expected to enumerate at least the 7 known adapters, found {checked} — the \
directory layout changed and this test is no longer looking at anything \
(that is how a reachability test rots into a no-op)"
);
assert!(
unreachable.is_empty(),
"these adapters exist in crates/inputs but are NOT reachable from the CLI: \
{unreachable:?}. Add each to builtins::link_builtins(), or add an entry to \
REGISTRATION_EXEMPT with the reason it cannot be statically registered."
);
}
#[test]
fn registration_exemptions_still_exist_and_carry_a_reason() {
let inputs_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.expect("workspace root")
.join("crates/inputs");
for (crate_name, reason) in REGISTRATION_EXEMPT {
assert!(
inputs_dir.join(crate_name).is_dir(),
"REGISTRATION_EXEMPT names '{crate_name}', which no longer exists — remove \
the stale exemption"
);
assert!(
reason.len() > 40,
"exemption for '{crate_name}' needs a real justification, not a stub"
);
}
}
}