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