1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::store_commit::StoreBatchCommitRef;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct WriteId(String);
13
14impl WriteId {
15 pub fn from_generated(value: String) -> Self {
16 Self(value)
17 }
18
19 pub fn as_str(&self) -> &str {
20 &self.0
21 }
22}
23
24impl fmt::Display for WriteId {
25 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26 formatter.write_str(&self.0)
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct PublishedPosition {
34 pub device_id: String,
35 pub commit: StoreBatchCommitRef,
36}
37
38impl PublishedPosition {
39 pub fn commit(&self) -> &StoreBatchCommitRef {
40 &self.commit
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case", deny_unknown_fields)]
47pub enum WriteBlock {
48 InvalidPackage {
49 reason: String,
50 },
51 InvalidProtocolState {
52 reason: String,
53 },
54 MissingBlob {
55 namespace: String,
56 id: String,
57 },
58 LocalUserBlob {
59 namespace: String,
60 id: String,
61 },
62 RotationRequired {
63 circle_id: crate::circle::CircleId,
64 removed_members: Vec<String>,
65 },
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case", deny_unknown_fields)]
71pub enum WriteStatus {
72 LocalOnly,
73 Pending,
74 Publishing,
75 Published(Box<PublishedPosition>),
76 Blocked(WriteBlock),
77 Resolved(WriteResolution),
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(rename_all = "snake_case", deny_unknown_fields)]
82pub enum WriteResolution {
83 Discarded,
84 Retracted { witness: WriteRetractionWitness },
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(deny_unknown_fields)]
90pub struct WriteRetractionWitness {
91 original: PublishedPosition,
92 nonactivation: crate::remote_object::CandidateNonactivation,
93}
94
95#[derive(Debug, thiserror::Error)]
96pub enum WriteRetractionError {
97 #[error("invalid candidate nonactivation: {0}")]
98 Nonactivation(#[from] crate::remote_object::RemoteObjectRecordError),
99 #[error("write retraction proof names another published commit")]
100 CommitMismatch,
101}
102
103impl WriteRetractionWitness {
104 pub fn new(
105 original: PublishedPosition,
106 nonactivation: crate::remote_object::CandidateNonactivation,
107 ) -> Result<Self, WriteRetractionError> {
108 let candidate = nonactivation.reference()?;
109 if original.commit() != &candidate {
110 return Err(WriteRetractionError::CommitMismatch);
111 }
112 let witness = Self {
113 original,
114 nonactivation,
115 };
116 witness.validate()?;
117 Ok(witness)
118 }
119
120 pub fn original_position(&self) -> &PublishedPosition {
121 &self.original
122 }
123
124 pub fn validate(&self) -> Result<(), WriteRetractionError> {
125 let candidate = self.nonactivation.reference()?;
126 if self.original.commit() != &candidate {
127 return Err(WriteRetractionError::CommitMismatch);
128 }
129 Ok(())
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
135#[serde(deny_unknown_fields)]
136pub struct AffectedRow {
137 pub table: String,
138 pub primary_key: String,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct PendingWrite {
144 pub write_id: WriteId,
145 pub status: WriteStatus,
146 pub affected_rows: Vec<AffectedRow>,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct WriteReceipt<R> {
152 pub value: R,
153 pub write_id: WriteId,
154 pub status: WriteStatus,
155}