baserow_rs/
mapper.rs

1use serde::de::DeserializeOwned;
2use serde_json::Value;
3use std::collections::HashMap;
4use tracing::{debug, instrument, warn};
5
6use crate::TableField;
7
8/// A trait for mapping between Baserow field IDs and their human-readable names
9///
10/// This trait provides functionality to map between numeric field IDs used by Baserow
11/// and their corresponding human-readable names. This is useful when working with the API
12/// where field IDs are required, but you want to reference fields by their names in your code.
13pub trait FieldMapper: Clone {
14    /// Maps a collection of table fields, building the internal mapping structures
15    ///
16    /// # Arguments
17    /// * `fields` - A vector of TableField structs containing field metadata
18    fn map_fields(&mut self, fields: Vec<TableField>);
19
20    /// Gets the field ID corresponding to a field name
21    ///
22    /// # Arguments
23    /// * `name` - The human-readable field name
24    ///
25    /// # Returns
26    /// * `Option<u64>` - The field ID if found, None otherwise
27    fn get_field_id(&self, name: &str) -> Option<u64>;
28
29    /// Gets the field name corresponding to a field ID
30    ///
31    /// # Arguments
32    /// * `id` - The numeric field ID
33    ///
34    /// # Returns
35    /// * `Option<String>` - The field name if found, None otherwise
36    fn get_field_name(&self, id: u64) -> Option<String>;
37
38    /// Gets all mapped fields
39    ///
40    /// # Returns
41    /// * `Vec<TableField>` - A vector of all mapped table fields
42    fn get_fields(&self) -> Vec<TableField>;
43}
44
45/// Default implementation of the FieldMapper trait
46///
47/// Provides bidirectional mapping between field IDs and names using HashMaps
48/// for efficient lookups.
49#[derive(Clone, Default)]
50pub struct TableMapper {
51    fields: Vec<TableField>,
52    ids_to_names: HashMap<u64, String>,
53    names_to_ids: HashMap<String, u64>,
54}
55
56impl TableMapper {
57    /// Creates a new empty TableMapper
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// Deserializes a row into a user-defined type
63    ///
64    /// # Type Parameters
65    /// * `T` - The type to deserialize into. Must implement DeserializeOwned.
66    ///
67    /// # Arguments
68    /// * `row` - The row data as a HashMap
69    ///
70    /// # Returns
71    /// * `Result<T, serde_json::Error>` - The deserialized struct or an error
72    #[instrument(skip(self, row), fields(row_keys = ?row.keys().collect::<Vec<_>>()), err)]
73    pub fn deserialize_row<T>(&self, row: HashMap<String, Value>) -> Result<T, serde_json::Error>
74    where
75        T: DeserializeOwned,
76    {
77        // First convert field IDs to names
78        let converted = self.convert_to_field_names(row);
79        // Then deserialize
80        serde_json::from_value(serde_json::to_value(converted)?)
81    }
82
83    /// Converts field IDs to field names in a row
84    ///
85    /// # Arguments
86    /// * `row` - The row data with field IDs as keys
87    ///
88    /// # Returns
89    /// * HashMap with field names as keys
90    #[instrument(skip(self, row), fields(row_keys = ?row.keys().collect::<Vec<_>>()))]
91    pub fn convert_to_field_names(&self, row: HashMap<String, Value>) -> HashMap<String, Value> {
92        let mut converted = HashMap::new();
93        for (key, value) in row {
94            // Try to parse as a raw field ID first
95            if let Ok(field_id) = key.parse::<u64>() {
96                if let Some(name) = self.get_field_name(field_id) {
97                    debug!(field_id = field_id, field_name = ?name, "Converted raw field ID to name");
98                    converted.insert(name, value);
99                    continue;
100                }
101            }
102            // Then try with field_ prefix
103            if let Some(field_id) = key
104                .strip_prefix("field_")
105                .and_then(|id| id.parse::<u64>().ok())
106            {
107                if let Some(name) = self.get_field_name(field_id) {
108                    debug!(field_id = field_id, field_name = ?name, "Converted prefixed field ID to name");
109                    converted.insert(name, value);
110                    continue;
111                }
112                warn!(field_id = field_id, "No name mapping found for field ID");
113            }
114            debug!(key = ?key, "Keeping original key");
115            converted.insert(key, value);
116        }
117        debug!(
118            field_count = converted.len(),
119            "Completed field name conversion"
120        );
121        converted
122    }
123
124    /// Converts field names to field IDs in a row
125    ///
126    /// # Arguments
127    /// * `row` - The row data with field names as keys
128    ///
129    /// # Returns
130    /// * HashMap with field IDs as keys
131    #[instrument(skip(self, row), fields(row_keys = ?row.keys().collect::<Vec<_>>()))]
132    pub fn convert_to_field_ids(&self, row: HashMap<String, Value>) -> HashMap<String, Value> {
133        let mut converted = HashMap::new();
134        for (key, value) in row {
135            if let Some(id) = self.get_field_id(&key) {
136                let field_key = format!("field_{}", id);
137                debug!(field_name = ?key, field_id = id, "Converted field name to ID");
138                converted.insert(field_key, value);
139                continue;
140            }
141            debug!(key = ?key, "Keeping original key");
142            converted.insert(key, value);
143        }
144        debug!(
145            field_count = converted.len(),
146            "Completed field ID conversion"
147        );
148        converted
149    }
150}
151
152impl FieldMapper for TableMapper {
153    #[instrument(skip(self, fields), fields(field_count = fields.len()))]
154    fn map_fields(&mut self, fields: Vec<TableField>) {
155        // Clear existing mappings
156        self.ids_to_names.clear();
157        self.names_to_ids.clear();
158
159        // Add new mappings
160        fields.iter().for_each(|field| {
161            debug!(field_id = field.id, field_name = ?field.name, "Mapping field");
162            self.ids_to_names.insert(field.id, field.name.clone());
163            self.names_to_ids.insert(field.name.clone(), field.id);
164        });
165
166        self.fields = fields;
167    }
168
169    #[instrument(skip(self))]
170    fn get_field_id(&self, name: &str) -> Option<u64> {
171        let id = self.names_to_ids.get(name).copied();
172        if id.is_none() {
173            warn!(field_name = ?name, "Field name not found in mapping");
174        }
175        id
176    }
177
178    #[instrument(skip(self))]
179    fn get_field_name(&self, id: u64) -> Option<String> {
180        let name = self.ids_to_names.get(&id).cloned();
181        if name.is_none() {
182            warn!(field_id = id, "Field ID not found in mapping");
183        }
184        name
185    }
186
187    fn get_fields(&self) -> Vec<TableField> {
188        self.fields.clone()
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    fn create_test_field(id: u64, name: &str) -> TableField {
197        TableField {
198            id,
199            table_id: 1,
200            name: name.to_string(),
201            order: 0,
202            r#type: "text".to_string(),
203            primary: false,
204            read_only: false,
205            description: None,
206        }
207    }
208
209    #[test]
210    fn test_mapping_fields() {
211        let mut mapper = TableMapper::new();
212        let fields = vec![
213            create_test_field(1, "Name"),
214            create_test_field(2, "Email"),
215            create_test_field(3, "Age"),
216        ];
217
218        mapper.map_fields(fields.clone());
219
220        // Test field storage
221        assert_eq!(mapper.get_fields().len(), 3);
222
223        // Test ID to name mapping
224        assert_eq!(mapper.get_field_name(1), Some("Name".to_string()));
225        assert_eq!(mapper.get_field_name(2), Some("Email".to_string()));
226        assert_eq!(mapper.get_field_name(3), Some("Age".to_string()));
227        assert_eq!(mapper.get_field_name(4), None);
228
229        // Test name to ID mapping
230        assert_eq!(mapper.get_field_id("Name"), Some(1));
231        assert_eq!(mapper.get_field_id("Email"), Some(2));
232        assert_eq!(mapper.get_field_id("Age"), Some(3));
233        assert_eq!(mapper.get_field_id("Unknown"), None);
234    }
235
236    #[test]
237    fn test_remapping_fields() {
238        let mut mapper = TableMapper::new();
239
240        // Initial mapping
241        let initial_fields = vec![create_test_field(1, "Name"), create_test_field(2, "Email")];
242        mapper.map_fields(initial_fields);
243
244        // Remap with updated fields
245        let updated_fields = vec![
246            create_test_field(1, "FullName"), // Changed name
247            create_test_field(2, "Email"),
248            create_test_field(3, "Phone"), // New field
249        ];
250        mapper.map_fields(updated_fields);
251
252        // Verify updated mappings
253        assert_eq!(mapper.get_field_name(1), Some("FullName".to_string()));
254        assert_eq!(mapper.get_field_id("FullName"), Some(1));
255        assert_eq!(mapper.get_field_name(3), Some("Phone".to_string()));
256        assert_eq!(mapper.get_field_id("Phone"), Some(3));
257
258        // Old name should no longer exist
259        assert_eq!(mapper.get_field_id("Name"), None);
260    }
261}