1use serde::{Deserialize, Serialize};
4
5use super::CollaborationCodecError;
6use crate::object::{
7 Attribution, ChangeId, ContentHash, StateId, VisibilityTier,
8 collaboration::{
9 COLLABORATION_OPERATION_SCHEMA_VERSION, CollabOpId, CollaborationAnchor,
10 CollaborationIdempotencyKey, CollaborationOperationBodyV1, CollaborationOperationEnvelope,
11 CollaborationResolution, DiscussionRecordId, DiscussionTurnV1, LegacyDiscussionId,
12 LegacyDiscussionResolutionV1, LegacySourceLocator,
13 },
14};
15
16#[derive(Serialize, Deserialize)]
17struct WireOperationV1 {
18 schema_version: u16,
19 discussion_id: DiscussionRecordId,
20 parents: Vec<CollabOpId>,
21 idempotency_key: CollaborationIdempotencyKey,
22 author: Attribution,
23 occurred_at_ms: i64,
24 body: WireBodyV1,
25}
26
27#[derive(Serialize, Deserialize)]
28#[serde(rename_all = "snake_case", tag = "kind")]
29enum WireAnchorV1 {
30 Repository,
31 State {
32 state_id: StateId,
33 },
34 Change {
35 change_id: ChangeId,
36 },
37 Path {
38 state_id: StateId,
39 path: String,
40 },
41 Symbol {
42 state_id: StateId,
43 path: String,
44 symbol: String,
45 },
46}
47
48#[derive(Serialize, Deserialize)]
49struct WireTurnV1 {
50 body: String,
51 content_hash: ContentHash,
52}
53
54#[derive(Serialize, Deserialize)]
55#[serde(rename_all = "snake_case", tag = "kind")]
56enum WireResolutionV1 {
57 AddressedByState { state_id: StateId },
58 AddressedByChange { change_id: ChangeId },
59 Dismissed { reason: String },
60 Annotation { annotation_id: String },
61}
62
63#[derive(Serialize, Deserialize)]
64#[serde(rename_all = "snake_case", tag = "kind")]
65enum WireLegacyResolutionV1 {
66 Open,
67 AddressedByState { state_id: StateId },
68 Dismissed { reason: String },
69 Annotation { annotation_id: String },
70}
71
72#[derive(Serialize, Deserialize)]
73#[serde(rename_all = "snake_case", tag = "kind")]
74enum WireBodyV1 {
75 Open {
76 title: String,
77 anchor: WireAnchorV1,
78 visibility: VisibilityTier,
79 turn: WireTurnV1,
80 },
81 AppendTurn {
82 turn: WireTurnV1,
83 },
84 Resolve {
85 resolution: WireResolutionV1,
86 },
87 Reopen {
88 reason: String,
89 },
90 ResolveConflict {
91 competing: Vec<CollabOpId>,
92 selected: CollabOpId,
93 },
94 LegacyImported {
95 source: LegacySourceLocator,
96 legacy_discussion_id: LegacyDiscussionId,
97 aliases: Vec<LegacySourceLocator>,
98 title: String,
99 anchor: WireAnchorV1,
100 visibility: VisibilityTier,
101 turns: Vec<WireTurnV1>,
102 resolution: WireLegacyResolutionV1,
103 },
104}
105
106pub(super) fn encode(
107 operation: &CollaborationOperationEnvelope,
108) -> Result<Vec<u8>, CollaborationCodecError> {
109 let wire = WireOperationV1 {
110 schema_version: COLLABORATION_OPERATION_SCHEMA_VERSION,
111 discussion_id: operation.discussion_id,
112 parents: operation.parents.clone(),
113 idempotency_key: operation.idempotency_key.clone(),
114 author: operation.author.clone(),
115 occurred_at_ms: operation.occurred_at_ms,
116 body: operation.body.clone().into(),
117 };
118 rmp_serde::to_vec_named(&wire)
119 .map_err(|error| CollaborationCodecError::Encoding(error.to_string()))
120}
121
122pub(super) fn decode(
123 bytes: &[u8],
124) -> Result<CollaborationOperationEnvelope, CollaborationCodecError> {
125 let wire: WireOperationV1 = rmp_serde::from_slice(bytes)
126 .map_err(|error| CollaborationCodecError::Decoding(error.to_string()))?;
127 Ok(CollaborationOperationEnvelope {
128 discussion_id: wire.discussion_id,
129 parents: wire.parents,
130 idempotency_key: wire.idempotency_key,
131 author: wire.author,
132 occurred_at_ms: wire.occurred_at_ms,
133 body: wire.body.into(),
134 })
135}
136
137impl From<CollaborationAnchor> for WireAnchorV1 {
138 fn from(value: CollaborationAnchor) -> Self {
139 match value {
140 CollaborationAnchor::Repository => Self::Repository,
141 CollaborationAnchor::State { state_id } => Self::State { state_id },
142 CollaborationAnchor::Change { change_id } => Self::Change { change_id },
143 CollaborationAnchor::Path { state_id, path } => Self::Path { state_id, path },
144 CollaborationAnchor::Symbol {
145 state_id,
146 path,
147 symbol,
148 } => Self::Symbol {
149 state_id,
150 path,
151 symbol,
152 },
153 }
154 }
155}
156
157impl From<WireAnchorV1> for CollaborationAnchor {
158 fn from(value: WireAnchorV1) -> Self {
159 match value {
160 WireAnchorV1::Repository => Self::Repository,
161 WireAnchorV1::State { state_id } => Self::State { state_id },
162 WireAnchorV1::Change { change_id } => Self::Change { change_id },
163 WireAnchorV1::Path { state_id, path } => Self::Path { state_id, path },
164 WireAnchorV1::Symbol {
165 state_id,
166 path,
167 symbol,
168 } => Self::Symbol {
169 state_id,
170 path,
171 symbol,
172 },
173 }
174 }
175}
176
177impl From<DiscussionTurnV1> for WireTurnV1 {
178 fn from(value: DiscussionTurnV1) -> Self {
179 Self {
180 body: value.body,
181 content_hash: value.content_hash,
182 }
183 }
184}
185
186impl From<WireTurnV1> for DiscussionTurnV1 {
187 fn from(value: WireTurnV1) -> Self {
188 Self {
189 body: value.body,
190 content_hash: value.content_hash,
191 }
192 }
193}
194
195impl From<CollaborationResolution> for WireResolutionV1 {
196 fn from(value: CollaborationResolution) -> Self {
197 match value {
198 CollaborationResolution::AddressedByState { state_id } => {
199 Self::AddressedByState { state_id }
200 }
201 CollaborationResolution::AddressedByChange { change_id } => {
202 Self::AddressedByChange { change_id }
203 }
204 CollaborationResolution::Dismissed { reason } => Self::Dismissed { reason },
205 CollaborationResolution::Annotation { annotation_id } => {
206 Self::Annotation { annotation_id }
207 }
208 }
209 }
210}
211
212impl From<WireResolutionV1> for CollaborationResolution {
213 fn from(value: WireResolutionV1) -> Self {
214 match value {
215 WireResolutionV1::AddressedByState { state_id } => Self::AddressedByState { state_id },
216 WireResolutionV1::AddressedByChange { change_id } => {
217 Self::AddressedByChange { change_id }
218 }
219 WireResolutionV1::Dismissed { reason } => Self::Dismissed { reason },
220 WireResolutionV1::Annotation { annotation_id } => Self::Annotation { annotation_id },
221 }
222 }
223}
224
225impl From<LegacyDiscussionResolutionV1> for WireLegacyResolutionV1 {
226 fn from(value: LegacyDiscussionResolutionV1) -> Self {
227 match value {
228 LegacyDiscussionResolutionV1::Open => Self::Open,
229 LegacyDiscussionResolutionV1::AddressedByState { state_id } => {
230 Self::AddressedByState { state_id }
231 }
232 LegacyDiscussionResolutionV1::Dismissed { reason } => Self::Dismissed { reason },
233 LegacyDiscussionResolutionV1::Annotation { annotation_id } => {
234 Self::Annotation { annotation_id }
235 }
236 }
237 }
238}
239
240impl From<WireLegacyResolutionV1> for LegacyDiscussionResolutionV1 {
241 fn from(value: WireLegacyResolutionV1) -> Self {
242 match value {
243 WireLegacyResolutionV1::Open => Self::Open,
244 WireLegacyResolutionV1::AddressedByState { state_id } => {
245 Self::AddressedByState { state_id }
246 }
247 WireLegacyResolutionV1::Dismissed { reason } => Self::Dismissed { reason },
248 WireLegacyResolutionV1::Annotation { annotation_id } => {
249 Self::Annotation { annotation_id }
250 }
251 }
252 }
253}
254
255impl From<CollaborationOperationBodyV1> for WireBodyV1 {
256 fn from(value: CollaborationOperationBodyV1) -> Self {
257 match value {
258 CollaborationOperationBodyV1::Open {
259 title,
260 anchor,
261 visibility,
262 turn,
263 } => Self::Open {
264 title,
265 anchor: anchor.into(),
266 visibility,
267 turn: turn.into(),
268 },
269 CollaborationOperationBodyV1::AppendTurn { turn } => {
270 Self::AppendTurn { turn: turn.into() }
271 }
272 CollaborationOperationBodyV1::Resolve { resolution } => Self::Resolve {
273 resolution: resolution.into(),
274 },
275 CollaborationOperationBodyV1::Reopen { reason } => Self::Reopen { reason },
276 CollaborationOperationBodyV1::ResolveConflict {
277 competing,
278 selected,
279 } => Self::ResolveConflict {
280 competing,
281 selected,
282 },
283 CollaborationOperationBodyV1::LegacyImported {
284 source,
285 legacy_discussion_id,
286 aliases,
287 title,
288 anchor,
289 visibility,
290 turns,
291 resolution,
292 } => Self::LegacyImported {
293 source,
294 legacy_discussion_id,
295 aliases,
296 title,
297 anchor: anchor.into(),
298 visibility,
299 turns: turns.into_iter().map(Into::into).collect(),
300 resolution: resolution.into(),
301 },
302 }
303 }
304}
305
306impl From<WireBodyV1> for CollaborationOperationBodyV1 {
307 fn from(value: WireBodyV1) -> Self {
308 match value {
309 WireBodyV1::Open {
310 title,
311 anchor,
312 visibility,
313 turn,
314 } => Self::Open {
315 title,
316 anchor: anchor.into(),
317 visibility,
318 turn: turn.into(),
319 },
320 WireBodyV1::AppendTurn { turn } => Self::AppendTurn { turn: turn.into() },
321 WireBodyV1::Resolve { resolution } => Self::Resolve {
322 resolution: resolution.into(),
323 },
324 WireBodyV1::Reopen { reason } => Self::Reopen { reason },
325 WireBodyV1::ResolveConflict {
326 competing,
327 selected,
328 } => Self::ResolveConflict {
329 competing,
330 selected,
331 },
332 WireBodyV1::LegacyImported {
333 source,
334 legacy_discussion_id,
335 aliases,
336 title,
337 anchor,
338 visibility,
339 turns,
340 resolution,
341 } => Self::LegacyImported {
342 source,
343 legacy_discussion_id,
344 aliases,
345 title,
346 anchor: anchor.into(),
347 visibility,
348 turns: turns.into_iter().map(Into::into).collect(),
349 resolution: resolution.into(),
350 },
351 }
352 }
353}