use serde::de::DeserializeOwned;
use serde_json::Value;
use std::collections::HashMap;
use tracing::{debug, instrument, warn};
use crate::TableField;
pub trait FieldMapper: Clone {
fn map_fields(&mut self, fields: Vec<TableField>);
fn get_field_id(&self, name: &str) -> Option<u64>;
fn get_field_name(&self, id: u64) -> Option<String>;
fn get_fields(&self) -> Vec<TableField>;
}
#[derive(Clone, Default)]
pub struct TableMapper {
fields: Vec<TableField>,
ids_to_names: HashMap<u64, String>,
names_to_ids: HashMap<String, u64>,
}
impl TableMapper {
pub fn new() -> Self {
Self::default()
}
#[instrument(skip(self, row), fields(row_keys = ?row.keys().collect::<Vec<_>>()), err)]
pub fn deserialize_row<T>(&self, row: HashMap<String, Value>) -> Result<T, serde_json::Error>
where
T: DeserializeOwned,
{
let converted = self.convert_to_field_names(row);
serde_json::from_value(serde_json::to_value(converted)?)
}
#[instrument(skip(self, row), fields(row_keys = ?row.keys().collect::<Vec<_>>()))]
pub fn convert_to_field_names(&self, row: HashMap<String, Value>) -> HashMap<String, Value> {
let mut converted = HashMap::new();
for (key, value) in row {
if let Ok(field_id) = key.parse::<u64>() {
if let Some(name) = self.get_field_name(field_id) {
debug!(field_id = field_id, field_name = ?name, "Converted raw field ID to name");
converted.insert(name, value);
continue;
}
}
if let Some(field_id) = key
.strip_prefix("field_")
.and_then(|id| id.parse::<u64>().ok())
{
if let Some(name) = self.get_field_name(field_id) {
debug!(field_id = field_id, field_name = ?name, "Converted prefixed field ID to name");
converted.insert(name, value);
continue;
}
warn!(field_id = field_id, "No name mapping found for field ID");
}
debug!(key = ?key, "Keeping original key");
converted.insert(key, value);
}
debug!(
field_count = converted.len(),
"Completed field name conversion"
);
converted
}
#[instrument(skip(self, row), fields(row_keys = ?row.keys().collect::<Vec<_>>()))]
pub fn convert_to_field_ids(&self, row: HashMap<String, Value>) -> HashMap<String, Value> {
let mut converted = HashMap::new();
for (key, value) in row {
if let Some(id) = self.get_field_id(&key) {
let field_key = format!("field_{}", id);
debug!(field_name = ?key, field_id = id, "Converted field name to ID");
converted.insert(field_key, value);
continue;
}
debug!(key = ?key, "Keeping original key");
converted.insert(key, value);
}
debug!(
field_count = converted.len(),
"Completed field ID conversion"
);
converted
}
}
impl FieldMapper for TableMapper {
#[instrument(skip(self, fields), fields(field_count = fields.len()))]
fn map_fields(&mut self, fields: Vec<TableField>) {
self.ids_to_names.clear();
self.names_to_ids.clear();
fields.iter().for_each(|field| {
debug!(field_id = field.id, field_name = ?field.name, "Mapping field");
self.ids_to_names.insert(field.id, field.name.clone());
self.names_to_ids.insert(field.name.clone(), field.id);
});
self.fields = fields;
}
#[instrument(skip(self))]
fn get_field_id(&self, name: &str) -> Option<u64> {
let id = self.names_to_ids.get(name).copied();
if id.is_none() {
warn!(field_name = ?name, "Field name not found in mapping");
}
id
}
#[instrument(skip(self))]
fn get_field_name(&self, id: u64) -> Option<String> {
let name = self.ids_to_names.get(&id).cloned();
if name.is_none() {
warn!(field_id = id, "Field ID not found in mapping");
}
name
}
fn get_fields(&self) -> Vec<TableField> {
self.fields.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_field(id: u64, name: &str) -> TableField {
TableField {
id,
table_id: 1,
name: name.to_string(),
order: 0,
r#type: "text".to_string(),
primary: false,
read_only: false,
description: None,
}
}
#[test]
fn test_mapping_fields() {
let mut mapper = TableMapper::new();
let fields = vec![
create_test_field(1, "Name"),
create_test_field(2, "Email"),
create_test_field(3, "Age"),
];
mapper.map_fields(fields.clone());
assert_eq!(mapper.get_fields().len(), 3);
assert_eq!(mapper.get_field_name(1), Some("Name".to_string()));
assert_eq!(mapper.get_field_name(2), Some("Email".to_string()));
assert_eq!(mapper.get_field_name(3), Some("Age".to_string()));
assert_eq!(mapper.get_field_name(4), None);
assert_eq!(mapper.get_field_id("Name"), Some(1));
assert_eq!(mapper.get_field_id("Email"), Some(2));
assert_eq!(mapper.get_field_id("Age"), Some(3));
assert_eq!(mapper.get_field_id("Unknown"), None);
}
#[test]
fn test_remapping_fields() {
let mut mapper = TableMapper::new();
let initial_fields = vec![create_test_field(1, "Name"), create_test_field(2, "Email")];
mapper.map_fields(initial_fields);
let updated_fields = vec![
create_test_field(1, "FullName"), create_test_field(2, "Email"),
create_test_field(3, "Phone"), ];
mapper.map_fields(updated_fields);
assert_eq!(mapper.get_field_name(1), Some("FullName".to_string()));
assert_eq!(mapper.get_field_id("FullName"), Some(1));
assert_eq!(mapper.get_field_name(3), Some("Phone".to_string()));
assert_eq!(mapper.get_field_id("Phone"), Some(3));
assert_eq!(mapper.get_field_id("Name"), None);
}
}