use std::{collections::BTreeSet, path::PathBuf};
const READ_ONLY_VARIANTS: &[&str] = &[
"Status",
"Watch",
"Diagnose",
"Doctor",
"GitOverlay",
"Version",
"Log",
"Show",
"Inspect",
"Diff",
"Compare",
"Blame",
"Completion",
"Help",
"Index",
"Monitor",
"Query",
"Presence",
"HarnessBridge",
"Semantic",
"Retro",
"Schemas",
];
#[test]
fn every_state_changing_arm_resolves_op_id() {
let main_rs = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src")
.join("main.rs");
let source = std::fs::read_to_string(&main_rs)
.unwrap_or_else(|e| panic!("read {}: {e}", main_rs.display()));
let arms = extract_command_arms(&source);
assert!(
!arms.is_empty(),
"no Commands::<Variant> arms found in {} — has the dispatch shape changed?",
main_rs.display()
);
let read_only: BTreeSet<&str> = READ_ONLY_VARIANTS.iter().copied().collect();
let mut missing = Vec::new();
let mut classified = BTreeSet::new();
for arm in &arms {
classified.insert(arm.variant.as_str());
if read_only.contains(arm.variant.as_str()) {
continue;
}
if !arm.body.contains("resolve_operation_id(") {
missing.push(arm.variant.clone());
}
}
assert!(
missing.is_empty(),
"the following Commands variants are state-changing but their arm in main.rs \
doesn't call `resolve_operation_id(&cli)?`: {missing:?}.\n\
Either wire the call (add `resolve_operation_id(&cli)?;` at the top of the \
arm body) or, if the verb is genuinely read-only, add it to \
READ_ONLY_VARIANTS in this test."
);
for ro in READ_ONLY_VARIANTS {
assert!(
classified.contains(*ro),
"READ_ONLY_VARIANTS lists `{ro}` but no Commands::{ro} arm was found in \
main.rs — drop the entry or update the variant name."
);
}
}
#[derive(Debug)]
struct Arm {
variant: String,
body: String,
}
fn extract_command_arms(source: &str) -> Vec<Arm> {
let bytes = source.as_bytes();
let mut arms = Vec::new();
let dispatch_marker = "match &cli.command";
let dispatch_start = source
.find(dispatch_marker)
.expect("main.rs must contain a `match &cli.command` dispatch");
let dispatch_open_brace = dispatch_start
+ dispatch_marker.len()
+ source[dispatch_start + dispatch_marker.len()..]
.find('{')
.expect("dispatch match must have an opening brace");
let dispatch_close = match_close_brace(bytes, dispatch_open_brace)
.expect("dispatch match must have a balanced closing brace");
let scope_end = dispatch_close;
let needle = "Commands::";
let mut cursor = dispatch_open_brace + 1;
while cursor < scope_end {
let Some(rel) = source[cursor..scope_end].find(needle) else {
break;
};
let start = cursor + rel;
cursor = start + needle.len();
if start > 0 {
let prev = bytes[start - 1];
if prev.is_ascii_alphanumeric() || prev == b'_' {
continue;
}
}
let variant = read_variant_name(&source[cursor..]);
if variant.is_empty() {
continue;
}
let after_variant = cursor + variant.len();
let Some(arrow_off) = find_match_arrow(&source[after_variant..]) else {
continue;
};
let arrow = after_variant + arrow_off;
let body_start = arrow + 2;
let body_text =
if let Some(brace_off) = first_non_whitespace_is_brace(&source[body_start..]) {
let block_open = body_start + brace_off;
let close = match_close_brace(bytes, block_open).unwrap_or(bytes.len());
source[block_open..=close.min(bytes.len() - 1)].to_string()
} else {
let end = expr_arm_end(&source[body_start..]);
source[body_start..body_start + end].to_string()
};
arms.push(Arm {
variant: variant.to_string(),
body: body_text,
});
}
arms
}
fn read_variant_name(s: &str) -> String {
s.chars()
.take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
.collect()
}
fn find_match_arrow(s: &str) -> Option<usize> {
let bytes = s.as_bytes();
let mut depth_paren: i32 = 0;
let mut depth_brace: i32 = 0;
let mut i = 0;
while i + 1 < bytes.len() {
match bytes[i] {
b'(' => depth_paren += 1,
b')' => depth_paren -= 1,
b'{' => depth_brace += 1,
b'}' => {
if depth_brace == 0 {
return None;
}
depth_brace -= 1;
}
b'=' if bytes[i + 1] == b'>' && depth_paren == 0 && depth_brace == 0 => return Some(i),
_ => {}
}
i += 1;
}
None
}
fn first_non_whitespace_is_brace(s: &str) -> Option<usize> {
for (i, c) in s.char_indices() {
if c.is_whitespace() {
continue;
}
return if c == '{' { Some(i) } else { None };
}
None
}
fn match_close_brace(bytes: &[u8], open: usize) -> Option<usize> {
let mut depth: i32 = 0;
let mut i = open;
while i < bytes.len() {
match bytes[i] {
b'{' => depth += 1,
b'}' => {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
_ => {}
}
i += 1;
}
None
}
fn expr_arm_end(s: &str) -> usize {
let bytes = s.as_bytes();
let mut depth_paren: i32 = 0;
let mut depth_brace: i32 = 0;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'(' => depth_paren += 1,
b')' => depth_paren -= 1,
b'{' => depth_brace += 1,
b'}' => depth_brace -= 1,
b',' if depth_paren == 0 && depth_brace == 0 => return i,
_ => {}
}
i += 1;
}
bytes.len()
}