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    /// `VT_ARRAY | VT_I4`, a one-dimensional SAFEARRAY of 32-bit integers.
42    ///
43    /// Copied out rather than borrowed: the SAFEARRAY belongs to the VARIANT
44    /// that carried it, and that is freed as soon as the call returns.
45    I32Array(Vec<i32>),
46}
47
48impl Value {
49    /// The variant name, used in type-mismatch diagnostics.
50    #[must_use]
51    pub const fn kind(&self) -> &'static str {
52        match self {
53            Self::Empty => "Empty",
54            Self::Null => "Null",
55            Self::Bool(_) => "Bool",
56            Self::I32(_) => "I32",
57            Self::I64(_) => "I64",
58            Self::F64(_) => "F64",
59            Self::Str(_) => "Str",
60            Self::NullObject => "NullObject",
61            Self::U64(_) => "U64",
62            Self::Object(_) => "Object",
63            Self::I32Array(_) => "I32Array",
64        }
65    }
66
67    /// Reads the value as an `i32` (`VT_I4`).
68    ///
69    /// # Errors
70    /// [`ComError::UnexpectedType`] if the value is not an [`Value::I32`].
71    pub const fn as_i32(&self) -> Result<i32, ComError> {
72        match self {
73            Self::I32(value) => Ok(*value),
74            other => Err(ComError::UnexpectedType {
75                expected: "I32",
76                actual: other.kind(),
77            }),
78        }
79    }
80
81    /// Reads the value as an `i64` (`VT_I8`).
82    ///
83    /// # Errors
84    /// [`ComError::UnexpectedType`] if the value is not an [`Value::I64`].
85    pub const fn as_i64(&self) -> Result<i64, ComError> {
86        match self {
87            Self::I64(value) => Ok(*value),
88            other => Err(ComError::UnexpectedType {
89                expected: "I64",
90                actual: other.kind(),
91            }),
92        }
93    }
94
95    /// Reads the value as a `f64` (`VT_R8`).
96    ///
97    /// # Errors
98    /// [`ComError::UnexpectedType`] if the value is not an [`Value::F64`].
99    pub const fn as_f64(&self) -> Result<f64, ComError> {
100        match self {
101            Self::F64(value) => Ok(*value),
102            other => Err(ComError::UnexpectedType {
103                expected: "F64",
104                actual: other.kind(),
105            }),
106        }
107    }
108
109    /// Reads the value as a `bool` (`VT_BOOL`).
110    ///
111    /// # Errors
112    /// [`ComError::UnexpectedType`] if the value is not a [`Value::Bool`].
113    pub const fn as_bool(&self) -> Result<bool, ComError> {
114        match self {
115            Self::Bool(value) => Ok(*value),
116            other => Err(ComError::UnexpectedType {
117                expected: "Bool",
118                actual: other.kind(),
119            }),
120        }
121    }
122
123    /// Consumes the value as a list of 32-bit integers (`VT_ARRAY | VT_I4`).
124    ///
125    /// # Errors
126    /// [`ComError::UnexpectedType`] if the value is not an integer array.
127    pub fn into_i32_array(self) -> Result<Vec<i32>, ComError> {
128        match self {
129            Self::I32Array(elements) => Ok(elements),
130            other => Err(ComError::UnexpectedType {
131                expected: "I32Array",
132                actual: other.kind(),
133            }),
134        }
135    }
136
137    /// Consumes the value as an owned `String` (`VT_BSTR`).
138    ///
139    /// # Errors
140    /// [`ComError::UnexpectedType`] if the value is not a [`Value::Str`].
141    pub fn into_string(self) -> Result<String, ComError> {
142        match self {
143            Self::Str(value) => Ok(value),
144            other => Err(ComError::UnexpectedType {
145                expected: "Str",
146                actual: other.kind(),
147            }),
148        }
149    }
150
151    /// Consumes the value as a nested COM object (`VT_DISPATCH`).
152    ///
153    /// # Errors
154    /// [`ComError::UnexpectedType`] if the value is not a [`Value::Object`].
155    pub fn into_object(self) -> Result<Box<dyn Dispatch>, ComError> {
156        match self {
157            Self::Object(dispatch) => Ok(dispatch),
158            other => Err(ComError::UnexpectedType {
159                expected: "Object",
160                actual: other.kind(),
161            }),
162        }
163    }
164}
165
166/// The kind of value an engine member writes back through a by-reference
167/// argument.
168///
169/// Some members report their result in the return value and their explanation
170/// in out-parameters: an expression check answers pass or fail, then says what
171/// was wrong and where. Reading only the return value throws that away.
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173#[non_exhaustive]
174pub enum OutKind {
175    /// A string the engine allocates, `VT_BYREF | VT_BSTR`.
176    Text,
177    /// A 32-bit integer, `VT_BYREF | VT_I4`.
178    Int,
179    /// A boolean, `VT_BYREF | VT_BOOL`.
180    Bool,
181}