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}