Skip to main content

bdat/table/
convert.rs

1use thiserror::Error;
2
3use crate::legacy::{LegacyColumn, LegacyRow, LegacyTable, LegacyTableBuilder};
4use crate::modern::{ModernColumn, ModernRow, ModernTable, ModernTableBuilder};
5use crate::{BdatVersion, Cell, LegacyVersion, RowId, ValueType};
6
7use super::column::ColumnMap;
8
9/// Error encountered while converting tables
10/// to a different format.
11#[derive(Error, Debug)]
12pub enum FormatConvertError {
13    /// One of the table's columns has an unsupported value type.
14    ///
15    /// For example, legacy tables do not support hash-ref fields.
16    #[error("unsupported value type {0:?}")]
17    UnsupportedValueType(ValueType),
18    /// One of the table's values has an unsupported cell type.
19    ///
20    /// For instance, modern tables only support single-value cells.
21    #[error("unsupported cell")]
22    UnsupportedCell,
23    /// The max number of rows in the table has been reached, so no
24    /// more rows can be added.
25    #[error("max row count exceeded")]
26    MaxRowCountExceeded,
27    /// The destination format (legacy) does not support the row ID because it is too high.
28    /// This can happen if the base ID or any of the rows's ID is outside of the format's
29    /// row ID boundaries.
30    #[error("unsupported row ID {0}")]
31    UnsupportedRowId(RowId),
32    /// The destination format does not support hashed labels.
33    #[error("unsupported label type")]
34    UnsupportedLabelType,
35}
36
37// Modern table -> Legacy table
38
39impl<'b> TryFrom<ModernColumn<'b>> for LegacyColumn<'b> {
40    type Error = FormatConvertError;
41
42    fn try_from(modern_col: ModernColumn<'b>) -> Result<Self, Self::Error> {
43        // any legacy version works here
44        if !modern_col
45            .value_type()
46            .is_supported(BdatVersion::Legacy(LegacyVersion::Switch))
47        {
48            return Err(FormatConvertError::UnsupportedValueType(
49                modern_col.value_type(),
50            ));
51        }
52        Ok(Self {
53            value_type: modern_col.value_type,
54            label: modern_col
55                .label
56                .try_into()
57                .map_err(|_| FormatConvertError::UnsupportedLabelType)?,
58            count: 1,
59            flags: Vec::new(),
60        })
61    }
62}
63
64impl<'b> From<ModernRow<'b>> for LegacyRow<'b> {
65    fn from(value: ModernRow<'b>) -> Self {
66        Self {
67            cells: value.values.into_iter().map(Cell::Single).collect(),
68        }
69    }
70}
71
72impl<'b> TryFrom<ModernTable<'b>> for LegacyTable<'b> {
73    type Error = FormatConvertError;
74
75    fn try_from(modern_table: ModernTable<'b>) -> Result<Self, Self::Error> {
76        let rows: Vec<_> = modern_table.rows.into_iter().map(Into::into).collect();
77        let base_id = u16::try_from(modern_table.base_id)
78            .map_err(|_| FormatConvertError::UnsupportedRowId(modern_table.base_id))?;
79        let name = modern_table
80            .name
81            .try_into()
82            .map_err(|_| FormatConvertError::UnsupportedLabelType)?;
83        let columns: Result<ColumnMap<_, _>, FormatConvertError> = modern_table
84            .columns
85            .into_iter()
86            .map(TryInto::try_into)
87            .collect();
88        let row_len =
89            u16::try_from(rows.len()).map_err(|_| FormatConvertError::MaxRowCountExceeded)?;
90        if base_id.checked_add(row_len).is_none() {
91            // If there are enough rows to overflow from base_id, then we definitely have a row
92            // with ID u16::MAX
93            return Err(FormatConvertError::UnsupportedRowId(u16::MAX as u32));
94        }
95        Ok(LegacyTableBuilder::from_table(name, base_id, columns?, rows).build())
96    }
97}
98
99// Legacy table -> Modern table
100
101impl<'b> TryFrom<LegacyColumn<'b>> for ModernColumn<'b> {
102    type Error = FormatConvertError;
103
104    fn try_from(legacy_col: LegacyColumn<'b>) -> Result<Self, Self::Error> {
105        if !legacy_col.value_type().is_supported(BdatVersion::Modern) {
106            return Err(FormatConvertError::UnsupportedValueType(
107                legacy_col.value_type(),
108            ));
109        }
110        Ok(Self {
111            value_type: legacy_col.value_type,
112            label: legacy_col.label.into(),
113        })
114    }
115}
116
117impl<'b> TryFrom<LegacyRow<'b>> for ModernRow<'b> {
118    type Error = FormatConvertError;
119
120    fn try_from(legacy_row: LegacyRow<'b>) -> Result<Self, Self::Error> {
121        let values: Result<Vec<_>, FormatConvertError> = legacy_row
122            .into_cells()
123            .map(|c| match c {
124                Cell::Single(v) => Ok(v),
125                _ => Err(FormatConvertError::UnsupportedCell),
126            })
127            .collect();
128        Ok(Self { values: values? })
129    }
130}
131
132impl<'b> TryFrom<LegacyTable<'b>> for ModernTable<'b> {
133    type Error = FormatConvertError;
134
135    fn try_from(legacy_table: LegacyTable<'b>) -> Result<Self, Self::Error> {
136        let columns: Result<ColumnMap<_>, FormatConvertError> = legacy_table
137            .columns
138            .into_iter()
139            .map(TryInto::try_into)
140            .collect();
141        let rows: Result<Vec<_>, FormatConvertError> = legacy_table
142            .rows
143            .into_iter()
144            .map(TryInto::try_into)
145            .collect();
146
147        Ok(ModernTableBuilder::from_table(
148            legacy_table.name.into(),
149            legacy_table.base_id as u32,
150            columns?,
151            rows?,
152        )
153        .build())
154    }
155}