Skip to main content

submilli_engine/stdlib/
uuid.rs

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