1use serde::{Deserialize, Serialize};
16
17use weave_core::conflict::{ConflictKind, EntityConflict};
18
19pub const SCHEMA_VERSION_1_1: &str = "1.1.0";
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct Sides {
39 pub ours: &'static str,
41 pub theirs: &'static str,
42 pub ours_mid: &'static str,
44 pub theirs_mid: &'static str,
45}
46
47impl Sides {
48 pub const MERGE: Sides = Sides {
50 ours: "Ours",
51 theirs: "Theirs",
52 ours_mid: "ours",
53 theirs_mid: "theirs",
54 };
55 pub(crate) const PATCH: Sides = Sides {
57 ours: "The target",
58 theirs: "The patch",
59 ours_mid: "the target",
60 theirs_mid: "the patch",
61 };
62
63 fn initial(self, is_ours: bool) -> &'static str {
64 if is_ours {
65 self.ours
66 } else {
67 self.theirs
68 }
69 }
70 fn mid(self, is_ours: bool) -> &'static str {
71 if is_ours {
72 self.ours_mid
73 } else {
74 self.theirs_mid
75 }
76 }
77}
78
79pub fn conflict_advice(c: &EntityConflict, sides: Sides) -> (&'static str, String) {
94 let name = &c.entity_name;
95 let suggestion = match &c.kind {
96 ConflictKind::RenameRename {
97 base_name,
98 ours_name,
99 theirs_name,
100 } => format!(
101 "{} renamed `{base_name}` to `{ours_name}` while {} renamed it to `{theirs_name}`. \
102 Pick one name, then rewrite every call site to it.",
103 sides.ours, sides.theirs_mid
104 ),
105 ConflictKind::RenameModify {
106 old_name,
107 new_name,
108 renamed_in_ours,
109 } => format!(
110 "{} renamed `{old_name}` to `{new_name}` while the other edited its body. \
111 Keep the new name and replay the body edit onto it.",
112 sides.initial(*renamed_in_ours)
113 ),
114 ConflictKind::ModifyDelete { modified_in_ours } => format!(
115 "`{name}` was deleted on one side and modified on the other ({} kept it). \
116 Decide whether it still has a reason to exist before resolving.",
117 sides.mid(*modified_in_ours)
118 ),
119 ConflictKind::BothAdded => format!(
120 "`{name}` was added independently by {} and {}. Reconcile them into one definition.",
121 sides.ours_mid, sides.theirs_mid
122 ),
123 ConflictKind::BothModified => format!(
124 "{} and {} both changed the body of `{name}` in ways weave will not guess between.",
125 sides.ours, sides.theirs_mid
126 ),
127 };
128 (c.kind.wire_kind(), suggestion)
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
132pub struct Findings {
133 pub schema_version: String,
134 pub file: String,
135 #[serde(skip_serializing_if = "Option::is_none")]
138 pub scope: Option<String>,
139 pub clean: bool,
140 pub confidence: String,
141 pub findings: Vec<Finding>,
142 pub ops: Vec<Op>,
143 pub bindings: Bindings,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
147pub struct Finding {
148 pub class: String,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub kind: Option<String>,
151 pub entity: String,
152 pub entity_type: String,
153 pub source: String,
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub side: Option<String>,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub complexity: Option<String>,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub confidence: Option<String>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub base: Option<String>,
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub ours: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub theirs: Option<String>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub suggestion: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub warning_kind: Option<String>,
171 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 pub related: Vec<RelatedRef>,
173}
174
175impl Finding {
176 pub(crate) fn derived(class: &str, entity: &str, entity_type: &str) -> Self {
177 Finding {
178 class: class.to_string(),
179 kind: None,
180 entity: entity.to_string(),
181 entity_type: entity_type.to_string(),
182 source: "derived".to_string(),
183 side: None,
184 complexity: None,
185 confidence: None,
186 base: None,
187 ours: None,
188 theirs: None,
189 suggestion: None,
190 warning_kind: None,
191 related: Vec::new(),
192 }
193 }
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
197pub struct RelatedRef {
198 pub entity: String,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub entity_type: Option<String>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub file: Option<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
206pub struct Op {
207 pub entity: String,
208 pub entity_type: String,
209 pub op: String,
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub from: Option<String>,
212 #[serde(skip_serializing_if = "Option::is_none")]
213 pub to: Option<String>,
214 #[serde(default, skip_serializing_if = "is_false")]
215 pub conflicted: bool,
216 #[serde(default, skip_serializing_if = "is_false")]
217 pub fallback: bool,
218}
219
220#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
221pub struct Bindings {
222 pub renames: Vec<Rename>,
223 pub references: Vec<Reference>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
227pub struct Rename {
228 pub from: String,
229 pub to: String,
230 pub side: String,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234pub struct Reference {
235 pub entity: String,
236 pub defined_in_output: bool,
237 pub referenced_in_output: bool,
238}
239
240fn is_false(b: &bool) -> bool {
241 !*b
242}