dbf_dextractor 0.1.2

Extract and deserialize dbf files
use std::convert::TryFrom;
use std::fmt;
use crate::error::UnsupportedFieldTypeError;

const FIELD_DESCRIPTOR_LENGTH: usize = 32;

pub fn read_field_info(buf: &[u8]) -> Result<Vec<FieldInfo>, UnsupportedFieldTypeError> {
    buf.chunks(FIELD_DESCRIPTOR_LENGTH)
        .scan(1usize, |acc_offset, info| {
            if info.len() < FIELD_DESCRIPTOR_LENGTH {
                return None;
            }

            if info[0] == b'\r' {
                return None;
            }

            let name_bytes = &info[0..=10];
            let field_type = info[11];
            let length = info[16] as usize;
            let offset = *acc_offset;
            *acc_offset += length;

            match FieldInfo::new(name_bytes, field_type, length, offset) {
                Ok(value) => Some(Ok(value)),
                Err(e) => Some(Err(e)),
            }
        })
        .collect()
}

#[derive(Clone, Debug)]
pub enum FieldType {
    Logical,
    Character,
    Integer,
    Numeric,
    Float,
    Date,
    Timestamp,
    Memo,
    Binary,
    General,
}

impl fmt::Display for FieldType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            FieldType::Logical => "Logical",
            FieldType::Character => "Character",
            FieldType::Integer => "Integer",
            FieldType::Numeric => "Numeric",
            FieldType::Float => "Float",
            FieldType::Date => "Date",
            FieldType::Timestamp => "Timestamp",
            FieldType::Memo => "Memo",
            FieldType::Binary => "Binary",
            FieldType::General => "General",
        };

        f.write_str(name)
    }
}

impl TryFrom<u8> for FieldType {
    type Error = UnsupportedFieldTypeError;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        let value = value as char;
        match value {
            'B' => Ok(FieldType::Binary),
            'C' => Ok(FieldType::Character),
            'D' => Ok(FieldType::Date),
            'F' => Ok(FieldType::Float),
            'G' => Ok(FieldType::General),
            'I' => Ok(FieldType::Integer),
            'L' => Ok(FieldType::Logical),
            'M' => Ok(FieldType::Memo),
            'N' => Ok(FieldType::Numeric),
            'T' => Ok(FieldType::Timestamp),
            _ => Err(UnsupportedFieldTypeError(value)),
        }
    }
}

#[derive(Clone, Debug)]
pub struct FieldInfo {
    pub name: String,
    pub field_type: FieldType,
    pub length: usize,
    pub offset: usize,
}

impl FieldInfo {
    pub fn new(
        name_bytes: &[u8],
        field_type: u8,
        length: usize,
        offset: usize,
    ) -> Result<Self, UnsupportedFieldTypeError> {
        let name = name_bytes
            .iter()
            .take_while(|b| **b != 0u8)
            .map(|b| *b as char)
            .fold(String::new(), |mut acc, val| {
                acc.push(val);
                acc
            });

        let field_type = FieldType::try_from(field_type)?;

        Ok(Self {
            name,
            field_type,
            length,
            offset,
        })
    }
}

#[derive(Debug)]
pub enum FieldValue {
    Binary(Vec<u8>),
    Character(String),
    Date(u16, u8, u8),
    Float(f64),
    General(Vec<u8>),
    Integer(i32),
    Numeric(f64),
    Logical(bool),
    Memo(String),
    Timestamp(u16, u8, u8, u8, u8, u8, u16),
    Null,
}