diff options
Diffstat (limited to 'storage/variant/src/lib.rs')
-rw-r--r-- | storage/variant/src/lib.rs | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/storage/variant/src/lib.rs b/storage/variant/src/lib.rs new file mode 100644 index 0000000000..4734705299 --- /dev/null +++ b/storage/variant/src/lib.rs @@ -0,0 +1,230 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; +extern crate nserror; +extern crate nsstring; +extern crate xpcom; + +mod bag; + +use std::{ + borrow::Cow, + convert::{TryFrom, TryInto}, +}; + +use libc::c_double; +use nserror::{nsresult, NS_ERROR_CANNOT_CONVERT_DATA, NS_OK}; +use nsstring::{nsACString, nsAString, nsCString, nsString}; +use xpcom::{getter_addrefs, interfaces::nsIVariant, RefPtr}; + +pub use crate::bag::HashPropertyBag; + +extern "C" { + fn NS_GetDataType(variant: *const nsIVariant) -> u16; + fn NS_NewStorageNullVariant(result: *mut *const nsIVariant); + fn NS_NewStorageBooleanVariant(value: bool, result: *mut *const nsIVariant); + fn NS_NewStorageIntegerVariant(value: i64, result: *mut *const nsIVariant); + fn NS_NewStorageFloatVariant(value: c_double, result: *mut *const nsIVariant); + fn NS_NewStorageTextVariant(value: *const nsAString, result: *mut *const nsIVariant); + fn NS_NewStorageUTF8TextVariant(value: *const nsACString, result: *mut *const nsIVariant); +} + +// These are the relevant parts of the nsXPTTypeTag enum in xptinfo.h, +// which nsIVariant.idl reflects into the nsIDataType struct class and uses +// to constrain the values of nsIVariant::dataType. +#[repr(u16)] +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum DataType { + Int32 = 2, + Int64 = 3, + Double = 9, + Bool = 10, + Void = 13, + CharStr = 15, + WCharStr = 16, + StringSizeIs = 20, + WStringSizeIs = 21, + Utf8String = 24, + CString = 25, + AString = 26, + EmptyArray = 254, + Empty = 255, +} + +impl TryFrom<u16> for DataType { + type Error = nsresult; + + /// Converts a raw type tag for an `nsIVariant` into a `DataType` variant. + /// Returns `NS_ERROR_CANNOT_CONVERT_DATA` if the type isn't one that we + /// support. + fn try_from(raw: u16) -> Result<Self, Self::Error> { + Ok(match raw { + 2 => DataType::Int32, + 3 => DataType::Int64, + 9 => DataType::Double, + 10 => DataType::Bool, + 13 => DataType::Void, + 15 => DataType::CharStr, + 16 => DataType::WCharStr, + 20 => DataType::StringSizeIs, + 21 => DataType::WStringSizeIs, + 24 => DataType::Utf8String, + 25 => DataType::CString, + 26 => DataType::AString, + 254 => DataType::EmptyArray, + 255 => DataType::Empty, + _ => Err(NS_ERROR_CANNOT_CONVERT_DATA)?, + }) + } +} + +/// Extension methods implemented on `nsIVariant` types, to make them easier +/// to work with. +pub trait NsIVariantExt { + /// Returns the raw type tag for this variant. Call + /// `DataType::try_from()` on this tag to turn it into a `DataType`. + fn get_data_type(&self) -> u16; + + /// Tries to clone this variant, failing with `NS_ERROR_CANNOT_CONVERT_DATA` + /// if its type is unsupported. + fn try_clone(&self) -> Result<RefPtr<nsIVariant>, nsresult>; +} + +impl NsIVariantExt for nsIVariant { + fn get_data_type(&self) -> u16 { + unsafe { NS_GetDataType(self) } + } + + fn try_clone(&self) -> Result<RefPtr<nsIVariant>, nsresult> { + Ok(match self.get_data_type().try_into()? { + DataType::Bool => bool::from_variant(self)?.into_variant(), + DataType::Int32 => i32::from_variant(self)?.into_variant(), + DataType::Int64 => i64::from_variant(self)?.into_variant(), + DataType::Double => f64::from_variant(self)?.into_variant(), + DataType::AString | DataType::WCharStr | DataType::WStringSizeIs => { + nsString::from_variant(self)?.into_variant() + } + DataType::CString + | DataType::CharStr + | DataType::StringSizeIs + | DataType::Utf8String => nsCString::from_variant(self)?.into_variant(), + DataType::Void | DataType::EmptyArray | DataType::Empty => ().into_variant(), + }) + } +} + +pub trait VariantType { + fn type_name() -> Cow<'static, str>; + fn into_variant(self) -> RefPtr<nsIVariant>; + fn from_variant(variant: &nsIVariant) -> Result<Self, nsresult> + where + Self: Sized; +} + +/// Implements traits to convert between variants and their types. +macro_rules! variant { + ($typ:ident, $constructor:ident, $getter:ident) => { + impl VariantType for $typ { + fn type_name() -> Cow<'static, str> { + stringify!($typ).into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + // getter_addrefs returns a Result<RefPtr<T>, nsresult>, + // but we know that our $constructor is infallible, so we can + // safely unwrap and return the RefPtr. + getter_addrefs(|p| { + unsafe { $constructor(self.into(), p) }; + NS_OK + }) + .unwrap() + } + fn from_variant(variant: &nsIVariant) -> Result<$typ, nsresult> { + let mut result = $typ::default(); + let rv = unsafe { variant.$getter(&mut result) }; + if rv.succeeded() { + Ok(result) + } else { + Err(rv) + } + } + } + }; + (* $typ:ident, $constructor:ident, $getter:ident) => { + impl VariantType for $typ { + fn type_name() -> Cow<'static, str> { + stringify!($typ).into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + // getter_addrefs returns a Result<RefPtr<T>, nsresult>, + // but we know that our $constructor is infallible, so we can + // safely unwrap and return the RefPtr. + getter_addrefs(|p| { + unsafe { $constructor(&*self, p) }; + NS_OK + }) + .unwrap() + } + fn from_variant(variant: &nsIVariant) -> Result<$typ, nsresult> { + let mut result = $typ::new(); + let rv = unsafe { variant.$getter(&mut *result) }; + if rv.succeeded() { + Ok(result) + } else { + Err(rv) + } + } + } + }; +} + +// The unit type (()) is a reasonable equivalation of the null variant. +// The macro can't produce its implementations of VariantType, however, +// so we implement them concretely. +impl VariantType for () { + fn type_name() -> Cow<'static, str> { + "()".into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + // getter_addrefs returns a Result<RefPtr<T>, nsresult>, + // but we know that NS_NewStorageNullVariant is infallible, so we can + // safely unwrap and return the RefPtr. + getter_addrefs(|p| { + unsafe { NS_NewStorageNullVariant(p) }; + NS_OK + }) + .unwrap() + } + fn from_variant(_variant: &nsIVariant) -> Result<Self, nsresult> { + Ok(()) + } +} + +impl<T> VariantType for Option<T> +where + T: VariantType, +{ + fn type_name() -> Cow<'static, str> { + format!("Option<{}>", T::type_name()).into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + match self { + Some(v) => v.into_variant(), + None => ().into_variant(), + } + } + fn from_variant(variant: &nsIVariant) -> Result<Self, nsresult> { + Ok(match variant.get_data_type().try_into() { + Ok(DataType::Void) | Ok(DataType::EmptyArray) | Ok(DataType::Empty) => None, + _ => Some(VariantType::from_variant(variant)?), + }) + } +} + +variant!(bool, NS_NewStorageBooleanVariant, GetAsBool); +variant!(i32, NS_NewStorageIntegerVariant, GetAsInt32); +variant!(i64, NS_NewStorageIntegerVariant, GetAsInt64); +variant!(f64, NS_NewStorageFloatVariant, GetAsDouble); +variant!(*nsString, NS_NewStorageTextVariant, GetAsAString); +variant!(*nsCString, NS_NewStorageUTF8TextVariant, GetAsAUTF8String); |