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 WasmModuleError {
10 Empty,
12 InvalidName,
14 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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
43pub struct ModuleName(String);
44
45impl ModuleName {
46 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 #[must_use]
53 pub fn as_str(&self) -> &str {
54 &self.0
55 }
56
57 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
94pub enum ModuleKind {
95 #[default]
97 CoreBinary,
98 CoreText,
100 Component,
102 WitPackage,
104}
105
106impl ModuleKind {
107 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
149pub enum ModuleItemKind {
150 #[default]
152 Function,
153 Table,
155 Memory,
157 Global,
159}
160
161#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub enum ValidationStatus {
164 #[default]
166 NotValidated,
167 Valid,
169 Invalid,
171}
172
173impl ValidationStatus {
174 #[must_use]
176 pub const fn is_valid(self) -> bool {
177 matches!(self, Self::Valid)
178 }
179}
180
181#[derive(Clone, Debug, Eq, Hash, PartialEq)]
183pub struct ModuleImport {
184 module: String,
185 name: String,
186 kind: ModuleItemKind,
187}
188
189impl ModuleImport {
190 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 #[must_use]
205 pub fn module(&self) -> &str {
206 &self.module
207 }
208
209 #[must_use]
211 pub fn name(&self) -> &str {
212 &self.name
213 }
214
215 #[must_use]
217 pub const fn kind(&self) -> ModuleItemKind {
218 self.kind
219 }
220}
221
222#[derive(Clone, Debug, Eq, Hash, PartialEq)]
224pub struct ModuleExport {
225 name: String,
226 kind: ModuleItemKind,
227}
228
229impl ModuleExport {
230 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 #[must_use]
240 pub fn name(&self) -> &str {
241 &self.name
242 }
243
244 #[must_use]
246 pub const fn kind(&self) -> ModuleItemKind {
247 self.kind
248 }
249}
250
251#[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 #[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 #[must_use]
276 pub fn with_name(mut self, name: ModuleName) -> Self {
277 self.name = Some(name);
278 self
279 }
280
281 #[must_use]
283 pub fn with_import(mut self, import: ModuleImport) -> Self {
284 self.imports.push(import);
285 self
286 }
287
288 #[must_use]
290 pub fn with_export(mut self, export: ModuleExport) -> Self {
291 self.exports.push(export);
292 self
293 }
294
295 #[must_use]
297 pub const fn with_validation_status(mut self, status: ValidationStatus) -> Self {
298 self.validation_status = status;
299 self
300 }
301
302 #[must_use]
304 pub const fn kind(&self) -> ModuleKind {
305 self.kind
306 }
307
308 #[must_use]
310 pub fn imports(&self) -> &[ModuleImport] {
311 &self.imports
312 }
313
314 #[must_use]
316 pub fn exports(&self) -> &[ModuleExport] {
317 &self.exports
318 }
319
320 #[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}