1use super::workspace_object::WorkspaceObject;
4use crate::Error;
5use crate::dispids::workspace_file;
6use rs_teststand_sys::{Dispatch, Value};
7
8#[derive(Debug)]
10pub struct WorkspaceFile {
11 dispatch: Box<dyn Dispatch>,
12}
13
14impl WorkspaceFile {
15 pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
17 Self { dispatch }
18 }
19
20 pub fn as_property_object_file(&self) -> Result<crate::PropertyObjectFile, Error> {
28 Ok(crate::PropertyObjectFile::new(
29 self.dispatch
30 .call(workspace_file::AS_PROPERTY_OBJECT_FILE, &[])?
31 .into_object()?,
32 ))
33 }
34
35 pub fn root_workspace_object(&self) -> Result<WorkspaceObject, Error> {
40 let dispatch = self
41 .dispatch
42 .get(workspace_file::ROOT_WORKSPACE_OBJECT)?
43 .into_object()?;
44 Ok(WorkspaceObject::new(dispatch))
45 }
46
47 const NO_PROVIDER: &'static str = "<None>";
52
53 pub fn provider_name(&self) -> Result<Option<String>, Error> {
65 match self.dispatch.get(workspace_file::PROVIDER_NAME)? {
66 Value::Str(name) if name == Self::NO_PROVIDER => Ok(None),
67 Value::Str(name) => Ok(Some(name)),
68 Value::Null | Value::Empty => Ok(None),
69 other => Err(Error::UnexpectedType {
70 expected: "String or Null",
71 actual: other.kind(),
72 }),
73 }
74 }
75
76 pub fn set_provider_name(&self, name: &str) -> Result<(), Error> {
83 self.dispatch
84 .put(workspace_file::PROVIDER_NAME, Value::Str(name.to_owned()))?;
85 Ok(())
86 }
87
88 pub fn is_connected_to_sc_provider(&self) -> Result<bool, Error> {
98 Ok(self
99 .dispatch
100 .get(workspace_file::IS_CONNECTED_TO_SC_PROVIDER)?
101 .as_bool()?)
102 }
103
104 pub fn save_workspace_and_project_files(&self, options: i32) -> Result<bool, Error> {
121 Ok(self
122 .dispatch
123 .call(
124 workspace_file::SAVE_WORKSPACE_AND_PROJECT_FILES,
125 &[Value::I32(options)],
126 )?
127 .as_bool()?)
128 }
129
130 pub fn find_workspace_object(&self, path: &str) -> Result<Option<WorkspaceObject>, Error> {
135 let val = self.dispatch.call(
136 workspace_file::FIND_WORKSPACE_OBJECT,
137 &[Value::Str(path.to_string())],
138 )?;
139 match val {
140 Value::Object(dispatch) => Ok(Some(WorkspaceObject::new(dispatch))),
141 Value::Null | Value::Empty => Ok(None),
142 other => Err(Error::UnexpectedType {
143 expected: "Object or Null",
144 actual: other.kind(),
145 }),
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::WorkspaceFile;
153 use crate::Error;
154 use crate::dispids::workspace_file;
155 use rs_teststand_sys::{ComError, Value};
156 use std::collections::HashMap;
157
158 #[derive(Debug)]
159 struct FakeDispatch {
160 responses: HashMap<i32, Value>,
161 }
162
163 impl rs_teststand_sys::Dispatch for FakeDispatch {
164 fn get(&self, _dispid: i32) -> Result<Value, ComError> {
165 Err(ComError::hresult(0, "fake"))
166 }
167
168 fn put(&self, _dispid: i32, _value: Value) -> Result<(), ComError> {
169 Err(ComError::hresult(0, "fake"))
170 }
171
172 fn call(&self, dispid: i32, _args: &[Value]) -> Result<Value, ComError> {
173 self.responses.get(&dispid).map_or_else(
174 || Err(ComError::hresult(0, "fake: unscripted dispid")),
175 |val| match val {
176 Value::Null => Ok(Value::Null),
177 _ => Err(ComError::hresult(0, "fake")),
178 },
179 )
180 }
181 }
182
183 #[test]
184 fn find_workspace_object_returns_none_when_null() -> Result<(), Error> {
185 let fake = FakeDispatch {
186 responses: HashMap::from([(workspace_file::FIND_WORKSPACE_OBJECT, Value::Null)]),
187 };
188 let ws = WorkspaceFile::new(Box::new(fake));
189 let obj = ws.find_workspace_object("nonexistent")?;
190 assert!(obj.is_none());
191 Ok(())
192 }
193 #[derive(Debug)]
195 struct FakeProps {
196 properties: HashMap<i32, Value>,
197 }
198
199 impl rs_teststand_sys::Dispatch for FakeProps {
200 fn get(&self, dispid: i32) -> Result<Value, ComError> {
201 match self.properties.get(&dispid) {
202 Some(Value::Str(text)) => Ok(Value::Str(text.clone())),
203 Some(Value::Bool(flag)) => Ok(Value::Bool(*flag)),
204 Some(Value::Null) => Ok(Value::Null),
205 Some(Value::I32(number)) => Ok(Value::I32(*number)),
206 _ => Err(ComError::hresult(0, "fake: unscripted dispid")),
207 }
208 }
209
210 fn put(&self, _dispid: i32, _value: Value) -> Result<(), ComError> {
211 Ok(())
212 }
213
214 fn call(&self, _dispid: i32, _args: &[Value]) -> Result<Value, ComError> {
215 Err(ComError::hresult(0, "fake: unscripted call"))
216 }
217 }
218
219 fn with_properties(pairs: Vec<(i32, Value)>) -> WorkspaceFile {
220 WorkspaceFile::new(Box::new(FakeProps {
221 properties: pairs.into_iter().collect(),
222 }))
223 }
224
225 #[test]
226 fn the_no_provider_sentinel_reads_as_none() -> Result<(), Error> {
227 let file = with_properties(vec![(
231 workspace_file::PROVIDER_NAME,
232 Value::Str("<None>".to_owned()),
233 )]);
234 assert_eq!(file.provider_name()?, None);
235 Ok(())
236 }
237
238 #[test]
239 fn a_null_provider_also_reads_as_none() -> Result<(), Error> {
240 let file = with_properties(vec![(workspace_file::PROVIDER_NAME, Value::Null)]);
243 assert_eq!(file.provider_name()?, None);
244 Ok(())
245 }
246
247 #[test]
248 fn an_empty_provider_name_is_not_the_same_as_none() -> Result<(), Error> {
249 let file = with_properties(vec![(
252 workspace_file::PROVIDER_NAME,
253 Value::Str(String::new()),
254 )]);
255 assert_eq!(file.provider_name()?, Some(String::new()));
256 Ok(())
257 }
258
259 #[test]
260 fn a_named_provider_is_returned_verbatim() -> Result<(), Error> {
261 let file = with_properties(vec![(
262 workspace_file::PROVIDER_NAME,
263 Value::Str("Perforce SCM".to_owned()),
264 )]);
265 assert_eq!(file.provider_name()?, Some("Perforce SCM".to_owned()));
266 Ok(())
267 }
268
269 #[test]
270 fn provider_connection_state_is_read_as_a_flag() -> Result<(), Error> {
271 let file = with_properties(vec![(
272 workspace_file::IS_CONNECTED_TO_SC_PROVIDER,
273 Value::Bool(true),
274 )]);
275 assert!(file.is_connected_to_sc_provider()?);
276 Ok(())
277 }
278
279 #[test]
280 fn an_unexpected_provider_type_is_reported_not_swallowed() {
281 let file = with_properties(vec![(workspace_file::PROVIDER_NAME, Value::I32(7))]);
282 assert!(matches!(
283 file.provider_name(),
284 Err(Error::UnexpectedType { .. })
285 ));
286 }
287}