win-variant 0.3.0

This is a rust crate that aims to provide a more ergonomic way of working with variants in winapi based projects.
Documentation
use std::convert::TryFrom;

use serde::Serialize;
use widestring::U16CString;
use winapi::{shared::wtypes::VT_ARRAY, um::oaidl::VARIANT};

use crate::SafeArray;

use super::errors::VariantResultError;
use super::{BOOL_FALSE, BOOL_TRUE};

use winapi::shared::wtypes::{
    VT_BOOL, VT_BSTR, VT_EMPTY, VT_I2, VT_I4, VT_I8, VT_INT, VT_R4, VT_R8, VT_SAFEARRAY, VT_UI2,
    VT_UI4, VT_UI8, VT_UINT,
};

/// VariantResult is a tyoe that can be converted from VARIANT to automatically
/// convert the VARIANT value to a rust type that is easier to work with.
/// The VARIANT can be dropped after the conversion as the data is copied
/// into the VariantResult.
#[derive(Debug)]
pub enum VariantResult {
    Empty,
    BOOL(bool),
    I16(i16),
    U16(u16),
    Int(i32),
    Uint(u32),
    I64(i64),
    U64(u64),
    F32(f32),
    F64(f64),
    String(String),
    SafeArray(SafeArray),
}

impl TryFrom<VARIANT> for VariantResult {
    type Error = VariantResultError;

    fn try_from(value: VARIANT) -> Result<Self, Self::Error> {
        unsafe {
            let n2 = value.n1.n2();
            let n3 = n2.n3;

            match n2.vt as u32 {
                VT_EMPTY => Ok(VariantResult::Empty),
                VT_BOOL => match *n3.boolVal() {
                    BOOL_TRUE => Ok(VariantResult::BOOL(true)),
                    BOOL_FALSE => Ok(VariantResult::BOOL(false)),
                    v => Err(VariantResultError::InvalidBool(v)),
                },
                VT_BSTR => {
                    let p = match n3.bstrVal().as_ref() {
                        Some(p) => p,
                        None => return Err(VariantResultError::NullPointer),
                    };
                    let u16_str = U16CString::from_ptr_str(p);
                    Ok(VariantResult::String(u16_str.to_string()?))
                }
                VT_I2 => {
                    let v = n3.iVal();
                    Ok(VariantResult::I16(*v))
                }
                VT_UI2 => {
                    let v = n3.uiVal();
                    Ok(VariantResult::U16(*v))
                }
                VT_I4 => {
                    let v = n3.lVal();
                    Ok(VariantResult::Int(*v))
                }
                VT_UI4 => {
                    let v = n3.ulVal();
                    Ok(VariantResult::Uint(*v))
                }
                VT_INT => {
                    let v = n3.intVal();
                    Ok(VariantResult::Int(*v))
                }
                VT_UINT => {
                    let v = n3.uintVal();
                    Ok(VariantResult::Uint(*v))
                }
                VT_I8 => {
                    let v = n3.llVal();
                    Ok(VariantResult::I64(*v))
                }
                VT_UI8 => {
                    let v = n3.ullVal();
                    Ok(VariantResult::U64(*v))
                }
                VT_R4 => {
                    let v = n3.fltVal();
                    Ok(VariantResult::F32(*v))
                }
                VT_R8 => {
                    let v = n3.dblVal();
                    Ok(VariantResult::F64(*v))
                }
                VT_SAFEARRAY => {
                    let p = n3.parray();
                    Ok(VariantResult::SafeArray(SafeArray::from(*p)))
                }
                vt => {
                    if vt & VT_ARRAY != 0 {
                        let p = n3.parray();
                        Ok(VariantResult::SafeArray(SafeArray::from(*p)))
                    } else {
                        Err(VariantResultError::UnsupportedTypeConversion(n2.vt))
                    }
                }
            }
        }
    }
}

impl Serialize for VariantResult {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            VariantResult::BOOL(v) => serializer.serialize_bool(*v),
            VariantResult::Empty => serializer.serialize_none(),
            VariantResult::F32(v) => serializer.serialize_f32(*v),
            VariantResult::F64(v) => serializer.serialize_f64(*v),
            VariantResult::I16(v) => serializer.serialize_i16(*v),
            VariantResult::I64(v) => serializer.serialize_i64(*v),
            VariantResult::Int(v) => serializer.serialize_i32(*v),
            VariantResult::String(v) => serializer.serialize_str(v),
            VariantResult::U16(v) => serializer.serialize_u16(*v),
            VariantResult::U64(v) => serializer.serialize_u64(*v),
            VariantResult::Uint(v) => serializer.serialize_u32(*v),
            VariantResult::SafeArray(sa) => sa.serialize(serializer),
        }
    }
}

#[test]
fn it_serializes() {
    let _ = env_logger::builder().is_test(true).try_init();

    let src2d = ndarray::arr2(&[
        ["hello", "world"],
        ["of", "multi"],
        ["dimensional", "arrays"],
    ])
    .into_dyn();
    let sa2d = SafeArray::try_from(&src2d).unwrap();

    let src = vec!["hello", "world"];
    let sa = SafeArray::try_from(&src).unwrap();

    let scenarios = vec![
        (VariantResult::BOOL(true), "true"),
        (VariantResult::BOOL(false), "false"),
        (VariantResult::I16(10), "10"),
        (VariantResult::I64(-128), "-128"),
        (VariantResult::Int(-12), "-12"),
        (VariantResult::F32(10.01), "10.01"),
        (VariantResult::F64(-128.95), "-128.95"),
        (VariantResult::String("hello".to_owned()), "\"hello\""),
        (VariantResult::U16(2), "2"),
        (VariantResult::Uint(16), "16"),
        (VariantResult::U64(128), "128"),
        (VariantResult::Empty, "null"),
        (VariantResult::SafeArray(sa), r#"["hello","world"]"#),
        (
            VariantResult::SafeArray(sa2d),
            r#"{"v":1,"dim":[3,2],"data":["hello","world","of","multi","dimensional","arrays"]}"#,
        ),
    ];

    for (test, expected) in scenarios {
        assert_eq!(serde_json::to_string(&test).unwrap(), expected);
    }
}