foundry_cli/introspect/
registry.rs1use super::document::{Capabilities, ExitCodeInfo};
20
21#[derive(Clone, Debug)]
23pub struct CommandMeta {
24 pub command_id: Option<&'static str>,
28 pub capabilities: Capabilities,
30 pub capabilities_declared: bool,
33 pub exit_codes: &'static [ExitCodeInfo],
35}
36
37impl CommandMeta {
38 pub const DEFAULT: Self = Self {
40 command_id: None,
41 capabilities: Capabilities::NONE,
42 capabilities_declared: false,
43 exit_codes: &[],
44 };
45}
46
47impl Default for CommandMeta {
48 fn default() -> Self {
49 Self::DEFAULT
50 }
51}
52
53#[derive(Clone, Copy, Debug)]
58pub struct CommandRegistry {
59 entries: &'static [RegistryEntry],
60}
61
62#[derive(Clone, Debug)]
64pub struct RegistryEntry {
65 pub path: &'static [&'static str],
70 pub meta: CommandMeta,
72}
73
74impl CommandRegistry {
75 pub const fn new(entries: &'static [RegistryEntry]) -> Self {
77 Self { entries }
78 }
79
80 pub const EMPTY: Self = Self::new(&[]);
82
83 pub fn lookup(&self, path: &[&str]) -> Option<&CommandMeta> {
86 self.entries.iter().find(|e| e.path == path).map(|e| &e.meta)
87 }
88
89 pub fn entries(&self) -> impl Iterator<Item = &RegistryEntry> {
91 self.entries.iter()
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::introspect::document::OutputMode;
99 use std::borrow::Cow;
100
101 fn fixture_registry() -> CommandRegistry {
102 static ENTRIES: &[RegistryEntry] = &[RegistryEntry {
103 path: &["build"],
104 meta: CommandMeta {
105 command_id: Some("forge.build"),
106 capabilities: Capabilities {
107 output_mode: OutputMode::Envelope,
108 result_schema_ref: None,
109 event_schema_ref: None,
110 session_schema_ref: None,
111 reads_stdin: false,
112 supports_output_path: false,
113 requires_project: true,
114 side_effects: super::super::document::SideEffects::FsWrite,
115 long_running: false,
116 stateful: false,
117 },
118 capabilities_declared: true,
119 exit_codes: &[],
120 },
121 }];
122 CommandRegistry::new(ENTRIES)
123 }
124
125 #[test]
126 fn lookup_returns_registered_meta() {
127 let r = fixture_registry();
128 let meta = r.lookup(&["build"]).expect("registered");
129 assert_eq!(meta.command_id, Some("forge.build"));
130 assert!(matches!(meta.capabilities.output_mode, OutputMode::Envelope));
131 }
132
133 #[test]
134 fn lookup_returns_none_for_unregistered_path() {
135 assert!(fixture_registry().lookup(&["unknown"]).is_none());
136 }
137
138 #[test]
139 fn empty_registry_yields_no_entries() {
140 assert_eq!(CommandRegistry::EMPTY.entries().count(), 0);
141 }
142
143 #[test]
146 fn lookup_supports_empty_path_for_root_default() {
147 static ENTRIES: &[RegistryEntry] = &[RegistryEntry {
148 path: &[],
149 meta: CommandMeta {
150 command_id: Some("anvil.start"),
151 capabilities: Capabilities::NONE,
152 capabilities_declared: false,
153 exit_codes: &[],
154 },
155 }];
156 let registry = CommandRegistry::new(ENTRIES);
157 let meta = registry.lookup(&[]).expect("root/default entry");
158 assert_eq!(meta.command_id, Some("anvil.start"));
159 }
160
161 #[test]
164 fn static_registry_supports_real_strings() {
165 static EXITS: &[ExitCodeInfo] = &[ExitCodeInfo {
166 code: 2,
167 name: Cow::Borrowed("TestFailure"),
168 description: Cow::Borrowed("at least one test failed"),
169 }];
170 static ENTRIES: &[RegistryEntry] = &[RegistryEntry {
171 path: &["test"],
172 meta: CommandMeta {
173 command_id: Some("forge.test"),
174 capabilities: Capabilities {
175 output_mode: OutputMode::Envelope,
176 result_schema_ref: Some(Cow::Borrowed("foundry:test-result@v1")),
177 event_schema_ref: None,
178 session_schema_ref: None,
179 reads_stdin: false,
180 supports_output_path: false,
181 requires_project: true,
182 side_effects: super::super::document::SideEffects::None,
183 long_running: false,
184 stateful: false,
185 },
186 capabilities_declared: true,
187 exit_codes: EXITS,
188 },
189 }];
190 let registry = CommandRegistry::new(ENTRIES);
191
192 let meta = registry.lookup(&["test"]).unwrap();
193 assert_eq!(meta.capabilities.result_schema_ref.as_deref(), Some("foundry:test-result@v1"));
194 assert_eq!(meta.exit_codes[0].name, "TestFailure");
195 }
196}