1use ryo_source::pure::PureVis;
20use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(tag = "type")]
28pub enum SerializableMutation {
29 Rename { symbol_id: String, to: String },
35
36 ChangeVisibility {
39 symbol_id: String,
40 visibility: String,
41 },
42
43 AddFunction {
46 symbol_id: String,
47 name: String,
48 params: Vec<(String, String)>,
49 return_type: Option<String>,
50 body: String,
51 is_pub: bool,
52 },
53
54 RemoveFunction { symbol_id: String },
57
58 AddStruct {
60 name: String,
61 fields: Vec<(String, String, bool)>, is_pub: bool,
63 },
64
65 RemoveStruct { name: String },
67
68 AddField {
70 struct_name: String,
71 field_name: String,
72 field_type: String,
73 is_pub: bool,
74 },
75
76 RemoveField {
78 struct_name: String,
79 field_name: String,
80 },
81
82 AddUse { path: String },
84
85 RemoveUse { path: String },
87
88 AddDerive {
91 symbol_id: String,
92 derives: Vec<String>,
93 },
94
95 RemoveDerive {
98 symbol_id: String,
99 derives: Vec<String>,
100 },
101
102 AddEnum {
105 symbol_id: String,
106 name: String,
107 variants: Vec<String>,
108 derives: Vec<String>,
109 is_pub: bool,
110 },
111
112 RemoveEnum { symbol_id: String },
115
116 AddVariant {
118 enum_name: String,
119 variant_name: String,
120 },
121
122 RemoveVariant {
124 enum_name: String,
125 variant_name: String,
126 },
127
128 AddMatchArm {
131 symbol_id: String,
132 enum_name: String,
133 pattern: String,
134 body: String,
135 },
136
137 RemoveMatchArm {
140 symbol_id: String,
141 enum_name: String,
142 pattern: String,
143 },
144
145 ReplaceMatchArm {
148 symbol_id: String,
149 enum_name: String,
150 old_pattern: String,
151 new_pattern: String,
152 new_body: String,
153 },
154
155 AddStructLiteralField {
157 struct_name: String,
158 field_name: String,
159 value: String,
160 },
161
162 AddConst {
165 symbol_id: String,
166 name: String,
167 ty: String,
168 value: String,
169 is_pub: bool,
170 },
171
172 RemoveConst { symbol_id: String },
175
176 AddTypeAlias {
179 symbol_id: String,
180 name: String,
181 ty: String,
182 is_pub: bool,
183 },
184
185 RemoveTypeAlias { symbol_id: String },
188
189 RemoveTrait { name: String },
191
192 AddImpl {
195 symbol_id: String,
196 target: String,
197 trait_name: Option<String>,
198 },
199
200 RemoveImpl { symbol_id: String },
203
204 AddItem { symbol_id: String, content: String },
207
208 RemoveItem {
211 symbol_id: String,
212 item_kind: String,
213 },
214
215 Generic {
221 mutation_type: String,
222 description: String,
223 },
224}
225
226impl SerializableMutation {
227 pub fn mutation_type(&self) -> &str {
229 match self {
230 Self::Rename { .. } => "Rename",
231 Self::ChangeVisibility { .. } => "ChangeVisibility",
232 Self::AddFunction { .. } => "AddFunction",
233 Self::RemoveFunction { .. } => "RemoveFunction",
234 Self::AddStruct { .. } => "AddStruct",
235 Self::RemoveStruct { .. } => "RemoveStruct",
236 Self::AddField { .. } => "AddField",
237 Self::RemoveField { .. } => "RemoveField",
238 Self::AddUse { .. } => "AddUse",
239 Self::RemoveUse { .. } => "RemoveUse",
240 Self::AddDerive { .. } => "AddDerive",
241 Self::RemoveDerive { .. } => "RemoveDerive",
242 Self::AddEnum { .. } => "AddEnum",
243 Self::RemoveEnum { .. } => "RemoveEnum",
244 Self::AddVariant { .. } => "AddVariant",
245 Self::RemoveVariant { .. } => "RemoveVariant",
246 Self::AddMatchArm { .. } => "AddMatchArm",
247 Self::RemoveMatchArm { .. } => "RemoveMatchArm",
248 Self::ReplaceMatchArm { .. } => "ReplaceMatchArm",
249 Self::AddStructLiteralField { .. } => "AddStructLiteralField",
250 Self::AddConst { .. } => "AddConst",
251 Self::RemoveConst { .. } => "RemoveConst",
252 Self::AddTypeAlias { .. } => "AddTypeAlias",
253 Self::RemoveTypeAlias { .. } => "RemoveTypeAlias",
254 Self::RemoveTrait { .. } => "RemoveTrait",
255 Self::AddImpl { .. } => "AddImpl",
256 Self::RemoveImpl { .. } => "RemoveImpl",
257 Self::AddItem { .. } => "AddItem",
258 Self::RemoveItem { .. } => "RemoveItem",
259 Self::Generic { mutation_type, .. } => mutation_type,
260 }
261 }
262
263 pub fn to_mutation(&self) -> Option<Box<dyn super::Mutation>> {
267 use super::*;
268
269 match self {
270 Self::Rename { symbol_id, to } => {
271 let id = ryo_symbol::SymbolId::parse(symbol_id)?;
272 Some(Box::new(RenameMutation::new(id, to)))
273 }
274
275 Self::ChangeVisibility {
276 symbol_id,
277 visibility,
278 } => {
279 let id = ryo_symbol::SymbolId::parse(symbol_id)?;
280 let vis = parse_visibility(visibility);
281 Some(Box::new(ChangeVisibilityMutation::new(id, vis)))
282 }
283
284 Self::AddFunction { .. } => None,
287
288 Self::RemoveFunction { .. } => None,
291
292 Self::AddStruct { .. } => None,
295
296 Self::RemoveStruct { .. } => None,
299
300 Self::AddField { .. } => None,
303
304 Self::RemoveField { .. } => None,
307
308 Self::AddUse { path } => Some(Box::new(AddUseMutation::new(path))),
309
310 Self::RemoveUse { path } => Some(Box::new(RemoveUseMutation::new(path))),
311
312 Self::AddDerive { .. } => None,
315
316 Self::RemoveDerive { .. } => None,
319
320 Self::AddEnum { .. } => None,
323
324 Self::RemoveEnum { .. } => None,
327
328 Self::AddVariant { .. } => None,
330
331 Self::RemoveVariant { .. } => None,
333
334 Self::AddMatchArm { .. } => None,
337
338 Self::RemoveMatchArm { .. } => None,
341
342 Self::ReplaceMatchArm { .. } => None,
345
346 Self::AddStructLiteralField { .. } => None,
349
350 Self::AddConst { .. } => None,
353
354 Self::RemoveConst { .. } => None,
357
358 Self::AddTypeAlias { .. } => None,
361
362 Self::RemoveTypeAlias { .. } => None,
365
366 Self::RemoveTrait { .. } => None,
369
370 Self::AddImpl { .. } => None,
373
374 Self::RemoveImpl { .. } => None,
377
378 Self::AddItem { .. } => None,
381
382 Self::RemoveItem { .. } => None,
385
386 Self::Generic { .. } => None,
387 }
388 }
389
390 pub fn to_json(&self) -> serde_json::Value {
392 serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
393 }
394
395 pub fn from_json(value: &serde_json::Value) -> Option<Self> {
397 serde_json::from_value(value.clone()).ok()
398 }
399}
400
401pub trait ToSerializable {
403 fn to_serializable(&self) -> SerializableMutation;
404}
405
406fn parse_visibility(s: &str) -> PureVis {
411 match s {
412 "pub" => PureVis::Public,
413 "pub(crate)" => PureVis::Crate,
414 "pub(super)" => PureVis::Super,
415 "" | "private" => PureVis::Private,
416 other if other.starts_with("pub(in ") => {
417 let path = other
418 .strip_prefix("pub(in ")
419 .and_then(|s| s.strip_suffix(')'));
420 if let Some(p) = path {
421 PureVis::In(p.to_string())
422 } else {
423 PureVis::Private
424 }
425 }
426 _ => PureVis::Private,
427 }
428}
429
430fn visibility_to_string(vis: &PureVis) -> String {
431 match vis {
432 PureVis::Public => "pub".to_string(),
433 PureVis::Crate => "pub(crate)".to_string(),
434 PureVis::Super => "pub(super)".to_string(),
435 PureVis::Private => String::new(),
436 PureVis::In(path) => format!("pub(in {})", path),
437 }
438}
439
440impl ToSerializable for super::RenameMutation {
445 fn to_serializable(&self) -> SerializableMutation {
446 SerializableMutation::Rename {
447 symbol_id: format!("{:?}", self.symbol_id),
448 to: self.to.clone(),
449 }
450 }
451}
452
453impl ToSerializable for super::ChangeVisibilityMutation {
454 fn to_serializable(&self) -> SerializableMutation {
455 SerializableMutation::ChangeVisibility {
456 symbol_id: format!("{:?}", self.symbol_id),
457 visibility: visibility_to_string(&self.to),
458 }
459 }
460}
461
462impl ToSerializable for super::AddFunctionMutation {
463 fn to_serializable(&self) -> SerializableMutation {
464 SerializableMutation::AddFunction {
465 symbol_id: format!("{}", self.parent),
466 name: self.name.clone(),
467 params: self.params.clone(),
468 return_type: self.return_type.clone(),
469 body: self.body.clone(),
470 is_pub: self.is_pub,
471 }
472 }
473}
474
475impl ToSerializable for super::RemoveFunctionMutation {
476 fn to_serializable(&self) -> SerializableMutation {
477 SerializableMutation::RemoveFunction {
478 symbol_id: format!("{}", self.symbol_id),
479 }
480 }
481}
482
483impl ToSerializable for super::AddStructMutation {
484 fn to_serializable(&self) -> SerializableMutation {
485 SerializableMutation::AddStruct {
486 name: self.name.clone(),
487 fields: self
488 .fields
489 .iter()
490 .map(|f| (f.name.clone(), f.ty.clone(), f.is_pub))
491 .collect(),
492 is_pub: self.is_pub,
493 }
494 }
495}
496
497impl ToSerializable for super::RemoveStructMutation {
498 fn to_serializable(&self) -> SerializableMutation {
499 SerializableMutation::RemoveStruct {
500 name: self.struct_id.to_string(),
501 }
502 }
503}
504
505impl ToSerializable for super::AddFieldMutation {
506 fn to_serializable(&self) -> SerializableMutation {
507 SerializableMutation::AddField {
508 struct_name: format!("{:?}", self.struct_id),
511 field_name: self.field_name.clone(),
512 field_type: self.field_type.clone(),
513 is_pub: self.is_pub,
514 }
515 }
516}
517
518impl ToSerializable for super::RemoveFieldMutation {
519 fn to_serializable(&self) -> SerializableMutation {
520 SerializableMutation::RemoveField {
521 struct_name: format!("{:?}", self.struct_id),
523 field_name: self.field_name.clone(),
524 }
525 }
526}
527
528impl ToSerializable for super::AddUseMutation {
529 fn to_serializable(&self) -> SerializableMutation {
530 SerializableMutation::AddUse {
531 path: self.path.clone(),
532 }
533 }
534}
535
536impl ToSerializable for super::RemoveUseMutation {
537 fn to_serializable(&self) -> SerializableMutation {
538 SerializableMutation::RemoveUse {
539 path: self.path.clone(),
540 }
541 }
542}
543
544impl ToSerializable for super::AddDeriveMutation {
545 fn to_serializable(&self) -> SerializableMutation {
546 SerializableMutation::AddDerive {
547 symbol_id: format!("{:?}", self.symbol_id),
548 derives: self.derives.clone(),
549 }
550 }
551}
552
553impl ToSerializable for super::RemoveDeriveMutation {
554 fn to_serializable(&self) -> SerializableMutation {
555 SerializableMutation::RemoveDerive {
556 symbol_id: format!("{:?}", self.symbol_id),
557 derives: self.derives.clone(),
558 }
559 }
560}
561
562impl ToSerializable for super::AddEnumMutation {
563 fn to_serializable(&self) -> SerializableMutation {
564 SerializableMutation::AddEnum {
565 symbol_id: format!("{:?}", self.parent),
566 name: self.name.clone(),
567 variants: self.variants.iter().map(|(name, _)| name.clone()).collect(),
569 derives: self.derives.clone(),
570 is_pub: self.is_pub,
571 }
572 }
573}
574
575impl ToSerializable for super::RemoveEnumMutation {
576 fn to_serializable(&self) -> SerializableMutation {
577 SerializableMutation::RemoveEnum {
578 symbol_id: format!("{:?}", self.symbol_id),
579 }
580 }
581}
582
583impl ToSerializable for super::AddVariantMutation {
584 fn to_serializable(&self) -> SerializableMutation {
585 SerializableMutation::AddVariant {
586 enum_name: format!("{:?}", self.enum_id),
587 variant_name: self.variant_name.clone(),
588 }
589 }
590}
591
592impl ToSerializable for super::RemoveVariantMutation {
593 fn to_serializable(&self) -> SerializableMutation {
594 SerializableMutation::RemoveVariant {
595 enum_name: format!("{:?}", self.enum_id),
596 variant_name: self.variant_name.clone(),
597 }
598 }
599}
600
601impl ToSerializable for super::AddMatchArmMutation {
602 fn to_serializable(&self) -> SerializableMutation {
603 SerializableMutation::AddMatchArm {
604 symbol_id: self.function_id.to_string(),
605 enum_name: self.enum_name.clone(),
606 pattern: self.pattern.clone(),
607 body: self.body.clone(),
608 }
609 }
610}
611
612impl ToSerializable for super::RemoveMatchArmMutation {
613 fn to_serializable(&self) -> SerializableMutation {
614 SerializableMutation::RemoveMatchArm {
615 symbol_id: self.function_id.to_string(),
616 enum_name: self.enum_name.clone(),
617 pattern: self.pattern.clone(),
618 }
619 }
620}
621
622impl ToSerializable for super::ReplaceMatchArmMutation {
623 fn to_serializable(&self) -> SerializableMutation {
624 SerializableMutation::ReplaceMatchArm {
625 symbol_id: self.function_id.to_string(),
626 enum_name: self.enum_name.clone(),
627 old_pattern: self.old_pattern.clone(),
628 new_pattern: self.new_pattern.clone(),
629 new_body: self.new_body.clone(),
630 }
631 }
632}
633
634impl ToSerializable for super::AddStructLiteralFieldMutation {
635 fn to_serializable(&self) -> SerializableMutation {
636 SerializableMutation::AddStructLiteralField {
637 struct_name: format!("{:?}", self.struct_id),
638 field_name: self.field_name.clone(),
639 value: self.value.clone(),
640 }
641 }
642}
643
644impl ToSerializable for super::AddConstMutation {
645 fn to_serializable(&self) -> SerializableMutation {
646 SerializableMutation::AddConst {
647 symbol_id: format!("{:?}", self.symbol_id),
648 name: self.name.clone(),
649 ty: self.ty.clone(),
650 value: self.value.clone(),
651 is_pub: self.is_pub,
652 }
653 }
654}
655
656impl ToSerializable for super::RemoveConstMutation {
657 fn to_serializable(&self) -> SerializableMutation {
658 SerializableMutation::RemoveConst {
659 symbol_id: format!("{:?}", self.symbol_id),
660 }
661 }
662}
663
664impl ToSerializable for super::AddTypeAliasMutation {
665 fn to_serializable(&self) -> SerializableMutation {
666 SerializableMutation::AddTypeAlias {
667 symbol_id: format!("{:?}", self.symbol_id),
668 name: self.name.clone(),
669 ty: self.ty.clone(),
670 is_pub: self.is_pub,
671 }
672 }
673}
674
675impl ToSerializable for super::RemoveTypeAliasMutation {
676 fn to_serializable(&self) -> SerializableMutation {
677 SerializableMutation::RemoveTypeAlias {
678 symbol_id: format!("{:?}", self.symbol_id),
679 }
680 }
681}
682
683impl ToSerializable for super::RemoveTraitMutation {
684 fn to_serializable(&self) -> SerializableMutation {
685 SerializableMutation::RemoveTrait {
686 name: self.trait_id.to_string(),
687 }
688 }
689}
690
691impl ToSerializable for super::AddImplMutation {
692 fn to_serializable(&self) -> SerializableMutation {
693 SerializableMutation::AddImpl {
694 symbol_id: self.parent.to_string(),
695 target: self.target.clone(),
696 trait_name: self.trait_name.clone(),
697 }
698 }
699}
700
701impl ToSerializable for super::RemoveImplMutation {
702 fn to_serializable(&self) -> SerializableMutation {
703 SerializableMutation::RemoveImpl {
704 symbol_id: self.symbol_id.to_string(),
705 }
706 }
707}
708
709impl ToSerializable for super::AddItemMutation {
710 fn to_serializable(&self) -> SerializableMutation {
711 SerializableMutation::AddItem {
712 symbol_id: self.parent.to_string(),
713 content: self.content.clone(),
714 }
715 }
716}
717
718impl ToSerializable for super::RemoveItemMutation {
719 fn to_serializable(&self) -> SerializableMutation {
720 SerializableMutation::RemoveItem {
721 symbol_id: self.symbol_id.to_string(),
722 item_kind: format!("{:?}", self.item_kind),
723 }
724 }
725}
726
727pub fn to_generic(mutation: &dyn super::Mutation) -> SerializableMutation {
734 SerializableMutation::Generic {
735 mutation_type: mutation.mutation_type().to_string(),
736 description: mutation.describe(),
737 }
738}
739
740#[cfg(test)]
741mod tests {
742 use super::*;
743 use ryo_symbol::{SymbolKind, SymbolPath, SymbolRegistry};
744
745 #[test]
746 fn test_rename_roundtrip() {
747 let mut registry = SymbolRegistry::new();
748 let path = SymbolPath::parse("test_crate::foo").unwrap();
749 let symbol_id = registry.register(path, SymbolKind::Function).unwrap();
750
751 let original = super::super::RenameMutation::new(symbol_id, "bar");
752
753 let serializable = original.to_serializable();
754 let json = serializable.to_json();
755 let restored = SerializableMutation::from_json(&json).unwrap();
756 let mutation = restored.to_mutation().unwrap();
757
758 assert_eq!(mutation.mutation_type(), "Rename");
759 assert!(mutation.describe().contains("bar"));
761 }
762
763 #[test]
764 fn test_add_function_roundtrip() {
765 let mut registry = SymbolRegistry::new();
766 let path = SymbolPath::parse("test_crate").unwrap();
767 let parent_id = registry.register(path, SymbolKind::Mod).unwrap();
768
769 let original = super::super::AddFunctionMutation::new(parent_id, "my_func")
770 .with_params(vec![("x".to_string(), "i32".to_string())])
771 .with_return_type("i32")
772 .with_body("x + 1")
773 .public();
774
775 let serializable = original.to_serializable();
776 let json = serializable.to_json();
777 let restored = SerializableMutation::from_json(&json).unwrap();
778
779 assert!(restored.to_mutation().is_none());
781 assert_eq!(restored.mutation_type(), "AddFunction");
782 }
783
784 #[test]
785 fn test_generic_fallback() {
786 let mut registry = SymbolRegistry::new();
787 let path = SymbolPath::parse("test_crate::a").unwrap();
788 let symbol_id = registry.register(path, SymbolKind::Function).unwrap();
789
790 let mutation = super::super::RenameMutation::new(symbol_id, "b");
791 let generic = to_generic(&mutation);
792
793 assert_eq!(generic.mutation_type(), "Rename");
794 if let SerializableMutation::Generic { description, .. } = generic {
795 assert!(description.contains("b"));
796 }
797 }
798}