use ryo_source::pure::PureVis;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SerializableMutation {
Rename { symbol_id: String, to: String },
ChangeVisibility {
symbol_id: String,
visibility: String,
},
AddFunction {
symbol_id: String,
name: String,
params: Vec<(String, String)>,
return_type: Option<String>,
body: String,
is_pub: bool,
},
RemoveFunction { symbol_id: String },
AddStruct {
name: String,
fields: Vec<(String, String, bool)>, is_pub: bool,
},
RemoveStruct { name: String },
AddField {
struct_name: String,
field_name: String,
field_type: String,
is_pub: bool,
},
RemoveField {
struct_name: String,
field_name: String,
},
AddUse { path: String },
RemoveUse { path: String },
AddDerive {
symbol_id: String,
derives: Vec<String>,
},
RemoveDerive {
symbol_id: String,
derives: Vec<String>,
},
AddEnum {
symbol_id: String,
name: String,
variants: Vec<String>,
derives: Vec<String>,
is_pub: bool,
},
RemoveEnum { symbol_id: String },
AddVariant {
enum_name: String,
variant_name: String,
},
RemoveVariant {
enum_name: String,
variant_name: String,
},
AddMatchArm {
symbol_id: String,
enum_name: String,
pattern: String,
body: String,
},
RemoveMatchArm {
symbol_id: String,
enum_name: String,
pattern: String,
},
ReplaceMatchArm {
symbol_id: String,
enum_name: String,
old_pattern: String,
new_pattern: String,
new_body: String,
},
AddStructLiteralField {
struct_name: String,
field_name: String,
value: String,
},
AddConst {
symbol_id: String,
name: String,
ty: String,
value: String,
is_pub: bool,
},
RemoveConst { symbol_id: String },
AddTypeAlias {
symbol_id: String,
name: String,
ty: String,
is_pub: bool,
},
RemoveTypeAlias { symbol_id: String },
RemoveTrait { name: String },
AddImpl {
symbol_id: String,
target: String,
trait_name: Option<String>,
},
RemoveImpl { symbol_id: String },
AddItem { symbol_id: String, content: String },
RemoveItem {
symbol_id: String,
item_kind: String,
},
Generic {
mutation_type: String,
description: String,
},
}
impl SerializableMutation {
pub fn mutation_type(&self) -> &str {
match self {
Self::Rename { .. } => "Rename",
Self::ChangeVisibility { .. } => "ChangeVisibility",
Self::AddFunction { .. } => "AddFunction",
Self::RemoveFunction { .. } => "RemoveFunction",
Self::AddStruct { .. } => "AddStruct",
Self::RemoveStruct { .. } => "RemoveStruct",
Self::AddField { .. } => "AddField",
Self::RemoveField { .. } => "RemoveField",
Self::AddUse { .. } => "AddUse",
Self::RemoveUse { .. } => "RemoveUse",
Self::AddDerive { .. } => "AddDerive",
Self::RemoveDerive { .. } => "RemoveDerive",
Self::AddEnum { .. } => "AddEnum",
Self::RemoveEnum { .. } => "RemoveEnum",
Self::AddVariant { .. } => "AddVariant",
Self::RemoveVariant { .. } => "RemoveVariant",
Self::AddMatchArm { .. } => "AddMatchArm",
Self::RemoveMatchArm { .. } => "RemoveMatchArm",
Self::ReplaceMatchArm { .. } => "ReplaceMatchArm",
Self::AddStructLiteralField { .. } => "AddStructLiteralField",
Self::AddConst { .. } => "AddConst",
Self::RemoveConst { .. } => "RemoveConst",
Self::AddTypeAlias { .. } => "AddTypeAlias",
Self::RemoveTypeAlias { .. } => "RemoveTypeAlias",
Self::RemoveTrait { .. } => "RemoveTrait",
Self::AddImpl { .. } => "AddImpl",
Self::RemoveImpl { .. } => "RemoveImpl",
Self::AddItem { .. } => "AddItem",
Self::RemoveItem { .. } => "RemoveItem",
Self::Generic { mutation_type, .. } => mutation_type,
}
}
pub fn to_mutation(&self) -> Option<Box<dyn super::Mutation>> {
use super::*;
match self {
Self::Rename { symbol_id, to } => {
let id = ryo_symbol::SymbolId::parse(symbol_id)?;
Some(Box::new(RenameMutation::new(id, to)))
}
Self::ChangeVisibility {
symbol_id,
visibility,
} => {
let id = ryo_symbol::SymbolId::parse(symbol_id)?;
let vis = parse_visibility(visibility);
Some(Box::new(ChangeVisibilityMutation::new(id, vis)))
}
Self::AddFunction { .. } => None,
Self::RemoveFunction { .. } => None,
Self::AddStruct { .. } => None,
Self::RemoveStruct { .. } => None,
Self::AddField { .. } => None,
Self::RemoveField { .. } => None,
Self::AddUse { path } => Some(Box::new(AddUseMutation::new(path))),
Self::RemoveUse { path } => Some(Box::new(RemoveUseMutation::new(path))),
Self::AddDerive { .. } => None,
Self::RemoveDerive { .. } => None,
Self::AddEnum { .. } => None,
Self::RemoveEnum { .. } => None,
Self::AddVariant { .. } => None,
Self::RemoveVariant { .. } => None,
Self::AddMatchArm { .. } => None,
Self::RemoveMatchArm { .. } => None,
Self::ReplaceMatchArm { .. } => None,
Self::AddStructLiteralField { .. } => None,
Self::AddConst { .. } => None,
Self::RemoveConst { .. } => None,
Self::AddTypeAlias { .. } => None,
Self::RemoveTypeAlias { .. } => None,
Self::RemoveTrait { .. } => None,
Self::AddImpl { .. } => None,
Self::RemoveImpl { .. } => None,
Self::AddItem { .. } => None,
Self::RemoveItem { .. } => None,
Self::Generic { .. } => None,
}
}
pub fn to_json(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
}
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
serde_json::from_value(value.clone()).ok()
}
}
pub trait ToSerializable {
fn to_serializable(&self) -> SerializableMutation;
}
fn parse_visibility(s: &str) -> PureVis {
match s {
"pub" => PureVis::Public,
"pub(crate)" => PureVis::Crate,
"pub(super)" => PureVis::Super,
"" | "private" => PureVis::Private,
other if other.starts_with("pub(in ") => {
let path = other
.strip_prefix("pub(in ")
.and_then(|s| s.strip_suffix(')'));
if let Some(p) = path {
PureVis::In(p.to_string())
} else {
PureVis::Private
}
}
_ => PureVis::Private,
}
}
fn visibility_to_string(vis: &PureVis) -> String {
match vis {
PureVis::Public => "pub".to_string(),
PureVis::Crate => "pub(crate)".to_string(),
PureVis::Super => "pub(super)".to_string(),
PureVis::Private => String::new(),
PureVis::In(path) => format!("pub(in {})", path),
}
}
impl ToSerializable for super::RenameMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::Rename {
symbol_id: format!("{:?}", self.symbol_id),
to: self.to.clone(),
}
}
}
impl ToSerializable for super::ChangeVisibilityMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::ChangeVisibility {
symbol_id: format!("{:?}", self.symbol_id),
visibility: visibility_to_string(&self.to),
}
}
}
impl ToSerializable for super::AddFunctionMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddFunction {
symbol_id: format!("{}", self.parent),
name: self.name.clone(),
params: self.params.clone(),
return_type: self.return_type.clone(),
body: self.body.clone(),
is_pub: self.is_pub,
}
}
}
impl ToSerializable for super::RemoveFunctionMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveFunction {
symbol_id: format!("{}", self.symbol_id),
}
}
}
impl ToSerializable for super::AddStructMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddStruct {
name: self.name.clone(),
fields: self
.fields
.iter()
.map(|f| (f.name.clone(), f.ty.clone(), f.is_pub))
.collect(),
is_pub: self.is_pub,
}
}
}
impl ToSerializable for super::RemoveStructMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveStruct {
name: self.struct_id.to_string(),
}
}
}
impl ToSerializable for super::AddFieldMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddField {
struct_name: format!("{:?}", self.struct_id),
field_name: self.field_name.clone(),
field_type: self.field_type.clone(),
is_pub: self.is_pub,
}
}
}
impl ToSerializable for super::RemoveFieldMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveField {
struct_name: format!("{:?}", self.struct_id),
field_name: self.field_name.clone(),
}
}
}
impl ToSerializable for super::AddUseMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddUse {
path: self.path.clone(),
}
}
}
impl ToSerializable for super::RemoveUseMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveUse {
path: self.path.clone(),
}
}
}
impl ToSerializable for super::AddDeriveMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddDerive {
symbol_id: format!("{:?}", self.symbol_id),
derives: self.derives.clone(),
}
}
}
impl ToSerializable for super::RemoveDeriveMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveDerive {
symbol_id: format!("{:?}", self.symbol_id),
derives: self.derives.clone(),
}
}
}
impl ToSerializable for super::AddEnumMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddEnum {
symbol_id: format!("{:?}", self.parent),
name: self.name.clone(),
variants: self.variants.iter().map(|(name, _)| name.clone()).collect(),
derives: self.derives.clone(),
is_pub: self.is_pub,
}
}
}
impl ToSerializable for super::RemoveEnumMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveEnum {
symbol_id: format!("{:?}", self.symbol_id),
}
}
}
impl ToSerializable for super::AddVariantMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddVariant {
enum_name: format!("{:?}", self.enum_id),
variant_name: self.variant_name.clone(),
}
}
}
impl ToSerializable for super::RemoveVariantMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveVariant {
enum_name: format!("{:?}", self.enum_id),
variant_name: self.variant_name.clone(),
}
}
}
impl ToSerializable for super::AddMatchArmMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddMatchArm {
symbol_id: self.function_id.to_string(),
enum_name: self.enum_name.clone(),
pattern: self.pattern.clone(),
body: self.body.clone(),
}
}
}
impl ToSerializable for super::RemoveMatchArmMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveMatchArm {
symbol_id: self.function_id.to_string(),
enum_name: self.enum_name.clone(),
pattern: self.pattern.clone(),
}
}
}
impl ToSerializable for super::ReplaceMatchArmMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::ReplaceMatchArm {
symbol_id: self.function_id.to_string(),
enum_name: self.enum_name.clone(),
old_pattern: self.old_pattern.clone(),
new_pattern: self.new_pattern.clone(),
new_body: self.new_body.clone(),
}
}
}
impl ToSerializable for super::AddStructLiteralFieldMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddStructLiteralField {
struct_name: format!("{:?}", self.struct_id),
field_name: self.field_name.clone(),
value: self.value.clone(),
}
}
}
impl ToSerializable for super::AddConstMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddConst {
symbol_id: format!("{:?}", self.symbol_id),
name: self.name.clone(),
ty: self.ty.clone(),
value: self.value.clone(),
is_pub: self.is_pub,
}
}
}
impl ToSerializable for super::RemoveConstMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveConst {
symbol_id: format!("{:?}", self.symbol_id),
}
}
}
impl ToSerializable for super::AddTypeAliasMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddTypeAlias {
symbol_id: format!("{:?}", self.symbol_id),
name: self.name.clone(),
ty: self.ty.clone(),
is_pub: self.is_pub,
}
}
}
impl ToSerializable for super::RemoveTypeAliasMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveTypeAlias {
symbol_id: format!("{:?}", self.symbol_id),
}
}
}
impl ToSerializable for super::RemoveTraitMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveTrait {
name: self.trait_id.to_string(),
}
}
}
impl ToSerializable for super::AddImplMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddImpl {
symbol_id: self.parent.to_string(),
target: self.target.clone(),
trait_name: self.trait_name.clone(),
}
}
}
impl ToSerializable for super::RemoveImplMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveImpl {
symbol_id: self.symbol_id.to_string(),
}
}
}
impl ToSerializable for super::AddItemMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::AddItem {
symbol_id: self.parent.to_string(),
content: self.content.clone(),
}
}
}
impl ToSerializable for super::RemoveItemMutation {
fn to_serializable(&self) -> SerializableMutation {
SerializableMutation::RemoveItem {
symbol_id: self.symbol_id.to_string(),
item_kind: format!("{:?}", self.item_kind),
}
}
}
pub fn to_generic(mutation: &dyn super::Mutation) -> SerializableMutation {
SerializableMutation::Generic {
mutation_type: mutation.mutation_type().to_string(),
description: mutation.describe(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use ryo_symbol::{SymbolKind, SymbolPath, SymbolRegistry};
#[test]
fn test_rename_roundtrip() {
let mut registry = SymbolRegistry::new();
let path = SymbolPath::parse("test_crate::foo").unwrap();
let symbol_id = registry.register(path, SymbolKind::Function).unwrap();
let original = super::super::RenameMutation::new(symbol_id, "bar");
let serializable = original.to_serializable();
let json = serializable.to_json();
let restored = SerializableMutation::from_json(&json).unwrap();
let mutation = restored.to_mutation().unwrap();
assert_eq!(mutation.mutation_type(), "Rename");
assert!(mutation.describe().contains("bar"));
}
#[test]
fn test_add_function_roundtrip() {
let mut registry = SymbolRegistry::new();
let path = SymbolPath::parse("test_crate").unwrap();
let parent_id = registry.register(path, SymbolKind::Mod).unwrap();
let original = super::super::AddFunctionMutation::new(parent_id, "my_func")
.with_params(vec![("x".to_string(), "i32".to_string())])
.with_return_type("i32")
.with_body("x + 1")
.public();
let serializable = original.to_serializable();
let json = serializable.to_json();
let restored = SerializableMutation::from_json(&json).unwrap();
assert!(restored.to_mutation().is_none());
assert_eq!(restored.mutation_type(), "AddFunction");
}
#[test]
fn test_generic_fallback() {
let mut registry = SymbolRegistry::new();
let path = SymbolPath::parse("test_crate::a").unwrap();
let symbol_id = registry.register(path, SymbolKind::Function).unwrap();
let mutation = super::super::RenameMutation::new(symbol_id, "b");
let generic = to_generic(&mutation);
assert_eq!(generic.mutation_type(), "Rename");
if let SerializableMutation::Generic { description, .. } = generic {
assert!(description.contains("b"));
}
}
}