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 WasmTargetError {
10 Empty,
12 Unknown,
14}
15
16impl fmt::Display for WasmTargetError {
17 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 Self::Empty => formatter.write_str("WebAssembly target cannot be empty"),
20 Self::Unknown => formatter.write_str("unknown WebAssembly target"),
21 }
22 }
23}
24
25impl Error for WasmTargetError {}
26
27#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum WasmTargetProfile {
30 #[default]
32 UnknownUnknown,
33 WasiPreview1,
35 WasiPreview1Threads,
37 WasiPreview2,
39 Wasm32V1None,
41}
42
43impl WasmTargetProfile {
44 #[must_use]
46 pub const fn as_str(self) -> &'static str {
47 match self {
48 Self::UnknownUnknown => "unknown-unknown",
49 Self::WasiPreview1 => "wasip1",
50 Self::WasiPreview1Threads => "wasip1-threads",
51 Self::WasiPreview2 => "wasip2",
52 Self::Wasm32V1None => "wasm32v1-none",
53 }
54 }
55}
56
57impl fmt::Display for WasmTargetProfile {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 formatter.write_str(self.as_str())
60 }
61}
62
63#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
65pub enum WasmTarget {
66 #[default]
68 Wasm32UnknownUnknown,
69 Wasm32WasiP1,
71 Wasm32WasiP1Threads,
73 Wasm32WasiP2,
75 Wasm32V1None,
77}
78
79impl WasmTarget {
80 #[must_use]
82 pub const fn as_str(self) -> &'static str {
83 match self {
84 Self::Wasm32UnknownUnknown => "wasm32-unknown-unknown",
85 Self::Wasm32WasiP1 => "wasm32-wasip1",
86 Self::Wasm32WasiP1Threads => "wasm32-wasip1-threads",
87 Self::Wasm32WasiP2 => "wasm32-wasip2",
88 Self::Wasm32V1None => "wasm32v1-none",
89 }
90 }
91
92 #[must_use]
94 pub const fn family(self) -> &'static str {
95 "wasm32"
96 }
97
98 #[must_use]
100 pub const fn pointer_width(self) -> u8 {
101 32
102 }
103
104 #[must_use]
106 pub const fn profile(self) -> WasmTargetProfile {
107 match self {
108 Self::Wasm32UnknownUnknown => WasmTargetProfile::UnknownUnknown,
109 Self::Wasm32WasiP1 => WasmTargetProfile::WasiPreview1,
110 Self::Wasm32WasiP1Threads => WasmTargetProfile::WasiPreview1Threads,
111 Self::Wasm32WasiP2 => WasmTargetProfile::WasiPreview2,
112 Self::Wasm32V1None => WasmTargetProfile::Wasm32V1None,
113 }
114 }
115
116 #[must_use]
118 pub const fn supports_threads(self) -> bool {
119 matches!(self, Self::Wasm32WasiP1Threads)
120 }
121}
122
123impl fmt::Display for WasmTarget {
124 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125 formatter.write_str(self.as_str())
126 }
127}
128
129impl FromStr for WasmTarget {
130 type Err = WasmTargetError;
131
132 fn from_str(value: &str) -> Result<Self, Self::Err> {
133 let trimmed = value.trim();
134 if trimmed.is_empty() {
135 return Err(WasmTargetError::Empty);
136 }
137 match trimmed.to_ascii_lowercase().as_str() {
138 "wasm32-unknown-unknown" => Ok(Self::Wasm32UnknownUnknown),
139 "wasm32-wasip1" => Ok(Self::Wasm32WasiP1),
140 "wasm32-wasip1-threads" => Ok(Self::Wasm32WasiP1Threads),
141 "wasm32-wasip2" => Ok(Self::Wasm32WasiP2),
142 "wasm32v1-none" => Ok(Self::Wasm32V1None),
143 _ => Err(WasmTargetError::Unknown),
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::{WasmTarget, WasmTargetError, WasmTargetProfile};
151
152 #[test]
153 fn parses_common_targets() {
154 assert_eq!(
155 "wasm32-wasip1".parse::<WasmTarget>(),
156 Ok(WasmTarget::Wasm32WasiP1)
157 );
158 assert_eq!("".parse::<WasmTarget>(), Err(WasmTargetError::Empty));
159 }
160
161 #[test]
162 fn exposes_target_metadata() {
163 let target = WasmTarget::Wasm32WasiP1Threads;
164
165 assert_eq!(target.family(), "wasm32");
166 assert_eq!(target.pointer_width(), 32);
167 assert_eq!(target.profile(), WasmTargetProfile::WasiPreview1Threads);
168 assert!(target.supports_threads());
169 assert_eq!(target.to_string(), "wasm32-wasip1-threads");
170 }
171}