Skip to main content

lib_bcsv_jmap/
field.rs

1use std::fmt;
2
3/// Data types supported by BCSV format
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5#[repr(u8)]
6pub enum FieldType {
7    /// Signed 32-bit integer - (4 bytes)
8    Long = 0,
9    /// Inline string - (32 bytes fixed). Deprecated
10    String = 1,
11    /// 32-bit floating point (4 bytes)
12    Float = 2,
13    /// Unsigned 32-bit integer (4 bytes)
14    UnsignedLong = 3,
15    /// Signed 16-bit integer (2 bytes)
16    Short = 4,
17    /// Signed 8-bit integer (1 byte)
18    Char = 5,
19    /// String stored in string table (4 byte offset)
20    StringOffset = 6,
21}
22
23impl FieldType {
24    /// Size in bytes for this field type
25    pub const fn size(&self) -> usize {
26        match self {
27            FieldType::Long => 4,
28            FieldType::String => 32,
29            FieldType::Float => 4,
30            FieldType::UnsignedLong => 4,
31            FieldType::Short => 2,
32            FieldType::Char => 1,
33            FieldType::StringOffset => 4,
34        }
35    }
36
37    /// Default bitmask for this field type
38    pub const fn default_mask(&self) -> u32 {
39        match self {
40            FieldType::Long => 0xFFFFFFFF,
41            FieldType::String => 0x00000000,
42            FieldType::Float => 0xFFFFFFFF,
43            FieldType::UnsignedLong => 0xFFFFFFFF,
44            FieldType::Short => 0x0000FFFF,
45            FieldType::Char => 0x000000FF,
46            FieldType::StringOffset => 0xFFFFFFFF,
47        }
48    }
49
50    /// Sorting order for field layout (used when calculating offsets)
51    pub const fn order(&self) -> u8 {
52        match self {
53            FieldType::String => 0,
54            FieldType::Float => 1,
55            FieldType::Long => 2,
56            FieldType::UnsignedLong => 3,
57            FieldType::Short => 4,
58            FieldType::Char => 5,
59            FieldType::StringOffset => 6,
60        }
61    }
62
63    /// Parse field type from raw byte value
64    pub fn from_raw(value: u8) -> Option<Self> {
65        match value {
66            0 => Some(FieldType::Long),
67            1 => Some(FieldType::String),
68            2 => Some(FieldType::Float),
69            3 => Some(FieldType::UnsignedLong),
70            4 => Some(FieldType::Short),
71            5 => Some(FieldType::Char),
72            6 => Some(FieldType::StringOffset),
73            _ => None,
74        }
75    }
76
77    /// Get the name of this field type for CSV export
78    pub fn csv_name(&self) -> &'static str {
79        match self {
80            FieldType::Long => "Int",
81            FieldType::String => "EmbeddedString",
82            FieldType::Float => "Float",
83            FieldType::UnsignedLong => "UnsignedInt",
84            FieldType::Short => "Short",
85            FieldType::Char => "Char",
86            FieldType::StringOffset => "String",
87        }
88    }
89
90    /// Parse field type from CSV name
91    pub fn from_csv_name(name: &str) -> Option<Self> {
92        match name {
93            "Int" => Some(FieldType::Long),
94            "EmbeddedString" => Some(FieldType::String),
95            "Float" => Some(FieldType::Float),
96            "UnsignedInt" => Some(FieldType::UnsignedLong),
97            "Short" => Some(FieldType::Short),
98            "Char" => Some(FieldType::Char),
99            "String" => Some(FieldType::StringOffset),
100            _ => None,
101        }
102    }
103}
104
105impl fmt::Display for FieldType {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        write!(f, "{}", self.csv_name())
108    }
109}
110
111/// A value that can be stored in a JMap field
112#[derive(Debug, Clone, PartialEq)]
113pub enum FieldValue {
114    /// Integer value (for Long, UnsignedLong, Short, Char)
115    Int(i32),
116    /// Floating point value
117    Float(f32),
118    /// String value (for String or StringOffset)
119    String(String),
120}
121
122impl FieldValue {
123    /// Get the default value for a field type
124    pub fn default_for(field_type: FieldType) -> Self {
125        match field_type {
126            FieldType::Long
127            | FieldType::UnsignedLong
128            | FieldType::Short
129            | FieldType::Char => FieldValue::Int(0),
130            FieldType::Float => FieldValue::Float(0.0),
131            FieldType::String | FieldType::StringOffset => FieldValue::String(String::new()),
132        }
133    }
134
135    /// Check if this value is compatible with a field type
136    pub fn is_compatible_with(&self, field_type: FieldType) -> bool {
137        match (self, field_type) {
138            (FieldValue::Int(_), FieldType::Long)
139            | (FieldValue::Int(_), FieldType::UnsignedLong)
140            | (FieldValue::Int(_), FieldType::Short)
141            | (FieldValue::Int(_), FieldType::Char) => true,
142            (FieldValue::Float(_), FieldType::Float) => true,
143            (FieldValue::String(_), FieldType::String)
144            | (FieldValue::String(_), FieldType::StringOffset) => true,
145            _ => false,
146        }
147    }
148
149    /// Get as integer, if this is an Int value
150    pub fn as_int(&self) -> Option<i32> {
151        match self {
152            FieldValue::Int(v) => Some(*v),
153            _ => None,
154        }
155    }
156
157    /// Get as float, if this is a Float value
158    pub fn as_float(&self) -> Option<f32> {
159        match self {
160            FieldValue::Float(v) => Some(*v),
161            _ => None,
162        }
163    }
164
165    /// Get as string reference, if this is a String value
166    pub fn as_str(&self) -> Option<&str> {
167        match self {
168            FieldValue::String(v) => Some(v),
169            _ => None,
170        }
171    }
172
173    /// Get the type name for error messages
174    pub fn type_name(&self) -> &'static str {
175        match self {
176            FieldValue::Int(_) => "Int",
177            FieldValue::Float(_) => "Float",
178            FieldValue::String(_) => "String",
179        }
180    }
181}
182
183impl fmt::Display for FieldValue {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        match self {
186            FieldValue::Int(v) => write!(f, "{}", v),
187            FieldValue::Float(v) => write!(f, "{}", v),
188            FieldValue::String(v) => write!(f, "{}", v),
189        }
190    }
191}
192
193impl From<i32> for FieldValue {
194    fn from(v: i32) -> Self {
195        FieldValue::Int(v)
196    }
197}
198
199impl From<f32> for FieldValue {
200    fn from(v: f32) -> Self {
201        FieldValue::Float(v)
202    }
203}
204
205impl From<String> for FieldValue {
206    fn from(v: String) -> Self {
207        FieldValue::String(v)
208    }
209}
210
211impl From<&str> for FieldValue {
212    fn from(v: &str) -> Self {
213        FieldValue::String(v.to_string())
214    }
215}
216
217/// Definition of a field (column) in a BCSV
218#[derive(Debug, Clone)]
219pub struct Field {
220    /// Hash of the field name
221    pub hash: u32,
222    /// Bitmask for the field value
223    pub mask: u32,
224    /// Offset within an entry (set during packing/unpacking)
225    pub offset: u16,
226    /// Data type of the field
227    pub field_type: FieldType,
228    /// Bit shift amount
229    pub shift: u8,
230
231    /// Default value for new entries
232    pub default: FieldValue,
233}
234
235impl Field {
236    /// Create a new field with the given parameters
237    pub fn new(hash: u32, field_type: FieldType) -> Self {
238        Self {
239            hash,
240            field_type,
241            mask: field_type.default_mask(),
242            shift: 0,
243            offset: 0,
244            default: FieldValue::default_for(field_type),
245        }
246    }
247
248    /// Create a new field with a custom default value
249    pub fn with_default(hash: u32, field_type: FieldType, default: FieldValue) -> Self {
250        Self {
251            hash,
252            field_type,
253            mask: field_type.default_mask(),
254            shift: 0,
255            offset: 0,
256            default,
257        }
258    }
259
260    /// Size of this field in bytes
261    pub fn size(&self) -> usize {
262        self.field_type.size()
263    }
264}