Skip to main content

rs_teststand/workspace/
workspace_object.rs

1//! TestStand `WorkspaceObject` (`IWorkspaceObject`) wrapper.
2
3use crate::Error;
4use crate::dispids::workspace_object;
5use rs_teststand_sys::{Dispatch, Value};
6
7/// Safe wrapper for TestStand™ `WorkspaceObject` (`IWorkspaceObject`).
8#[derive(Debug)]
9pub struct WorkspaceObject {
10    dispatch: Box<dyn Dispatch>,
11}
12
13impl WorkspaceObject {
14    /// Creates a new `WorkspaceObject` wrapper around a COM dispatch seam.
15    pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
16        Self { dispatch }
17    }
18
19    /// Reads display name (`WorkspaceObject.DisplayName`).
20    ///
21    /// # Errors
22    /// [`Error`] if the COM call fails or returns an unexpected type.
23    pub fn display_name(&self) -> Result<String, Error> {
24        Ok(self
25            .dispatch
26            .get(workspace_object::DISPLAY_NAME)?
27            .into_string()?)
28    }
29
30    /// Writes display name (`WorkspaceObject.DisplayName`).
31    ///
32    /// # Errors
33    /// [`Error`] if the COM call fails.
34    pub fn set_display_name(&self, value: &str) -> Result<(), Error> {
35        self.dispatch.put(
36            workspace_object::DISPLAY_NAME,
37            Value::Str(value.to_string()),
38        )?;
39        Ok(())
40    }
41
42    /// Reads relative path (`WorkspaceObject.Path`).
43    ///
44    /// # Errors
45    /// [`Error`] if the COM call fails or returns an unexpected type.
46    pub fn path(&self) -> Result<String, Error> {
47        Ok(self.dispatch.get(workspace_object::PATH)?.into_string()?)
48    }
49
50    /// Writes relative path (`WorkspaceObject.Path`).
51    ///
52    /// # Errors
53    /// [`Error`] if the COM call fails.
54    pub fn set_path(&self, value: &str) -> Result<(), Error> {
55        self.dispatch
56            .put(workspace_object::PATH, Value::Str(value.to_string()))?;
57        Ok(())
58    }
59
60    /// Reads file exists status (`WorkspaceObject.FileExists`).
61    ///
62    /// # Errors
63    /// [`Error`] if the COM call fails or returns an unexpected type.
64    pub fn file_exists(&self) -> Result<bool, Error> {
65        Ok(self
66            .dispatch
67            .get(workspace_object::FILE_EXISTS)?
68            .as_bool()?)
69    }
70
71    /// Reads object type discriminant (`WorkspaceObject.ObjectType`).
72    ///
73    /// # Errors
74    /// [`Error`] if the COM call fails or returns an unexpected type.
75    pub fn object_type(&self) -> Result<i32, Error> {
76        Ok(self.dispatch.get(workspace_object::OBJECT_TYPE)?.as_i32()?)
77    }
78
79    /// Reads number of contained child objects (`WorkspaceObject.NumContainedObjects`).
80    ///
81    /// # Errors
82    /// [`Error`] if the COM call fails or returns an unexpected type.
83    pub fn num_contained_objects(&self) -> Result<i32, Error> {
84        Ok(self
85            .dispatch
86            .get(workspace_object::NUM_CONTAINED_OBJECTS)?
87            .as_i32()?)
88    }
89
90    /// Retrieves a contained child object by 0-based index (`WorkspaceObject.GetContainedObject`).
91    ///
92    /// # Errors
93    /// [`Error`] if the COM call fails or returns an unexpected type.
94    pub fn get_contained_object(&self, index: i32) -> Result<Self, Error> {
95        let dispatch = self
96            .dispatch
97            .call(workspace_object::GET_CONTAINED_OBJECT, &[Value::I32(index)])?
98            .into_object()?;
99        Ok(Self::new(dispatch))
100    }
101
102    /// Reads absolute file path (`WorkspaceObject.GetAbsolutePath`).
103    ///
104    /// # Errors
105    /// [`Error`] if the COM call fails or returns an unexpected type.
106    pub fn get_absolute_path(&self) -> Result<String, Error> {
107        Ok(self
108            .dispatch
109            .call(workspace_object::GET_ABSOLUTE_PATH, &[])?
110            .into_string()?)
111    }
112
113    /// Creates a new child file object (`WorkspaceObject.NewFile`).
114    ///
115    /// # Errors
116    /// [`Error`] if the COM call fails or returns an unexpected type.
117    pub fn new_file(&self, path_string: &str) -> Result<Self, Error> {
118        let dispatch = self
119            .dispatch
120            .call(
121                workspace_object::NEW_FILE,
122                &[Value::Str(path_string.to_string())],
123            )?
124            .into_object()?;
125        Ok(Self::new(dispatch))
126    }
127
128    /// Creates a new child folder object (`WorkspaceObject.NewFolder`).
129    ///
130    /// # Errors
131    /// [`Error`] if the COM call fails or returns an unexpected type.
132    pub fn new_folder(&self, folder_name: &str) -> Result<Self, Error> {
133        let dispatch = self
134            .dispatch
135            .call(
136                workspace_object::NEW_FOLDER,
137                &[Value::Str(folder_name.to_string())],
138            )?
139            .into_object()?;
140        Ok(Self::new(dispatch))
141    }
142
143    /// Removes a child object by index (`WorkspaceObject.RemoveObject`).
144    ///
145    /// # Errors
146    /// [`Error`] if the COM call fails or returns an unexpected type.
147    pub fn remove_object(&self, index: i32) -> Result<Self, Error> {
148        let dispatch = self
149            .dispatch
150            .call(workspace_object::REMOVE_OBJECT, &[Value::I32(index)])?
151            .into_object()?;
152        Ok(Self::new(dispatch))
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::WorkspaceObject;
159    use crate::Error;
160    use crate::dispids::workspace_object;
161    use rs_teststand_sys::{ComError, Value};
162    use std::collections::HashMap;
163
164    #[derive(Debug)]
165    struct FakeDispatch {
166        responses: HashMap<i32, Value>,
167    }
168
169    impl rs_teststand_sys::Dispatch for FakeDispatch {
170        fn get(&self, dispid: i32) -> Result<Value, ComError> {
171            self.responses.get(&dispid).map_or_else(
172                || Err(ComError::hresult(0, "fake: unscripted dispid")),
173                |val| match val {
174                    Value::Str(s) => Ok(Value::Str(s.clone())),
175                    Value::Bool(b) => Ok(Value::Bool(*b)),
176                    Value::I32(n) => Ok(Value::I32(*n)),
177                    _ => Err(ComError::hresult(0, "fake")),
178                },
179            )
180        }
181
182        fn put(&self, _dispid: i32, _value: Value) -> Result<(), ComError> {
183            Err(ComError::hresult(0, "fake"))
184        }
185
186        fn call(&self, _dispid: i32, _args: &[Value]) -> Result<Value, ComError> {
187            Err(ComError::hresult(0, "fake"))
188        }
189    }
190
191    #[test]
192    fn display_name_reads_bstr_property() -> Result<(), Error> {
193        let fake = FakeDispatch {
194            responses: HashMap::from([(
195                workspace_object::DISPLAY_NAME,
196                Value::Str("MyFolder".to_string()),
197            )]),
198        };
199        let obj = WorkspaceObject::new(Box::new(fake));
200        assert_eq!(obj.display_name()?, "MyFolder");
201        Ok(())
202    }
203
204    #[test]
205    fn num_contained_objects_reads_i4_property() -> Result<(), Error> {
206        let fake = FakeDispatch {
207            responses: HashMap::from([(workspace_object::NUM_CONTAINED_OBJECTS, Value::I32(5))]),
208        };
209        let obj = WorkspaceObject::new(Box::new(fake));
210        assert_eq!(obj.num_contained_objects()?, 5);
211        Ok(())
212    }
213
214    #[test]
215    fn file_exists_reads_bool_property() -> Result<(), Error> {
216        let fake = FakeDispatch {
217            responses: HashMap::from([(workspace_object::FILE_EXISTS, Value::Bool(true))]),
218        };
219        let obj = WorkspaceObject::new(Box::new(fake));
220        assert!(obj.file_exists()?);
221        Ok(())
222    }
223}