Skip to main content

ferroui_remote_protocol/
assembly.rs

1//! The table of the exported types of an assembly: what the resolver of the
2//! original gets from `Assembly.ExportedTypes` and from the identifier
3//! attribute of each type.
4//!
5//! A crate that declares message classes of its own has such a table and
6//! passes it to [`DefaultMessageTypeResolver::new`](crate::DefaultMessageTypeResolver::new).
7
8use crate::design_messages::{StartDesignerSessionMessage, UpdateXamlMessage, UpdateXamlResultMessage};
9use crate::ferro_remote_message_guid_attribute::{FerroRemoteMessage, FerroRemoteMessageGuidAttribute};
10use crate::input_messages::{
11    KeyEventMessage, PointerMovedEventMessage, PointerPressedEventMessage, PointerReleasedEventMessage,
12    ScrollEventMessage, TextInputEventMessage,
13};
14use crate::metsys_bson::ClassType;
15use crate::transport_messages::HtmlTransportStartedMessage;
16use crate::viewport_messages::{
17    ClientRenderInfoMessage, ClientSupportedPixelFormatsMessage, ClientViewportAllocatedMessage, FrameMessage,
18    FrameReceivedMessage, MeasureViewportMessage, RequestViewportResizeMessage,
19};
20
21/// An exported type of an assembly with the identifier attribute it carries.
22pub struct ExportedType {
23    pub class: &'static ClassType,
24    pub attribute: Option<FerroRemoteMessageGuidAttribute>,
25}
26
27impl ExportedType {
28    /// The entry of a message class.
29    pub const fn of<T: FerroRemoteMessage>() -> ExportedType {
30        ExportedType { class: T::CLASS, attribute: Some(T::ATTRIBUTE) }
31    }
32}
33
34/// An assembly, as far as a resolver reads one.
35pub struct Assembly {
36    pub name: &'static str,
37    pub exported_types: &'static [ExportedType],
38}
39
40/// This library: every message class of the protocol.
41pub static ASSEMBLY: Assembly = Assembly {
42    name: "FerroUI.Remote.Protocol",
43    exported_types: &[
44        // ViewportMessages
45        ExportedType::of::<MeasureViewportMessage>(),
46        ExportedType::of::<ClientViewportAllocatedMessage>(),
47        ExportedType::of::<RequestViewportResizeMessage>(),
48        ExportedType::of::<ClientSupportedPixelFormatsMessage>(),
49        ExportedType::of::<ClientRenderInfoMessage>(),
50        ExportedType::of::<FrameReceivedMessage>(),
51        ExportedType::of::<FrameMessage>(),
52        // InputMessages
53        ExportedType::of::<PointerMovedEventMessage>(),
54        ExportedType::of::<PointerPressedEventMessage>(),
55        ExportedType::of::<PointerReleasedEventMessage>(),
56        ExportedType::of::<ScrollEventMessage>(),
57        ExportedType::of::<KeyEventMessage>(),
58        ExportedType::of::<TextInputEventMessage>(),
59        // DesignMessages
60        ExportedType::of::<UpdateXamlMessage>(),
61        ExportedType::of::<UpdateXamlResultMessage>(),
62        ExportedType::of::<StartDesignerSessionMessage>(),
63        // TransportMessages
64        ExportedType::of::<HtmlTransportStartedMessage>(),
65    ],
66};
67
68// Tests of the port: the table stands for what reflection finds in the
69// original, so it is compared with the list of the upstream message classes.
70#[cfg(test)]
71mod tests {
72    use std::collections::HashSet;
73    use std::sync::Arc;
74
75    use super::*;
76    use crate::default_message_type_resolver::DefaultMessageTypeResolver;
77    use crate::design_messages::ExceptionDetails;
78    use crate::guid::Guid;
79    use crate::i_message_type_resolver::IMessageTypeResolver;
80    use crate::input_messages::{InputEventMessageBase, InputModifiers, MouseButton, PointerEventMessageBase};
81    use crate::key::Key;
82    use crate::metsys_bson::{BinaryReader, BsonClass, BsonObject, Deserializer, Serializer, Type, Value, ValueRef};
83    use crate::physical_key::PhysicalKey;
84    use crate::viewport_messages::PixelFormat;
85
86    /// A message as a connection carries one.
87    type Message = Arc<dyn BsonObject>;
88
89    /// Every class of the upstream library that carries the identifier
90    /// attribute, with the text of the attribute as it is written there.
91    const UPSTREAM_MESSAGES: &[(&str, &str)] = &[
92        ("MeasureViewportMessage", "6E3C5310-E2B1-4C3D-8688-01183AA48C5B"),
93        ("ClientViewportAllocatedMessage", "BD7A8DE6-3DB8-4A13-8583-D6D4AB189A31"),
94        ("RequestViewportResizeMessage", "9B47B3D8-61DF-4C38-ACD4-8C1BB72554AC"),
95        ("ClientSupportedPixelFormatsMessage", "63481025-7016-43FE-BADC-F2FD0F88609E"),
96        ("ClientRenderInfoMessage", "7A3c25d3-3652-438D-8EF1-86E942CC96C0"),
97        ("FrameReceivedMessage", "68014F8A-289D-4851-8D34-5367EDA7F827"),
98        ("FrameMessage", "F58313EE-FE69-4536-819D-F52EDF201A0E"),
99        ("PointerMovedEventMessage", "6228F0B9-99F2-4F62-A621-414DA2881648"),
100        ("PointerPressedEventMessage", "7E9E2818-F93F-411A-800E-6B1AEB11DA46"),
101        ("PointerReleasedEventMessage", "4ADC84EE-E7C8-4BCF-986C-DE3A2F78EDE4"),
102        ("ScrollEventMessage", "79301A05-F02D-4B90-BB39-472563B504AE"),
103        ("KeyEventMessage", "1C3B691E-3D54-4237-BFB0-9FEA83BC1DB8"),
104        ("TextInputEventMessage", "C174102E-7405-4594-916F-B10B8248A17D"),
105        ("UpdateXamlMessage", "9AEC9A2E-6315-4066-B4BA-E9A9EFD0F8CC"),
106        ("UpdateXamlResultMessage", "B7A70093-0C5D-47FD-9261-22086D43A2E2"),
107        ("StartDesignerSessionMessage", "854887CF-2694-4EB6-B499-7461B6FB96C7"),
108        ("HtmlTransportStartedMessage", "53778004-78fa-4381-8ec3-176a6f2328b6"),
109    ];
110
111    fn modifiers() -> InputEventMessageBase {
112        InputEventMessageBase { modifiers: Some(vec![InputModifiers::Control, InputModifiers::MiddleMouseButton]) }
113    }
114
115    fn pointer() -> PointerEventMessageBase {
116        PointerEventMessageBase { base: modifiers(), x: 12.5, y: -3.25 }
117    }
118
119    /// One object of every message class, with no property at its default.
120    fn samples() -> Vec<Message> {
121        vec![
122            Arc::new(MeasureViewportMessage { width: 100.5, height: 200.25 }),
123            Arc::new(ClientViewportAllocatedMessage { width: 640.0, height: 480.0, dpi_x: 96.0, dpi_y: 192.0 }),
124            Arc::new(RequestViewportResizeMessage { width: 1.5, height: 2.5 }),
125            Arc::new(ClientSupportedPixelFormatsMessage {
126                formats: Some(vec![PixelFormat::Bgra8888, PixelFormat::Rgba8888]),
127            }),
128            Arc::new(ClientRenderInfoMessage { dpi_x: 120.0, dpi_y: 144.0 }),
129            Arc::new(FrameReceivedMessage { sequence_id: 1 << 40 }),
130            Arc::new(FrameMessage {
131                sequence_id: 42,
132                format: PixelFormat::Bgra8888,
133                data: Some(vec![1, 2, 3, 4, 0, 255]),
134                width: 3,
135                height: 2,
136                stride: 12,
137                dpi_x: 96.0,
138                dpi_y: 97.0,
139            }),
140            Arc::new(PointerMovedEventMessage { base: pointer() }),
141            Arc::new(PointerPressedEventMessage { base: pointer(), button: MouseButton::Middle }),
142            Arc::new(PointerReleasedEventMessage { base: pointer(), button: MouseButton::Right }),
143            Arc::new(ScrollEventMessage { base: pointer(), delta_x: 0.5, delta_y: -1.0 }),
144            Arc::new(KeyEventMessage {
145                base: modifiers(),
146                is_down: true,
147                key: Key::Escape,
148                physical_key: PhysicalKey::Backquote,
149                key_symbol: Some("`".to_string()),
150            }),
151            Arc::new(TextInputEventMessage { base: modifiers(), text: "text".to_string() }),
152            Arc::new(UpdateXamlMessage {
153                xaml: Some("<Window />".to_string()),
154                assembly_path: Some("/bin/app".to_string()),
155                xaml_file_project_path: Some("/Views/Main.xaml".to_string()),
156            }),
157            Arc::new(UpdateXamlResultMessage {
158                error: Some("error".to_string()),
159                handle: Some("1234".to_string()),
160                exception: Some(ExceptionDetails {
161                    exception_type: Some("Exception".to_string()),
162                    message: Some("Here".to_string()),
163                    line_number: Some(5),
164                    line_position: Some(6),
165                }),
166            }),
167            Arc::new(StartDesignerSessionMessage { session_id: Some("session".to_string()) }),
168            Arc::new(HtmlTransportStartedMessage { uri: Some("http://127.0.0.1:5000/".to_string()) }),
169        ]
170    }
171
172    fn round_trip(message: &Message) -> Value {
173        let bytes = Serializer::serialize_object(ValueRef::Object(&**message)).unwrap();
174        Deserializer::deserialize_type(&mut BinaryReader::new(&bytes), &Type::Class(message.get_type()), None).unwrap()
175    }
176
177    fn property_names(class: &ClassType) -> Vec<&'static str> {
178        class.properties.iter().map(|p| p.name).collect()
179    }
180
181    #[test]
182    fn every_upstream_message_class_is_in_the_table_with_its_identifier() {
183        assert_eq!(UPSTREAM_MESSAGES.len(), 17);
184        assert_eq!(ASSEMBLY.exported_types.len(), UPSTREAM_MESSAGES.len());
185        for (name, guid) in UPSTREAM_MESSAGES {
186            let exported = ASSEMBLY
187                .exported_types
188                .iter()
189                .find(|t| t.class.name == *name)
190                .unwrap_or_else(|| panic!("{} is not in the table", name));
191            assert_eq!(exported.attribute.unwrap().guid(), Guid::parse(guid).unwrap(), "{}", name);
192        }
193        let guids: HashSet<Guid> = ASSEMBLY.exported_types.iter().map(|t| t.attribute.unwrap().guid()).collect();
194        assert_eq!(guids.len(), UPSTREAM_MESSAGES.len());
195    }
196
197    #[test]
198    fn the_resolver_maps_every_identifier_to_its_class_and_back() {
199        let resolver = DefaultMessageTypeResolver::new(&[]);
200        for (name, guid) in UPSTREAM_MESSAGES {
201            let guid = Guid::parse(guid).unwrap();
202            let class = resolver.get_by_guid(guid).unwrap();
203            assert_eq!(class.name, *name);
204            assert_eq!(resolver.get_guid(class).unwrap(), guid);
205        }
206        assert!(matches!(resolver.get_by_guid(Guid::EMPTY), Err(crate::Error::KeyNotFound(_))));
207        assert!(matches!(resolver.get_guid(ExceptionDetails::CLASS), Err(crate::Error::KeyNotFound(_))));
208        assert_eq!(MeasureViewportMessage::GUID, Guid::parse("6E3C5310-E2B1-4C3D-8688-01183AA48C5B").unwrap());
209    }
210
211    #[test]
212    fn every_message_class_round_trips_with_values_that_are_not_the_defaults() {
213        let samples = samples();
214        let sampled: HashSet<&str> = samples.iter().map(|m| m.get_type().name).collect();
215        let listed: HashSet<&str> = UPSTREAM_MESSAGES.iter().map(|(name, _)| *name).collect();
216        assert_eq!(sampled, listed);
217        for message in &samples {
218            // A sample that equals the default of its class would prove nothing.
219            let default = (message.get_type().create_instance)();
220            let helper = crate::metsys_bson::TypeHelper::get_helper_for_type(message.get_type());
221            for property in helper.get_properties() {
222                assert_ne!(
223                    property.getter(&**message).to_value(),
224                    property.getter(&*default).to_value(),
225                    "{}.{} of the sample is the default",
226                    message.get_type().name,
227                    property.name()
228                );
229            }
230            match round_trip(message) {
231                Value::Object(read) => assert!(read.equals(&**message), "{:?} was read as {:?}", message, read),
232                other => panic!("{:?} was read as {:?}", message, other),
233            }
234        }
235    }
236
237    #[test]
238    fn every_message_class_round_trips_with_its_defaults() {
239        for exported in ASSEMBLY.exported_types {
240            let message: Message = Arc::from((exported.class.create_instance)());
241            match round_trip(&message) {
242                Value::Object(read) => assert!(read.equals(&*message), "{:?} was read as {:?}", message, read),
243                other => panic!("{:?} was read as {:?}", message, other),
244            }
245        }
246    }
247
248    #[test]
249    fn properties_are_written_under_the_upstream_names_derived_class_first() {
250        assert_eq!(property_names(MeasureViewportMessage::CLASS), ["Width", "Height"]);
251        assert_eq!(property_names(ClientViewportAllocatedMessage::CLASS), ["Width", "Height", "DpiX", "DpiY"]);
252        assert_eq!(property_names(RequestViewportResizeMessage::CLASS), ["Width", "Height"]);
253        assert_eq!(property_names(ClientSupportedPixelFormatsMessage::CLASS), ["Formats"]);
254        assert_eq!(property_names(ClientRenderInfoMessage::CLASS), ["DpiX", "DpiY"]);
255        assert_eq!(property_names(FrameReceivedMessage::CLASS), ["SequenceId"]);
256        assert_eq!(
257            property_names(FrameMessage::CLASS),
258            ["SequenceId", "Format", "Data", "Width", "Height", "Stride", "DpiX", "DpiY"]
259        );
260        assert_eq!(property_names(PointerMovedEventMessage::CLASS), ["X", "Y", "Modifiers"]);
261        assert_eq!(property_names(PointerPressedEventMessage::CLASS), ["Button", "X", "Y", "Modifiers"]);
262        assert_eq!(property_names(PointerReleasedEventMessage::CLASS), ["Button", "X", "Y", "Modifiers"]);
263        assert_eq!(property_names(ScrollEventMessage::CLASS), ["DeltaX", "DeltaY", "X", "Y", "Modifiers"]);
264        assert_eq!(property_names(KeyEventMessage::CLASS), ["IsDown", "Key", "PhysicalKey", "KeySymbol", "Modifiers"]);
265        assert_eq!(property_names(TextInputEventMessage::CLASS), ["Text", "Modifiers"]);
266        assert_eq!(property_names(UpdateXamlMessage::CLASS), ["Xaml", "AssemblyPath", "XamlFileProjectPath"]);
267        assert_eq!(property_names(UpdateXamlResultMessage::CLASS), ["Error", "Handle", "Exception"]);
268        assert_eq!(property_names(StartDesignerSessionMessage::CLASS), ["SessionId"]);
269        assert_eq!(property_names(HtmlTransportStartedMessage::CLASS), ["Uri"]);
270        assert_eq!(property_names(ExceptionDetails::CLASS), ["ExceptionType", "Message", "LineNumber", "LinePosition"]);
271    }
272
273    #[test]
274    fn the_document_of_a_message_with_base_class_properties() {
275        let message = PointerPressedEventMessage {
276            base: PointerEventMessageBase { base: InputEventMessageBase { modifiers: None }, x: 1.0, y: 2.0 },
277            button: MouseButton::Left,
278        };
279        let bytes = Serializer::serialize(&message).unwrap();
280        assert_eq!(
281            bytes,
282            [
283                0x32, 0, 0, 0, // the document
284                0x10, 0x42, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0, 1, 0, 0, 0, // Button
285                0x01, 0x58, 0, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F, // X
286                0x01, 0x59, 0, 0, 0, 0, 0, 0, 0, 0, 0x40, // Y
287                0x0A, 0x4D, 0x6F, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0, // Modifiers
288                0
289            ]
290        );
291        assert_eq!(Deserializer::deserialize::<PointerPressedEventMessage>(&bytes, None).unwrap(), message);
292    }
293
294    #[test]
295    fn the_document_of_a_message_with_null_properties() {
296        let message = UpdateXamlResultMessage { error: None, handle: Some("h".to_string()), exception: None };
297        let bytes = Serializer::serialize(&message).unwrap();
298        assert_eq!(
299            bytes,
300            [
301                0x25, 0, 0, 0, // the document
302                0x0A, 0x45, 0x72, 0x72, 0x6F, 0x72, 0, // Error
303                0x02, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0, 2, 0, 0, 0, 0x68, 0, // Handle
304                0x0A, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0, // Exception
305                0
306            ]
307        );
308        assert_eq!(Deserializer::deserialize::<UpdateXamlResultMessage>(&bytes, None).unwrap(), message);
309    }
310
311    #[test]
312    fn exception_details_of_an_error() {
313        let error = crate::Error::InvalidOperation("Here".to_string());
314        let details = ExceptionDetails::from_exception("InvalidOperationException", &error, Some((5, 6)));
315        assert_eq!(details.exception_type.as_deref(), Some("InvalidOperationException"));
316        assert_eq!(details.message.as_deref(), Some("Here"));
317        assert_eq!((details.line_number, details.line_position), (Some(5), Some(6)));
318        let details = ExceptionDetails::from_exception("Error", &error, None);
319        assert_eq!((details.line_number, details.line_position), (None, None));
320        assert_eq!(ExceptionDetails::new(), ExceptionDetails::default());
321    }
322}