use std::collections::HashSet;
use serde_json::{Map, Value};
use super::id_policy::{IDPolicy, validate_id};
#[derive(Debug, Clone)]
pub struct InputProcessingConfig {
pub id_policy: IDPolicy,
pub validate_ids: bool,
pub id_field_names: HashSet<String>,
}
impl Default for InputProcessingConfig {
fn default() -> Self {
Self {
id_policy: IDPolicy::default(),
validate_ids: true,
id_field_names: Self::default_id_field_names(),
}
}
}
impl InputProcessingConfig {
fn default_id_field_names() -> HashSet<String> {
[
"id",
"userId",
"user_id",
"postId",
"post_id",
"commentId",
"comment_id",
"authorId",
"author_id",
"ownerId",
"owner_id",
"creatorId",
"creator_id",
"tenantId",
"tenant_id",
]
.iter()
.map(|s| (*s).to_string())
.collect()
}
pub fn add_id_field(&mut self, field_name: String) {
self.id_field_names.insert(field_name);
}
#[must_use]
pub fn strict_uuid() -> Self {
Self {
id_policy: IDPolicy::UUID,
validate_ids: true,
id_field_names: Self::default_id_field_names(),
}
}
#[must_use]
pub fn opaque() -> Self {
Self {
id_policy: IDPolicy::OPAQUE,
validate_ids: false, id_field_names: Self::default_id_field_names(),
}
}
}
pub fn process_variables(
variables: &Value,
config: &InputProcessingConfig,
) -> Result<Value, ProcessingError> {
if !config.validate_ids {
return Ok(variables.clone());
}
match variables {
Value::Object(obj) => {
let mut result = Map::new();
for (key, value) in obj {
let processed_value = process_value(value, config, key)?;
result.insert(key.clone(), processed_value);
}
Ok(Value::Object(result))
},
Value::Null => Ok(Value::Null),
other => Ok(other.clone()),
}
}
fn process_value(
value: &Value,
config: &InputProcessingConfig,
field_name: &str,
) -> Result<Value, ProcessingError> {
match value {
Value::String(s)
if {
let base_field = field_name.split('[').next().unwrap_or(field_name);
config.id_field_names.contains(base_field)
} =>
{
validate_id(s, config.id_policy).map_err(|e| ProcessingError {
field_path: field_name.to_string(),
reason: format!("Invalid ID value: {e}"),
})?;
Ok(Value::String(s.clone()))
},
Value::Object(obj) => {
let mut result = Map::new();
for (key, nested_value) in obj {
let processed = process_value(nested_value, config, key)?;
result.insert(key.clone(), processed);
}
Ok(Value::Object(result))
},
Value::Array(arr) => {
let processed_items: Result<Vec<_>, _> = arr
.iter()
.enumerate()
.map(|(idx, item)| {
let array_field = format!("{field_name}[{idx}]");
process_value(item, config, &array_field)
})
.collect();
Ok(Value::Array(processed_items?))
},
other => Ok(other.clone()),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcessingError {
pub field_path: String,
pub reason: String,
}
impl std::fmt::Display for ProcessingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Error in field '{}': {}", self.field_path, self.reason)
}
}
impl std::error::Error for ProcessingError {}