1use 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
21pub struct ExportedType {
23 pub class: &'static ClassType,
24 pub attribute: Option<FerroRemoteMessageGuidAttribute>,
25}
26
27impl ExportedType {
28 pub const fn of<T: FerroRemoteMessage>() -> ExportedType {
30 ExportedType { class: T::CLASS, attribute: Some(T::ATTRIBUTE) }
31 }
32}
33
34pub struct Assembly {
36 pub name: &'static str,
37 pub exported_types: &'static [ExportedType],
38}
39
40pub static ASSEMBLY: Assembly = Assembly {
42 name: "FerroUI.Remote.Protocol",
43 exported_types: &[
44 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 ExportedType::of::<PointerMovedEventMessage>(),
54 ExportedType::of::<PointerPressedEventMessage>(),
55 ExportedType::of::<PointerReleasedEventMessage>(),
56 ExportedType::of::<ScrollEventMessage>(),
57 ExportedType::of::<KeyEventMessage>(),
58 ExportedType::of::<TextInputEventMessage>(),
59 ExportedType::of::<UpdateXamlMessage>(),
61 ExportedType::of::<UpdateXamlResultMessage>(),
62 ExportedType::of::<StartDesignerSessionMessage>(),
63 ExportedType::of::<HtmlTransportStartedMessage>(),
65 ],
66};
67
68#[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 type Message = Arc<dyn BsonObject>;
88
89 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 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 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, 0x10, 0x42, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0, 1, 0, 0, 0, 0x01, 0x58, 0, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F, 0x01, 0x59, 0, 0, 0, 0, 0, 0, 0, 0, 0x40, 0x0A, 0x4D, 0x6F, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0, 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, 0x0A, 0x45, 0x72, 0x72, 0x6F, 0x72, 0, 0x02, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0, 2, 0, 0, 0, 0x68, 0, 0x0A, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0, 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}