#![allow(deprecated)]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub mod toml_plugin;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct LibraryMapping {
pub python_module: String,
pub rust_crate: String,
pub python_version_req: String,
pub rust_crate_version: String,
pub items: HashMap<String, ItemMapping>,
pub features: Vec<String>,
pub confidence: MappingConfidence,
pub provenance: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ItemMapping {
pub rust_name: String,
pub pattern: TransformPattern,
pub type_transform: Option<TypeTransform>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(tag = "type")]
pub enum TransformPattern {
#[default]
Direct,
MethodCall { extra_args: Vec<String> },
PropertyToMethod,
Constructor { method: String },
ReorderArgs { indices: Vec<usize> },
TypedTemplate {
pattern: String,
params: Vec<String>,
param_types: Vec<ParamType>,
},
#[deprecated(note = "Use TypedTemplate for type-safe templates")]
Template { template: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ParamType {
Expr,
String,
Number,
Bytes,
Bool,
Path,
List,
Dict,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TypeTransform {
pub python_type: String,
pub rust_type: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum MappingConfidence {
Verified,
Community,
#[default]
Experimental,
}
#[derive(Debug, Default)]
pub struct MappingRegistry {
core: HashMap<String, LibraryMapping>,
extensions: HashMap<String, LibraryMapping>,
overrides: HashMap<String, LibraryMapping>,
}
impl MappingRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_defaults() -> Self {
let mut registry = Self::new();
registry.register_core_defaults();
registry
}
pub fn lookup(&self, module: &str, item: &str) -> Option<&ItemMapping> {
self.overrides
.get(module)
.or_else(|| self.extensions.get(module))
.or_else(|| self.core.get(module))
.and_then(|m| m.items.get(item))
}
pub fn lookup_module(&self, module: &str) -> Option<&LibraryMapping> {
self.overrides
.get(module)
.or_else(|| self.extensions.get(module))
.or_else(|| self.core.get(module))
}
pub fn register_core(&mut self, mapping: LibraryMapping) {
self.core.insert(mapping.python_module.clone(), mapping);
}
pub fn register_extension(&mut self, mapping: LibraryMapping) {
self.extensions
.insert(mapping.python_module.clone(), mapping);
}
pub fn register_override(&mut self, mapping: LibraryMapping) {
self.overrides
.insert(mapping.python_module.clone(), mapping);
}
pub fn module_count(&self) -> usize {
let mut seen = std::collections::HashSet::new();
for key in self.core.keys() {
seen.insert(key.as_str());
}
for key in self.extensions.keys() {
seen.insert(key.as_str());
}
for key in self.overrides.keys() {
seen.insert(key.as_str());
}
seen.len()
}
fn register_core_defaults(&mut self) {
self.register_core(LibraryMapping {
python_module: "json".to_string(),
rust_crate: "serde_json".to_string(),
python_version_req: "*".to_string(),
rust_crate_version: "1.0".to_string(),
items: HashMap::from([
(
"loads".to_string(),
ItemMapping {
rust_name: "from_str".to_string(),
pattern: TransformPattern::Direct,
type_transform: None,
},
),
(
"dumps".to_string(),
ItemMapping {
rust_name: "to_string".to_string(),
pattern: TransformPattern::Direct,
type_transform: None,
},
),
]),
features: vec![],
confidence: MappingConfidence::Verified,
provenance: "https://docs.rs/serde_json/".to_string(),
});
self.register_core(LibraryMapping {
python_module: "os".to_string(),
rust_crate: "std".to_string(),
python_version_req: "*".to_string(),
rust_crate_version: "*".to_string(),
items: HashMap::from([
(
"getcwd".to_string(),
ItemMapping {
rust_name: "env::current_dir".to_string(),
pattern: TransformPattern::Direct,
type_transform: None,
},
),
(
"getenv".to_string(),
ItemMapping {
rust_name: "env::var".to_string(),
pattern: TransformPattern::Direct,
type_transform: None,
},
),
]),
features: vec![],
confidence: MappingConfidence::Verified,
provenance: "https://doc.rust-lang.org/std/".to_string(),
});
self.register_core(LibraryMapping {
python_module: "re".to_string(),
rust_crate: "regex".to_string(),
python_version_req: "*".to_string(),
rust_crate_version: "1.0".to_string(),
items: HashMap::from([
(
"compile".to_string(),
ItemMapping {
rust_name: "Regex::new".to_string(),
pattern: TransformPattern::Constructor {
method: "new".to_string(),
},
type_transform: None,
},
),
(
"match".to_string(),
ItemMapping {
rust_name: "is_match".to_string(),
pattern: TransformPattern::MethodCall { extra_args: vec![] },
type_transform: None,
},
),
]),
features: vec![],
confidence: MappingConfidence::Verified,
provenance: "https://docs.rs/regex/".to_string(),
});
}
}
pub trait MappingPlugin: Send + Sync {
fn id(&self) -> &str;
fn version(&self) -> &str;
fn register(&self, registry: &mut MappingRegistry);
fn validate(&self) -> Result<(), ValidationError> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ValidationError {
pub message: String,
pub mapping: Option<String>,
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Validation error: {}", self.message)
}
}
impl std::error::Error for ValidationError {}
impl TransformPattern {
pub fn validate_reorder_args(indices: &[usize]) -> Result<(), ValidationError> {
let n = indices.len();
let mut seen = vec![false; n];
for &idx in indices {
if idx >= n {
return Err(ValidationError {
message: format!("Index {} out of bounds for {} args", idx, n),
mapping: None,
});
}
if seen[idx] {
return Err(ValidationError {
message: format!("Duplicate index {} in permutation", idx),
mapping: None,
});
}
seen[idx] = true;
}
Ok(())
}
pub fn validate_typed_template(
pattern: &str,
params: &[String],
param_types: &[ParamType],
) -> Result<(), ValidationError> {
if params.len() != param_types.len() {
return Err(ValidationError {
message: format!(
"Param count {} != type count {}",
params.len(),
param_types.len()
),
mapping: None,
});
}
for param in params {
let placeholder = format!("{{{}}}", param);
if !pattern.contains(&placeholder) {
return Err(ValidationError {
message: format!("Param '{}' not found in pattern", param),
mapping: None,
});
}
}
Ok(())
}
}