1use wasmtime::{ArrayRef, Linker, Rooted};
13
14use crate::session::SessionData;
15
16pub(crate) type StringArg = Option<Rooted<ArrayRef>>;
21pub(crate) type StringArgTriple = (StringArg, StringArg, StringArg);
24
25pub mod account;
26pub mod catch_each;
27pub mod commodity;
28pub mod config;
29pub mod env_io;
30mod generated_specs;
31pub mod meta;
32pub mod raise;
33mod render;
34pub mod report;
35pub mod split;
36pub mod ssh_key;
37pub mod template;
38pub mod transaction;
39pub mod user;
40
41pub use generated_specs::all_compiler_specs;
42pub use render::{RENDER_NATIVE_ALLOWLIST, link_render, render_compiler_specs};
43
44pub fn link(linker: &mut Linker<SessionData>) -> wasmtime::Result<()> {
49 meta::register(linker)?;
50 env_io::register(linker)?;
51 raise::register(linker)?;
52 catch_each::register(linker)?;
53 account::register(linker)?;
54 commodity::register(linker)?;
55 config::register(linker)?;
56 report::register(linker)?;
57 split::register(linker)?;
58 ssh_key::register(linker)?;
59 template::register(linker)?;
60 transaction::register(linker)?;
61 user::register(linker)?;
62 Ok(())
63}
64
65#[must_use]
71pub fn all_registered_commands() -> Vec<&'static str> {
72 [
73 account::REGISTERED_COMMANDS,
74 commodity::REGISTERED_COMMANDS,
75 config::REGISTERED_COMMANDS,
76 report::REGISTERED_COMMANDS,
77 split::REGISTERED_COMMANDS,
78 ssh_key::REGISTERED_COMMANDS,
79 transaction::REGISTERED_COMMANDS,
80 user::REGISTERED_COMMANDS,
81 ]
82 .iter()
83 .flat_map(|d| d.iter().copied())
84 .collect()
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn registry_lists_every_planned_command() {
93 let names = all_registered_commands();
94 assert_eq!(names.len(), 36, "command count drifted: {names:?}");
95 }
96
97 #[test]
98 fn registry_uses_kebab_case_only() {
99 for name in all_registered_commands() {
100 assert!(
101 name.chars()
102 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
103 "non-kebab-case name: {name}"
104 );
105 assert!(!name.starts_with('-'), "leading dash: {name}");
106 assert!(!name.ends_with('-'), "trailing dash: {name}");
107 }
108 }
109
110 #[test]
111 fn registry_has_no_duplicates() {
112 let names = all_registered_commands();
113 let mut sorted = names.clone();
114 sorted.sort_unstable();
115 sorted.dedup();
116 assert_eq!(
117 names.len(),
118 sorted.len(),
119 "duplicate command names: {names:?}"
120 );
121 }
122
123 #[test]
124 fn registry_excludes_add_ssh_key_by_design() {
125 let names = all_registered_commands();
126 assert!(
127 !names.contains(&"add-ssh-key"),
128 "add-ssh-key must not be exposed via the eval channel; pubkey upload uses the dedicated ssh-copy-id exec path"
129 );
130 }
131
132 #[test]
133 fn each_domain_has_at_least_one_command() {
134 assert!(!account::REGISTERED_COMMANDS.is_empty());
135 assert!(!commodity::REGISTERED_COMMANDS.is_empty());
136 assert!(!config::REGISTERED_COMMANDS.is_empty());
137 assert!(!report::REGISTERED_COMMANDS.is_empty());
138 assert!(!split::REGISTERED_COMMANDS.is_empty());
139 assert!(!ssh_key::REGISTERED_COMMANDS.is_empty());
140 assert!(!transaction::REGISTERED_COMMANDS.is_empty());
141 assert!(!user::REGISTERED_COMMANDS.is_empty());
142 }
143
144 #[test]
145 fn link_succeeds_against_session_data_linker() {
146 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
147 .expect("engine");
148 let mut linker: wasmtime::Linker<crate::session::SessionData> =
149 wasmtime::Linker::new(&engine);
150 link(&mut linker).expect("link must succeed");
151 }
152
153 #[test]
154 fn each_empty_server_domain_register_succeeds_in_isolation() {
155 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
160 .expect("engine");
161 let _engine = engine;
166 }
167
168 #[test]
169 fn account_register_succeeds_against_session_data() {
170 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
171 .expect("engine");
172 let mut linker: wasmtime::Linker<crate::session::SessionData> =
173 wasmtime::Linker::new(&engine);
174 account::register(&mut linker).expect("account::register must succeed");
175 }
176
177 #[test]
178 fn commodity_register_succeeds_against_session_data() {
179 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
180 .expect("engine");
181 let mut linker: wasmtime::Linker<crate::session::SessionData> =
182 wasmtime::Linker::new(&engine);
183 commodity::register(&mut linker).expect("commodity::register must succeed");
184 }
185
186 #[test]
187 fn transaction_register_succeeds_against_session_data() {
188 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
189 .expect("engine");
190 let mut linker: wasmtime::Linker<crate::session::SessionData> =
191 wasmtime::Linker::new(&engine);
192 transaction::register(&mut linker).expect("transaction::register must succeed");
193 }
194
195 #[test]
196 fn report_register_succeeds_against_session_data() {
197 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
198 .expect("engine");
199 let mut linker: wasmtime::Linker<crate::session::SessionData> =
200 wasmtime::Linker::new(&engine);
201 report::register(&mut linker).expect("report::register must succeed");
202 }
203
204 #[test]
205 fn user_register_succeeds_against_session_data() {
206 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
207 .expect("engine");
208 let mut linker: wasmtime::Linker<crate::session::SessionData> =
209 wasmtime::Linker::new(&engine);
210 user::register(&mut linker).expect("user::register must succeed");
211 }
212
213 #[test]
214 fn split_register_succeeds_against_session_data() {
215 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
216 .expect("engine");
217 let mut linker: wasmtime::Linker<crate::session::SessionData> =
218 wasmtime::Linker::new(&engine);
219 split::register(&mut linker).expect("split::register must succeed");
220 }
221
222 #[test]
223 fn ssh_key_register_succeeds_against_session_data() {
224 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
225 .expect("engine");
226 let mut linker: wasmtime::Linker<crate::session::SessionData> =
227 wasmtime::Linker::new(&engine);
228 ssh_key::register(&mut linker).expect("ssh_key::register must succeed");
229 }
230
231 #[test]
232 fn config_register_succeeds_against_session_data() {
233 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
238 .expect("engine");
239 let mut linker: wasmtime::Linker<crate::session::SessionData> =
240 wasmtime::Linker::new(&engine);
241 config::register(&mut linker).expect("config::register must succeed");
242 }
243}