1#![allow(deprecated)]
26
27use serde::{Deserialize, Serialize};
28use std::collections::HashMap;
29
30pub mod toml_plugin;
31
32#[cfg(test)]
33mod tests;
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46pub struct LibraryMapping {
47 pub python_module: String,
49
50 pub rust_crate: String,
52
53 pub python_version_req: String,
55
56 pub rust_crate_version: String,
58
59 pub items: HashMap<String, ItemMapping>,
61
62 pub features: Vec<String>,
64
65 pub confidence: MappingConfidence,
67
68 pub provenance: String,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
74pub struct ItemMapping {
75 pub rust_name: String,
77
78 pub pattern: TransformPattern,
80
81 pub type_transform: Option<TypeTransform>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
87#[serde(tag = "type")]
88pub enum TransformPattern {
89 #[default]
91 Direct,
92
93 MethodCall { extra_args: Vec<String> },
95
96 PropertyToMethod,
98
99 Constructor { method: String },
101
102 ReorderArgs { indices: Vec<usize> },
104
105 TypedTemplate {
107 pattern: String,
108 params: Vec<String>,
109 param_types: Vec<ParamType>,
110 },
111
112 #[deprecated(note = "Use TypedTemplate for type-safe templates")]
114 Template { template: String },
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
119pub enum ParamType {
120 Expr,
121 String,
122 Number,
123 Bytes,
124 Bool,
125 Path,
126 List,
127 Dict,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
132pub struct TypeTransform {
133 pub python_type: String,
135 pub rust_type: String,
137}
138
139#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
141pub enum MappingConfidence {
142 Verified,
144 Community,
146 #[default]
148 Experimental,
149}
150
151#[derive(Debug, Default)]
159pub struct MappingRegistry {
160 core: HashMap<String, LibraryMapping>,
162
163 extensions: HashMap<String, LibraryMapping>,
165
166 overrides: HashMap<String, LibraryMapping>,
168}
169
170impl MappingRegistry {
171 pub fn new() -> Self {
173 Self::default()
174 }
175
176 pub fn with_defaults() -> Self {
178 let mut registry = Self::new();
179 registry.register_core_defaults();
180 registry
181 }
182
183 pub fn lookup(&self, module: &str, item: &str) -> Option<&ItemMapping> {
187 self.overrides
188 .get(module)
189 .or_else(|| self.extensions.get(module))
190 .or_else(|| self.core.get(module))
191 .and_then(|m| m.items.get(item))
192 }
193
194 pub fn lookup_module(&self, module: &str) -> Option<&LibraryMapping> {
196 self.overrides
197 .get(module)
198 .or_else(|| self.extensions.get(module))
199 .or_else(|| self.core.get(module))
200 }
201
202 pub fn register_core(&mut self, mapping: LibraryMapping) {
204 self.core.insert(mapping.python_module.clone(), mapping);
205 }
206
207 pub fn register_extension(&mut self, mapping: LibraryMapping) {
209 self.extensions
210 .insert(mapping.python_module.clone(), mapping);
211 }
212
213 pub fn register_override(&mut self, mapping: LibraryMapping) {
215 self.overrides
216 .insert(mapping.python_module.clone(), mapping);
217 }
218
219 pub fn module_count(&self) -> usize {
221 let mut seen = std::collections::HashSet::new();
222 for key in self.core.keys() {
223 seen.insert(key.as_str());
224 }
225 for key in self.extensions.keys() {
226 seen.insert(key.as_str());
227 }
228 for key in self.overrides.keys() {
229 seen.insert(key.as_str());
230 }
231 seen.len()
232 }
233
234 fn register_core_defaults(&mut self) {
236 self.register_core(LibraryMapping {
238 python_module: "json".to_string(),
239 rust_crate: "serde_json".to_string(),
240 python_version_req: "*".to_string(),
241 rust_crate_version: "1.0".to_string(),
242 items: HashMap::from([
243 (
244 "loads".to_string(),
245 ItemMapping {
246 rust_name: "from_str".to_string(),
247 pattern: TransformPattern::Direct,
248 type_transform: None,
249 },
250 ),
251 (
252 "dumps".to_string(),
253 ItemMapping {
254 rust_name: "to_string".to_string(),
255 pattern: TransformPattern::Direct,
256 type_transform: None,
257 },
258 ),
259 ]),
260 features: vec![],
261 confidence: MappingConfidence::Verified,
262 provenance: "https://docs.rs/serde_json/".to_string(),
263 });
264
265 self.register_core(LibraryMapping {
267 python_module: "os".to_string(),
268 rust_crate: "std".to_string(),
269 python_version_req: "*".to_string(),
270 rust_crate_version: "*".to_string(),
271 items: HashMap::from([
272 (
273 "getcwd".to_string(),
274 ItemMapping {
275 rust_name: "env::current_dir".to_string(),
276 pattern: TransformPattern::Direct,
277 type_transform: None,
278 },
279 ),
280 (
281 "getenv".to_string(),
282 ItemMapping {
283 rust_name: "env::var".to_string(),
284 pattern: TransformPattern::Direct,
285 type_transform: None,
286 },
287 ),
288 ]),
289 features: vec![],
290 confidence: MappingConfidence::Verified,
291 provenance: "https://doc.rust-lang.org/std/".to_string(),
292 });
293
294 self.register_core(LibraryMapping {
296 python_module: "re".to_string(),
297 rust_crate: "regex".to_string(),
298 python_version_req: "*".to_string(),
299 rust_crate_version: "1.0".to_string(),
300 items: HashMap::from([
301 (
302 "compile".to_string(),
303 ItemMapping {
304 rust_name: "Regex::new".to_string(),
305 pattern: TransformPattern::Constructor {
306 method: "new".to_string(),
307 },
308 type_transform: None,
309 },
310 ),
311 (
312 "match".to_string(),
313 ItemMapping {
314 rust_name: "is_match".to_string(),
315 pattern: TransformPattern::MethodCall { extra_args: vec![] },
316 type_transform: None,
317 },
318 ),
319 ]),
320 features: vec![],
321 confidence: MappingConfidence::Verified,
322 provenance: "https://docs.rs/regex/".to_string(),
323 });
324 }
325}
326
327pub trait MappingPlugin: Send + Sync {
333 fn id(&self) -> &str;
335
336 fn version(&self) -> &str;
338
339 fn register(&self, registry: &mut MappingRegistry);
341
342 fn validate(&self) -> Result<(), ValidationError> {
344 Ok(())
345 }
346}
347
348#[derive(Debug, Clone)]
350pub struct ValidationError {
351 pub message: String,
352 pub mapping: Option<String>,
353}
354
355impl std::fmt::Display for ValidationError {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 write!(f, "Validation error: {}", self.message)
358 }
359}
360
361impl std::error::Error for ValidationError {}
362
363impl TransformPattern {
368 pub fn validate_reorder_args(indices: &[usize]) -> Result<(), ValidationError> {
372 let n = indices.len();
373 let mut seen = vec![false; n];
374
375 for &idx in indices {
376 if idx >= n {
377 return Err(ValidationError {
378 message: format!("Index {} out of bounds for {} args", idx, n),
379 mapping: None,
380 });
381 }
382 if seen[idx] {
383 return Err(ValidationError {
384 message: format!("Duplicate index {} in permutation", idx),
385 mapping: None,
386 });
387 }
388 seen[idx] = true;
389 }
390
391 Ok(())
392 }
393
394 pub fn validate_typed_template(
398 pattern: &str,
399 params: &[String],
400 param_types: &[ParamType],
401 ) -> Result<(), ValidationError> {
402 if params.len() != param_types.len() {
404 return Err(ValidationError {
405 message: format!(
406 "Param count {} != type count {}",
407 params.len(),
408 param_types.len()
409 ),
410 mapping: None,
411 });
412 }
413
414 for param in params {
416 let placeholder = format!("{{{}}}", param);
417 if !pattern.contains(&placeholder) {
418 return Err(ValidationError {
419 message: format!("Param '{}' not found in pattern", param),
420 mapping: None,
421 });
422 }
423 }
424
425 Ok(())
426 }
427}