Skip to main content

oxigdal_shapefile/dbf/
record.rs

1//! DBF record and field type definitions
2//!
3//! This module handles DBF (dBase) field types and record structures for
4//! attribute data in Shapefiles.
5
6use crate::error::{Result, ShapefileError};
7use std::io::{Read, Write};
8
9/// DBF field types
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FieldType {
12    /// Character string (ASCII)
13    Character,
14    /// Numeric (integer or float)
15    Number,
16    /// Logical (boolean)
17    Logical,
18    /// Date (YYYYMMDD)
19    Date,
20    /// Float (like Number but explicitly floating point)
21    Float,
22    /// Memo (reference to memo file - not commonly used in Shapefiles)
23    Memo,
24}
25
26impl FieldType {
27    /// Converts a field type code to a `FieldType`
28    pub fn from_code(code: u8) -> Result<Self> {
29        match code {
30            b'C' => Ok(Self::Character),
31            b'N' => Ok(Self::Number),
32            b'L' => Ok(Self::Logical),
33            b'D' => Ok(Self::Date),
34            b'F' => Ok(Self::Float),
35            b'M' => Ok(Self::Memo),
36            _ => Err(ShapefileError::DbfError {
37                message: format!("invalid field type code: {}", code as char),
38                field: None,
39                record: None,
40            }),
41        }
42    }
43
44    /// Converts a `FieldType` to its code
45    pub fn to_code(self) -> u8 {
46        match self {
47            Self::Character => b'C',
48            Self::Number => b'N',
49            Self::Logical => b'L',
50            Self::Date => b'D',
51            Self::Float => b'F',
52            Self::Memo => b'M',
53        }
54    }
55
56    /// Returns the name of the field type
57    pub fn name(self) -> &'static str {
58        match self {
59            Self::Character => "Character",
60            Self::Number => "Number",
61            Self::Logical => "Logical",
62            Self::Date => "Date",
63            Self::Float => "Float",
64            Self::Memo => "Memo",
65        }
66    }
67}
68
69/// DBF field descriptor (32 bytes)
70#[derive(Debug, Clone)]
71pub struct FieldDescriptor {
72    /// Field name (up to 10 characters, null-terminated)
73    pub name: String,
74    /// Field type
75    pub field_type: FieldType,
76    /// Field length in bytes
77    pub length: u8,
78    /// Decimal count (for numeric fields)
79    pub decimal_count: u8,
80}
81
82impl FieldDescriptor {
83    /// Creates a new field descriptor
84    pub fn new(name: String, field_type: FieldType, length: u8, decimal_count: u8) -> Result<Self> {
85        if name.len() > 10 {
86            return Err(ShapefileError::InvalidFieldDescriptor {
87                message: format!("field name too long: {} (max 10)", name.len()),
88                field: Some(name),
89            });
90        }
91
92        if name.is_empty() {
93            return Err(ShapefileError::InvalidFieldDescriptor {
94                message: "field name cannot be empty".to_string(),
95                field: None,
96            });
97        }
98
99        Ok(Self {
100            name,
101            field_type,
102            length,
103            decimal_count,
104        })
105    }
106
107    /// Reads a field descriptor from a reader
108    pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
109        // Read field name (11 bytes)
110        let mut name_bytes = [0u8; 11];
111        reader
112            .read_exact(&mut name_bytes)
113            .map_err(|_| ShapefileError::unexpected_eof("reading field name"))?;
114
115        // Convert to string, stopping at first null byte
116        let name_end = name_bytes.iter().position(|&b| b == 0).unwrap_or(11);
117        let name = String::from_utf8_lossy(&name_bytes[..name_end]).to_string();
118
119        // Read field type (1 byte)
120        let mut type_byte = [0u8; 1];
121        reader
122            .read_exact(&mut type_byte)
123            .map_err(|_| ShapefileError::unexpected_eof("reading field type"))?;
124        let field_type = FieldType::from_code(type_byte[0])?;
125
126        // Skip reserved bytes (4 bytes)
127        let mut reserved = [0u8; 4];
128        reader
129            .read_exact(&mut reserved)
130            .map_err(|_| ShapefileError::unexpected_eof("reading field reserved bytes"))?;
131
132        // Read field length (1 byte)
133        let mut length = [0u8; 1];
134        reader
135            .read_exact(&mut length)
136            .map_err(|_| ShapefileError::unexpected_eof("reading field length"))?;
137
138        // Read decimal count (1 byte)
139        let mut decimal_count = [0u8; 1];
140        reader
141            .read_exact(&mut decimal_count)
142            .map_err(|_| ShapefileError::unexpected_eof("reading decimal count"))?;
143
144        // Skip remaining reserved bytes (14 bytes)
145        let mut reserved2 = [0u8; 14];
146        reader
147            .read_exact(&mut reserved2)
148            .map_err(|_| ShapefileError::unexpected_eof("reading field reserved bytes 2"))?;
149
150        Ok(Self {
151            name,
152            field_type,
153            length: length[0],
154            decimal_count: decimal_count[0],
155        })
156    }
157
158    /// Writes a field descriptor to a writer
159    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
160        // Write field name (11 bytes, null-padded)
161        let mut name_bytes = [0u8; 11];
162        let name_slice = self.name.as_bytes();
163        let copy_len = name_slice.len().min(10);
164        name_bytes[..copy_len].copy_from_slice(&name_slice[..copy_len]);
165        writer.write_all(&name_bytes).map_err(ShapefileError::Io)?;
166
167        // Write field type (1 byte)
168        writer
169            .write_all(&[self.field_type.to_code()])
170            .map_err(ShapefileError::Io)?;
171
172        // Write reserved bytes (4 bytes)
173        writer.write_all(&[0u8; 4]).map_err(ShapefileError::Io)?;
174
175        // Write field length (1 byte)
176        writer
177            .write_all(&[self.length])
178            .map_err(ShapefileError::Io)?;
179
180        // Write decimal count (1 byte)
181        writer
182            .write_all(&[self.decimal_count])
183            .map_err(ShapefileError::Io)?;
184
185        // Write remaining reserved bytes (14 bytes)
186        writer.write_all(&[0u8; 14]).map_err(ShapefileError::Io)?;
187
188        Ok(())
189    }
190}
191
192/// DBF field value
193#[derive(Debug, Clone, PartialEq)]
194pub enum FieldValue {
195    /// String value
196    String(String),
197    /// Integer value
198    Integer(i64),
199    /// Float value
200    Float(f64),
201    /// Boolean value
202    Boolean(bool),
203    /// Date value (YYYYMMDD string)
204    Date(String),
205    /// Null value
206    Null,
207}
208
209impl FieldValue {
210    /// Parses a field value from raw bytes
211    pub fn parse(bytes: &[u8], field_type: FieldType, decimal_count: u8) -> Result<Self> {
212        // Trim whitespace
213        let trimmed = String::from_utf8_lossy(bytes).trim().to_string();
214
215        if trimmed.is_empty() {
216            return Ok(Self::Null);
217        }
218
219        match field_type {
220            FieldType::Character => Ok(Self::String(trimmed)),
221            FieldType::Number | FieldType::Float => {
222                if decimal_count > 0 {
223                    // Parse as float
224                    trimmed
225                        .parse::<f64>()
226                        .map(Self::Float)
227                        .map_err(|_| ShapefileError::DbfError {
228                            message: format!("failed to parse float: {}", trimmed),
229                            field: None,
230                            record: None,
231                        })
232                } else {
233                    // Parse as integer
234                    trimmed.parse::<i64>().map(Self::Integer).map_err(|_| {
235                        ShapefileError::DbfError {
236                            message: format!("failed to parse integer: {}", trimmed),
237                            field: None,
238                            record: None,
239                        }
240                    })
241                }
242            }
243            FieldType::Logical => {
244                let value = match trimmed.chars().next() {
245                    Some('T') | Some('t') | Some('Y') | Some('y') => true,
246                    Some('F') | Some('f') | Some('N') | Some('n') => false,
247                    _ => {
248                        return Err(ShapefileError::DbfError {
249                            message: format!("invalid logical value: {}", trimmed),
250                            field: None,
251                            record: None,
252                        });
253                    }
254                };
255                Ok(Self::Boolean(value))
256            }
257            FieldType::Date => {
258                // Validate date format (YYYYMMDD)
259                if trimmed.len() != 8 {
260                    return Err(ShapefileError::DbfError {
261                        message: format!("invalid date format: {} (expected YYYYMMDD)", trimmed),
262                        field: None,
263                        record: None,
264                    });
265                }
266                Ok(Self::Date(trimmed))
267            }
268            FieldType::Memo => Ok(Self::String(trimmed)),
269        }
270    }
271
272    /// Formats a field value to bytes for writing
273    pub fn format(&self, length: usize) -> Vec<u8> {
274        let mut buffer = vec![b' '; length];
275
276        let content = match self {
277            Self::String(s) => s.clone(),
278            Self::Integer(i) => i.to_string(),
279            Self::Float(f) => f.to_string(),
280            Self::Boolean(b) => {
281                if *b {
282                    "T".to_string()
283                } else {
284                    "F".to_string()
285                }
286            }
287            Self::Date(d) => d.clone(),
288            Self::Null => String::new(),
289        };
290
291        let bytes = content.as_bytes();
292        let copy_len = bytes.len().min(length);
293        buffer[..copy_len].copy_from_slice(&bytes[..copy_len]);
294
295        buffer
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use std::io::Cursor;
303
304    #[test]
305    fn test_field_type_conversion() {
306        assert_eq!(
307            FieldType::from_code(b'C').expect("valid field type code"),
308            FieldType::Character
309        );
310        assert_eq!(FieldType::Character.to_code(), b'C');
311        assert_eq!(FieldType::Number.name(), "Number");
312    }
313
314    #[test]
315    fn test_field_descriptor_round_trip() {
316        let desc = FieldDescriptor::new("NAME".to_string(), FieldType::Character, 50, 0)
317            .expect("valid field descriptor");
318
319        let mut buffer = Vec::new();
320        desc.write(&mut buffer).expect("write field descriptor");
321
322        assert_eq!(buffer.len(), 32); // Field descriptor is 32 bytes
323
324        let mut cursor = Cursor::new(buffer);
325        let read_desc = FieldDescriptor::read(&mut cursor).expect("read field descriptor");
326
327        assert_eq!(read_desc.name, "NAME");
328        assert_eq!(read_desc.field_type, FieldType::Character);
329        assert_eq!(read_desc.length, 50);
330    }
331
332    #[test]
333    fn test_field_value_parsing() {
334        // String
335        let value =
336            FieldValue::parse(b"  test  ", FieldType::Character, 0).expect("parse character field");
337        assert_eq!(value, FieldValue::String("test".to_string()));
338
339        // Integer
340        let value =
341            FieldValue::parse(b"  123  ", FieldType::Number, 0).expect("parse integer field");
342        assert_eq!(value, FieldValue::Integer(123));
343
344        // Float
345        let value = FieldValue::parse(b" 12.34 ", FieldType::Number, 2).expect("parse float field");
346        assert_eq!(value, FieldValue::Float(12.34));
347
348        // Boolean
349        let value =
350            FieldValue::parse(b"T", FieldType::Logical, 0).expect("parse logical field true");
351        assert_eq!(value, FieldValue::Boolean(true));
352
353        let value =
354            FieldValue::parse(b"F", FieldType::Logical, 0).expect("parse logical field false");
355        assert_eq!(value, FieldValue::Boolean(false));
356
357        // Date
358        let value = FieldValue::parse(b"20240125", FieldType::Date, 0).expect("parse date field");
359        assert_eq!(value, FieldValue::Date("20240125".to_string()));
360
361        // Null (empty)
362        let value = FieldValue::parse(b"   ", FieldType::Character, 0).expect("parse empty field");
363        assert_eq!(value, FieldValue::Null);
364    }
365
366    #[test]
367    fn test_field_value_formatting() {
368        let value = FieldValue::String("test".to_string());
369        let formatted = value.format(10);
370        assert_eq!(formatted.len(), 10);
371        assert_eq!(&formatted[..4], b"test");
372
373        let value = FieldValue::Integer(123);
374        let formatted = value.format(10);
375        assert_eq!(&formatted[..3], b"123");
376
377        let value = FieldValue::Boolean(true);
378        let formatted = value.format(1);
379        assert_eq!(formatted[0], b'T');
380    }
381}