1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! Built-in `flags` command group: introspection for declared feature flags.
//!
//! Mounted unconditionally by [`crate::Cli::new`] (feature-flag introspection does
//! not depend on any other opt-in system, unlike `env` or `config`). The group
//! exposes:
//!
//! - `flags list` — list every flagged command/group/module node discovered while
//! pruning the command tree, with the stage and visibility decided for this run.
//! - `flags info <key>` — show the active policy for one flag key plus every node
//! that resolved to it.
use serde_json::json;
use crate::{
CommandResult, CommandSpec, GroupSpec, RuntimeCommandSpec, RuntimeGroupSpec,
error::CliCoreError,
};
/// Builds the built-in `flags` command group.
#[must_use]
pub fn flags_command_group() -> RuntimeGroupSpec {
RuntimeGroupSpec::new(GroupSpec::new("flags", "Inspect declared feature flags"))
.with_command(RuntimeCommandSpec::new_with_context(
CommandSpec::new("list", "List every flagged command/group/module node")
.no_auth(true)
.with_system("flags"),
async |ctx| {
let items: Vec<_> = ctx
.middleware
.flag_registry
.entries()
.iter()
.map(|entry| {
json!({
"path": entry.path,
"key": entry.key,
"stage": entry.stage.as_str(),
"visible": entry.visible,
})
})
.collect();
Ok(CommandResult::new(json!(items)))
},
))
.with_command(RuntimeCommandSpec::new_with_context(
CommandSpec::new("info", "Show the policy and nodes for one flag key")
.no_auth(true)
.with_system("flags")
.with_arg(
clap::Arg::new("key")
.value_name("KEY")
.required(true)
.help("Flag key to inspect"),
),
async |ctx| {
let key = string_arg(&ctx.args, "key");
if key.is_empty() {
return Err(CliCoreError::message("missing flag key"));
}
let matches = ctx.middleware.flag_registry.by_key(&key);
if matches.is_empty() {
return Err(CliCoreError::message(format!("no such flag: {key}")));
}
let has_override = ctx.middleware.flag_policy.overrides.contains_key(&key);
let min_stage = ctx.middleware.flag_policy.min_stage;
let override_stage = ctx
.middleware
.flag_policy
.overrides
.get(&key)
.map(|stage| json!(stage.as_str()))
.unwrap_or(serde_json::Value::Null);
let entries: Vec<_> = matches
.iter()
.map(|entry| {
// An override only "decides" this entry's visibility if removing
// it would flip the outcome; otherwise min_stage alone explains
// it, even though an override for this key happens to exist.
let visible_without_override = entry.stage >= min_stage;
let decided_by =
if has_override && visible_without_override != entry.visible {
"override"
} else {
"min_stage"
};
json!({
"path": entry.path,
"stage": entry.stage.as_str(),
"visible": entry.visible,
"decided_by": decided_by,
})
})
.collect();
Ok(CommandResult::new(json!({
"key": key,
"policy": {
"min_stage": ctx.middleware.flag_policy.min_stage.as_str(),
"override": override_stage,
},
"entries": entries,
})))
},
))
}
/// Reads a required string argument, defaulting to empty when absent.
fn string_arg(args: &serde_json::Map<String, serde_json::Value>, name: &str) -> String {
args.get(name)
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_owned()
}