use vb6core::error::{err_number, VBError, VBResult};
use crate::value::VBVariant;
pub fn arg<'a, T>(args: &'a [VBVariant], index: usize) -> VBResult<T>
where
T: TryFrom<&'a VBVariant, Error = VBError>,
{
let value = args
.get(index)
.ok_or_else(|| VBError::new(err_number::WRONG_NUMBER_OF_ARGUMENTS))?;
T::try_from(value)
}
pub fn opt_arg<'a, T>(args: &'a [VBVariant], index: usize) -> VBResult<Option<T>>
where
T: TryFrom<&'a VBVariant, Error = VBError>,
{
match args.get(index) {
None => Ok(None),
Some(value) => T::try_from(value).map(Some),
}
}
pub fn variant_arg(args: &[VBVariant], index: usize) -> VBResult<&VBVariant> {
args.get(index)
.ok_or_else(|| VBError::new(err_number::WRONG_NUMBER_OF_ARGUMENTS))
}
pub fn opt_variant_arg(args: &[VBVariant], index: usize) -> Option<&VBVariant> {
args.get(index)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Nullable<T> {
Value(T),
Null,
}
impl<T> Nullable<T> {
pub fn is_null(&self) -> bool {
matches!(self, Nullable::Null)
}
pub fn as_value(&self) -> Option<&T> {
match self {
Nullable::Value(value) => Some(value),
Nullable::Null => None,
}
}
}
impl<'a, T> TryFrom<&'a VBVariant> for Nullable<T>
where
T: TryFrom<&'a VBVariant, Error = VBError>,
{
type Error = VBError;
fn try_from(value: &'a VBVariant) -> Result<Self, Self::Error> {
if value.is_null() {
return Ok(Nullable::Null);
}
T::try_from(value).map(Nullable::Value)
}
}
impl<T> From<Nullable<T>> for VBVariant
where
T: Into<VBVariant>,
{
fn from(value: Nullable<T>) -> Self {
match value {
Nullable::Value(inner) => inner.into(),
Nullable::Null => VBVariant::Null,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::{
VBBoolean, VBByte, VBCurrency, VBDate, VBDouble, VBInteger, VBLong, VBSingle,
};
use vb6core::error::err_number;
#[test]
fn arg_coerces_through_wrapper_semantics() {
let args = [VBVariant::from_string("2.5")];
assert_eq!(arg::<VBLong>(&args, 0).unwrap().as_i32(), 2);
}
#[test]
fn missing_argument_is_450_before_conversion() {
let args = [VBVariant::from_string("nope")];
assert_eq!(
arg::<VBLong>(&args, 3).unwrap_err().number,
err_number::WRONG_NUMBER_OF_ARGUMENTS
);
}
#[test]
fn null_raises_94_for_typed_parameters() {
let args = [VBVariant::Null];
assert_eq!(
arg::<VBSingle>(&args, 0).unwrap_err().number,
err_number::INVALID_USE_OF_NULL
);
}
#[test]
fn cverr_re_raises_inner_error() {
let args = [VBVariant::from_error(VBError::new(31337))];
assert_eq!(arg::<VBBoolean>(&args, 0).unwrap_err().number, 31337);
}
#[test]
fn opt_arg_distinguishes_absent_from_unconvertible() {
let absent: [VBVariant; 0] = [];
assert_eq!(opt_arg::<VBInteger>(&absent, 0).unwrap(), None);
let bad = [VBVariant::from_string("x")];
assert_eq!(
opt_arg::<VBInteger>(&bad, 0).unwrap_err().number,
err_number::TYPE_MISMATCH
);
let good = [VBVariant::from_long(-7)];
assert_eq!(
opt_arg::<VBInteger>(&good, 0).unwrap().map(|v| v.as_i16()),
Some(-7)
);
}
#[test]
fn variant_args_check_presence_only() {
let args = [VBVariant::Null];
assert!(variant_arg(&args, 0).unwrap().is_null());
assert!(opt_variant_arg(&args, 5).is_none());
assert_eq!(
variant_arg(&args, 5).unwrap_err().number,
err_number::WRONG_NUMBER_OF_ARGUMENTS
);
}
#[test]
fn nullable_propagates_null_without_raising() {
let null = [VBVariant::Null];
let converted: Nullable<VBDate> = arg(&null, 0).unwrap();
assert!(converted.is_null());
assert_eq!(VBVariant::from(converted), VBVariant::Null);
}
#[test]
fn nullable_coerces_non_null_values() {
let args = [VBVariant::from_long(4)];
let converted: Nullable<VBByte> = arg(&args, 0).unwrap();
assert!(!converted.is_null());
assert_eq!(converted.as_value().map(|v| v.as_u8()), Some(4));
assert_eq!(VBVariant::from(converted), VBVariant::from_byte(4));
}
#[test]
fn nullable_still_rejects_type_mismatch() {
let args = [VBVariant::from_string("abc")];
let converted: Result<Nullable<VBCurrency>, _> = arg(&args, 0);
assert_eq!(converted.unwrap_err().number, err_number::TYPE_MISMATCH);
}
#[test]
fn nullable_empty_becomes_default_value() {
let empty = [VBVariant::Empty];
let converted: Nullable<VBDouble> = arg(&empty, 0).unwrap();
assert_eq!(converted.as_value().map(|v| v.as_f64()), Some(0.0));
}
}