1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum WasmImportError {
10 Empty,
12 InvalidName,
14 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 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 #[must_use]
59 pub fn as_str(&self) -> &str {
60 &self.0
61 }
62
63 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
105pub enum ImportKind {
106 #[default]
108 Function,
109 Memory,
111 Table,
113 Global,
115}
116
117impl ImportKind {
118 #[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#[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 #[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 #[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 #[must_use]
182 pub const fn module(&self) -> &ImportModuleName {
183 &self.module
184 }
185
186 #[must_use]
188 pub const fn name(&self) -> &ImportName {
189 &self.name
190 }
191
192 #[must_use]
194 pub const fn type_index(&self) -> Option<u32> {
195 self.type_index
196 }
197}
198
199#[derive(Clone, Debug, Eq, Hash, PartialEq)]
201pub struct ImportedMemory {
202 module: ImportModuleName,
203 name: ImportName,
204}
205
206impl ImportedMemory {
207 #[must_use]
209 pub const fn new(module: ImportModuleName, name: ImportName) -> Self {
210 Self { module, name }
211 }
212
213 #[must_use]
215 pub const fn module(&self) -> &ImportModuleName {
216 &self.module
217 }
218
219 #[must_use]
221 pub const fn name(&self) -> &ImportName {
222 &self.name
223 }
224}
225
226#[derive(Clone, Debug, Eq, Hash, PartialEq)]
228pub struct ImportedTable {
229 module: ImportModuleName,
230 name: ImportName,
231}
232
233impl ImportedTable {
234 #[must_use]
236 pub const fn new(module: ImportModuleName, name: ImportName) -> Self {
237 Self { module, name }
238 }
239}
240
241#[derive(Clone, Debug, Eq, Hash, PartialEq)]
243pub struct ImportedGlobal {
244 module: ImportModuleName,
245 name: ImportName,
246 mutable: bool,
247}
248
249impl ImportedGlobal {
250 #[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 #[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}