use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct MetadataNormalizer {
legacy_to_canonical: HashMap<&'static str, &'static str>,
}
impl MetadataNormalizer {
#[must_use]
pub fn new() -> Self {
Self {
legacy_to_canonical: Self::legacy_mappings(),
}
}
fn legacy_mappings() -> HashMap<&'static str, &'static str> {
let mut mappings = HashMap::new();
mappings.insert("async", "is_async");
mappings.insert("static", "is_static");
mappings.insert("abstract", "is_abstract");
mappings.insert("final", "is_final");
mappings.insert("override", "is_override");
mappings.insert("mutating", "is_mutating");
mappings.insert("generator", "is_generator");
mappings.insert("exported", "is_exported");
mappings.insert("throwing", "is_throwing");
mappings.insert("struct", "is_struct");
mappings.insert("enum", "is_enum");
mappings.insert("interface", "is_interface");
mappings.insert("protocol", "is_protocol");
mappings.insert("actor", "is_actor");
mappings.insert("extension", "is_extension");
mappings.insert("trait", "is_trait");
mappings.insert("sealed", "is_sealed");
mappings.insert("generics", "has_generics");
mappings.insert("computed", "is_computed");
mappings.insert("lazy", "is_lazy");
mappings.insert("weak", "is_weak");
mappings.insert("mutable", "is_mutable");
mappings.insert("const", "is_const");
mappings.insert("classmethod", "is_classmethod");
mappings.insert("staticmethod", "is_staticmethod");
mappings.insert("property_decorator", "is_property_decorator");
mappings.insert("receiver", "has_receiver");
mappings.insert("pointer_receiver", "is_pointer_receiver");
mappings.insert("synchronized", "is_synchronized");
mappings.insert("constexpr", "is_constexpr");
mappings.insert("noexcept", "is_noexcept");
mappings.insert("unsafe", "is_unsafe");
mappings.insert("const_fn", "is_const_fn");
mappings.insert("readonly", "is_readonly");
mappings.insert("factory", "is_factory");
mappings.insert("external", "is_external");
mappings
}
#[must_use]
pub fn normalize(&self, raw: HashMap<String, String>) -> HashMap<String, String> {
let mut normalized = HashMap::new();
for (key, value) in raw {
if let Some(&canonical_key) = self.legacy_to_canonical.get(key.as_str()) {
log::debug!(
"Normalizing metadata: legacy key '{key}' → canonical '{canonical_key}'"
);
normalized.insert(canonical_key.to_string(), value);
} else {
normalized.insert(key, value);
}
}
normalized
}
#[must_use]
pub fn is_legacy_key(&self, key: &str) -> bool {
self.legacy_to_canonical.contains_key(key)
}
#[must_use]
pub fn get_canonical(&self, key: &str) -> Option<&'static str> {
self.legacy_to_canonical.get(key).copied()
}
pub fn legacy_keys(&self) -> impl Iterator<Item = &&'static str> {
self.legacy_to_canonical.keys()
}
pub fn canonical_keys(&self) -> impl Iterator<Item = &&'static str> {
self.legacy_to_canonical.values()
}
pub fn mappings(&self) -> impl Iterator<Item = (&'static str, &'static str)> + '_ {
self.legacy_to_canonical.iter().map(|(&k, &v)| (k, v))
}
}
impl Default for MetadataNormalizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_short_form_to_canonical() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("async".to_string(), "true".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_async"),
Some(&"true".to_string())
); assert_eq!(canonical_metadata.get("async"), None); }
#[test]
fn test_normalize_multiple_short_forms() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("async".to_string(), "true".to_string());
raw.insert("static".to_string(), "true".to_string());
raw.insert("final".to_string(), "false".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_async"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_static"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_final"),
Some(&"false".to_string())
);
assert_eq!(canonical_metadata.len(), 3);
}
#[test]
fn test_preserve_unknown_keys() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("custom_plugin_key".to_string(), "value1".to_string());
raw.insert("another_custom".to_string(), "value2".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("custom_plugin_key"),
Some(&"value1".to_string())
);
assert_eq!(
canonical_metadata.get("another_custom"),
Some(&"value2".to_string())
);
assert_eq!(canonical_metadata.len(), 2);
}
#[test]
fn test_canonical_key_preserved() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("is_async".to_string(), "true".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_async"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.len(), 1);
}
#[test]
fn test_canonical_wins_over_short_form() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("async".to_string(), "false".to_string()); raw.insert("is_async".to_string(), "true".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert!(
canonical_metadata.get("is_async") == Some(&"true".to_string())
|| canonical_metadata.get("is_async") == Some(&"false".to_string())
);
assert_eq!(canonical_metadata.len(), 1);
}
#[test]
fn test_empty_metadata() {
let normalizer = MetadataNormalizer::new();
let raw = HashMap::new();
let canonical_metadata = normalizer.normalize(raw);
assert!(canonical_metadata.is_empty());
}
#[test]
fn test_mixed_short_canonical_unknown() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("async".to_string(), "true".to_string()); raw.insert("is_static".to_string(), "true".to_string()); raw.insert("custom".to_string(), "value".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_async"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_static"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.get("custom"), Some(&"value".to_string()));
assert_eq!(canonical_metadata.len(), 3);
}
#[test]
fn test_is_legacy_key() {
let normalizer = MetadataNormalizer::new();
assert!(normalizer.is_legacy_key("async"));
assert!(normalizer.is_legacy_key("static"));
assert!(normalizer.is_legacy_key("final"));
assert!(!normalizer.is_legacy_key("is_async"));
assert!(!normalizer.is_legacy_key("is_static"));
assert!(!normalizer.is_legacy_key("custom_key"));
}
#[test]
fn test_get_canonical() {
let normalizer = MetadataNormalizer::new();
assert_eq!(normalizer.get_canonical("async"), Some("is_async"));
assert_eq!(normalizer.get_canonical("static"), Some("is_static"));
assert_eq!(normalizer.get_canonical("throwing"), Some("is_throwing"));
assert_eq!(normalizer.get_canonical("is_async"), None);
assert_eq!(normalizer.get_canonical("custom"), None);
}
#[test]
fn test_visibility_key_not_normalized() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("visibility".to_string(), "public".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("visibility"),
Some(&"public".to_string())
);
assert_eq!(canonical_metadata.len(), 1);
}
#[test]
fn test_all_function_modifiers() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("async".to_string(), "true".to_string());
raw.insert("static".to_string(), "true".to_string());
raw.insert("abstract".to_string(), "true".to_string());
raw.insert("final".to_string(), "true".to_string());
raw.insert("override".to_string(), "true".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_async"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_static"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_abstract"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_final"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_override"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.len(), 5);
}
#[test]
fn test_all_class_modifiers() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("struct".to_string(), "true".to_string());
raw.insert("enum".to_string(), "true".to_string());
raw.insert("interface".to_string(), "true".to_string());
raw.insert("actor".to_string(), "true".to_string());
raw.insert("generics".to_string(), "true".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_struct"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.get("is_enum"), Some(&"true".to_string()));
assert_eq!(
canonical_metadata.get("is_interface"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_actor"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("has_generics"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.len(), 5);
}
#[test]
fn test_property_modifiers() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("computed".to_string(), "true".to_string());
raw.insert("lazy".to_string(), "true".to_string());
raw.insert("weak".to_string(), "true".to_string());
raw.insert("const".to_string(), "true".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_computed"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.get("is_lazy"), Some(&"true".to_string()));
assert_eq!(canonical_metadata.get("is_weak"), Some(&"true".to_string()));
assert_eq!(
canonical_metadata.get("is_const"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.len(), 4);
}
#[test]
fn test_language_specific_python() {
let normalizer = MetadataNormalizer::new();
let mut raw = HashMap::new();
raw.insert("classmethod".to_string(), "true".to_string());
raw.insert("staticmethod".to_string(), "true".to_string());
let canonical_metadata = normalizer.normalize(raw);
assert_eq!(
canonical_metadata.get("is_classmethod"),
Some(&"true".to_string())
);
assert_eq!(
canonical_metadata.get("is_staticmethod"),
Some(&"true".to_string())
);
assert_eq!(canonical_metadata.len(), 2);
}
#[test]
fn test_legacy_keys_iterator() {
let normalizer = MetadataNormalizer::new();
let legacy_keys: Vec<&&str> = normalizer.legacy_keys().collect();
assert!(legacy_keys.len() > 20); assert!(legacy_keys.contains(&&"async"));
assert!(legacy_keys.contains(&&"static"));
}
#[test]
fn test_canonical_keys_iterator() {
let normalizer = MetadataNormalizer::new();
let canonical_keys: Vec<&&str> = normalizer.canonical_keys().collect();
assert!(canonical_keys.len() > 20);
assert!(canonical_keys.contains(&&"is_async"));
assert!(canonical_keys.contains(&&"is_static"));
}
}