Skip to main content

rs_teststand_sys/
value.rs

1//! A safe, owned representation of a COM `VARIANT`.
2//!
3//! Wrappers work in terms of [`Value`] and never touch a raw `VARIANT`. The
4//! dispatch layer converts `VARIANT` → `Value` on the way out (copying strings
5//! and add-refing interfaces) so that ownership is unambiguous on the Rust side.
6
7use crate::dispatch::Dispatch;
8use crate::error::ComError;
9
10/// An owned value crossing the COM boundary, mapped from a `VARIANT`.
11#[derive(Debug)]
12pub enum Value {
13    /// `VT_EMPTY`, an uninitialized VARIANT.
14    Empty,
15    /// `VT_NULL`, SQL-style null.
16    Null,
17    /// `VT_BOOL`.
18    Bool(bool),
19    /// `VT_I4`, a 32-bit signed integer.
20    I32(i32),
21    /// `VT_I8`, a 64-bit signed integer (handles, large counts).
22    I64(i64),
23    /// A null *object reference* (`VT_DISPATCH` holding no pointer).
24    ///
25    /// Distinct from [`Self::Null`], which is `VT_NULL`. A member that takes an
26    /// object and documents "pass a null reference" wants this: `VT_NULL` is a
27    /// different type and is refused with `DISP_E_TYPEMISMATCH`.
28    NullObject,
29    /// An unsigned 64-bit integer (`VT_UI8`).
30    ///
31    /// Distinct from [`Self::I64`] because the engine matches numeric
32    /// representation strictly: a property stored as unsigned rejects a signed
33    /// variant rather than coercing it.
34    U64(u64),
35    /// `VT_R8`, a 64-bit float.
36    F64(f64),
37    /// `VT_BSTR`, an owned UTF-8 copy of the BSTR.
38    Str(String),
39    /// `VT_DISPATCH`, a nested COM object, ready to wrap.
40    Object(Box<dyn Dispatch>),
41}
42
43impl Value {
44    /// The variant name, used in type-mismatch diagnostics.
45    #[must_use]
46    pub const fn kind(&self) -> &'static str {
47        match self {
48            Self::Empty => "Empty",
49            Self::Null => "Null",
50            Self::Bool(_) => "Bool",
51            Self::I32(_) => "I32",
52            Self::I64(_) => "I64",
53            Self::F64(_) => "F64",
54            Self::Str(_) => "Str",
55            Self::NullObject => "NullObject",
56            Self::U64(_) => "U64",
57            Self::Object(_) => "Object",
58        }
59    }
60
61    /// Reads the value as an `i32` (`VT_I4`).
62    ///
63    /// # Errors
64    /// [`ComError::UnexpectedType`] if the value is not an [`Value::I32`].
65    pub const fn as_i32(&self) -> Result<i32, ComError> {
66        match self {
67            Self::I32(value) => Ok(*value),
68            other => Err(ComError::UnexpectedType {
69                expected: "I32",
70                actual: other.kind(),
71            }),
72        }
73    }
74
75    /// Reads the value as an `i64` (`VT_I8`).
76    ///
77    /// # Errors
78    /// [`ComError::UnexpectedType`] if the value is not an [`Value::I64`].
79    pub const fn as_i64(&self) -> Result<i64, ComError> {
80        match self {
81            Self::I64(value) => Ok(*value),
82            other => Err(ComError::UnexpectedType {
83                expected: "I64",
84                actual: other.kind(),
85            }),
86        }
87    }
88
89    /// Reads the value as a `f64` (`VT_R8`).
90    ///
91    /// # Errors
92    /// [`ComError::UnexpectedType`] if the value is not an [`Value::F64`].
93    pub const fn as_f64(&self) -> Result<f64, ComError> {
94        match self {
95            Self::F64(value) => Ok(*value),
96            other => Err(ComError::UnexpectedType {
97                expected: "F64",
98                actual: other.kind(),
99            }),
100        }
101    }
102
103    /// Reads the value as a `bool` (`VT_BOOL`).
104    ///
105    /// # Errors
106    /// [`ComError::UnexpectedType`] if the value is not a [`Value::Bool`].
107    pub const fn as_bool(&self) -> Result<bool, ComError> {
108        match self {
109            Self::Bool(value) => Ok(*value),
110            other => Err(ComError::UnexpectedType {
111                expected: "Bool",
112                actual: other.kind(),
113            }),
114        }
115    }
116
117    /// Consumes the value as an owned `String` (`VT_BSTR`).
118    ///
119    /// # Errors
120    /// [`ComError::UnexpectedType`] if the value is not a [`Value::Str`].
121    pub fn into_string(self) -> Result<String, ComError> {
122        match self {
123            Self::Str(value) => Ok(value),
124            other => Err(ComError::UnexpectedType {
125                expected: "Str",
126                actual: other.kind(),
127            }),
128        }
129    }
130
131    /// Consumes the value as a nested COM object (`VT_DISPATCH`).
132    ///
133    /// # Errors
134    /// [`ComError::UnexpectedType`] if the value is not a [`Value::Object`].
135    pub fn into_object(self) -> Result<Box<dyn Dispatch>, ComError> {
136        match self {
137            Self::Object(dispatch) => Ok(dispatch),
138            other => Err(ComError::UnexpectedType {
139                expected: "Object",
140                actual: other.kind(),
141            }),
142        }
143    }
144}