use rs_teststand_sys::Dispatch;
use crate::Error;
use crate::dispids::ui_message;
use crate::messaging::UIMessageCode;
use crate::property::PropertyObject;
#[derive(Debug)]
pub struct UIMessage {
dispatch: Box<dyn Dispatch>,
}
impl UIMessage {
pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
Self { dispatch }
}
pub fn event(&self) -> Result<i32, Error> {
Ok(self.dispatch.get(ui_message::EVENT)?.as_i32()?)
}
pub fn code(&self) -> Result<Result<UIMessageCode, i32>, Error> {
Ok(UIMessageCode::from_bits(self.event()?))
}
pub fn is_synchronous(&self) -> Result<bool, Error> {
Ok(self.dispatch.get(ui_message::IS_SYNCHRONOUS)?.as_bool()?)
}
pub fn numeric_data(&self) -> Result<f64, Error> {
Ok(self.dispatch.get(ui_message::NUMERIC_DATA)?.as_f64()?)
}
pub fn string_data(&self) -> Result<String, Error> {
Ok(self.dispatch.get(ui_message::STRING_DATA)?.into_string()?)
}
pub fn activex_data(&self) -> Result<Option<PropertyObject>, Error> {
Ok(
Self::optional_dispatch(self.dispatch.get(ui_message::ACTIVE_X_DATA)?)?
.map(PropertyObject::new),
)
}
pub fn execution(&self) -> Result<Option<crate::Execution>, Error> {
Ok(
Self::optional_dispatch(self.dispatch.get(ui_message::EXECUTION)?)?
.map(crate::Execution::new),
)
}
pub fn thread(&self) -> Result<Option<PropertyObject>, Error> {
Ok(
Self::optional_dispatch(self.dispatch.get(ui_message::THREAD)?)?
.map(PropertyObject::new),
)
}
pub fn as_property_object(&self) -> Result<PropertyObject, Error> {
Ok(PropertyObject::new(
self.dispatch
.call(ui_message::AS_PROPERTY_OBJECT, &[])?
.into_object()?,
))
}
pub fn acknowledge(&self) -> Result<(), Error> {
self.dispatch.call(ui_message::ACKNOWLEDGE, &[])?;
Ok(())
}
fn optional_dispatch(
value: rs_teststand_sys::Value,
) -> Result<Option<Box<dyn Dispatch>>, Error> {
match value {
rs_teststand_sys::Value::Object(dispatch) => Ok(Some(dispatch)),
rs_teststand_sys::Value::Null
| rs_teststand_sys::Value::NullObject
| rs_teststand_sys::Value::Empty => Ok(None),
other => Err(Error::UnexpectedType {
expected: "Object or Null",
actual: other.kind(),
}),
}
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use rs_teststand_sys::{ComError, Dispatch, Value};
use super::UIMessage;
use crate::dispids::ui_message;
#[derive(Debug)]
struct Probe {
answer: RefCell<Option<Value>>,
asked: RefCell<Vec<i32>>,
}
impl Probe {
fn new(answer: Value) -> Self {
Self {
answer: RefCell::new(Some(answer)),
asked: RefCell::new(Vec::new()),
}
}
}
impl Dispatch for Probe {
fn get(&self, dispid: i32) -> Result<Value, ComError> {
self.asked.borrow_mut().push(dispid);
Ok(self.answer.borrow_mut().take().unwrap_or(Value::Empty))
}
fn put(&self, _dispid: i32, _value: Value) -> Result<(), ComError> {
Ok(())
}
fn call(&self, _dispid: i32, _args: &[Value]) -> Result<Value, ComError> {
Ok(Value::Empty)
}
}
#[test]
fn activex_data_reads_the_documented_dispatch_id() {
let probe = Box::new(Probe::new(Value::NullObject));
let message = UIMessage::new(probe);
assert!(message.activex_data().is_ok());
}
#[test]
fn an_empty_activex_slot_reads_as_none() {
for empty in [Value::NullObject, Value::Null, Value::Empty] {
let message = UIMessage::new(Box::new(Probe::new(empty)));
let read = message.activex_data();
assert!(matches!(read, Ok(None)), "got {read:?}");
}
}
#[test]
fn a_posted_object_reads_back_as_a_property_tree() {
let carried = Box::new(Probe::new(Value::Empty));
let message = UIMessage::new(Box::new(Probe::new(Value::Object(carried))));
assert!(matches!(message.activex_data(), Ok(Some(_))));
}
#[test]
fn a_non_object_in_the_slot_is_an_error_rather_than_a_silent_none() {
let message = UIMessage::new(Box::new(Probe::new(Value::I32(7))));
assert!(message.activex_data().is_err());
}
#[test]
fn the_three_payload_slots_read_from_three_different_members() {
let ids = [
ui_message::NUMERIC_DATA,
ui_message::STRING_DATA,
ui_message::ACTIVE_X_DATA,
];
let mut unique = ids.to_vec();
unique.sort_unstable();
unique.dedup();
assert_eq!(unique.len(), ids.len(), "payload dispatch ids collide");
}
}