1use crate::error::{require, Error, Rule as FormatRule};
4use crate::value::{name, positive, row, Value};
5use crate::wire::{Decoder, Encoder, Wire};
6use crate::write_sources::ChangeId;
7use coven_foundation::id_source::MemberId;
8use coven_merge::{Cell, LostKey, LostValue, RowId, Rule, WriteId};
9use std::collections::{BTreeMap, BTreeSet};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Loss {
15 pub row: RowId,
17 pub generation: u64,
19 pub frozen: bool,
21 pub values: LossValues,
23 pub cause: LossCause,
25}
26impl Wire for Loss {
27 fn put(&self, out: &mut Encoder) -> Result<(), Error> {
28 self.row.put(out)?;
29 self.generation.put(out)?;
30 u8::from(self.frozen).put(out)?;
31 self.values.put(out)?;
32 self.cause.put(out)
33 }
34 fn get(input: &mut Decoder<'_>) -> Result<Self, Error> {
35 let row = Wire::get(input)?;
36 let generation = Wire::get(input)?;
37 let frozen = match u8::get(input)? {
38 0 => false,
39 1 => true,
40 tag => return Err(Error::UnknownTag { field: "loss retirement", tag }),
41 };
42 Ok(Self { row, generation, frozen, values: Wire::get(input)?, cause: Wire::get(input)? })
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum LossValues {
49 Cell {
51 column: String,
53 cell: Cell<Value>,
55 },
56 Row(BTreeMap<String, Cell<Value>>),
58}
59impl Wire for LossValues {
60 fn put(&self, out: &mut Encoder) -> Result<(), Error> {
61 match self {
62 Self::Cell { column, cell } => {
63 0u8.put(out)?;
64 column.put(out)?;
65 cell.put(out)
66 }
67 Self::Row(cells) => {
68 1u8.put(out)?;
69 cells.put(out)
70 }
71 }
72 }
73 fn get(input: &mut Decoder<'_>) -> Result<Self, Error> {
74 match u8::get(input)? {
75 0 => Ok(Self::Cell { column: crate::wire::get_name(input)?, cell: Wire::get(input)? }),
76 1 => Ok(Self::Row(crate::wire::get_name_map(input)?)),
77 tag => Err(Error::UnknownTag { field: "loss values", tag }),
78 }
79 }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
84pub enum LossCause {
85 Write(WriteId),
87 Rules(BTreeSet<Rule>),
89 Excluded {
92 write: WriteId,
94 version: u32,
96 },
97 Dependency {
101 write: WriteId,
103 sources: Vec<ChangeId>,
106 },
107 NotAuthorized {
111 write: WriteId,
113 reason: AuthorizationFailure,
115 },
116}
117
118#[derive(Clone, Debug, PartialEq, Eq)]
120pub enum AuthorizationFailure {
121 NotOwner {
124 owner: MemberId,
126 },
127 InvalidForeignKeyEffect,
130}
131impl Wire for AuthorizationFailure {
132 fn put(&self, out: &mut Encoder) -> Result<(), Error> {
133 match self {
134 Self::NotOwner { owner } => {
135 0u8.put(out)?;
136 owner.put(out)
137 }
138 Self::InvalidForeignKeyEffect => 1u8.put(out),
139 }
140 }
141 fn get(input: &mut Decoder<'_>) -> Result<Self, Error> {
142 match u8::get(input)? {
143 0 => Ok(Self::NotOwner { owner: Wire::get(input)? }),
144 1 => Ok(Self::InvalidForeignKeyEffect),
145 tag => Err(Error::UnknownTag { field: "authorization failure", tag }),
146 }
147 }
148}
149impl Wire for LossCause {
150 fn put(&self, out: &mut Encoder) -> Result<(), Error> {
151 match self {
152 Self::Write(write) => {
153 0u8.put(out)?;
154 write.put(out)
155 }
156 Self::Rules(rules) => {
157 1u8.put(out)?;
158 rules.put(out)
159 }
160 Self::Excluded { write, version } => {
161 2u8.put(out)?;
162 write.put(out)?;
163 version.put(out)
164 }
165 Self::Dependency { write, sources } => {
166 3u8.put(out)?;
167 write.put(out)?;
168 sources.put(out)
169 }
170 Self::NotAuthorized { write, reason } => {
171 4u8.put(out)?;
172 write.put(out)?;
173 reason.put(out)
174 }
175 }
176 }
177 fn get(input: &mut Decoder<'_>) -> Result<Self, Error> {
178 match u8::get(input)? {
179 0 => Ok(Self::Write(Wire::get(input)?)),
180 1 => Ok(Self::Rules(Wire::get(input)?)),
181 2 => Ok(Self::Excluded { write: Wire::get(input)?, version: Wire::get(input)? }),
182 3 => Ok(Self::Dependency { write: Wire::get(input)?, sources: Wire::get(input)? }),
183 4 => Ok(Self::NotAuthorized { write: Wire::get(input)?, reason: Wire::get(input)? }),
184 tag => Err(Error::UnknownTag { field: "loss cause", tag }),
185 }
186 }
187}
188
189#[derive(PartialEq, Eq, PartialOrd, Ord)]
191pub(crate) enum LossIdentity {
192 Cell(LostKey),
193 Row(BTreeMap<String, WriteId>),
194}
195impl LossCause {
196 pub fn change_write(&self) -> Option<WriteId> {
198 match self {
199 Self::Excluded { write, .. }
200 | Self::Dependency { write, .. }
201 | Self::NotAuthorized { write, .. } => Some(*write),
202 Self::Write(_) | Self::Rules(_) => None,
203 }
204 }
205
206 pub(crate) fn tag(&self) -> u8 {
207 match self {
208 Self::Write(_) => 0,
209 Self::Rules(_) => 1,
210 Self::Excluded { .. } => 2,
211 Self::Dependency { .. } => 3,
212 Self::NotAuthorized { .. } => 4,
213 }
214 }
215
216 pub fn freezes_change(&self) -> bool {
218 self.change_write().is_some()
219 }
220}
221
222impl Loss {
223 pub fn cell(row: RowId, key: LostKey, value: LostValue<Value>) -> Self {
225 Self {
226 row,
227 generation: value.incarnation,
228 frozen: false,
229 values: LossValues::Cell {
230 column: key.column,
231 cell: Cell { write: key.write, value: value.value },
232 },
233 cause: LossCause::Write(value.replaced_by),
234 }
235 }
236
237 pub fn freeze(mut self) -> Self {
240 self.frozen = true;
241 match &mut self.values {
242 LossValues::Cell { cell, .. } => cell.value.parents.clear(),
243 LossValues::Row(cells) => {
244 for cell in cells.values_mut() {
245 cell.value.parents.clear();
246 }
247 }
248 }
249 self
250 }
251
252 pub fn removed(state: &coven_merge::RowState<Value>, rules: BTreeSet<Rule>) -> Self {
254 Self {
255 row: state.row().clone(),
256 generation: state.generation(),
257 frozen: false,
258 values: LossValues::Row(state.cells().clone()),
259 cause: LossCause::Rules(rules),
260 }
261 }
262
263 pub(crate) fn key(&self) -> (RowId, u64, LossIdentity, bool, u8, Option<WriteId>) {
264 let identity = match &self.values {
265 LossValues::Cell { column, cell } => {
266 LossIdentity::Cell(LostKey { column: column.clone(), write: cell.write })
267 }
268 LossValues::Row(cells) => {
269 LossIdentity::Row(cells.iter().map(|(n, c)| (n.clone(), c.write)).collect())
270 }
271 };
272 (
273 self.row.clone(),
274 self.generation,
275 identity,
276 self.frozen,
277 self.cause.tag(),
278 self.cause.change_write(),
279 )
280 }
281
282 pub(crate) fn validate(&self) -> Result<(), Error> {
283 row(&self.row)?;
284 match (&self.values, &self.cause) {
285 (LossValues::Cell { .. }, LossCause::Write(write)) => positive(write.number)?,
286 (LossValues::Row(cells), LossCause::Rules(rules)) => {
287 require(
288 !cells.is_empty() && !rules.is_empty(),
289 "removed loss",
290 FormatRule::Required,
291 )?;
292 crate::merge_wire::rules(rules)?;
293 for rule in rules {
294 if matches!(rule, Rule::DeletedCircle(_) | Rule::OtherAudience) {
295 require(
296 matches!(self.row.audience, coven_merge::Audience::Circle(_)),
297 "loss audience",
298 FormatRule::Audience,
299 )?;
300 }
301 }
302 }
303 (LossValues::Row(cells), LossCause::Excluded { write, version }) => {
304 positive(write.number)?;
305 for cell in cells.values() {
309 positive(cell.write.number)?;
310 }
311 require(self.frozen, "excluded loss", FormatRule::Required)?;
312 require(*version > 0, "breaking schema version", FormatRule::Required)?;
313 }
314 (LossValues::Row(cells), LossCause::Dependency { write, sources }) => {
315 positive(write.number)?;
316 require(
317 cells.values().all(|cell| cell.write == *write),
318 "dependent setters",
319 FormatRule::Required,
320 )?;
321 require(self.frozen, "dependent loss", FormatRule::Required)?;
322 require(!sources.is_empty(), "dependency sources", FormatRule::Required)?;
323 for source in sources {
324 positive(source.write.number)?;
325 require(
326 dependency_source(&self.row, *write, source),
327 "dependency source row",
328 FormatRule::Required,
329 )?;
330 }
331 }
332 (LossValues::Row(cells), LossCause::NotAuthorized { write, .. }) => {
333 positive(write.number)?;
334 require(
335 cells.values().all(|cell| cell.write == *write),
336 "unauthorized setters",
337 FormatRule::Required,
338 )?;
339 require(self.frozen, "unauthorized loss", FormatRule::Required)?;
340 }
341 _ => {
342 return Err(Error::Invalid {
343 field: "loss cause",
344 rule: FormatRule::ColumnOperation,
345 })
346 }
347 }
348 if !self.cause.freezes_change() {
349 require(
350 coven_merge::generation_present(self.generation),
351 "loss incarnation",
352 FormatRule::Generation,
353 )?;
354 }
355 let cell = |n: &str, cell: &Cell<Value>| {
356 name(n)?;
357 positive(cell.write.number)?;
358 crate::merge_wire::column(&cell.value)?;
359 if self.frozen {
360 require(cell.value.parents.is_empty(), "frozen references", FormatRule::Required)?;
361 } else {
362 for parent in cell.value.parents.values() {
363 parent.validate_written(&self.row).map_err(Error::Merge)?;
364 }
365 }
366 Ok(())
367 };
368 match &self.values {
369 LossValues::Cell { column, cell: value } => cell(column, value),
370 LossValues::Row(cells) => cells.iter().try_for_each(|(n, c)| cell(n, c)),
371 }
372 }
373
374 pub(crate) fn named_writes(&self) -> Vec<WriteId> {
376 let mut writes: Vec<WriteId> = match &self.values {
377 LossValues::Cell { cell, .. } => vec![cell.write],
378 LossValues::Row(cells) => cells.values().map(|cell| cell.write).collect(),
379 };
380 match self.cause {
381 LossCause::Write(write)
382 | LossCause::Excluded { write, .. }
383 | LossCause::NotAuthorized { write, .. } => writes.push(write),
384 LossCause::Dependency { write, ref sources } => {
385 writes.push(write);
386 writes.extend(sources.iter().map(|s| s.write));
387 }
388 _ => {}
389 }
390 writes
391 }
392}
393
394pub(crate) fn dependency_source(row: &RowId, write: WriteId, source: &ChangeId) -> bool {
397 source.write == write || (source.row.key == row.key && source.row.audience == row.audience)
398}
399
400#[cfg(test)]
401#[path = "loss_tests.rs"]
402mod tests;