Skip to main content

xsd_schema/types/
convert.rs

1//! XPath2 type conversion API
2//!
3//! This module provides type conversion functions following XPath2/XQuery
4//! type promotion and casting rules.
5//!
6//! ## Conversion Types
7//!
8//! - **Casting**: Explicit type conversion via `cast as` (strict)
9//! - **Type Promotion**: Implicit conversion for numeric operations
10//! - **Atomization**: Converting nodes to atomic values
11//!
12//! ## Reference
13//!
14//! Based on XPath2 type conversion rules and the C# XPath2Convert implementation.
15
16use num_bigint::BigInt;
17use rust_decimal::Decimal;
18
19#[cfg(feature = "xsd11")]
20use super::sequence::{ItemType, SequenceType};
21use super::validators::{ValidationError, ValidatorRegistry};
22use super::value::{XmlAtomicValue, XmlValue, XmlValueKind};
23use super::{PrimitiveTypeCode, XmlTypeCode};
24
25/// Error type for conversion operations
26#[derive(Debug, Clone)]
27pub enum ConversionError {
28    /// Type conversion not allowed (XPTY0004)
29    TypeMismatch { from: XmlTypeCode, to: XmlTypeCode },
30    /// Invalid value for target type (FORG0001)
31    InvalidValue {
32        value: String,
33        target_type: &'static str,
34        message: String,
35    },
36    /// Overflow during conversion (FOAR0002)
37    Overflow {
38        value: String,
39        target_type: &'static str,
40    },
41    /// Empty sequence where value expected
42    EmptySequence,
43    /// Validation error during conversion
44    Validation(ValidationError),
45}
46
47impl std::fmt::Display for ConversionError {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Self::TypeMismatch { from, to } => {
51                write!(f, "XPTY0004: Cannot convert {:?} to {:?}", from, to)
52            }
53            Self::InvalidValue {
54                value,
55                target_type,
56                message,
57            } => {
58                write!(
59                    f,
60                    "FORG0001: Invalid {} value '{}': {}",
61                    target_type, value, message
62                )
63            }
64            Self::Overflow { value, target_type } => {
65                write!(
66                    f,
67                    "FOAR0002: Overflow converting '{}' to {}",
68                    value, target_type
69                )
70            }
71            Self::EmptySequence => {
72                write!(f, "XPTY0004: Empty sequence where value expected")
73            }
74            Self::Validation(e) => write!(f, "{}", e),
75        }
76    }
77}
78
79impl std::error::Error for ConversionError {}
80
81impl From<ValidationError> for ConversionError {
82    fn from(e: ValidationError) -> Self {
83        Self::Validation(e)
84    }
85}
86
87/// Result type for conversion operations
88pub type ConversionResult<T> = Result<T, ConversionError>;
89
90/// XPath2-compatible type converter
91///
92/// Handles type conversions following XPath2/XQuery rules.
93pub struct TypeConverter {
94    validators: ValidatorRegistry,
95}
96
97impl TypeConverter {
98    /// Create a new type converter
99    pub fn new() -> Self {
100        Self {
101            validators: ValidatorRegistry::new(),
102        }
103    }
104
105    /// Create with a custom validator registry
106    pub fn with_validators(validators: ValidatorRegistry) -> Self {
107        Self { validators }
108    }
109
110    /// Cast a value to the target type
111    ///
112    /// This is the strict casting operation used by `cast as`.
113    pub fn cast(&self, value: &XmlValue, target: XmlTypeCode) -> ConversionResult<XmlValue> {
114        // Same type - no conversion needed
115        if value.type_code == target {
116            return Ok(value.clone());
117        }
118
119        // Handle untyped atomic - cast via string
120        if value.is_untyped() {
121            let str_val = value.to_string_value();
122            return self
123                .validators
124                .validate(target, &str_val)
125                .map_err(ConversionError::from);
126        }
127
128        // Check if casting is allowed
129        if !can_cast(value.type_code, target) {
130            return Err(ConversionError::TypeMismatch {
131                from: value.type_code,
132                to: target,
133            });
134        }
135
136        // Perform the cast
137        self.perform_cast(value, target)
138    }
139
140    /// Convert value to string
141    pub fn to_string(&self, value: &XmlValue) -> String {
142        value.to_string_value()
143    }
144
145    /// Convert value to boolean (effective boolean value)
146    pub fn to_boolean(&self, value: &XmlValue) -> ConversionResult<bool> {
147        match &value.value {
148            XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) => Ok(*b),
149            XmlValueKind::Atomic(XmlAtomicValue::String(s)) => Ok(!s.is_empty()),
150            XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => Ok(*i != BigInt::from(0)),
151            XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => Ok(!d.is_zero()),
152            XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Ok(*f != 0.0 && !f.is_nan()),
153            XmlValueKind::Atomic(XmlAtomicValue::Double(d)) => Ok(*d != 0.0 && !d.is_nan()),
154            XmlValueKind::UntypedAtomic(s) => Ok(!s.is_empty()),
155            _ => Err(ConversionError::TypeMismatch {
156                from: value.type_code,
157                to: XmlTypeCode::Boolean,
158            }),
159        }
160    }
161
162    /// Convert value to double (numeric promotion)
163    pub fn to_double(&self, value: &XmlValue) -> ConversionResult<f64> {
164        match &value.value {
165            XmlValueKind::Atomic(XmlAtomicValue::Double(d)) => Ok(*d),
166            XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Ok(*f as f64),
167            XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => {
168                d.to_string()
169                    .parse()
170                    .map_err(|_| ConversionError::Overflow {
171                        value: d.to_string(),
172                        target_type: "double",
173                    })
174            }
175            XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => {
176                i.to_string()
177                    .parse()
178                    .map_err(|_| ConversionError::Overflow {
179                        value: i.to_string(),
180                        target_type: "double",
181                    })
182            }
183            XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) => Ok(if *b { 1.0 } else { 0.0 }),
184            XmlValueKind::UntypedAtomic(s) => {
185                s.trim().parse().map_err(|_| ConversionError::InvalidValue {
186                    value: s.clone(),
187                    target_type: "double",
188                    message: "Not a valid number".to_string(),
189                })
190            }
191            _ => Err(ConversionError::TypeMismatch {
192                from: value.type_code,
193                to: XmlTypeCode::Double,
194            }),
195        }
196    }
197
198    /// Convert value to decimal
199    pub fn to_decimal(&self, value: &XmlValue) -> ConversionResult<Decimal> {
200        match &value.value {
201            XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => Ok(*d),
202            XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => {
203                i.to_string()
204                    .parse()
205                    .map_err(|_| ConversionError::Overflow {
206                        value: i.to_string(),
207                        target_type: "decimal",
208                    })
209            }
210            XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => {
211                if f.is_nan() || f.is_infinite() {
212                    return Err(ConversionError::InvalidValue {
213                        value: f.to_string(),
214                        target_type: "decimal",
215                        message: "Cannot convert NaN or Infinity to decimal".to_string(),
216                    });
217                }
218                Decimal::try_from(*f).map_err(|_| ConversionError::Overflow {
219                    value: f.to_string(),
220                    target_type: "decimal",
221                })
222            }
223            XmlValueKind::Atomic(XmlAtomicValue::Double(d)) => {
224                if d.is_nan() || d.is_infinite() {
225                    return Err(ConversionError::InvalidValue {
226                        value: d.to_string(),
227                        target_type: "decimal",
228                        message: "Cannot convert NaN or Infinity to decimal".to_string(),
229                    });
230                }
231                Decimal::try_from(*d).map_err(|_| ConversionError::Overflow {
232                    value: d.to_string(),
233                    target_type: "decimal",
234                })
235            }
236            XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) => {
237                Ok(if *b { Decimal::ONE } else { Decimal::ZERO })
238            }
239            XmlValueKind::UntypedAtomic(s) => {
240                s.trim().parse().map_err(|_| ConversionError::InvalidValue {
241                    value: s.clone(),
242                    target_type: "decimal",
243                    message: "Not a valid decimal".to_string(),
244                })
245            }
246            _ => Err(ConversionError::TypeMismatch {
247                from: value.type_code,
248                to: XmlTypeCode::Decimal,
249            }),
250        }
251    }
252
253    /// Convert value to integer
254    pub fn to_integer(&self, value: &XmlValue) -> ConversionResult<BigInt> {
255        match &value.value {
256            XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => Ok(i.clone()),
257            XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => {
258                // Truncate to integer
259                let truncated = d.trunc();
260                truncated
261                    .to_string()
262                    .parse()
263                    .map_err(|_| ConversionError::Overflow {
264                        value: d.to_string(),
265                        target_type: "integer",
266                    })
267            }
268            XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => {
269                if f.is_nan() || f.is_infinite() {
270                    return Err(ConversionError::InvalidValue {
271                        value: f.to_string(),
272                        target_type: "integer",
273                        message: "Cannot convert NaN or Infinity to integer".to_string(),
274                    });
275                }
276                let truncated = f.trunc();
277                (truncated as i64)
278                    .to_string()
279                    .parse()
280                    .map_err(|_| ConversionError::Overflow {
281                        value: f.to_string(),
282                        target_type: "integer",
283                    })
284            }
285            XmlValueKind::Atomic(XmlAtomicValue::Double(d)) => {
286                if d.is_nan() || d.is_infinite() {
287                    return Err(ConversionError::InvalidValue {
288                        value: d.to_string(),
289                        target_type: "integer",
290                        message: "Cannot convert NaN or Infinity to integer".to_string(),
291                    });
292                }
293                let truncated = d.trunc();
294                (truncated as i64)
295                    .to_string()
296                    .parse()
297                    .map_err(|_| ConversionError::Overflow {
298                        value: d.to_string(),
299                        target_type: "integer",
300                    })
301            }
302            XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) => {
303                Ok(if *b { BigInt::from(1) } else { BigInt::from(0) })
304            }
305            XmlValueKind::UntypedAtomic(s) => {
306                s.trim().parse().map_err(|_| ConversionError::InvalidValue {
307                    value: s.clone(),
308                    target_type: "integer",
309                    message: "Not a valid integer".to_string(),
310                })
311            }
312            _ => Err(ConversionError::TypeMismatch {
313                from: value.type_code,
314                to: XmlTypeCode::Integer,
315            }),
316        }
317    }
318
319    /// Check if value matches the given sequence type
320    #[cfg(feature = "xsd11")]
321    pub fn matches(&self, value: &XmlValue, seq_type: &SequenceType) -> bool {
322        match &seq_type.item_type {
323            ItemType::AnyItem => true,
324            ItemType::AtomicType(XmlTypeCode::AnyAtomicType) => value.is_atomic(),
325            ItemType::AtomicType(code) => {
326                if value.type_code == *code {
327                    return true;
328                }
329                // Check derivation
330                derives_from(value.type_code, *code)
331            }
332            ItemType::AnyNode => false, // XmlValue doesn't hold nodes
333            _ => false,
334        }
335    }
336
337    /// Apply type promotion for numeric operations
338    ///
339    /// Returns the promoted type for two operands in an arithmetic operation.
340    pub fn promote_numeric(&self, left: XmlTypeCode, right: XmlTypeCode) -> Option<XmlTypeCode> {
341        if !left.is_numeric() || !right.is_numeric() {
342            return None;
343        }
344
345        // Promotion rules:
346        // - If either is double, result is double
347        // - If either is float, result is float
348        // - If either is decimal, result is decimal
349        // - Otherwise result is integer
350        if left == XmlTypeCode::Double || right == XmlTypeCode::Double {
351            Some(XmlTypeCode::Double)
352        } else if left == XmlTypeCode::Float || right == XmlTypeCode::Float {
353            Some(XmlTypeCode::Float)
354        } else if left == XmlTypeCode::Decimal || right == XmlTypeCode::Decimal {
355            Some(XmlTypeCode::Decimal)
356        } else {
357            Some(XmlTypeCode::Integer)
358        }
359    }
360
361    /// Promote a value to the target numeric type
362    pub fn promote_to(&self, value: &XmlValue, target: XmlTypeCode) -> ConversionResult<XmlValue> {
363        if value.type_code == target {
364            return Ok(value.clone());
365        }
366
367        match target {
368            XmlTypeCode::Double => {
369                let d = self.to_double(value)?;
370                Ok(XmlValue::double(d))
371            }
372            XmlTypeCode::Float => {
373                let d = self.to_double(value)?;
374                Ok(XmlValue::float(d as f32))
375            }
376            XmlTypeCode::Decimal => {
377                let d = self.to_decimal(value)?;
378                Ok(XmlValue::decimal(d))
379            }
380            XmlTypeCode::Integer => {
381                let i = self.to_integer(value)?;
382                Ok(XmlValue::integer(i))
383            }
384            _ => Err(ConversionError::TypeMismatch {
385                from: value.type_code,
386                to: target,
387            }),
388        }
389    }
390
391    /// Perform the actual cast operation
392    fn perform_cast(&self, value: &XmlValue, target: XmlTypeCode) -> ConversionResult<XmlValue> {
393        let str_val = value.to_string_value();
394
395        match target {
396            // Numeric targets - use conversion methods for better precision
397            XmlTypeCode::Double => {
398                let d = self.to_double(value)?;
399                Ok(XmlValue::double(d))
400            }
401            XmlTypeCode::Float => {
402                let d = self.to_double(value)?;
403                Ok(XmlValue::float(d as f32))
404            }
405            XmlTypeCode::Decimal => {
406                let d = self.to_decimal(value)?;
407                Ok(XmlValue::decimal(d))
408            }
409            XmlTypeCode::Integer => {
410                let i = self.to_integer(value)?;
411                Ok(XmlValue::integer(i))
412            }
413            XmlTypeCode::Boolean => {
414                let b = self.to_boolean(value)?;
415                Ok(XmlValue::boolean(b))
416            }
417            XmlTypeCode::String => Ok(XmlValue::string(str_val)),
418            XmlTypeCode::UntypedAtomic => Ok(XmlValue::untyped(str_val)),
419            // For other types, use the validator
420            _ => self
421                .validators
422                .validate(target, &str_val)
423                .map_err(ConversionError::from),
424        }
425    }
426}
427
428impl Default for TypeConverter {
429    fn default() -> Self {
430        Self::new()
431    }
432}
433
434/// Check if casting from one type to another is allowed
435fn can_cast(from: XmlTypeCode, to: XmlTypeCode) -> bool {
436    // Same type always allowed
437    if from == to {
438        return true;
439    }
440
441    // Get primitive types
442    let from_prim = PrimitiveTypeCode::from_type_code(from);
443    let to_prim = PrimitiveTypeCode::from_type_code(to);
444
445    match (from_prim, to_prim) {
446        // String can cast to anything (via parsing)
447        (Some(PrimitiveTypeCode::String), _) => true,
448
449        // Anything can cast to string
450        (_, Some(PrimitiveTypeCode::String)) => true,
451
452        // Numeric types can cast to each other
453        (Some(p1), Some(p2)) if p1.is_numeric() && p2.is_numeric() => true,
454
455        // Boolean can cast to numeric and vice versa
456        (Some(PrimitiveTypeCode::Boolean), Some(p)) if p.is_numeric() => true,
457        (Some(p), Some(PrimitiveTypeCode::Boolean)) if p.is_numeric() => true,
458
459        // Date/time types have limited casting
460        (Some(PrimitiveTypeCode::DateTime), Some(PrimitiveTypeCode::Date)) => true,
461        (Some(PrimitiveTypeCode::DateTime), Some(PrimitiveTypeCode::Time)) => true,
462        (Some(PrimitiveTypeCode::Date), Some(PrimitiveTypeCode::DateTime)) => true,
463
464        // Duration subtypes
465        (Some(PrimitiveTypeCode::Duration), Some(PrimitiveTypeCode::Duration)) => true,
466
467        // Binary types can cast to each other
468        (Some(PrimitiveTypeCode::HexBinary), Some(PrimitiveTypeCode::Base64Binary)) => true,
469        (Some(PrimitiveTypeCode::Base64Binary), Some(PrimitiveTypeCode::HexBinary)) => true,
470
471        // UntypedAtomic can cast to anything
472        _ if from == XmlTypeCode::UntypedAtomic => true,
473
474        // Anything can cast to untypedAtomic
475        _ if to == XmlTypeCode::UntypedAtomic => true,
476
477        // Derivation within same primitive
478        _ if from_prim == to_prim => true,
479
480        _ => false,
481    }
482}
483
484/// Check if a type derives from another (for type matching)
485#[cfg(feature = "xsd11")]
486fn derives_from(derived: XmlTypeCode, base: XmlTypeCode) -> bool {
487    if derived == base {
488        return true;
489    }
490
491    // Check derivation hierarchy
492    match base {
493        XmlTypeCode::AnySimpleType => true,
494        XmlTypeCode::AnyAtomicType => {
495            derived != XmlTypeCode::AnySimpleType && derived != XmlTypeCode::AnyType
496        }
497
498        // String hierarchy
499        XmlTypeCode::String => matches!(
500            derived,
501            XmlTypeCode::NormalizedString
502                | XmlTypeCode::Token
503                | XmlTypeCode::Language
504                | XmlTypeCode::NmToken
505                | XmlTypeCode::Name
506                | XmlTypeCode::NCName
507                | XmlTypeCode::Id
508                | XmlTypeCode::IdRef
509                | XmlTypeCode::Entity
510        ),
511        XmlTypeCode::NormalizedString => matches!(
512            derived,
513            XmlTypeCode::Token
514                | XmlTypeCode::Language
515                | XmlTypeCode::NmToken
516                | XmlTypeCode::Name
517                | XmlTypeCode::NCName
518                | XmlTypeCode::Id
519                | XmlTypeCode::IdRef
520                | XmlTypeCode::Entity
521        ),
522        XmlTypeCode::Token => matches!(
523            derived,
524            XmlTypeCode::Language
525                | XmlTypeCode::NmToken
526                | XmlTypeCode::Name
527                | XmlTypeCode::NCName
528                | XmlTypeCode::Id
529                | XmlTypeCode::IdRef
530                | XmlTypeCode::Entity
531        ),
532        XmlTypeCode::Name => matches!(
533            derived,
534            XmlTypeCode::NCName | XmlTypeCode::Id | XmlTypeCode::IdRef | XmlTypeCode::Entity
535        ),
536        XmlTypeCode::NCName => matches!(
537            derived,
538            XmlTypeCode::Id | XmlTypeCode::IdRef | XmlTypeCode::Entity
539        ),
540
541        // Numeric hierarchy
542        XmlTypeCode::Decimal => matches!(
543            derived,
544            XmlTypeCode::Integer
545                | XmlTypeCode::NonPositiveInteger
546                | XmlTypeCode::NegativeInteger
547                | XmlTypeCode::Long
548                | XmlTypeCode::Int
549                | XmlTypeCode::Short
550                | XmlTypeCode::Byte
551                | XmlTypeCode::NonNegativeInteger
552                | XmlTypeCode::UnsignedLong
553                | XmlTypeCode::UnsignedInt
554                | XmlTypeCode::UnsignedShort
555                | XmlTypeCode::UnsignedByte
556                | XmlTypeCode::PositiveInteger
557        ),
558        XmlTypeCode::Integer => matches!(
559            derived,
560            XmlTypeCode::NonPositiveInteger
561                | XmlTypeCode::NegativeInteger
562                | XmlTypeCode::Long
563                | XmlTypeCode::Int
564                | XmlTypeCode::Short
565                | XmlTypeCode::Byte
566                | XmlTypeCode::NonNegativeInteger
567                | XmlTypeCode::UnsignedLong
568                | XmlTypeCode::UnsignedInt
569                | XmlTypeCode::UnsignedShort
570                | XmlTypeCode::UnsignedByte
571                | XmlTypeCode::PositiveInteger
572        ),
573        XmlTypeCode::Long => matches!(
574            derived,
575            XmlTypeCode::Int | XmlTypeCode::Short | XmlTypeCode::Byte
576        ),
577        XmlTypeCode::Int => matches!(derived, XmlTypeCode::Short | XmlTypeCode::Byte),
578        XmlTypeCode::Short => matches!(derived, XmlTypeCode::Byte),
579        XmlTypeCode::NonNegativeInteger => matches!(
580            derived,
581            XmlTypeCode::UnsignedLong
582                | XmlTypeCode::UnsignedInt
583                | XmlTypeCode::UnsignedShort
584                | XmlTypeCode::UnsignedByte
585                | XmlTypeCode::PositiveInteger
586        ),
587        XmlTypeCode::UnsignedLong => matches!(
588            derived,
589            XmlTypeCode::UnsignedInt | XmlTypeCode::UnsignedShort | XmlTypeCode::UnsignedByte
590        ),
591        XmlTypeCode::UnsignedInt => {
592            matches!(
593                derived,
594                XmlTypeCode::UnsignedShort | XmlTypeCode::UnsignedByte
595            )
596        }
597        XmlTypeCode::UnsignedShort => matches!(derived, XmlTypeCode::UnsignedByte),
598        XmlTypeCode::NonPositiveInteger => matches!(derived, XmlTypeCode::NegativeInteger),
599
600        // Duration hierarchy
601        XmlTypeCode::Duration => {
602            matches!(
603                derived,
604                XmlTypeCode::YearMonthDuration | XmlTypeCode::DayTimeDuration
605            )
606        }
607
608        // DateTime hierarchy
609        XmlTypeCode::DateTime => matches!(derived, XmlTypeCode::DateTimeStamp),
610
611        _ => false,
612    }
613}
614
615/// Convert a Rust value to XmlValue
616pub trait IntoXmlValue {
617    fn into_xml_value(self) -> XmlValue;
618}
619
620impl IntoXmlValue for bool {
621    fn into_xml_value(self) -> XmlValue {
622        XmlValue::boolean(self)
623    }
624}
625
626impl IntoXmlValue for i32 {
627    fn into_xml_value(self) -> XmlValue {
628        XmlValue::integer(BigInt::from(self))
629    }
630}
631
632impl IntoXmlValue for i64 {
633    fn into_xml_value(self) -> XmlValue {
634        XmlValue::integer(BigInt::from(self))
635    }
636}
637
638impl IntoXmlValue for f64 {
639    fn into_xml_value(self) -> XmlValue {
640        XmlValue::double(self)
641    }
642}
643
644impl IntoXmlValue for f32 {
645    fn into_xml_value(self) -> XmlValue {
646        XmlValue::float(self)
647    }
648}
649
650impl IntoXmlValue for Decimal {
651    fn into_xml_value(self) -> XmlValue {
652        XmlValue::decimal(self)
653    }
654}
655
656impl IntoXmlValue for BigInt {
657    fn into_xml_value(self) -> XmlValue {
658        XmlValue::integer(self)
659    }
660}
661
662impl IntoXmlValue for String {
663    fn into_xml_value(self) -> XmlValue {
664        XmlValue::string(self)
665    }
666}
667
668impl IntoXmlValue for &str {
669    fn into_xml_value(self) -> XmlValue {
670        XmlValue::string(self)
671    }
672}
673
674// ============================================================================
675// Tests
676// ============================================================================
677
678#[cfg(test)]
679mod tests {
680    use super::*;
681
682    #[test]
683    fn test_to_boolean() {
684        let converter = TypeConverter::new();
685
686        assert!(converter.to_boolean(&XmlValue::boolean(true)).unwrap());
687        assert!(!converter.to_boolean(&XmlValue::boolean(false)).unwrap());
688        assert!(converter.to_boolean(&XmlValue::string("hello")).unwrap());
689        assert!(!converter.to_boolean(&XmlValue::string("")).unwrap());
690        assert!(converter
691            .to_boolean(&XmlValue::integer(BigInt::from(1)))
692            .unwrap());
693        assert!(!converter
694            .to_boolean(&XmlValue::integer(BigInt::from(0)))
695            .unwrap());
696        assert!(converter.to_boolean(&XmlValue::double(1.5)).unwrap());
697        assert!(!converter.to_boolean(&XmlValue::double(0.0)).unwrap());
698        assert!(!converter.to_boolean(&XmlValue::double(f64::NAN)).unwrap());
699    }
700
701    #[test]
702    fn test_to_double() {
703        let converter = TypeConverter::new();
704
705        assert_eq!(converter.to_double(&XmlValue::double(2.5)).unwrap(), 2.5);
706        assert_eq!(converter.to_double(&XmlValue::float(2.5)).unwrap(), 2.5);
707        assert_eq!(
708            converter
709                .to_double(&XmlValue::integer(BigInt::from(42)))
710                .unwrap(),
711            42.0
712        );
713        assert_eq!(
714            converter
715                .to_double(&XmlValue::decimal(Decimal::new(123, 1)))
716                .unwrap(),
717            12.3
718        );
719        assert_eq!(converter.to_double(&XmlValue::boolean(true)).unwrap(), 1.0);
720        assert_eq!(converter.to_double(&XmlValue::boolean(false)).unwrap(), 0.0);
721    }
722
723    #[test]
724    fn test_to_integer() {
725        let converter = TypeConverter::new();
726
727        assert_eq!(
728            converter
729                .to_integer(&XmlValue::integer(BigInt::from(42)))
730                .unwrap(),
731            BigInt::from(42)
732        );
733        assert_eq!(
734            converter.to_integer(&XmlValue::double(3.7)).unwrap(),
735            BigInt::from(3) // Truncated
736        );
737        assert_eq!(
738            converter
739                .to_integer(&XmlValue::decimal(Decimal::new(99, 1)))
740                .unwrap(),
741            BigInt::from(9) // Truncated from 9.9
742        );
743    }
744
745    #[test]
746    fn test_cast_string_to_integer() {
747        let converter = TypeConverter::new();
748        let str_val = XmlValue::untyped("42");
749
750        let result = converter.cast(&str_val, XmlTypeCode::Integer).unwrap();
751        assert_eq!(result.type_code, XmlTypeCode::Integer);
752        assert_eq!(result.as_integer(), Some(&BigInt::from(42)));
753    }
754
755    #[test]
756    fn test_cast_numeric_promotion() {
757        let converter = TypeConverter::new();
758
759        // Integer to double
760        let int_val = XmlValue::integer(BigInt::from(42));
761        let result = converter.cast(&int_val, XmlTypeCode::Double).unwrap();
762        assert_eq!(result.as_double(), Some(42.0));
763
764        // Double to string
765        let dbl_val = XmlValue::double(2.5);
766        let result = converter.cast(&dbl_val, XmlTypeCode::String).unwrap();
767        assert!(result.to_string_value().starts_with("2.5"));
768    }
769
770    #[test]
771    fn test_promote_numeric() {
772        let converter = TypeConverter::new();
773
774        // Integer + Integer = Integer
775        assert_eq!(
776            converter.promote_numeric(XmlTypeCode::Integer, XmlTypeCode::Integer),
777            Some(XmlTypeCode::Integer)
778        );
779
780        // Integer + Decimal = Decimal
781        assert_eq!(
782            converter.promote_numeric(XmlTypeCode::Integer, XmlTypeCode::Decimal),
783            Some(XmlTypeCode::Decimal)
784        );
785
786        // Decimal + Float = Float
787        assert_eq!(
788            converter.promote_numeric(XmlTypeCode::Decimal, XmlTypeCode::Float),
789            Some(XmlTypeCode::Float)
790        );
791
792        // Float + Double = Double
793        assert_eq!(
794            converter.promote_numeric(XmlTypeCode::Float, XmlTypeCode::Double),
795            Some(XmlTypeCode::Double)
796        );
797
798        // String + Integer = None (not numeric)
799        assert_eq!(
800            converter.promote_numeric(XmlTypeCode::String, XmlTypeCode::Integer),
801            None
802        );
803    }
804
805    #[cfg(feature = "xsd11")]
806    #[test]
807    fn test_derives_from() {
808        // Integer hierarchy
809        assert!(derives_from(XmlTypeCode::Integer, XmlTypeCode::Decimal));
810        assert!(derives_from(XmlTypeCode::Long, XmlTypeCode::Integer));
811        assert!(derives_from(XmlTypeCode::Int, XmlTypeCode::Long));
812        assert!(derives_from(XmlTypeCode::Short, XmlTypeCode::Int));
813        assert!(derives_from(XmlTypeCode::Byte, XmlTypeCode::Short));
814
815        // String hierarchy
816        assert!(derives_from(
817            XmlTypeCode::NormalizedString,
818            XmlTypeCode::String
819        ));
820        assert!(derives_from(
821            XmlTypeCode::Token,
822            XmlTypeCode::NormalizedString
823        ));
824        assert!(derives_from(XmlTypeCode::NCName, XmlTypeCode::Name));
825
826        // Duration hierarchy
827        assert!(derives_from(
828            XmlTypeCode::YearMonthDuration,
829            XmlTypeCode::Duration
830        ));
831        assert!(derives_from(
832            XmlTypeCode::DayTimeDuration,
833            XmlTypeCode::Duration
834        ));
835
836        // Negative cases
837        assert!(!derives_from(XmlTypeCode::String, XmlTypeCode::Integer));
838        assert!(!derives_from(XmlTypeCode::Decimal, XmlTypeCode::Integer));
839    }
840
841    #[test]
842    fn test_can_cast() {
843        // Same type
844        assert!(can_cast(XmlTypeCode::String, XmlTypeCode::String));
845
846        // String to/from anything
847        assert!(can_cast(XmlTypeCode::String, XmlTypeCode::Integer));
848        assert!(can_cast(XmlTypeCode::Integer, XmlTypeCode::String));
849
850        // Numeric conversions
851        assert!(can_cast(XmlTypeCode::Integer, XmlTypeCode::Double));
852        assert!(can_cast(XmlTypeCode::Decimal, XmlTypeCode::Float));
853
854        // Boolean and numeric
855        assert!(can_cast(XmlTypeCode::Boolean, XmlTypeCode::Integer));
856        assert!(can_cast(XmlTypeCode::Double, XmlTypeCode::Boolean));
857
858        // UntypedAtomic
859        assert!(can_cast(XmlTypeCode::UntypedAtomic, XmlTypeCode::Date));
860        assert!(can_cast(XmlTypeCode::DateTime, XmlTypeCode::UntypedAtomic));
861    }
862
863    #[test]
864    fn test_into_xml_value() {
865        assert_eq!(true.into_xml_value().as_boolean(), Some(true));
866        assert_eq!(42i32.into_xml_value().as_integer(), Some(&BigInt::from(42)));
867        assert_eq!(2.5f64.into_xml_value().as_double(), Some(2.5));
868        assert_eq!("hello".into_xml_value().as_string(), Some("hello"));
869    }
870}