Skip to main content

use_wasm_import/
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 import metadata is invalid.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum WasmImportError {
10    /// The supplied value was empty.
11    Empty,
12    /// The supplied name contains unsupported characters.
13    InvalidName,
14    /// The supplied import kind label was not recognized.
15    UnknownKind,
16}
17
18impl fmt::Display for WasmImportError {
19    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            Self::Empty => formatter.write_str("WebAssembly import value cannot be empty"),
22            Self::InvalidName => formatter.write_str("invalid WebAssembly import name"),
23            Self::UnknownKind => formatter.write_str("unknown WebAssembly import kind"),
24        }
25    }
26}
27
28impl Error for WasmImportError {}
29
30fn validate_import_text(value: &str) -> Result<&str, WasmImportError> {
31    let trimmed = value.trim();
32    if trimmed.is_empty() {
33        return Err(WasmImportError::Empty);
34    }
35    if trimmed.chars().any(|character| {
36        character.is_control()
37            || character.is_whitespace()
38            || !(character.is_ascii_alphanumeric()
39                || matches!(character, '_' | '-' | '.' | '/' | ':' | '$'))
40    }) {
41        return Err(WasmImportError::InvalidName);
42    }
43    Ok(trimmed)
44}
45
46macro_rules! import_text_newtype {
47    ($name:ident) => {
48        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
49        pub struct $name(String);
50
51        impl $name {
52            /// Creates validated import text metadata.
53            pub fn new(value: impl AsRef<str>) -> Result<Self, WasmImportError> {
54                validate_import_text(value.as_ref()).map(|value| Self(value.to_owned()))
55            }
56
57            /// Returns the stored text.
58            #[must_use]
59            pub fn as_str(&self) -> &str {
60                &self.0
61            }
62
63            /// Consumes the wrapper and returns the stored string.
64            #[must_use]
65            pub fn into_string(self) -> String {
66                self.0
67            }
68        }
69
70        impl AsRef<str> for $name {
71            fn as_ref(&self) -> &str {
72                self.as_str()
73            }
74        }
75
76        impl fmt::Display for $name {
77            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78                formatter.write_str(self.as_str())
79            }
80        }
81
82        impl FromStr for $name {
83            type Err = WasmImportError;
84
85            fn from_str(value: &str) -> Result<Self, Self::Err> {
86                Self::new(value)
87            }
88        }
89
90        impl TryFrom<&str> for $name {
91            type Error = WasmImportError;
92
93            fn try_from(value: &str) -> Result<Self, Self::Error> {
94                Self::new(value)
95            }
96        }
97    };
98}
99
100import_text_newtype!(ImportModuleName);
101import_text_newtype!(ImportName);
102
103/// External import kind.
104#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
105pub enum ImportKind {
106    /// Function import.
107    #[default]
108    Function,
109    /// Memory import.
110    Memory,
111    /// Table import.
112    Table,
113    /// Global import.
114    Global,
115}
116
117impl ImportKind {
118    /// Returns the stable kind label.
119    #[must_use]
120    pub const fn as_str(self) -> &'static str {
121        match self {
122            Self::Function => "function",
123            Self::Memory => "memory",
124            Self::Table => "table",
125            Self::Global => "global",
126        }
127    }
128}
129
130impl fmt::Display for ImportKind {
131    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132        formatter.write_str(self.as_str())
133    }
134}
135
136impl FromStr for ImportKind {
137    type Err = WasmImportError;
138
139    fn from_str(value: &str) -> Result<Self, Self::Err> {
140        let trimmed = value.trim();
141        if trimmed.is_empty() {
142            return Err(WasmImportError::Empty);
143        }
144        match trimmed.to_ascii_lowercase().as_str() {
145            "function" | "func" => Ok(Self::Function),
146            "memory" | "mem" => Ok(Self::Memory),
147            "table" => Ok(Self::Table),
148            "global" => Ok(Self::Global),
149            _ => Err(WasmImportError::UnknownKind),
150        }
151    }
152}
153
154/// Imported function metadata.
155#[derive(Clone, Debug, Eq, Hash, PartialEq)]
156pub struct ImportedFunction {
157    module: ImportModuleName,
158    name: ImportName,
159    type_index: Option<u32>,
160}
161
162impl ImportedFunction {
163    /// Creates imported function metadata.
164    #[must_use]
165    pub const fn new(module: ImportModuleName, name: ImportName) -> Self {
166        Self {
167            module,
168            name,
169            type_index: None,
170        }
171    }
172
173    /// Attaches a function type index.
174    #[must_use]
175    pub const fn with_type_index(mut self, type_index: u32) -> Self {
176        self.type_index = Some(type_index);
177        self
178    }
179
180    /// Returns the import module name.
181    #[must_use]
182    pub const fn module(&self) -> &ImportModuleName {
183        &self.module
184    }
185
186    /// Returns the import field name.
187    #[must_use]
188    pub const fn name(&self) -> &ImportName {
189        &self.name
190    }
191
192    /// Returns the optional function type index.
193    #[must_use]
194    pub const fn type_index(&self) -> Option<u32> {
195        self.type_index
196    }
197}
198
199/// Imported memory metadata.
200#[derive(Clone, Debug, Eq, Hash, PartialEq)]
201pub struct ImportedMemory {
202    module: ImportModuleName,
203    name: ImportName,
204}
205
206impl ImportedMemory {
207    /// Creates imported memory metadata.
208    #[must_use]
209    pub const fn new(module: ImportModuleName, name: ImportName) -> Self {
210        Self { module, name }
211    }
212
213    /// Returns the import module name.
214    #[must_use]
215    pub const fn module(&self) -> &ImportModuleName {
216        &self.module
217    }
218
219    /// Returns the import field name.
220    #[must_use]
221    pub const fn name(&self) -> &ImportName {
222        &self.name
223    }
224}
225
226/// Imported table metadata.
227#[derive(Clone, Debug, Eq, Hash, PartialEq)]
228pub struct ImportedTable {
229    module: ImportModuleName,
230    name: ImportName,
231}
232
233impl ImportedTable {
234    /// Creates imported table metadata.
235    #[must_use]
236    pub const fn new(module: ImportModuleName, name: ImportName) -> Self {
237        Self { module, name }
238    }
239}
240
241/// Imported global metadata.
242#[derive(Clone, Debug, Eq, Hash, PartialEq)]
243pub struct ImportedGlobal {
244    module: ImportModuleName,
245    name: ImportName,
246    mutable: bool,
247}
248
249impl ImportedGlobal {
250    /// Creates imported global metadata.
251    #[must_use]
252    pub const fn new(module: ImportModuleName, name: ImportName, mutable: bool) -> Self {
253        Self {
254            module,
255            name,
256            mutable,
257        }
258    }
259
260    /// Returns 'true' when the imported global is mutable.
261    #[must_use]
262    pub const fn is_mutable(&self) -> bool {
263        self.mutable
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::{ImportKind, ImportModuleName, ImportName, ImportedFunction, WasmImportError};
270
271    #[test]
272    fn validates_import_names() {
273        let module = ImportModuleName::new("wasi:cli").expect("valid module");
274        let name = ImportName::new("run").expect("valid import name");
275
276        assert_eq!(module.as_str(), "wasi:cli");
277        assert_eq!(name.to_string(), "run");
278        assert_eq!(
279            ImportName::new("bad name"),
280            Err(WasmImportError::InvalidName)
281        );
282    }
283
284    #[test]
285    fn parses_import_kinds_and_metadata() {
286        let kind = "func".parse::<ImportKind>().expect("known import kind");
287        let function = ImportedFunction::new(
288            ImportModuleName::new("env").expect("valid module"),
289            ImportName::new("call").expect("valid import"),
290        )
291        .with_type_index(2);
292
293        assert_eq!(kind, ImportKind::Function);
294        assert_eq!(kind.to_string(), "function");
295        assert_eq!(function.type_index(), Some(2));
296        assert_eq!(function.module().as_str(), "env");
297    }
298}