outlook_mapi/
prop_value.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4//! Define [`PropValue`] and [`PropValueData`].
5
6use crate::{PropTag, sys};
7use core::{ffi, ptr, slice};
8use windows::Win32::{
9    Foundation::{E_INVALIDARG, E_POINTER, FILETIME},
10    System::Com::CY,
11};
12use windows_core::*;
13
14/// Wrapper for a [`sys::SPropValue`] structure which allows pattern matching on [`PropValueData`].
15pub struct PropValue<'a> {
16    pub tag: PropTag,
17    pub value: PropValueData<'a>,
18}
19
20/// Enum with values from the original [`sys::SPropValue::Value`] union.
21pub enum PropValueData<'a> {
22    /// [`sys::PT_NULL`]
23    Null,
24
25    /// [`sys::PT_I2`] or [`sys::PT_SHORT`]
26    Short(i16),
27
28    /// [`sys::PT_I4`] or [`sys::PT_LONG`]
29    Long(i32),
30
31    /// [`sys::PT_PTR`] or [`sys::PT_FILE_HANDLE`]
32    Pointer(*mut ffi::c_void),
33
34    /// [`sys::PT_R4`] or [`sys::PT_FLOAT`]
35    Float(f32),
36
37    /// [`sys::PT_R8`] or [`sys::PT_DOUBLE`]
38    Double(f64),
39
40    /// [`sys::PT_BOOLEAN`]
41    Boolean(u16),
42
43    /// [`sys::PT_CURRENCY`]
44    Currency(i64),
45
46    /// [`sys::PT_APPTIME`]
47    AppTime(f64),
48
49    /// [`sys::PT_SYSTIME`]
50    FileTime(FILETIME),
51
52    /// [`sys::PT_STRING8`]
53    AnsiString(PCSTR),
54
55    /// [`sys::PT_BINARY`]
56    Binary(&'a [u8]),
57
58    /// [`sys::PT_UNICODE`]
59    Unicode(Vec<u16>),
60
61    /// [`sys::PT_CLSID`]
62    Guid(GUID),
63
64    /// [`sys::PT_I8`] or [`sys::PT_LONGLONG`]
65    LargeInteger(i64),
66
67    /// [`sys::PT_MV_SHORT`]
68    ShortArray(&'a [i16]),
69
70    /// [`sys::PT_MV_LONG`]
71    LongArray(&'a [i32]),
72
73    /// [`sys::PT_MV_FLOAT`]
74    FloatArray(&'a [f32]),
75
76    /// [`sys::PT_MV_DOUBLE`]
77    DoubleArray(Vec<f64>),
78
79    /// [`sys::PT_MV_CURRENCY`]
80    CurrencyArray(Vec<CY>),
81
82    /// [`sys::PT_MV_APPTIME`]
83    AppTimeArray(Vec<f64>),
84
85    /// [`sys::PT_MV_SYSTIME`]
86    FileTimeArray(Vec<FILETIME>),
87
88    /// [`sys::PT_MV_BINARY`]
89    BinaryArray(Vec<sys::SBinary>),
90
91    /// [`sys::PT_MV_STRING8`]
92    AnsiStringArray(Vec<PCSTR>),
93
94    /// [`sys::PT_MV_UNICODE`]
95    UnicodeArray(Vec<PCWSTR>),
96
97    /// [`sys::PT_MV_CLSID`]
98    GuidArray(Vec<GUID>),
99
100    /// [`sys::PT_MV_LONGLONG`]
101    LargeIntegerArray(Vec<i64>),
102
103    /// [`sys::PT_ERROR`]
104    Error(HRESULT),
105
106    /// [`sys::PT_OBJECT`]
107    Object(i32),
108}
109
110impl<'a> From<&'a sys::SPropValue> for PropValue<'a> {
111    /// Convert a [`sys::SPropValue`] reference into a friendlier [`PropValue`] type, which often
112    /// supports safe access to the [`sys::SPropValue::Value`] union.
113    fn from(value: &sys::SPropValue) -> Self {
114        let tag = PropTag(value.ulPropTag);
115        let prop_type = tag.prop_type().remove_flags(sys::MV_INSTANCE).into();
116        let data = unsafe {
117            match prop_type {
118                sys::PT_NULL => PropValueData::Null,
119                sys::PT_SHORT => PropValueData::Short(value.Value.i),
120                sys::PT_LONG => PropValueData::Long(value.Value.l),
121                sys::PT_PTR => PropValueData::Pointer(value.Value.lpv),
122                sys::PT_FLOAT => PropValueData::Float(value.Value.flt),
123                sys::PT_DOUBLE => PropValueData::Double(value.Value.dbl),
124                sys::PT_BOOLEAN => PropValueData::Boolean(value.Value.b),
125                sys::PT_CURRENCY => PropValueData::Currency(value.Value.cur.int64),
126                sys::PT_APPTIME => PropValueData::AppTime(value.Value.at),
127                sys::PT_SYSTIME => PropValueData::FileTime(value.Value.ft),
128                sys::PT_STRING8 => {
129                    if value.Value.lpszA.is_null() {
130                        PropValueData::Error(E_POINTER)
131                    } else {
132                        PropValueData::AnsiString(PCSTR::from_raw(value.Value.lpszA.as_ptr()))
133                    }
134                }
135                sys::PT_BINARY => {
136                    if value.Value.bin.lpb.is_null() {
137                        PropValueData::Error(E_POINTER)
138                    } else {
139                        PropValueData::Binary(slice::from_raw_parts(
140                            value.Value.bin.lpb,
141                            value.Value.bin.cb as usize,
142                        ))
143                    }
144                }
145                sys::PT_UNICODE => {
146                    if value.Value.lpszW.is_null() {
147                        PropValueData::Error(E_POINTER)
148                    } else {
149                        let mut aligned = Vec::new();
150                        let mut raw = value.Value.lpszW.as_ptr();
151                        loop {
152                            let next = ptr::read_unaligned(raw);
153                            raw = raw.offset(1);
154                            aligned.push(next);
155                            if next == 0 {
156                                break;
157                            }
158                        }
159                        PropValueData::Unicode(aligned)
160                    }
161                }
162                sys::PT_CLSID => {
163                    if value.Value.lpguid.is_null() {
164                        PropValueData::Error(E_POINTER)
165                    } else {
166                        PropValueData::Guid(ptr::read_unaligned(value.Value.lpguid))
167                    }
168                }
169                sys::PT_LONGLONG => PropValueData::LargeInteger(value.Value.li),
170                sys::PT_MV_SHORT => {
171                    if value.Value.MVi.lpi.is_null() {
172                        PropValueData::Error(E_POINTER)
173                    } else {
174                        PropValueData::ShortArray(slice::from_raw_parts(
175                            value.Value.MVi.lpi,
176                            value.Value.MVi.cValues as usize,
177                        ))
178                    }
179                }
180                sys::PT_MV_LONG => {
181                    if value.Value.MVl.lpl.is_null() {
182                        PropValueData::Error(E_POINTER)
183                    } else {
184                        PropValueData::LongArray(slice::from_raw_parts(
185                            value.Value.MVl.lpl,
186                            value.Value.MVl.cValues as usize,
187                        ))
188                    }
189                }
190                sys::PT_MV_FLOAT => {
191                    if value.Value.MVflt.lpflt.is_null() {
192                        PropValueData::Error(E_POINTER)
193                    } else {
194                        PropValueData::FloatArray(slice::from_raw_parts(
195                            value.Value.MVflt.lpflt,
196                            value.Value.MVflt.cValues as usize,
197                        ))
198                    }
199                }
200                sys::PT_MV_DOUBLE => {
201                    if value.Value.MVdbl.lpdbl.is_null() {
202                        PropValueData::Error(E_POINTER)
203                    } else {
204                        let count = value.Value.MVdbl.cValues as usize;
205                        let first = value.Value.MVdbl.lpdbl;
206                        let mut values = Vec::with_capacity(count);
207                        for idx in 0..count {
208                            values.push(ptr::read_unaligned(first.add(idx)))
209                        }
210                        PropValueData::DoubleArray(values)
211                    }
212                }
213                sys::PT_MV_CURRENCY => {
214                    if value.Value.MVcur.lpcur.is_null() {
215                        PropValueData::Error(E_POINTER)
216                    } else {
217                        let count = value.Value.MVcur.cValues as usize;
218                        let first = value.Value.MVcur.lpcur;
219                        let mut values = Vec::with_capacity(count);
220                        for idx in 0..count {
221                            values.push(ptr::read_unaligned(first.add(idx)))
222                        }
223                        PropValueData::CurrencyArray(values)
224                    }
225                }
226                sys::PT_MV_APPTIME => {
227                    if value.Value.MVat.lpat.is_null() {
228                        PropValueData::Error(E_POINTER)
229                    } else {
230                        let count = value.Value.MVat.cValues as usize;
231                        let first = value.Value.MVat.lpat;
232                        let mut values = Vec::with_capacity(count);
233                        for idx in 0..count {
234                            values.push(ptr::read_unaligned(first.add(idx)))
235                        }
236                        PropValueData::AppTimeArray(values)
237                    }
238                }
239                sys::PT_MV_SYSTIME => {
240                    if value.Value.MVft.lpft.is_null() {
241                        PropValueData::Error(E_POINTER)
242                    } else {
243                        let count = value.Value.MVft.cValues as usize;
244                        let first = value.Value.MVft.lpft;
245                        let mut values = Vec::with_capacity(count);
246                        for idx in 0..count {
247                            values.push(ptr::read_unaligned(first.add(idx)))
248                        }
249                        PropValueData::FileTimeArray(values)
250                    }
251                }
252                sys::PT_MV_BINARY => {
253                    if value.Value.MVbin.lpbin.is_null() {
254                        PropValueData::Error(E_POINTER)
255                    } else {
256                        let count = value.Value.MVbin.cValues as usize;
257                        let first = value.Value.MVbin.lpbin;
258                        let mut values = Vec::with_capacity(count);
259                        for idx in 0..count {
260                            values.push(ptr::read_unaligned(first.add(idx)))
261                        }
262                        PropValueData::BinaryArray(values)
263                    }
264                }
265                sys::PT_MV_STRING8 => {
266                    if value.Value.MVszA.lppszA.is_null() {
267                        PropValueData::Error(E_POINTER)
268                    } else {
269                        let count = value.Value.MVszA.cValues as usize;
270                        let first = value.Value.MVszA.lppszA;
271                        let mut values = Vec::with_capacity(count);
272                        for idx in 0..count {
273                            values.push(PCSTR(ptr::read_unaligned(first.add(idx)).0))
274                        }
275                        PropValueData::AnsiStringArray(values)
276                    }
277                }
278                sys::PT_MV_UNICODE => {
279                    if value.Value.MVszW.lppszW.is_null() {
280                        PropValueData::Error(E_POINTER)
281                    } else {
282                        let count = value.Value.MVszW.cValues as usize;
283                        let first = value.Value.MVszW.lppszW;
284                        let mut values = Vec::with_capacity(count);
285                        for idx in 0..count {
286                            values.push(PCWSTR(ptr::read_unaligned(first.add(idx)).0))
287                        }
288                        PropValueData::UnicodeArray(values)
289                    }
290                }
291                sys::PT_MV_CLSID => {
292                    if value.Value.MVguid.lpguid.is_null() {
293                        PropValueData::Error(E_POINTER)
294                    } else {
295                        let count = value.Value.MVguid.cValues as usize;
296                        let first = value.Value.MVguid.lpguid;
297                        let mut values = Vec::with_capacity(count);
298                        for idx in 0..count {
299                            values.push(ptr::read_unaligned(first.add(idx)))
300                        }
301                        PropValueData::GuidArray(values)
302                    }
303                }
304                sys::PT_MV_LONGLONG => {
305                    if value.Value.MVli.lpli.is_null() {
306                        PropValueData::Error(E_POINTER)
307                    } else {
308                        let count = value.Value.MVli.cValues as usize;
309                        let first = value.Value.MVli.lpli;
310                        let mut values = Vec::with_capacity(count);
311                        for idx in 0..count {
312                            values.push(ptr::read_unaligned(first.add(idx)))
313                        }
314                        PropValueData::LargeIntegerArray(values)
315                    }
316                }
317                sys::PT_ERROR => PropValueData::Error(HRESULT(value.Value.err)),
318                sys::PT_OBJECT => PropValueData::Object(value.Value.x),
319                _ => PropValueData::Error(E_INVALIDARG),
320            }
321        };
322        PropValue { tag, value: data }
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    use crate::{PropTag, PropType, sys};
331    use core::{iter, mem, ptr};
332    use windows_core::{s, w};
333
334    #[test]
335    fn test_null() {
336        let value = sys::SPropValue {
337            ulPropTag: sys::PR_NULL,
338            ..Default::default()
339        };
340        let value = PropValue::from(&value);
341        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_NULL);
342    }
343
344    #[test]
345    fn test_short() {
346        let mut value = sys::SPropValue {
347            ulPropTag: u32::from(
348                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_I2 as u16)),
349            ),
350            ..Default::default()
351        };
352        value.Value.i = 1;
353        let value = PropValue::from(&value);
354        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_I2);
355        assert!(matches!(value.value, PropValueData::Short(1)));
356    }
357
358    #[test]
359    fn test_long() {
360        let mut value = sys::SPropValue {
361            ulPropTag: u32::from(
362                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_I4 as u16)),
363            ),
364            ..Default::default()
365        };
366        value.Value.l = 2;
367        let value = PropValue::from(&value);
368        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_I4);
369        assert!(matches!(value.value, PropValueData::Long(2)));
370    }
371
372    #[test]
373    fn test_pointer() {
374        let mut value = sys::SPropValue {
375            ulPropTag: u32::from(
376                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_PTR as u16)),
377            ),
378            ..Default::default()
379        };
380        value.Value.lpv = ptr::null_mut();
381        let value = PropValue::from(&value);
382        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_PTR);
383        assert!(matches!(
384            value.value,
385            PropValueData::Pointer(ptr) if ptr.is_null()
386        ));
387    }
388
389    #[test]
390    fn test_float() {
391        let mut value = sys::SPropValue {
392            ulPropTag: u32::from(
393                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_R4 as u16)),
394            ),
395            ..Default::default()
396        };
397        value.Value.flt = 3.0;
398        let value = PropValue::from(&value);
399        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_R4);
400        assert!(matches!(value.value, PropValueData::Float(3.0)));
401    }
402
403    #[test]
404    fn test_double() {
405        let mut value = sys::SPropValue {
406            ulPropTag: u32::from(
407                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_R8 as u16)),
408            ),
409            ..Default::default()
410        };
411        value.Value.dbl = 4.0;
412        let value = PropValue::from(&value);
413        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_R8);
414        assert!(matches!(value.value, PropValueData::Double(4.0)));
415    }
416
417    #[test]
418    fn test_boolean() {
419        let mut value = sys::SPropValue {
420            ulPropTag: u32::from(
421                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_BOOLEAN as u16)),
422            ),
423            ..Default::default()
424        };
425        value.Value.b = 5;
426        let value = PropValue::from(&value);
427        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_BOOLEAN);
428        assert!(matches!(value.value, PropValueData::Boolean(5)));
429    }
430
431    #[test]
432    fn test_currency() {
433        let mut value = sys::SPropValue {
434            ulPropTag: u32::from(
435                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_CURRENCY as u16)),
436            ),
437            ..Default::default()
438        };
439        value.Value.cur.int64 = 6;
440        let value = PropValue::from(&value);
441        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_CURRENCY);
442        assert!(matches!(value.value, PropValueData::Currency(6)));
443    }
444
445    #[test]
446    fn test_app_time() {
447        let mut value = sys::SPropValue {
448            ulPropTag: u32::from(
449                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_APPTIME as u16)),
450            ),
451            ..Default::default()
452        };
453        value.Value.at = 7.0;
454        let value = PropValue::from(&value);
455        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_APPTIME);
456        assert!(matches!(value.value, PropValueData::AppTime(7.0)));
457    }
458
459    #[test]
460    fn test_file_time() {
461        let mut value = sys::SPropValue {
462            ulPropTag: u32::from(
463                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_SYSTIME as u16)),
464            ),
465            ..Default::default()
466        };
467        value.Value.ft.dwLowDateTime = 8;
468        let value = PropValue::from(&value);
469        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_SYSTIME);
470        assert!(matches!(
471            value.value,
472            PropValueData::FileTime(FILETIME {
473                dwHighDateTime: 0,
474                dwLowDateTime: 8
475            })
476        ));
477    }
478
479    #[test]
480    fn test_ansi_string() {
481        let expected = s!("nine");
482        let mut value = sys::SPropValue {
483            ulPropTag: u32::from(
484                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_STRING8 as u16)),
485            ),
486            ..Default::default()
487        };
488        value.Value.lpszA.0 = expected.0 as *mut _;
489        let value = PropValue::from(&value);
490        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_STRING8);
491        assert!(matches!(
492            value.value,
493            PropValueData::AnsiString(actual) if actual.0 == expected.0
494        ));
495    }
496
497    #[test]
498    fn test_binary() {
499        let expected = 10;
500        let mut value = sys::SPropValue {
501            ulPropTag: u32::from(
502                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_BINARY as u16)),
503            ),
504            ..Default::default()
505        };
506        value.Value.bin.cb = mem::size_of_val(&expected) as u32;
507        value.Value.bin.lpb = &expected as *const i32 as *mut i32 as *mut _;
508        let value = PropValue::from(&value);
509        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_BINARY);
510        assert!(matches!(
511            value.value,
512            PropValueData::Binary(actual)
513                if actual.len() == mem::size_of_val(&expected)
514                    && actual.as_ptr() as *const i32 == &expected as *const i32
515        ));
516    }
517
518    #[test]
519    fn test_unicode() {
520        let expected = w!("eleven");
521        let mut value = sys::SPropValue {
522            ulPropTag: u32::from(
523                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_UNICODE as u16)),
524            ),
525            ..Default::default()
526        };
527        value.Value.lpszW.0 = expected.0 as *mut _;
528        let value = PropValue::from(&value);
529        let PropValueData::Unicode(actual) = value.value else {
530            panic!("wrong type");
531        };
532        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_UNICODE);
533        let expected = unsafe { expected.as_wide() };
534        let expected: Vec<_> = expected.iter().copied().chain(iter::once(0)).collect();
535        assert_eq!(actual, expected);
536    }
537
538    #[test]
539    fn test_guid() {
540        let expected = GUID {
541            data1: 12,
542            ..Default::default()
543        };
544        let mut value = sys::SPropValue {
545            ulPropTag: u32::from(
546                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_CLSID as u16)),
547            ),
548            ..Default::default()
549        };
550        value.Value.lpguid = &expected as *const _ as *mut _;
551        let value = PropValue::from(&value);
552        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_CLSID);
553        assert!(matches!(
554            value.value,
555            PropValueData::Guid(GUID { data1: 12, .. })
556        ));
557    }
558
559    #[test]
560    fn test_large_integer() {
561        let mut value = sys::SPropValue {
562            ulPropTag: u32::from(
563                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_I8 as u16)),
564            ),
565            ..Default::default()
566        };
567        value.Value.li = 13;
568        let value = PropValue::from(&value);
569        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_I8);
570        assert!(matches!(value.value, PropValueData::LargeInteger(13)));
571    }
572
573    #[test]
574    fn test_short_array() {
575        let expected = [14_i16, 15];
576        let mut value = sys::SPropValue {
577            ulPropTag: u32::from(
578                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_SHORT as u16)),
579            ),
580            ..Default::default()
581        };
582        value.Value.MVi.cValues = expected.len() as u32;
583        value.Value.MVi.lpi = expected.as_ptr() as *mut _;
584        let value = PropValue::from(&value);
585        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_SHORT);
586        assert!(matches!(value.value, PropValueData::ShortArray([14, 15])));
587    }
588
589    #[test]
590    fn test_long_array() {
591        let expected = [15_i32, 16];
592        let mut value = sys::SPropValue {
593            ulPropTag: u32::from(
594                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_LONG as u16)),
595            ),
596            ..Default::default()
597        };
598        value.Value.MVl.cValues = expected.len() as u32;
599        value.Value.MVl.lpl = expected.as_ptr() as *mut _;
600        let value = PropValue::from(&value);
601        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_LONG);
602        assert!(matches!(value.value, PropValueData::LongArray([15, 16])));
603    }
604
605    #[test]
606    fn test_float_array() {
607        let expected = [16.0_f32, 17.0];
608        let mut value = sys::SPropValue {
609            ulPropTag: u32::from(
610                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_FLOAT as u16)),
611            ),
612            ..Default::default()
613        };
614        value.Value.MVflt.cValues = expected.len() as u32;
615        value.Value.MVflt.lpflt = expected.as_ptr() as *mut _;
616        let value = PropValue::from(&value);
617        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_FLOAT);
618        assert!(matches!(
619            value.value,
620            PropValueData::FloatArray([16.0, 17.0])
621        ));
622    }
623
624    #[test]
625    fn test_double_array() {
626        let expected = [17.0_f64, 18.0];
627        let mut value = sys::SPropValue {
628            ulPropTag: u32::from(
629                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_DOUBLE as u16)),
630            ),
631            ..Default::default()
632        };
633        value.Value.MVdbl.cValues = expected.len() as u32;
634        value.Value.MVdbl.lpdbl = expected.as_ptr() as *mut _;
635        let value = PropValue::from(&value);
636        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_DOUBLE);
637        let PropValueData::DoubleArray(values) = value.value else {
638            panic!("wrong type")
639        };
640        assert!(matches!(values.as_slice(), [17.0, 18.0]));
641    }
642
643    #[test]
644    fn test_currency_array() {
645        let expected = [CY { int64: 18 }, CY { int64: 19 }];
646        let mut value = sys::SPropValue {
647            ulPropTag: u32::from(
648                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_CURRENCY as u16)),
649            ),
650            ..Default::default()
651        };
652        value.Value.MVcur.cValues = expected.len() as u32;
653        value.Value.MVcur.lpcur = expected.as_ptr() as *mut _;
654        let value = PropValue::from(&value);
655        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_CURRENCY);
656        let PropValueData::CurrencyArray(values) = value.value else {
657            panic!("wrong type")
658        };
659        unsafe {
660            assert!(matches!(
661                values.as_slice(),
662                [CY { int64: 18 }, CY { int64: 19 }]
663            ));
664        }
665    }
666
667    #[test]
668    fn test_app_time_array() {
669        let expected = [19.0_f64, 20.0];
670        let mut value = sys::SPropValue {
671            ulPropTag: u32::from(
672                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_APPTIME as u16)),
673            ),
674            ..Default::default()
675        };
676        value.Value.MVat.cValues = expected.len() as u32;
677        value.Value.MVat.lpat = expected.as_ptr() as *mut _;
678        let value = PropValue::from(&value);
679        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_APPTIME);
680        let PropValueData::AppTimeArray(values) = value.value else {
681            panic!("wrong type")
682        };
683        assert!(matches!(values.as_slice(), [19.0, 20.0]));
684    }
685
686    #[test]
687    fn test_file_time_array() {
688        let expected = [
689            FILETIME {
690                dwHighDateTime: 20,
691                dwLowDateTime: 21,
692            },
693            FILETIME {
694                dwHighDateTime: 22,
695                dwLowDateTime: 23,
696            },
697        ];
698        let mut value = sys::SPropValue {
699            ulPropTag: u32::from(
700                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_SYSTIME as u16)),
701            ),
702            ..Default::default()
703        };
704        value.Value.MVft.cValues = expected.len() as u32;
705        value.Value.MVft.lpft = expected.as_ptr() as *mut _;
706        let value = PropValue::from(&value);
707        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_SYSTIME);
708        let PropValueData::FileTimeArray(values) = value.value else {
709            panic!("wrong type")
710        };
711        assert!(matches!(
712            values.as_slice(),
713            [
714                FILETIME {
715                    dwHighDateTime: 20,
716                    dwLowDateTime: 21,
717                },
718                FILETIME {
719                    dwHighDateTime: 22,
720                    dwLowDateTime: 23,
721                }
722            ]
723        ));
724    }
725
726    #[test]
727    fn test_binary_array() {
728        let expected1 = [24_u8, 25_u8];
729        let expected1 = sys::SBinary {
730            cb: expected1.len() as u32,
731            lpb: expected1.as_ptr() as *mut _,
732        };
733        let expected2 = [26_u8, 27_u8];
734        let expected2 = sys::SBinary {
735            cb: expected2.len() as u32,
736            lpb: expected2.as_ptr() as *mut _,
737        };
738        let expected = [expected1, expected2];
739        let mut value = sys::SPropValue {
740            ulPropTag: u32::from(
741                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_BINARY as u16)),
742            ),
743            ..Default::default()
744        };
745        value.Value.MVbin.cValues = expected.len() as u32;
746        value.Value.MVbin.lpbin = expected.as_ptr() as *mut _;
747        let value = PropValue::from(&value);
748        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_BINARY);
749        let PropValueData::BinaryArray(values) = value.value else {
750            panic!("wrong type")
751        };
752        assert!(matches!(
753            values.as_slice(),
754            [actual1, actual2]
755                if actual1.cb == expected[0].cb && actual1.lpb == expected[0].lpb
756                    && actual2.cb == expected[1].cb && actual2.lpb == expected[1].lpb
757        ));
758    }
759
760    #[test]
761    fn test_ansi_string_array() {
762        let expected = [s!("twenty-eight"), s!("twenty-nine")];
763        let mut value = sys::SPropValue {
764            ulPropTag: u32::from(
765                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_STRING8 as u16)),
766            ),
767            ..Default::default()
768        };
769        value.Value.MVszA.cValues = expected.len() as u32;
770        value.Value.MVszA.lppszA = expected.as_ptr() as *mut _;
771        let value = PropValue::from(&value);
772        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_STRING8);
773        let PropValueData::AnsiStringArray(values) = value.value else {
774            panic!("wrong type")
775        };
776        assert!(matches!(
777            values.as_slice(),
778            [actual1, actual2]
779                if actual1.0 == expected[0].0 && actual2.0 == expected[1].0
780        ));
781    }
782
783    #[test]
784    fn test_unicode_string_array() {
785        let expected = [w!("thirty"), w!("thirty-one")];
786        let mut value = sys::SPropValue {
787            ulPropTag: u32::from(
788                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_UNICODE as u16)),
789            ),
790            ..Default::default()
791        };
792        value.Value.MVszW.cValues = expected.len() as u32;
793        value.Value.MVszW.lppszW = expected.as_ptr() as *mut _;
794        let value = PropValue::from(&value);
795        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_UNICODE);
796        let PropValueData::UnicodeArray(values) = value.value else {
797            panic!("wrong type")
798        };
799        assert!(matches!(
800            values.as_slice(),
801            [actual1, actual2]
802                if actual1.0 == expected[0].0 && actual2.0 == expected[1].0
803        ));
804    }
805
806    #[test]
807    fn test_guid_array() {
808        let expected = [
809            GUID {
810                data1: 32,
811                ..Default::default()
812            },
813            GUID {
814                data2: 33,
815                ..Default::default()
816            },
817            GUID {
818                data3: 34,
819                ..Default::default()
820            },
821            GUID {
822                data4: [35, 0, 0, 0, 0, 0, 0, 0],
823                ..Default::default()
824            },
825        ];
826        let mut value = sys::SPropValue {
827            ulPropTag: u32::from(
828                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_CLSID as u16)),
829            ),
830            ..Default::default()
831        };
832        value.Value.MVguid.cValues = expected.len() as u32;
833        value.Value.MVguid.lpguid = expected.as_ptr() as *mut _;
834        let value = PropValue::from(&value);
835        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_CLSID);
836        let PropValueData::GuidArray(values) = value.value else {
837            panic!("wrong type")
838        };
839        assert!(matches!(
840            values.as_slice(),
841            [
842                GUID { data1: 32, .. },
843                GUID { data2: 33, .. },
844                GUID { data3: 34, .. },
845                GUID {
846                    data4: [35, ..],
847                    ..
848                }
849            ]
850        ));
851    }
852
853    #[test]
854    fn test_large_integer_array() {
855        let expected = [36_i64, 37];
856        let mut value = sys::SPropValue {
857            ulPropTag: u32::from(
858                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_MV_LONGLONG as u16)),
859            ),
860            ..Default::default()
861        };
862        value.Value.MVli.cValues = expected.len() as u32;
863        value.Value.MVli.lpli = expected.as_ptr() as *mut _;
864        let value = PropValue::from(&value);
865        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_MV_LONGLONG);
866        let PropValueData::LargeIntegerArray(values) = value.value else {
867            panic!("wrong type")
868        };
869        assert!(matches!(values.as_slice(), [36, 37]));
870    }
871
872    #[test]
873    fn test_error() {
874        let mut value = sys::SPropValue {
875            ulPropTag: u32::from(
876                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_ERROR as u16)),
877            ),
878            ..Default::default()
879        };
880        value.Value.err = 38;
881        let value = PropValue::from(&value);
882        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_ERROR);
883        assert!(matches!(value.value, PropValueData::Error(HRESULT(38))));
884    }
885
886    #[test]
887    fn test_object() {
888        let mut value = sys::SPropValue {
889            ulPropTag: u32::from(
890                PropTag(sys::PR_NULL).change_prop_type(PropType::new(sys::PT_OBJECT as u16)),
891            ),
892            ..Default::default()
893        };
894        value.Value.x = 39;
895        let value = PropValue::from(&value);
896        assert_eq!(u32::from(value.tag.prop_type()), sys::PT_OBJECT);
897        assert!(matches!(value.value, PropValueData::Object(39)));
898    }
899}