use crate::design_messages::{StartDesignerSessionMessage, UpdateXamlMessage, UpdateXamlResultMessage};
use crate::ferro_remote_message_guid_attribute::{FerroRemoteMessage, FerroRemoteMessageGuidAttribute};
use crate::input_messages::{
KeyEventMessage, PointerMovedEventMessage, PointerPressedEventMessage, PointerReleasedEventMessage,
ScrollEventMessage, TextInputEventMessage,
};
use crate::metsys_bson::ClassType;
use crate::transport_messages::HtmlTransportStartedMessage;
use crate::viewport_messages::{
ClientRenderInfoMessage, ClientSupportedPixelFormatsMessage, ClientViewportAllocatedMessage, FrameMessage,
FrameReceivedMessage, MeasureViewportMessage, RequestViewportResizeMessage,
};
pub struct ExportedType {
pub class: &'static ClassType,
pub attribute: Option<FerroRemoteMessageGuidAttribute>,
}
impl ExportedType {
pub const fn of<T: FerroRemoteMessage>() -> ExportedType {
ExportedType { class: T::CLASS, attribute: Some(T::ATTRIBUTE) }
}
}
pub struct Assembly {
pub name: &'static str,
pub exported_types: &'static [ExportedType],
}
pub static ASSEMBLY: Assembly = Assembly {
name: "FerroUI.Remote.Protocol",
exported_types: &[
ExportedType::of::<MeasureViewportMessage>(),
ExportedType::of::<ClientViewportAllocatedMessage>(),
ExportedType::of::<RequestViewportResizeMessage>(),
ExportedType::of::<ClientSupportedPixelFormatsMessage>(),
ExportedType::of::<ClientRenderInfoMessage>(),
ExportedType::of::<FrameReceivedMessage>(),
ExportedType::of::<FrameMessage>(),
ExportedType::of::<PointerMovedEventMessage>(),
ExportedType::of::<PointerPressedEventMessage>(),
ExportedType::of::<PointerReleasedEventMessage>(),
ExportedType::of::<ScrollEventMessage>(),
ExportedType::of::<KeyEventMessage>(),
ExportedType::of::<TextInputEventMessage>(),
ExportedType::of::<UpdateXamlMessage>(),
ExportedType::of::<UpdateXamlResultMessage>(),
ExportedType::of::<StartDesignerSessionMessage>(),
ExportedType::of::<HtmlTransportStartedMessage>(),
],
};
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::sync::Arc;
use super::*;
use crate::default_message_type_resolver::DefaultMessageTypeResolver;
use crate::design_messages::ExceptionDetails;
use crate::guid::Guid;
use crate::i_message_type_resolver::IMessageTypeResolver;
use crate::input_messages::{InputEventMessageBase, InputModifiers, MouseButton, PointerEventMessageBase};
use crate::key::Key;
use crate::metsys_bson::{BinaryReader, BsonClass, BsonObject, Deserializer, Serializer, Type, Value, ValueRef};
use crate::physical_key::PhysicalKey;
use crate::viewport_messages::PixelFormat;
type Message = Arc<dyn BsonObject>;
const UPSTREAM_MESSAGES: &[(&str, &str)] = &[
("MeasureViewportMessage", "6E3C5310-E2B1-4C3D-8688-01183AA48C5B"),
("ClientViewportAllocatedMessage", "BD7A8DE6-3DB8-4A13-8583-D6D4AB189A31"),
("RequestViewportResizeMessage", "9B47B3D8-61DF-4C38-ACD4-8C1BB72554AC"),
("ClientSupportedPixelFormatsMessage", "63481025-7016-43FE-BADC-F2FD0F88609E"),
("ClientRenderInfoMessage", "7A3c25d3-3652-438D-8EF1-86E942CC96C0"),
("FrameReceivedMessage", "68014F8A-289D-4851-8D34-5367EDA7F827"),
("FrameMessage", "F58313EE-FE69-4536-819D-F52EDF201A0E"),
("PointerMovedEventMessage", "6228F0B9-99F2-4F62-A621-414DA2881648"),
("PointerPressedEventMessage", "7E9E2818-F93F-411A-800E-6B1AEB11DA46"),
("PointerReleasedEventMessage", "4ADC84EE-E7C8-4BCF-986C-DE3A2F78EDE4"),
("ScrollEventMessage", "79301A05-F02D-4B90-BB39-472563B504AE"),
("KeyEventMessage", "1C3B691E-3D54-4237-BFB0-9FEA83BC1DB8"),
("TextInputEventMessage", "C174102E-7405-4594-916F-B10B8248A17D"),
("UpdateXamlMessage", "9AEC9A2E-6315-4066-B4BA-E9A9EFD0F8CC"),
("UpdateXamlResultMessage", "B7A70093-0C5D-47FD-9261-22086D43A2E2"),
("StartDesignerSessionMessage", "854887CF-2694-4EB6-B499-7461B6FB96C7"),
("HtmlTransportStartedMessage", "53778004-78fa-4381-8ec3-176a6f2328b6"),
];
fn modifiers() -> InputEventMessageBase {
InputEventMessageBase { modifiers: Some(vec![InputModifiers::Control, InputModifiers::MiddleMouseButton]) }
}
fn pointer() -> PointerEventMessageBase {
PointerEventMessageBase { base: modifiers(), x: 12.5, y: -3.25 }
}
fn samples() -> Vec<Message> {
vec![
Arc::new(MeasureViewportMessage { width: 100.5, height: 200.25 }),
Arc::new(ClientViewportAllocatedMessage { width: 640.0, height: 480.0, dpi_x: 96.0, dpi_y: 192.0 }),
Arc::new(RequestViewportResizeMessage { width: 1.5, height: 2.5 }),
Arc::new(ClientSupportedPixelFormatsMessage {
formats: Some(vec![PixelFormat::Bgra8888, PixelFormat::Rgba8888]),
}),
Arc::new(ClientRenderInfoMessage { dpi_x: 120.0, dpi_y: 144.0 }),
Arc::new(FrameReceivedMessage { sequence_id: 1 << 40 }),
Arc::new(FrameMessage {
sequence_id: 42,
format: PixelFormat::Bgra8888,
data: Some(vec![1, 2, 3, 4, 0, 255]),
width: 3,
height: 2,
stride: 12,
dpi_x: 96.0,
dpi_y: 97.0,
}),
Arc::new(PointerMovedEventMessage { base: pointer() }),
Arc::new(PointerPressedEventMessage { base: pointer(), button: MouseButton::Middle }),
Arc::new(PointerReleasedEventMessage { base: pointer(), button: MouseButton::Right }),
Arc::new(ScrollEventMessage { base: pointer(), delta_x: 0.5, delta_y: -1.0 }),
Arc::new(KeyEventMessage {
base: modifiers(),
is_down: true,
key: Key::Escape,
physical_key: PhysicalKey::Backquote,
key_symbol: Some("`".to_string()),
}),
Arc::new(TextInputEventMessage { base: modifiers(), text: "text".to_string() }),
Arc::new(UpdateXamlMessage {
xaml: Some("<Window />".to_string()),
assembly_path: Some("/bin/app".to_string()),
xaml_file_project_path: Some("/Views/Main.xaml".to_string()),
}),
Arc::new(UpdateXamlResultMessage {
error: Some("error".to_string()),
handle: Some("1234".to_string()),
exception: Some(ExceptionDetails {
exception_type: Some("Exception".to_string()),
message: Some("Here".to_string()),
line_number: Some(5),
line_position: Some(6),
}),
}),
Arc::new(StartDesignerSessionMessage { session_id: Some("session".to_string()) }),
Arc::new(HtmlTransportStartedMessage { uri: Some("http://127.0.0.1:5000/".to_string()) }),
]
}
fn round_trip(message: &Message) -> Value {
let bytes = Serializer::serialize_object(ValueRef::Object(&**message)).unwrap();
Deserializer::deserialize_type(&mut BinaryReader::new(&bytes), &Type::Class(message.get_type()), None).unwrap()
}
fn property_names(class: &ClassType) -> Vec<&'static str> {
class.properties.iter().map(|p| p.name).collect()
}
#[test]
fn every_upstream_message_class_is_in_the_table_with_its_identifier() {
assert_eq!(UPSTREAM_MESSAGES.len(), 17);
assert_eq!(ASSEMBLY.exported_types.len(), UPSTREAM_MESSAGES.len());
for (name, guid) in UPSTREAM_MESSAGES {
let exported = ASSEMBLY
.exported_types
.iter()
.find(|t| t.class.name == *name)
.unwrap_or_else(|| panic!("{} is not in the table", name));
assert_eq!(exported.attribute.unwrap().guid(), Guid::parse(guid).unwrap(), "{}", name);
}
let guids: HashSet<Guid> = ASSEMBLY.exported_types.iter().map(|t| t.attribute.unwrap().guid()).collect();
assert_eq!(guids.len(), UPSTREAM_MESSAGES.len());
}
#[test]
fn the_resolver_maps_every_identifier_to_its_class_and_back() {
let resolver = DefaultMessageTypeResolver::new(&[]);
for (name, guid) in UPSTREAM_MESSAGES {
let guid = Guid::parse(guid).unwrap();
let class = resolver.get_by_guid(guid).unwrap();
assert_eq!(class.name, *name);
assert_eq!(resolver.get_guid(class).unwrap(), guid);
}
assert!(matches!(resolver.get_by_guid(Guid::EMPTY), Err(crate::Error::KeyNotFound(_))));
assert!(matches!(resolver.get_guid(ExceptionDetails::CLASS), Err(crate::Error::KeyNotFound(_))));
assert_eq!(MeasureViewportMessage::GUID, Guid::parse("6E3C5310-E2B1-4C3D-8688-01183AA48C5B").unwrap());
}
#[test]
fn every_message_class_round_trips_with_values_that_are_not_the_defaults() {
let samples = samples();
let sampled: HashSet<&str> = samples.iter().map(|m| m.get_type().name).collect();
let listed: HashSet<&str> = UPSTREAM_MESSAGES.iter().map(|(name, _)| *name).collect();
assert_eq!(sampled, listed);
for message in &samples {
let default = (message.get_type().create_instance)();
let helper = crate::metsys_bson::TypeHelper::get_helper_for_type(message.get_type());
for property in helper.get_properties() {
assert_ne!(
property.getter(&**message).to_value(),
property.getter(&*default).to_value(),
"{}.{} of the sample is the default",
message.get_type().name,
property.name()
);
}
match round_trip(message) {
Value::Object(read) => assert!(read.equals(&**message), "{:?} was read as {:?}", message, read),
other => panic!("{:?} was read as {:?}", message, other),
}
}
}
#[test]
fn every_message_class_round_trips_with_its_defaults() {
for exported in ASSEMBLY.exported_types {
let message: Message = Arc::from((exported.class.create_instance)());
match round_trip(&message) {
Value::Object(read) => assert!(read.equals(&*message), "{:?} was read as {:?}", message, read),
other => panic!("{:?} was read as {:?}", message, other),
}
}
}
#[test]
fn properties_are_written_under_the_upstream_names_derived_class_first() {
assert_eq!(property_names(MeasureViewportMessage::CLASS), ["Width", "Height"]);
assert_eq!(property_names(ClientViewportAllocatedMessage::CLASS), ["Width", "Height", "DpiX", "DpiY"]);
assert_eq!(property_names(RequestViewportResizeMessage::CLASS), ["Width", "Height"]);
assert_eq!(property_names(ClientSupportedPixelFormatsMessage::CLASS), ["Formats"]);
assert_eq!(property_names(ClientRenderInfoMessage::CLASS), ["DpiX", "DpiY"]);
assert_eq!(property_names(FrameReceivedMessage::CLASS), ["SequenceId"]);
assert_eq!(
property_names(FrameMessage::CLASS),
["SequenceId", "Format", "Data", "Width", "Height", "Stride", "DpiX", "DpiY"]
);
assert_eq!(property_names(PointerMovedEventMessage::CLASS), ["X", "Y", "Modifiers"]);
assert_eq!(property_names(PointerPressedEventMessage::CLASS), ["Button", "X", "Y", "Modifiers"]);
assert_eq!(property_names(PointerReleasedEventMessage::CLASS), ["Button", "X", "Y", "Modifiers"]);
assert_eq!(property_names(ScrollEventMessage::CLASS), ["DeltaX", "DeltaY", "X", "Y", "Modifiers"]);
assert_eq!(property_names(KeyEventMessage::CLASS), ["IsDown", "Key", "PhysicalKey", "KeySymbol", "Modifiers"]);
assert_eq!(property_names(TextInputEventMessage::CLASS), ["Text", "Modifiers"]);
assert_eq!(property_names(UpdateXamlMessage::CLASS), ["Xaml", "AssemblyPath", "XamlFileProjectPath"]);
assert_eq!(property_names(UpdateXamlResultMessage::CLASS), ["Error", "Handle", "Exception"]);
assert_eq!(property_names(StartDesignerSessionMessage::CLASS), ["SessionId"]);
assert_eq!(property_names(HtmlTransportStartedMessage::CLASS), ["Uri"]);
assert_eq!(property_names(ExceptionDetails::CLASS), ["ExceptionType", "Message", "LineNumber", "LinePosition"]);
}
#[test]
fn the_document_of_a_message_with_base_class_properties() {
let message = PointerPressedEventMessage {
base: PointerEventMessageBase { base: InputEventMessageBase { modifiers: None }, x: 1.0, y: 2.0 },
button: MouseButton::Left,
};
let bytes = Serializer::serialize(&message).unwrap();
assert_eq!(
bytes,
[
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
]
);
assert_eq!(Deserializer::deserialize::<PointerPressedEventMessage>(&bytes, None).unwrap(), message);
}
#[test]
fn the_document_of_a_message_with_null_properties() {
let message = UpdateXamlResultMessage { error: None, handle: Some("h".to_string()), exception: None };
let bytes = Serializer::serialize(&message).unwrap();
assert_eq!(
bytes,
[
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
]
);
assert_eq!(Deserializer::deserialize::<UpdateXamlResultMessage>(&bytes, None).unwrap(), message);
}
#[test]
fn exception_details_of_an_error() {
let error = crate::Error::InvalidOperation("Here".to_string());
let details = ExceptionDetails::from_exception("InvalidOperationException", &error, Some((5, 6)));
assert_eq!(details.exception_type.as_deref(), Some("InvalidOperationException"));
assert_eq!(details.message.as_deref(), Some("Here"));
assert_eq!((details.line_number, details.line_position), (Some(5), Some(6)));
let details = ExceptionDetails::from_exception("Error", &error, None);
assert_eq!((details.line_number, details.line_position), (None, None));
assert_eq!(ExceptionDetails::new(), ExceptionDetails::default());
}
}