use crate::ingestion::IngestionResult;
use crate::log_feature;
use crate::logging::features::LogFeature;
use crate::schema::types::{KeyValue, Mutation};
use crate::MutationType;
use serde_json::Value;
use std::collections::HashMap;
pub struct MutationGenerator;
impl MutationGenerator {
pub fn new() -> Self {
Self
}
#[allow(clippy::too_many_arguments)]
pub fn generate_mutations(
&self,
schema_name: &str,
keys_and_values: &HashMap<String, String>,
fields_and_values: &HashMap<String, Value>,
mutation_mappers: &HashMap<String, String>,
trust_distance: u32,
pub_key: String,
source_file_name: Option<String>,
) -> IngestionResult<Vec<Mutation>> {
log_feature!(
LogFeature::Ingestion,
info,
"Generating mutations for schema '{}' with {} mappers, {} input fields",
schema_name,
mutation_mappers.len(),
fields_and_values.len()
);
let mut mutations = Vec::new();
let mapped_fields = if mutation_mappers.is_empty() {
log_feature!(
LogFeature::Ingestion,
info,
"No mutation mappers provided, using all {} fields directly",
fields_and_values.len()
);
fields_and_values.clone()
} else {
let mut result = HashMap::new();
for (json_field, schema_field) in mutation_mappers {
if let Some(value) = fields_and_values.get(json_field) {
let field_name = if schema_field.contains('.') {
schema_field.rsplit('.').next().unwrap_or(schema_field)
} else {
schema_field.as_str()
};
result.insert(field_name.to_string(), value.clone());
log_feature!(
LogFeature::Ingestion,
debug,
"Mapped field: {} -> {}",
json_field,
field_name
);
} else {
log_feature!(
LogFeature::Ingestion,
warn,
"Mapper references missing JSON field: {}",
json_field
);
}
}
log_feature!(
LogFeature::Ingestion,
info,
"Applied mutation mappers: {} JSON fields -> {} schema fields",
fields_and_values.len(),
result.len()
);
result
};
if !mapped_fields.is_empty() {
let key_value = KeyValue::new(
keys_and_values.get("hash_field").cloned(),
keys_and_values.get("range_field").cloned(),
);
let mut mutation = Mutation::new(
schema_name.to_string(),
mapped_fields,
key_value,
pub_key,
trust_distance,
MutationType::Create,
);
if let Some(filename) = source_file_name {
mutation = mutation.with_source_file_name(filename);
}
mutations.push(mutation);
log_feature!(
LogFeature::Ingestion,
info,
"Created mutation with {} fields",
mutations[0].fields_and_values.len()
);
} else {
log_feature!(
LogFeature::Ingestion,
warn,
"No valid field mappings found, no mutations generated"
);
}
Ok(mutations)
}
}
impl Default for MutationGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_generate_mutations() {
let generator = MutationGenerator::new();
let mut keys_and_values = HashMap::new();
keys_and_values.insert("hash_field".to_string(), "hash_key".to_string());
keys_and_values.insert("range_field".to_string(), "range_key".to_string());
let mut fields_and_values = HashMap::new();
fields_and_values.insert("name".to_string(), json!("John"));
fields_and_values.insert("age".to_string(), json!(30));
let mut mappers = HashMap::new();
mappers.insert("name".to_string(), "UserSchema.name".to_string());
mappers.insert("age".to_string(), "UserSchema.age".to_string());
let result = generator
.generate_mutations(
"UserSchema",
&keys_and_values,
&fields_and_values,
&mappers,
0,
"test-key".to_string(),
None,
)
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].fields_and_values.len(), 2);
}
}