topcoat_runtime/surrogate/
signal.rs1use std::ops::Deref;
2
3use ref_cast::RefCast;
4use serde::{Deserialize, Serialize, de};
5
6use crate::{
7 Signal, SignalId, StrSurrogate, Surrogate, Surrogated, impl_surrogate, impl_surrogate_mut,
8 impl_surrogate_ref,
9};
10
11#[derive(Debug, RefCast)]
12#[repr(transparent)]
13pub struct SignalSurrogate<T>(Signal<T>);
14
15impl<T> SignalSurrogate<T> {
16 #[inline]
17 pub(crate) const fn new(v: Signal<T>) -> Self {
18 Self(v)
19 }
20}
21
22impl<T> SignalSurrogate<T>
23where
24 for<'b> &'b T: Surrogated,
25{
26 #[must_use]
32 pub fn read(&self) -> <&T as Surrogated>::Surrogate {
33 self.0.read_untracked().into_surrogate()
34 }
35}
36
37impl<T> SignalSurrogate<T>
38where
39 T: Surrogated + Clone,
40{
41 #[must_use]
46 pub fn get(&self) -> <T as Surrogated>::Surrogate {
47 self.0.get_untracked().into_surrogate()
48 }
49}
50
51impl<T> SignalSurrogate<T>
52where
53 T: Surrogated,
54{
55 #[track_caller]
61 pub fn set(&self, _v: T::Surrogate) {
62 write_in_browser_only();
63 }
64}
65
66impl SignalSurrogate<bool> {
67 #[track_caller]
73 pub fn toggle(&self) {
74 write_in_browser_only();
75 }
76}
77
78impl SignalSurrogate<f64> {
79 #[track_caller]
85 pub fn increment(&self) {
86 write_in_browser_only();
87 }
88
89 #[track_caller]
95 pub fn decrement(&self) {
96 write_in_browser_only();
97 }
98}
99
100impl SignalSurrogate<String> {
101 #[track_caller]
112 pub fn push_str(&self, _s: impl Deref<Target = StrSurrogate>) {
113 write_in_browser_only();
114 }
115}
116
117#[track_caller]
119fn write_in_browser_only() -> ! {
120 panic!("expressions in which a signal is written to cannot be run server-side");
121}
122
123impl_surrogate!({T} Signal<T>, SignalSurrogate<T>);
124impl_surrogate_ref!({T} Signal<T>, SignalSurrogate<T>);
125impl_surrogate_mut!({T} Signal<T>, SignalSurrogate<T>);
126
127impl<T> Serialize for SignalSurrogate<T> {
128 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
129 where
130 S: serde::Serializer,
131 {
132 #[derive(Serialize)]
133 struct TaggedSignal {
134 t: &'static str,
135 id: std::string::String,
136 }
137
138 TaggedSignal {
139 t: "Signal",
140 id: self.0.id().to_string(),
141 }
142 .serialize(serializer)
143 }
144}
145
146impl<'de, T> Deserialize<'de> for SignalSurrogate<T>
153where
154 T: Surrogated,
155 T::Surrogate: Deserialize<'de>,
156{
157 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158 where
159 D: serde::Deserializer<'de>,
160 {
161 #[derive(Deserialize)]
162 #[serde(deny_unknown_fields, bound(deserialize = "V: Deserialize<'de>"))]
163 struct TaggedSignal<V> {
164 t: std::string::String,
165 id: SignalId,
166 v: V,
167 }
168
169 let tagged = TaggedSignal::<T::Surrogate>::deserialize(deserializer)?;
170 if tagged.t != "Signal" {
171 return Err(de::Error::invalid_value(
172 de::Unexpected::Str(&tagged.t),
173 &"Signal",
174 ));
175 }
176 Ok(Self(Signal::new(tagged.id, tagged.v.into_real())))
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use std::panic::Location;
183
184 use serde_json::json;
185
186 use super::*;
187
188 #[track_caller]
190 fn surrogate<T>(value: T) -> SignalSurrogate<T> {
191 SignalSurrogate::new(Signal::new(SignalId::derive(Location::caller()), value))
192 }
193
194 #[test]
195 fn serializes_as_its_id() {
196 let signal = surrogate(String::from("shoes"));
197
198 assert_eq!(
199 serde_json::to_value(&signal).unwrap(),
200 json!({ "t": "Signal", "id": signal.0.id().to_string() })
201 );
202 }
203
204 #[test]
205 fn deserializes_from_its_id_and_value() {
206 let id = SignalId::derive(Location::caller());
207
208 let signal: SignalSurrogate<String> =
209 serde_json::from_value(json!({ "t": "Signal", "id": id.to_string(), "v": "shoes" }))
210 .unwrap();
211
212 assert_eq!(signal.0.id(), id);
213 assert_eq!(*signal.0.read_untracked(), "shoes");
214 }
215
216 #[test]
217 fn deserializes_a_value_through_its_surrogate() {
218 let id = SignalId::derive(Location::caller());
219
220 let signal: SignalSurrogate<Option<f64>> = serde_json::from_value(json!({
221 "t": "Signal",
222 "id": id.to_string(),
223 "v": { "t": "Option", "v": 5.0 },
224 }))
225 .unwrap();
226
227 assert_eq!(*signal.0.read_untracked(), Some(5.0));
228 }
229
230 #[test]
231 fn rejects_another_tag() {
232 let id = SignalId::derive(Location::caller());
233
234 let error = serde_json::from_value::<SignalSurrogate<String>>(
235 json!({ "t": "Procedure", "id": id.to_string(), "v": "shoes" }),
236 )
237 .unwrap_err();
238
239 assert!(error.to_string().contains("expected Signal"), "{error}");
240 }
241
242 #[test]
243 fn rejects_a_missing_value() {
244 let id = SignalId::derive(Location::caller());
245
246 let error = serde_json::from_value::<SignalSurrogate<String>>(
247 json!({ "t": "Signal", "id": id.to_string() }),
248 )
249 .unwrap_err();
250
251 assert!(error.to_string().contains("missing field `v`"), "{error}");
252 }
253
254 #[test]
255 fn rejects_a_value_of_another_type() {
256 let id = SignalId::derive(Location::caller());
257
258 serde_json::from_value::<SignalSurrogate<f64>>(json!({
259 "t": "Signal",
260 "id": id.to_string(),
261 "v": "shoes",
262 }))
263 .unwrap_err();
264 }
265
266 #[test]
267 fn rejects_a_malformed_id() {
268 serde_json::from_value::<SignalSurrogate<String>>(json!({
269 "t": "Signal",
270 "id": "not hex",
271 "v": "shoes",
272 }))
273 .unwrap_err();
274 }
275
276 #[test]
277 #[should_panic(expected = "cannot be run server-side")]
278 fn toggle_panics_server_side() {
279 surrogate(false).toggle();
280 }
281
282 #[test]
283 #[should_panic(expected = "cannot be run server-side")]
284 fn increment_panics_server_side() {
285 surrogate(0.0).increment();
286 }
287
288 #[test]
289 #[should_panic(expected = "cannot be run server-side")]
290 fn decrement_panics_server_side() {
291 surrogate(0.0).decrement();
292 }
293
294 #[test]
295 #[should_panic(expected = "cannot be run server-side")]
296 fn push_str_panics_server_side() {
297 surrogate(String::new()).push_str(StrSurrogate::ref_cast(""));
298 }
299}