Skip to main content

use_wasm_module/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Error returned when module metadata is invalid.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum WasmModuleError {
10    /// The supplied value was empty.
11    Empty,
12    /// The supplied value contains unsupported characters.
13    InvalidName,
14    /// The supplied label was not recognized.
15    UnknownLabel,
16}
17
18impl fmt::Display for WasmModuleError {
19    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            Self::Empty => formatter.write_str("WebAssembly module value cannot be empty"),
22            Self::InvalidName => formatter.write_str("invalid WebAssembly module name"),
23            Self::UnknownLabel => formatter.write_str("unknown WebAssembly module label"),
24        }
25    }
26}
27
28impl Error for WasmModuleError {}
29
30fn validate_module_text(value: &str) -> Result<&str, WasmModuleError> {
31    let trimmed = value.trim();
32    if trimmed.is_empty() {
33        return Err(WasmModuleError::Empty);
34    }
35    if trimmed.chars().any(char::is_control) {
36        return Err(WasmModuleError::InvalidName);
37    }
38    Ok(trimmed)
39}
40
41/// Validated module name metadata.
42#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
43pub struct ModuleName(String);
44
45impl ModuleName {
46    /// Creates a validated module name.
47    pub fn new(value: impl AsRef<str>) -> Result<Self, WasmModuleError> {
48        validate_module_text(value.as_ref()).map(|value| Self(value.to_owned()))
49    }
50
51    /// Returns the stored module name.
52    #[must_use]
53    pub fn as_str(&self) -> &str {
54        &self.0
55    }
56
57    /// Consumes the wrapper and returns the stored module name.
58    #[must_use]
59    pub fn into_string(self) -> String {
60        self.0
61    }
62}
63
64impl AsRef<str> for ModuleName {
65    fn as_ref(&self) -> &str {
66        self.as_str()
67    }
68}
69
70impl fmt::Display for ModuleName {
71    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72        formatter.write_str(self.as_str())
73    }
74}
75
76impl FromStr for ModuleName {
77    type Err = WasmModuleError;
78
79    fn from_str(value: &str) -> Result<Self, Self::Err> {
80        Self::new(value)
81    }
82}
83
84impl TryFrom<&str> for ModuleName {
85    type Error = WasmModuleError;
86
87    fn try_from(value: &str) -> Result<Self, Self::Error> {
88        Self::new(value)
89    }
90}
91
92/// Coarse module kind metadata.
93#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
94pub enum ModuleKind {
95    /// Core Wasm binary module.
96    #[default]
97    CoreBinary,
98    /// Core Wasm text module.
99    CoreText,
100    /// Component Model component.
101    Component,
102    /// WIT package metadata.
103    WitPackage,
104}
105
106impl ModuleKind {
107    /// Returns a stable kind label.
108    #[must_use]
109    pub const fn as_str(self) -> &'static str {
110        match self {
111            Self::CoreBinary => "core-binary",
112            Self::CoreText => "core-text",
113            Self::Component => "component",
114            Self::WitPackage => "wit-package",
115        }
116    }
117}
118
119impl fmt::Display for ModuleKind {
120    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
121        formatter.write_str(self.as_str())
122    }
123}
124
125impl FromStr for ModuleKind {
126    type Err = WasmModuleError;
127
128    fn from_str(value: &str) -> Result<Self, Self::Err> {
129        let trimmed = value.trim();
130        if trimmed.is_empty() {
131            return Err(WasmModuleError::Empty);
132        }
133        match trimmed
134            .to_ascii_lowercase()
135            .replace(['_', ' '], "-")
136            .as_str()
137        {
138            "core-binary" | "binary" => Ok(Self::CoreBinary),
139            "core-text" | "text" | "wat" => Ok(Self::CoreText),
140            "component" => Ok(Self::Component),
141            "wit-package" | "wit" => Ok(Self::WitPackage),
142            _ => Err(WasmModuleError::UnknownLabel),
143        }
144    }
145}
146
147/// Import/export external item kind.
148#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
149pub enum ModuleItemKind {
150    /// Function item.
151    #[default]
152    Function,
153    /// Table item.
154    Table,
155    /// Memory item.
156    Memory,
157    /// Global item.
158    Global,
159}
160
161/// Module validation status metadata.
162#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub enum ValidationStatus {
164    /// Validation has not run.
165    #[default]
166    NotValidated,
167    /// Validation passed.
168    Valid,
169    /// Validation failed.
170    Invalid,
171}
172
173impl ValidationStatus {
174    /// Returns 'true' when validation passed.
175    #[must_use]
176    pub const fn is_valid(self) -> bool {
177        matches!(self, Self::Valid)
178    }
179}
180
181/// Module import metadata.
182#[derive(Clone, Debug, Eq, Hash, PartialEq)]
183pub struct ModuleImport {
184    module: String,
185    name: String,
186    kind: ModuleItemKind,
187}
188
189impl ModuleImport {
190    /// Creates module import metadata.
191    pub fn new(
192        module: impl AsRef<str>,
193        name: impl AsRef<str>,
194        kind: ModuleItemKind,
195    ) -> Result<Self, WasmModuleError> {
196        Ok(Self {
197            module: validate_module_text(module.as_ref())?.to_owned(),
198            name: validate_module_text(name.as_ref())?.to_owned(),
199            kind,
200        })
201    }
202
203    /// Returns the import module label.
204    #[must_use]
205    pub fn module(&self) -> &str {
206        &self.module
207    }
208
209    /// Returns the import name.
210    #[must_use]
211    pub fn name(&self) -> &str {
212        &self.name
213    }
214
215    /// Returns the imported item kind.
216    #[must_use]
217    pub const fn kind(&self) -> ModuleItemKind {
218        self.kind
219    }
220}
221
222/// Module export metadata.
223#[derive(Clone, Debug, Eq, Hash, PartialEq)]
224pub struct ModuleExport {
225    name: String,
226    kind: ModuleItemKind,
227}
228
229impl ModuleExport {
230    /// Creates module export metadata.
231    pub fn new(name: impl AsRef<str>, kind: ModuleItemKind) -> Result<Self, WasmModuleError> {
232        Ok(Self {
233            name: validate_module_text(name.as_ref())?.to_owned(),
234            kind,
235        })
236    }
237
238    /// Returns the export name.
239    #[must_use]
240    pub fn name(&self) -> &str {
241        &self.name
242    }
243
244    /// Returns the exported item kind.
245    #[must_use]
246    pub const fn kind(&self) -> ModuleItemKind {
247        self.kind
248    }
249}
250
251/// Module-level metadata collected without parsing or executing a module.
252#[derive(Clone, Debug, Default, Eq, PartialEq)]
253pub struct ModuleMetadata {
254    name: Option<ModuleName>,
255    kind: ModuleKind,
256    imports: Vec<ModuleImport>,
257    exports: Vec<ModuleExport>,
258    validation_status: ValidationStatus,
259}
260
261impl ModuleMetadata {
262    /// Creates empty module metadata for a kind.
263    #[must_use]
264    pub const fn new(kind: ModuleKind) -> Self {
265        Self {
266            name: None,
267            kind,
268            imports: Vec::new(),
269            exports: Vec::new(),
270            validation_status: ValidationStatus::NotValidated,
271        }
272    }
273
274    /// Sets the module name.
275    #[must_use]
276    pub fn with_name(mut self, name: ModuleName) -> Self {
277        self.name = Some(name);
278        self
279    }
280
281    /// Adds an import.
282    #[must_use]
283    pub fn with_import(mut self, import: ModuleImport) -> Self {
284        self.imports.push(import);
285        self
286    }
287
288    /// Adds an export.
289    #[must_use]
290    pub fn with_export(mut self, export: ModuleExport) -> Self {
291        self.exports.push(export);
292        self
293    }
294
295    /// Sets the validation status.
296    #[must_use]
297    pub const fn with_validation_status(mut self, status: ValidationStatus) -> Self {
298        self.validation_status = status;
299        self
300    }
301
302    /// Returns the module kind.
303    #[must_use]
304    pub const fn kind(&self) -> ModuleKind {
305        self.kind
306    }
307
308    /// Returns module imports.
309    #[must_use]
310    pub fn imports(&self) -> &[ModuleImport] {
311        &self.imports
312    }
313
314    /// Returns module exports.
315    #[must_use]
316    pub fn exports(&self) -> &[ModuleExport] {
317        &self.exports
318    }
319
320    /// Returns validation status metadata.
321    #[must_use]
322    pub const fn validation_status(&self) -> ValidationStatus {
323        self.validation_status
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::{
330        ModuleExport, ModuleImport, ModuleItemKind, ModuleKind, ModuleMetadata, ModuleName,
331        ValidationStatus, WasmModuleError,
332    };
333
334    #[test]
335    fn validates_module_names_and_kinds() {
336        let name = ModuleName::new("example").expect("valid module name");
337        let kind = "wat".parse::<ModuleKind>().expect("known module kind");
338
339        assert_eq!(name.as_str(), "example");
340        assert_eq!(kind, ModuleKind::CoreText);
341        assert_eq!(ModuleName::new("\u{7}"), Err(WasmModuleError::InvalidName));
342    }
343
344    #[test]
345    fn stores_module_metadata() {
346        let metadata = ModuleMetadata::new(ModuleKind::CoreBinary)
347            .with_name(ModuleName::new("example").expect("valid module name"))
348            .with_import(
349                ModuleImport::new("env", "memory", ModuleItemKind::Memory).expect("valid import"),
350            )
351            .with_export(ModuleExport::new("run", ModuleItemKind::Function).expect("valid export"))
352            .with_validation_status(ValidationStatus::Valid);
353
354        assert_eq!(metadata.kind(), ModuleKind::CoreBinary);
355        assert_eq!(metadata.imports().len(), 1);
356        assert_eq!(metadata.exports().len(), 1);
357        assert!(metadata.validation_status().is_valid());
358    }
359}