Skip to main content

use_wasm_export/
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 export metadata is invalid.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum WasmExportError {
10    /// The supplied value was empty.
11    Empty,
12    /// The supplied name contains unsupported characters.
13    InvalidName,
14    /// The supplied export kind label was not recognized.
15    UnknownKind,
16}
17
18impl fmt::Display for WasmExportError {
19    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            Self::Empty => formatter.write_str("WebAssembly export value cannot be empty"),
22            Self::InvalidName => formatter.write_str("invalid WebAssembly export name"),
23            Self::UnknownKind => formatter.write_str("unknown WebAssembly export kind"),
24        }
25    }
26}
27
28impl Error for WasmExportError {}
29
30fn validate_export_text(value: &str) -> Result<&str, WasmExportError> {
31    let trimmed = value.trim();
32    if trimmed.is_empty() {
33        return Err(WasmExportError::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(WasmExportError::InvalidName);
42    }
43    Ok(trimmed)
44}
45
46/// Validated WebAssembly export name.
47#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
48pub struct ExportName(String);
49
50impl ExportName {
51    /// Creates a validated export name.
52    pub fn new(value: impl AsRef<str>) -> Result<Self, WasmExportError> {
53        validate_export_text(value.as_ref()).map(|value| Self(value.to_owned()))
54    }
55
56    /// Returns the stored export name.
57    #[must_use]
58    pub fn as_str(&self) -> &str {
59        &self.0
60    }
61
62    /// Consumes the wrapper and returns the stored string.
63    #[must_use]
64    pub fn into_string(self) -> String {
65        self.0
66    }
67}
68
69impl AsRef<str> for ExportName {
70    fn as_ref(&self) -> &str {
71        self.as_str()
72    }
73}
74
75impl fmt::Display for ExportName {
76    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77        formatter.write_str(self.as_str())
78    }
79}
80
81impl FromStr for ExportName {
82    type Err = WasmExportError;
83
84    fn from_str(value: &str) -> Result<Self, Self::Err> {
85        Self::new(value)
86    }
87}
88
89impl TryFrom<&str> for ExportName {
90    type Error = WasmExportError;
91
92    fn try_from(value: &str) -> Result<Self, Self::Error> {
93        Self::new(value)
94    }
95}
96
97/// External export kind.
98#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
99pub enum ExportKind {
100    /// Function export.
101    #[default]
102    Function,
103    /// Memory export.
104    Memory,
105    /// Table export.
106    Table,
107    /// Global export.
108    Global,
109}
110
111impl ExportKind {
112    /// Returns the stable kind label.
113    #[must_use]
114    pub const fn as_str(self) -> &'static str {
115        match self {
116            Self::Function => "function",
117            Self::Memory => "memory",
118            Self::Table => "table",
119            Self::Global => "global",
120        }
121    }
122}
123
124impl fmt::Display for ExportKind {
125    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
126        formatter.write_str(self.as_str())
127    }
128}
129
130impl FromStr for ExportKind {
131    type Err = WasmExportError;
132
133    fn from_str(value: &str) -> Result<Self, Self::Err> {
134        let trimmed = value.trim();
135        if trimmed.is_empty() {
136            return Err(WasmExportError::Empty);
137        }
138        match trimmed.to_ascii_lowercase().as_str() {
139            "function" | "func" => Ok(Self::Function),
140            "memory" | "mem" => Ok(Self::Memory),
141            "table" => Ok(Self::Table),
142            "global" => Ok(Self::Global),
143            _ => Err(WasmExportError::UnknownKind),
144        }
145    }
146}
147
148/// Exported function metadata.
149#[derive(Clone, Debug, Eq, Hash, PartialEq)]
150pub struct ExportedFunction {
151    name: ExportName,
152    index: u32,
153}
154
155impl ExportedFunction {
156    /// Creates exported function metadata.
157    #[must_use]
158    pub const fn new(name: ExportName, index: u32) -> Self {
159        Self { name, index }
160    }
161
162    /// Returns the export name.
163    #[must_use]
164    pub const fn name(&self) -> &ExportName {
165        &self.name
166    }
167
168    /// Returns the function index.
169    #[must_use]
170    pub const fn index(&self) -> u32 {
171        self.index
172    }
173}
174
175/// Exported memory metadata.
176#[derive(Clone, Debug, Eq, Hash, PartialEq)]
177pub struct ExportedMemory {
178    name: ExportName,
179    index: u32,
180}
181
182impl ExportedMemory {
183    /// Creates exported memory metadata.
184    #[must_use]
185    pub const fn new(name: ExportName, index: u32) -> Self {
186        Self { name, index }
187    }
188}
189
190/// Exported table metadata.
191#[derive(Clone, Debug, Eq, Hash, PartialEq)]
192pub struct ExportedTable {
193    name: ExportName,
194    index: u32,
195}
196
197impl ExportedTable {
198    /// Creates exported table metadata.
199    #[must_use]
200    pub const fn new(name: ExportName, index: u32) -> Self {
201        Self { name, index }
202    }
203}
204
205/// Exported global metadata.
206#[derive(Clone, Debug, Eq, Hash, PartialEq)]
207pub struct ExportedGlobal {
208    name: ExportName,
209    index: u32,
210    mutable: bool,
211}
212
213impl ExportedGlobal {
214    /// Creates exported global metadata.
215    #[must_use]
216    pub const fn new(name: ExportName, index: u32, mutable: bool) -> Self {
217        Self {
218            name,
219            index,
220            mutable,
221        }
222    }
223
224    /// Returns 'true' when the exported global is mutable.
225    #[must_use]
226    pub const fn is_mutable(&self) -> bool {
227        self.mutable
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::{ExportKind, ExportName, ExportedFunction, WasmExportError};
234
235    #[test]
236    fn validates_export_names() {
237        let name = ExportName::new("run").expect("valid export name");
238
239        assert_eq!(name.as_str(), "run");
240        assert_eq!(name.to_string(), "run");
241        assert_eq!(
242            ExportName::new("bad name"),
243            Err(WasmExportError::InvalidName)
244        );
245    }
246
247    #[test]
248    fn parses_export_kinds_and_metadata() {
249        let kind = "mem".parse::<ExportKind>().expect("known export kind");
250        let function = ExportedFunction::new(ExportName::new("run").expect("valid export"), 3);
251
252        assert_eq!(kind, ExportKind::Memory);
253        assert_eq!(kind.to_string(), "memory");
254        assert_eq!(function.name().as_str(), "run");
255        assert_eq!(function.index(), 3);
256    }
257}