Skip to main content

use_wasm_feature/
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 a WebAssembly feature label cannot be parsed.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum WasmFeatureError {
10    /// The supplied feature label was empty.
11    Empty,
12    /// The supplied feature label is not in the known feature list.
13    Unknown,
14}
15
16impl fmt::Display for WasmFeatureError {
17    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Self::Empty => formatter.write_str("WebAssembly feature label cannot be empty"),
20            Self::Unknown => formatter.write_str("unknown WebAssembly feature label"),
21        }
22    }
23}
24
25impl Error for WasmFeatureError {}
26
27/// Coarse stability status for feature labels.
28#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum WasmFeatureStatus {
30    /// Broadly implemented or standardized enough for stable metadata labels.
31    #[default]
32    Stable,
33    /// Useful, but still treated as experimental by this primitive set.
34    Experimental,
35    /// Historical or discouraged label.
36    Deprecated,
37}
38
39impl WasmFeatureStatus {
40    /// Returns a stable status label.
41    #[must_use]
42    pub const fn as_str(self) -> &'static str {
43        match self {
44            Self::Stable => "stable",
45            Self::Experimental => "experimental",
46            Self::Deprecated => "deprecated",
47        }
48    }
49}
50
51impl fmt::Display for WasmFeatureStatus {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        formatter.write_str(self.as_str())
54    }
55}
56
57/// Common WebAssembly feature flags.
58#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
59pub enum WasmFeature {
60    /// SIMD instructions.
61    #[default]
62    Simd,
63    /// Threading primitives.
64    Threads,
65    /// Reference types.
66    ReferenceTypes,
67    /// Bulk memory operations.
68    BulkMemory,
69    /// Tail calls.
70    TailCalls,
71    /// Exception handling.
72    Exceptions,
73    /// Garbage collection proposal family.
74    Gc,
75    /// 64-bit memory indexes.
76    Memory64,
77    /// Multiple return values.
78    MultiValue,
79    /// Component Model support.
80    ComponentModel,
81}
82
83impl WasmFeature {
84    /// Returns the canonical feature label.
85    #[must_use]
86    pub const fn as_str(self) -> &'static str {
87        match self {
88            Self::Simd => "simd",
89            Self::Threads => "threads",
90            Self::ReferenceTypes => "reference-types",
91            Self::BulkMemory => "bulk-memory",
92            Self::TailCalls => "tail-calls",
93            Self::Exceptions => "exceptions",
94            Self::Gc => "gc",
95            Self::Memory64 => "memory64",
96            Self::MultiValue => "multi-value",
97            Self::ComponentModel => "component-model",
98        }
99    }
100
101    /// Returns the coarse feature status.
102    #[must_use]
103    pub const fn status(self) -> WasmFeatureStatus {
104        match self {
105            Self::Exceptions | Self::Gc | Self::Memory64 | Self::TailCalls => {
106                WasmFeatureStatus::Experimental
107            },
108            Self::Simd
109            | Self::Threads
110            | Self::ReferenceTypes
111            | Self::BulkMemory
112            | Self::MultiValue
113            | Self::ComponentModel => WasmFeatureStatus::Stable,
114        }
115    }
116
117    /// Returns 'true' when the status is stable.
118    #[must_use]
119    pub const fn is_stable(self) -> bool {
120        matches!(self.status(), WasmFeatureStatus::Stable)
121    }
122}
123
124impl fmt::Display for WasmFeature {
125    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
126        formatter.write_str(self.as_str())
127    }
128}
129
130impl FromStr for WasmFeature {
131    type Err = WasmFeatureError;
132
133    fn from_str(value: &str) -> Result<Self, Self::Err> {
134        let trimmed = value.trim();
135        if trimmed.is_empty() {
136            return Err(WasmFeatureError::Empty);
137        }
138        let normalized: String = trimmed
139            .chars()
140            .map(|character| {
141                if character == '_' || character.is_whitespace() {
142                    '-'
143                } else {
144                    character.to_ascii_lowercase()
145                }
146            })
147            .collect();
148        match normalized.as_str() {
149            "simd" => Ok(Self::Simd),
150            "threads" => Ok(Self::Threads),
151            "reference-types" => Ok(Self::ReferenceTypes),
152            "bulk-memory" => Ok(Self::BulkMemory),
153            "tail-calls" => Ok(Self::TailCalls),
154            "exceptions" => Ok(Self::Exceptions),
155            "gc" => Ok(Self::Gc),
156            "memory64" => Ok(Self::Memory64),
157            "multi-value" => Ok(Self::MultiValue),
158            "component-model" => Ok(Self::ComponentModel),
159            _ => Err(WasmFeatureError::Unknown),
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::{WasmFeature, WasmFeatureError, WasmFeatureStatus};
167
168    #[test]
169    fn parses_feature_labels() {
170        assert_eq!(
171            "bulk memory".parse::<WasmFeature>(),
172            Ok(WasmFeature::BulkMemory)
173        );
174        assert_eq!("".parse::<WasmFeature>(), Err(WasmFeatureError::Empty));
175    }
176
177    #[test]
178    fn exposes_status_labels() {
179        assert!(WasmFeature::Simd.is_stable());
180        assert_eq!(
181            WasmFeature::Memory64.status(),
182            WasmFeatureStatus::Experimental
183        );
184        assert_eq!(WasmFeature::ComponentModel.to_string(), "component-model");
185        assert_eq!(WasmFeatureStatus::Stable.to_string(), "stable");
186    }
187}