Skip to main content

csv_nose/
field_type.rs

1use std::fmt;
2
3/// Data type detected for a CSV field.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
5pub enum Type {
6    /// Unsigned integer (non-negative whole number).
7    Unsigned,
8    /// Signed integer (whole number, possibly negative).
9    Signed,
10    /// Floating point number.
11    Float,
12    /// Boolean value (true/false, yes/no, 0/1, etc.).
13    Boolean,
14    /// Date value (without time component).
15    Date,
16    /// `DateTime` value (date with time component).
17    DateTime,
18    /// Null/empty value.
19    NULL,
20    /// Text/string value (fallback type).
21    #[default]
22    Text,
23}
24
25impl fmt::Display for Type {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            Type::Unsigned => write!(f, "Unsigned"),
29            Type::Signed => write!(f, "Signed"),
30            Type::Float => write!(f, "Float"),
31            Type::Boolean => write!(f, "Boolean"),
32            Type::Date => write!(f, "Date"),
33            Type::DateTime => write!(f, "DateTime"),
34            Type::NULL => write!(f, "NULL"),
35            Type::Text => write!(f, "Text"),
36        }
37    }
38}
39
40impl Type {
41    /// Number of variants in the Type enum.
42    pub const COUNT: usize = 8;
43
44    /// Returns the index for this type (0-7), suitable for array indexing.
45    /// This index is based on type priority (see `priority()`), not enum
46    /// declaration order: NULL=0, Boolean=1, Unsigned=2, Signed=3, Float=4,
47    /// Date=5, DateTime=6, Text=7.
48    #[inline]
49    pub const fn as_index(&self) -> usize {
50        self.priority() as usize
51    }
52
53    /// Returns true if this type is numeric.
54    #[inline]
55    pub const fn is_numeric(&self) -> bool {
56        matches!(self, Type::Unsigned | Type::Signed | Type::Float)
57    }
58
59    /// Returns true if this type is temporal.
60    #[inline]
61    pub const fn is_temporal(&self) -> bool {
62        matches!(self, Type::Date | Type::DateTime)
63    }
64
65    /// Returns the type priority for type inference.
66    /// Higher priority types are preferred when merging types.
67    pub const fn priority(&self) -> u8 {
68        match self {
69            Type::NULL => 0,
70            Type::Boolean => 1,
71            Type::Unsigned => 2,
72            Type::Signed => 3,
73            Type::Float => 4,
74            Type::Date => 5,
75            Type::DateTime => 6,
76            Type::Text => 7,
77        }
78    }
79
80    /// Merge two types, returning the most general type that can represent both.
81    pub fn merge(self, other: Type) -> Type {
82        if self == other {
83            return self;
84        }
85
86        // NULL can be promoted to any type
87        if self == Type::NULL {
88            return other;
89        }
90        if other == Type::NULL {
91            return self;
92        }
93
94        // Numeric type promotion
95        match (self, other) {
96            (Type::Unsigned, Type::Signed) | (Type::Signed, Type::Unsigned) => Type::Signed,
97            (Type::Unsigned, Type::Float)
98            | (Type::Float, Type::Unsigned)
99            | (Type::Signed, Type::Float)
100            | (Type::Float, Type::Signed) => Type::Float,
101            (Type::Date, Type::DateTime) | (Type::DateTime, Type::Date) => Type::DateTime,
102            // Everything else becomes Text
103            _ => Type::Text,
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_type_merge() {
114        assert_eq!(Type::Unsigned.merge(Type::Unsigned), Type::Unsigned);
115        assert_eq!(Type::Unsigned.merge(Type::Signed), Type::Signed);
116        assert_eq!(Type::Unsigned.merge(Type::Float), Type::Float);
117        assert_eq!(Type::NULL.merge(Type::Unsigned), Type::Unsigned);
118        assert_eq!(Type::Date.merge(Type::DateTime), Type::DateTime);
119        assert_eq!(Type::Boolean.merge(Type::Text), Type::Text);
120    }
121}