1use std::fmt::Display;
10
11use wasm_bindgen::JsValue;
12
13#[derive(Debug, thiserror::Error)]
15pub enum WireError {
16 #[error("Failed to parse `{type_name}`: {message}")]
18 Parse {
19 type_name: &'static str,
21 message: String,
23 },
24}
25
26pub trait FromWasm: Sized {
28 type Wire;
30
31 fn from_wire(wire: Self::Wire) -> Result<Self, WireError>;
33}
34
35pub trait ToWasm {
37 type Wire;
39
40 fn to_wire(self) -> Result<Self::Wire, WireError>;
42}
43
44#[doc(hidden)]
47pub fn parse_wire_string<T, E: Display>(
48 type_name: &'static str,
49 wire: String,
50 parse: impl FnOnce(String) -> Result<T, E>,
51) -> Result<T, WireError> {
52 parse(wire).map_err(|err| WireError::Parse {
53 type_name,
54 message: err.to_string(),
55 })
56}
57
58#[doc(hidden)]
60pub fn format_wire_string<T>(value: T, format: impl FnOnce(T) -> String) -> String {
61 format(value)
62}
63
64crate::impl_wire_object!(
67 bool,
68 char,
69 f32,
70 f64,
71 i8,
72 i16,
73 i32,
74 i64,
75 i128,
76 isize,
77 u8,
78 u16,
79 u32,
80 u64,
81 u128,
82 usize,
83 (),
84 String,
85 JsValue,
86);
87
88impl<T: FromWasm> FromWasm for Vec<T> {
91 type Wire = Vec<T::Wire>;
92
93 fn from_wire(wire: Self::Wire) -> Result<Self, WireError> {
94 wire.into_iter().map(T::from_wire).collect()
95 }
96}
97
98impl<T: ToWasm> ToWasm for Vec<T> {
99 type Wire = Vec<T::Wire>;
100
101 fn to_wire(self) -> Result<Self::Wire, WireError> {
102 self.into_iter().map(T::to_wire).collect()
103 }
104}
105
106impl<T: FromWasm> FromWasm for Option<T> {
107 type Wire = Option<T::Wire>;
108
109 fn from_wire(wire: Self::Wire) -> Result<Self, WireError> {
110 wire.map(T::from_wire).transpose()
111 }
112}
113
114impl<T: ToWasm> ToWasm for Option<T> {
115 type Wire = Option<T::Wire>;
116
117 fn to_wire(self) -> Result<Self::Wire, WireError> {
118 self.map(T::to_wire).transpose()
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use std::convert::Infallible;
125
126 use super::*;
127
128 #[derive(Debug, PartialEq)]
129 struct Digit(u8);
130
131 impl std::str::FromStr for Digit {
132 type Err = String;
133
134 fn from_str(s: &str) -> Result<Self, Self::Err> {
135 s.parse()
136 .map(Digit)
137 .map_err(|_| format!("not a digit: {s}"))
138 }
139 }
140
141 impl Display for Digit {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 write!(f, "{}", self.0)
144 }
145 }
146
147 crate::impl_wire_string!(Digit);
148
149 struct Shout(String);
150
151 crate::impl_wire_string!(
152 Shout,
153 parse = |wire: String| Ok::<_, Infallible>(Shout(wire.to_uppercase())),
154 format = |value: Shout| value.0,
155 );
156
157 struct InputOnly(String);
158
159 crate::impl_wire_string!(
160 InputOnly,
161 parse = |wire| Ok::<_, Infallible>(InputOnly(wire))
162 );
163
164 #[test]
165 fn default_form_round_trips() {
166 let digit = Digit::from_wire("7".to_owned()).unwrap();
167 assert_eq!(digit, Digit(7));
168 assert_eq!(digit.to_wire().unwrap(), "7");
169 }
170
171 #[test]
172 fn malformed_string_is_a_parse_error() {
173 let err = Digit::from_wire("x".to_owned()).unwrap_err();
174 assert_eq!(err.to_string(), "Failed to parse `Digit`: not a digit: x");
175 }
176
177 #[test]
178 fn custom_form_uses_the_given_functions() {
179 let shout = Shout::from_wire("hi".to_owned()).unwrap();
180 assert_eq!(shout.to_wire().unwrap(), "HI");
181 assert_eq!(InputOnly::from_wire("hi".to_owned()).unwrap().0, "hi");
182 }
183
184 #[test]
185 fn a_bad_element_fails_the_whole_vec() {
186 let wire = vec!["1".to_owned(), "x".to_owned()];
187 assert!(Vec::<Digit>::from_wire(wire).is_err());
188 assert_eq!(Option::<Digit>::from_wire(None).unwrap(), None);
189 }
190}