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
78macro_rules! numeric_signal {
79 ($($number:ty),+ $(,)?) => {
80 $(impl SignalSurrogate<$number> {
81 #[track_caller]
87 pub fn increment(&self) {
88 write_in_browser_only();
89 }
90
91 #[track_caller]
97 pub fn decrement(&self) {
98 write_in_browser_only();
99 }
100 })+
101 };
102}
103
104numeric_signal!(
105 f64, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
106);
107
108impl SignalSurrogate<String> {
109 #[track_caller]
117 pub fn push_str(&self, _s: impl Deref<Target = StrSurrogate>) {
118 write_in_browser_only();
119 }
120}
121
122#[track_caller]
124fn write_in_browser_only() -> ! {
125 panic!("expressions in which a signal is written to cannot be run server-side");
126}
127
128impl_surrogate!({T} Signal<T>, SignalSurrogate<T>);
129impl_surrogate_ref!({T} Signal<T>, SignalSurrogate<T>);
130impl_surrogate_mut!({T} Signal<T>, SignalSurrogate<T>);
131
132impl<T> Serialize for SignalSurrogate<T> {
133 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134 where
135 S: serde::Serializer,
136 {
137 #[derive(Serialize)]
138 struct TaggedSignal {
139 t: &'static str,
140 id: std::string::String,
141 }
142
143 TaggedSignal {
144 t: "Signal",
145 id: self.0.id().to_string(),
146 }
147 .serialize(serializer)
148 }
149}
150
151impl<'de, T> Deserialize<'de> for SignalSurrogate<T>
158where
159 T: Surrogated,
160 T::Surrogate: Deserialize<'de>,
161{
162 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163 where
164 D: serde::Deserializer<'de>,
165 {
166 #[derive(Deserialize)]
167 #[serde(deny_unknown_fields, bound(deserialize = "V: Deserialize<'de>"))]
168 struct TaggedSignal<V> {
169 t: std::string::String,
170 id: SignalId,
171 v: V,
172 }
173
174 let tagged = TaggedSignal::<T::Surrogate>::deserialize(deserializer)?;
175 if tagged.t != "Signal" {
176 return Err(de::Error::invalid_value(
177 de::Unexpected::Str(&tagged.t),
178 &"Signal",
179 ));
180 }
181 Ok(Self(Signal::new(tagged.id, tagged.v.into_real())))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use std::panic::Location;
188
189 use serde_json::json;
190 use topcoat_core::identity::Identity;
191
192 use super::*;
193
194 #[track_caller]
196 fn surrogate<T>(value: T) -> SignalSurrogate<T> {
197 SignalSurrogate::new(Signal::new(
198 SignalId::derive(Identity::ROOT, Location::caller()),
199 value,
200 ))
201 }
202
203 #[test]
204 fn serializes_as_its_id() {
205 let signal = surrogate(String::from("shoes"));
206
207 assert_eq!(
208 serde_json::to_value(&signal).unwrap(),
209 json!({ "t": "Signal", "id": signal.0.id().to_string() })
210 );
211 }
212
213 #[test]
214 fn deserializes_from_its_id_and_value() {
215 let id = SignalId::derive(Identity::ROOT, Location::caller());
216
217 let signal: SignalSurrogate<String> =
218 serde_json::from_value(json!({ "t": "Signal", "id": id.to_string(), "v": "shoes" }))
219 .unwrap();
220
221 assert_eq!(signal.0.id(), id);
222 assert_eq!(*signal.0.read_untracked(), "shoes");
223 }
224
225 #[test]
226 fn deserializes_a_value_through_its_surrogate() {
227 let id = SignalId::derive(Identity::ROOT, Location::caller());
228
229 let signal: SignalSurrogate<Option<f64>> = serde_json::from_value(json!({
230 "t": "Signal",
231 "id": id.to_string(),
232 "v": { "t": "Option", "v": 5.0 },
233 }))
234 .unwrap();
235
236 assert_eq!(*signal.0.read_untracked(), Some(5.0));
237 }
238
239 #[test]
240 fn rejects_another_tag() {
241 let id = SignalId::derive(Identity::ROOT, Location::caller());
242
243 let error = serde_json::from_value::<SignalSurrogate<String>>(
244 json!({ "t": "Procedure", "id": id.to_string(), "v": "shoes" }),
245 )
246 .unwrap_err();
247
248 assert!(error.to_string().contains("expected Signal"), "{error}");
249 }
250
251 #[test]
252 fn rejects_a_missing_value() {
253 let id = SignalId::derive(Identity::ROOT, Location::caller());
254
255 let error = serde_json::from_value::<SignalSurrogate<String>>(
256 json!({ "t": "Signal", "id": id.to_string() }),
257 )
258 .unwrap_err();
259
260 assert!(error.to_string().contains("missing field `v`"), "{error}");
261 }
262
263 #[test]
264 fn rejects_a_value_of_another_type() {
265 let id = SignalId::derive(Identity::ROOT, Location::caller());
266
267 serde_json::from_value::<SignalSurrogate<f64>>(json!({
268 "t": "Signal",
269 "id": id.to_string(),
270 "v": "shoes",
271 }))
272 .unwrap_err();
273 }
274
275 #[test]
276 fn rejects_a_malformed_id() {
277 serde_json::from_value::<SignalSurrogate<String>>(json!({
278 "t": "Signal",
279 "id": "not hex",
280 "v": "shoes",
281 }))
282 .unwrap_err();
283 }
284
285 #[test]
286 #[should_panic(expected = "cannot be run server-side")]
287 fn toggle_panics_server_side() {
288 surrogate(false).toggle();
289 }
290
291 #[test]
292 #[should_panic(expected = "cannot be run server-side")]
293 fn increment_panics_server_side() {
294 surrogate(0.0).increment();
295 }
296
297 #[test]
298 #[should_panic(expected = "cannot be run server-side")]
299 fn decrement_panics_server_side() {
300 surrogate(0.0).decrement();
301 }
302
303 #[test]
304 #[should_panic(expected = "cannot be run server-side")]
305 fn push_str_panics_server_side() {
306 surrogate(String::new()).push_str(StrSurrogate::ref_cast(""));
307 }
308}