1use crate::runtime::effects;
7use crate::runtime::host::{
8 abi_arg, abi_result, intrinsic_string_type, read_string_arg, register_host_fn,
9 register_host_value_async, write_submilli_string_struct,
10};
11use wasmtime::{FuncType, HeapType, Linker, RefType, Val, ValType};
12
13use crate::runtime::StoreData;
14use crate::{PackageDeclaration, Param, Span, Type, ValueKind, ValueSymbol};
15
16pub const MODULE_NAME: &str = "submilli:uuid";
17
18pub fn package_declaration() -> PackageDeclaration {
19 let mut defs = PackageDeclaration::with_package(MODULE_NAME);
20 insert_fn(
21 &mut defs,
22 "v4",
23 Vec::new(),
24 Type::String,
25 "/**\n * Generate a random UUID v4 (RFC 4122).\n * Returns the canonical lowercase hyphenated form.\n */",
26 );
27 insert_fn(
28 &mut defs,
29 "v7",
30 Vec::new(),
31 Type::String,
32 "/**\n * Generate a time-ordered UUID v7 (RFC 9562).\n * Sortable / index-friendly; prefer over `v4` when the destination is a sorted store.\n * Returns the canonical lowercase hyphenated form.\n */",
33 );
34 insert_fn(
35 &mut defs,
36 "validate",
37 vec![Param::new("string", Type::String)],
38 Type::Boolean,
39 "/**\n * Returns `true` if `string` is a valid UUID (any version), `false` otherwise.\n * @param string The candidate UUID text.\n */",
40 );
41 defs
42}
43
44fn insert_fn(defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type, doc: &str) {
45 defs.values.insert(
46 name.to_string(),
47 ValueSymbol {
48 name: name.to_string(),
49 mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
50 declaration_span: Span::at(crate::FileId::UUID),
51 kind: ValueKind::Function {
52 generics: Vec::new(),
53 params,
54 ret,
55 type_predicate: None,
56 doc: crate::doc(crate::FileId::UUID, doc),
57 },
58 },
59 );
60}
61
62pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
63 let engine = linker.engine().clone();
64 let string = ValType::Ref(RefType::new(
65 false,
66 HeapType::ConcreteStruct(intrinsic_string_type(&engine)?),
67 ));
68
69 let gen_ty = FuncType::new(&engine, [], [string.clone()]);
70 register_host_value_async(
71 linker,
72 MODULE_NAME,
73 crate::mangle::package_symbol(MODULE_NAME, "v4"),
74 gen_ty.clone(),
75 |caller, _params| {
76 Box::pin(async move {
77 let value = effects::execute(caller, "uuid.v4", (), |_, _, ()| {
78 Box::pin(async { Ok(::uuid::Uuid::new_v4().to_string()) })
79 })
80 .await?;
81 let st = write_submilli_string_struct(caller, &value)?;
82 Ok(Val::AnyRef(Some(st.to_anyref())))
83 })
84 },
85 )?;
86
87 register_host_value_async(
88 linker,
89 MODULE_NAME,
90 crate::mangle::package_symbol(MODULE_NAME, "v7"),
91 gen_ty,
92 |caller, _params| {
93 Box::pin(async move {
94 let value = effects::execute(caller, "uuid.v7", (), |_, _, ()| {
95 Box::pin(async { Ok(::uuid::Uuid::now_v7().to_string()) })
96 })
97 .await?;
98 let st = write_submilli_string_struct(caller, &value)?;
99 Ok(Val::AnyRef(Some(st.to_anyref())))
100 })
101 },
102 )?;
103
104 let validate_ty = FuncType::new(&engine, [string], [ValType::I32]);
105 register_host_fn(
106 linker,
107 MODULE_NAME,
108 crate::mangle::package_symbol(MODULE_NAME, "validate"),
109 validate_ty,
110 |caller, params, results| {
111 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "uuid.validate")?;
112 *abi_result(results, 0)? = Val::I32(i32::from(::uuid::Uuid::parse_str(&s).is_ok()));
113 Ok(())
114 },
115 )?;
116
117 Ok(())
118}
119
120#[cfg(test)]
121mod tests {
122 use std::sync::Arc;
123
124 use crate::compile_script;
125 use crate::runtime::security::{CheckOutcome, SecurityCheck};
126 use crate::runtime::{
127 RuntimeConfig, StoreData, Vfs, dispatch_main_async, install_runtime_async,
128 };
129
130 struct RecordingCheck {
131 seen: std::sync::Mutex<Vec<(String, String)>>,
132 }
133 impl SecurityCheck for RecordingCheck {
134 fn check(
135 &self,
136 caller: &str,
137 capability: &str,
138 _context: &serde_json::Value,
139 ) -> CheckOutcome {
140 self.seen
141 .lock()
142 .unwrap()
143 .push((caller.to_string(), capability.to_string()));
144 CheckOutcome::Allow { rule: None }
145 }
146 }
147
148 #[tokio::test]
152 async fn uuid_call_leaves_caller_attribution_intact() {
153 let source = r#"
154 import { v4 } from "submilli:uuid";
155 import { writeText } from "submilli:fs";
156 function main(): void {
157 const _id = v4();
158 writeText("/x.txt", "hi");
159 }
160 "#;
161 let recording = Arc::new(RecordingCheck {
162 seen: std::sync::Mutex::new(Vec::new()),
163 });
164 let compiled =
165 compile_script(source, "test.subm", crate::FileId(0), &[], &[]).expect("compile clean");
166 let cfg = RuntimeConfig::default();
167 let engine = cfg.engine().expect("engine");
168 let mut data = StoreData::with_vfs("test-run".into(), Vfs::tempdir().expect("tempdir"));
169 data.security_check = recording.clone();
170 let mut store = cfg.store(&engine, data).expect("store");
171 let module = wasmtime::Module::new(&engine, &compiled.wasm).expect("module");
172 let mut linker = wasmtime::Linker::<StoreData>::new(&engine);
173 install_runtime_async(&mut linker, &mut store)
174 .await
175 .expect("install");
176 let inst = linker
177 .instantiate_async(&mut store, &module)
178 .await
179 .expect("instantiate");
180 dispatch_main_async(&mut store, &inst)
181 .await
182 .expect("main ran");
183
184 let seen = recording.seen.lock().unwrap().clone();
185 assert!(
186 !seen.is_empty(),
187 "the fs.* call should have been recorded by RecordingCheck"
188 );
189 for (caller, capability) in &seen {
190 assert_eq!(
191 caller, "main",
192 "expected caller=main for capability {capability}; got {caller}"
193 );
194 }
195 }
196}