use serde::{Deserialize, Serialize};
use weave_core::conflict::{ConflictKind, EntityConflict};
pub const SCHEMA_VERSION_1_1: &str = "1.1.0";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Sides {
pub ours: &'static str,
pub theirs: &'static str,
pub ours_mid: &'static str,
pub theirs_mid: &'static str,
}
impl Sides {
pub const MERGE: Sides = Sides {
ours: "Ours",
theirs: "Theirs",
ours_mid: "ours",
theirs_mid: "theirs",
};
pub(crate) const PATCH: Sides = Sides {
ours: "The target",
theirs: "The patch",
ours_mid: "the target",
theirs_mid: "the patch",
};
fn initial(self, is_ours: bool) -> &'static str {
if is_ours {
self.ours
} else {
self.theirs
}
}
fn mid(self, is_ours: bool) -> &'static str {
if is_ours {
self.ours_mid
} else {
self.theirs_mid
}
}
}
pub fn conflict_advice(c: &EntityConflict, sides: Sides) -> (&'static str, String) {
let name = &c.entity_name;
let suggestion = match &c.kind {
ConflictKind::RenameRename {
base_name,
ours_name,
theirs_name,
} => format!(
"{} renamed `{base_name}` to `{ours_name}` while {} renamed it to `{theirs_name}`. \
Pick one name, then rewrite every call site to it.",
sides.ours, sides.theirs_mid
),
ConflictKind::RenameModify {
old_name,
new_name,
renamed_in_ours,
} => format!(
"{} renamed `{old_name}` to `{new_name}` while the other edited its body. \
Keep the new name and replay the body edit onto it.",
sides.initial(*renamed_in_ours)
),
ConflictKind::ModifyDelete { modified_in_ours } => format!(
"`{name}` was deleted on one side and modified on the other ({} kept it). \
Decide whether it still has a reason to exist before resolving.",
sides.mid(*modified_in_ours)
),
ConflictKind::BothAdded => format!(
"`{name}` was added independently by {} and {}. Reconcile them into one definition.",
sides.ours_mid, sides.theirs_mid
),
ConflictKind::BothModified => format!(
"{} and {} both changed the body of `{name}` in ways weave will not guess between.",
sides.ours, sides.theirs_mid
),
};
(c.kind.wire_kind(), suggestion)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Findings {
pub schema_version: String,
pub file: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
pub clean: bool,
pub confidence: String,
pub findings: Vec<Finding>,
pub ops: Vec<Op>,
pub bindings: Bindings,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Finding {
pub class: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
pub entity: String,
pub entity_type: String,
pub source: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub side: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub complexity: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub confidence: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ours: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub theirs: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggestion: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub warning_kind: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub related: Vec<RelatedRef>,
}
impl Finding {
pub(crate) fn derived(class: &str, entity: &str, entity_type: &str) -> Self {
Finding {
class: class.to_string(),
kind: None,
entity: entity.to_string(),
entity_type: entity_type.to_string(),
source: "derived".to_string(),
side: None,
complexity: None,
confidence: None,
base: None,
ours: None,
theirs: None,
suggestion: None,
warning_kind: None,
related: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RelatedRef {
pub entity: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub entity_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Op {
pub entity: String,
pub entity_type: String,
pub op: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub conflicted: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub fallback: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Bindings {
pub renames: Vec<Rename>,
pub references: Vec<Reference>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Rename {
pub from: String,
pub to: String,
pub side: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Reference {
pub entity: String,
pub defined_in_output: bool,
pub referenced_in_output: bool,
}
fn is_false(b: &bool) -> bool {
!*b
}