1use super::outdated_encode_reordered::ValueRegister;
2use crate::{
3 arena::SharedArena,
4 change::{Change, ChangeRef},
5 container::{
6 list::list_op::{DeleteSpan, DeleteSpanWithId, InnerListOp},
7 map::MapSet,
8 richtext::TextStyleInfoFlag,
9 tree::tree_op::TreeOp,
10 },
11 op::{FutureInnerContent, InnerContent, Op, SliceRange},
12 oplog::BlockChangeRef,
13 version::Frontiers,
14 OpLog, VersionVector,
15};
16use either::Either;
17use itertools::Itertools;
18use json::{JsonChange, JsonOpContent, JsonSchema};
19use loro_common::{
20 ContainerID, ContainerType, HasCounterSpan, HasId, IdLp, IdSpan, LoroError, LoroResult,
21 LoroValue, PeerID, TreeID, ID,
22};
23use rle::{HasLength, RleVec, Sliceable};
24use std::sync::Arc;
25
26const SCHEMA_VERSION: u8 = 1;
27
28fn refine_vv(vv: &VersionVector, oplog: &OpLog) -> VersionVector {
29 let mut refined = VersionVector::new();
30 for (&peer, &counter) in vv.iter() {
31 if counter == 0 {
32 continue;
33 }
34 let end = oplog.vv().get(&peer).copied().unwrap_or(0);
35 if end <= counter {
36 refined.insert(peer, end);
37 } else {
38 refined.insert(peer, counter);
39 }
40 }
41 refined
42}
43
44pub(crate) fn export_json<'a, 'c: 'a>(
45 oplog: &'c OpLog,
46 start_vv: &VersionVector,
47 end_vv: &VersionVector,
48 with_peer_compression: bool,
49) -> JsonSchema {
50 let actual_start_vv = refine_vv(start_vv, oplog);
51 let actual_end_vv = refine_vv(end_vv, oplog);
52
53 let frontiers = oplog.dag.vv_to_frontiers(&actual_start_vv);
54
55 let diff_changes = init_encode(oplog, &actual_start_vv, &actual_end_vv);
56 if with_peer_compression {
57 let mut peer_register = ValueRegister::<PeerID>::new();
58 let changes = encode_changes(&diff_changes, &oplog.arena, Some(&mut peer_register));
59 JsonSchema {
60 changes,
61 schema_version: SCHEMA_VERSION,
62 peers: Some(peer_register.unwrap_vec()),
63 start_version: frontiers,
64 }
65 } else {
66 let changes = encode_changes(&diff_changes, &oplog.arena, None);
67 JsonSchema {
68 changes,
69 schema_version: SCHEMA_VERSION,
70 peers: None,
71 start_version: frontiers,
72 }
73 }
74}
75
76pub(crate) fn export_json_in_id_span(oplog: &OpLog, mut id_span: IdSpan) -> Vec<json::JsonChange> {
77 id_span.normalize_();
78 let end = oplog.vv().get(&id_span.peer).copied().unwrap_or(0);
79 if id_span.counter.start >= end {
80 return vec![];
81 }
82
83 id_span.counter.end = id_span.counter.end.min(end);
84 let mut diff_changes: Vec<Either<BlockChangeRef, Change>> = Vec::new();
85 while id_span.counter.end - id_span.counter.start > 0 {
86 let Some(change) = oplog.get_change_at(id_span.id_start()) else {
87 break;
88 };
89 let ctr_end = change.ctr_end();
90 if change.id.counter >= id_span.counter.start && change.ctr_end() <= id_span.counter.end {
91 diff_changes.push(Either::Left(change));
92 } else {
93 let start = if change.id.counter < id_span.counter.start {
94 (id_span.counter.start - change.id.counter) as usize
95 } else {
96 0
97 };
98
99 let end = if change.ctr_end() > id_span.counter.end {
100 (id_span.counter.end - change.id.counter) as usize
101 } else {
102 change.atom_len()
103 };
104
105 diff_changes.push(Either::Right(change.slice(start, end)));
106 }
107
108 id_span.counter.start = ctr_end;
109 }
110
111 encode_changes(&diff_changes, &oplog.arena, None)
112}
113
114pub(crate) fn decode_json_changes(
115 json: JsonSchema,
116 arena: &SharedArena,
117) -> LoroResult<Vec<Change>> {
118 decode_changes(json, arena)
119}
120
121fn init_encode<'s, 'a: 's>(
122 oplog: &'a OpLog,
123 start_vv: &VersionVector,
124 end_vv: &VersionVector,
125) -> Vec<Either<BlockChangeRef, Change>> {
126 let mut diff_changes: Vec<Either<BlockChangeRef, Change>> = Vec::new();
127 for change in oplog.iter_changes_peer_by_peer(start_vv, end_vv) {
128 let start_cnt = start_vv.get(&change.id.peer).copied().unwrap_or(0);
129 let end_cnt = end_vv.get(&change.id.peer).copied().unwrap_or(0);
130 if change.id.counter < start_cnt {
131 if change.ctr_end() <= start_cnt {
132 continue;
133 }
134
135 let offset = start_cnt - change.id.counter;
136 let to = change
137 .atom_len()
138 .min((end_cnt - change.id.counter) as usize);
139 diff_changes.push(Either::Right(change.slice(offset as usize, to)));
140 } else if change.id.counter + change.atom_len() as i32 > end_cnt {
141 let len = end_cnt - change.id.counter;
142 diff_changes.push(Either::Right(change.slice(0, len as usize)));
143 } else {
144 diff_changes.push(Either::Left(change));
145 }
146 }
147 diff_changes.sort_by_key(|x| match x {
148 Either::Left(c) => c.lamport,
149 Either::Right(c) => c.lamport,
150 });
151 diff_changes
152}
153
154fn register_id(id: &ID, peer_register: Option<&mut ValueRegister<PeerID>>) -> ID {
155 let peer = match peer_register {
156 Some(peer_register) => peer_register.register(&id.peer) as PeerID,
157 None => id.peer,
158 };
159 ID::new(peer as PeerID, id.counter)
160}
161
162fn register_idlp(idlp: &IdLp, peer_register: Option<&mut ValueRegister<PeerID>>) -> IdLp {
163 let peer = match peer_register {
164 Some(peer_register) => peer_register.register(&idlp.peer) as PeerID,
165 None => idlp.peer,
166 };
167 IdLp {
168 peer,
169 lamport: idlp.lamport,
170 }
171}
172
173fn register_tree_id(tree: &TreeID, peer_register: Option<&mut ValueRegister<PeerID>>) -> TreeID {
174 TreeID {
175 peer: match peer_register {
176 Some(peer_register) => peer_register.register(&tree.peer) as PeerID,
177 None => tree.peer,
178 },
179 counter: tree.counter,
180 }
181}
182
183fn register_container_id(
184 container: ContainerID,
185 peer_register: Option<&mut ValueRegister<PeerID>>,
186) -> ContainerID {
187 match container {
188 ContainerID::Normal {
189 peer,
190 counter,
191 container_type,
192 } => ContainerID::Normal {
193 peer: match peer_register {
194 Some(peer_register) => peer_register.register(&peer) as PeerID,
195 None => peer,
196 },
197 counter,
198 container_type,
199 },
200 r => r,
201 }
202}
203
204fn convert_container_id(
205 container: ContainerID,
206 peers: &Option<Vec<PeerID>>,
207) -> LoroResult<ContainerID> {
208 match container {
209 ContainerID::Normal {
210 peer,
211 counter,
212 container_type,
213 } => Ok(ContainerID::Normal {
214 peer: get_peer_from_peers(peers, peer)?,
215 counter,
216 container_type,
217 }),
218 r => Ok(r),
219 }
220}
221
222pub(crate) fn get_peer_from_peers(peers: &Option<Vec<PeerID>>, peer: PeerID) -> LoroResult<PeerID> {
223 match peers {
224 Some(peers) => peers.get(peer as usize).copied().ok_or_else(|| {
225 LoroError::DecodeError("peer index out of range in compressed peer table".into())
226 }),
227 None => Ok(peer),
228 }
229}
230
231fn convert_id(id: &ID, peers: &Option<Vec<PeerID>>) -> LoroResult<ID> {
232 Ok(ID {
233 peer: get_peer_from_peers(peers, id.peer)?,
234 counter: id.counter,
235 })
236}
237
238fn convert_idlp(idlp: &IdLp, peers: &Option<Vec<PeerID>>) -> LoroResult<IdLp> {
239 Ok(IdLp {
240 lamport: idlp.lamport,
241 peer: get_peer_from_peers(peers, idlp.peer)?,
242 })
243}
244
245fn convert_tree_id(tree: &TreeID, peers: &Option<Vec<PeerID>>) -> LoroResult<TreeID> {
246 Ok(TreeID {
247 peer: get_peer_from_peers(peers, tree.peer)?,
248 counter: tree.counter,
249 })
250}
251pub(crate) fn encode_change_to_json(change: ChangeRef<'_>, arena: &SharedArena) -> JsonSchema {
252 let f = change.deps.clone();
253 let changes = vec![encode_change(change, arena, None)];
254 JsonSchema {
255 changes,
256 schema_version: SCHEMA_VERSION,
257 peers: None,
258 start_version: f,
259 }
260}
261
262fn encode_changes(
263 diff_changes: &[Either<BlockChangeRef, Change>],
264 arena: &SharedArena,
265 mut peer_register: Option<&mut ValueRegister<PeerID>>,
266) -> Vec<json::JsonChange> {
267 let mut changes = Vec::with_capacity(diff_changes.len());
268 for change in diff_changes.iter() {
269 let change: &Change = match change {
270 Either::Left(c) => c,
271 Either::Right(c) => c,
272 };
273 let c = encode_change(
274 ChangeRef::from_change(change),
275 arena,
276 peer_register.as_deref_mut(),
277 );
278 changes.push(c);
279 }
280 changes
281}
282
283pub(crate) fn encode_change(
284 change: ChangeRef<'_, Op>,
285 arena: &SharedArena,
286 mut peer_register: Option<&mut ValueRegister<PeerID>>,
287) -> JsonChange {
288 let mut ops = Vec::with_capacity(change.ops.len());
289 for Op {
290 counter,
291 container,
292 content,
293 } in change.ops.iter()
294 {
295 let mut container = arena.get_container_id(*container).unwrap();
296 if container.is_normal() {
297 container = register_container_id(container, peer_register.as_deref_mut());
298 }
299 let op = match container.container_type() {
300 ContainerType::List => match content {
301 InnerContent::List(list) => JsonOpContent::List(match list {
302 InnerListOp::Insert { slice, pos } => {
303 let mut values =
304 arena.get_values(slice.0.start as usize..slice.0.end as usize);
305 values.iter_mut().for_each(|x| {
306 if let LoroValue::Container(id) = x {
307 if id.is_normal() {
308 *id = register_container_id(
309 id.clone(),
310 peer_register.as_deref_mut(),
311 );
312 }
313 }
314 });
315 json::ListOp::Insert {
316 pos: *pos as u32,
317 value: values,
318 }
319 }
320 InnerListOp::Delete(DeleteSpanWithId {
321 id_start,
322 span: DeleteSpan { pos, signed_len },
323 }) => json::ListOp::Delete {
324 pos: *pos as i32,
325 len: *signed_len as i32,
326 start_id: register_id(id_start, peer_register.as_deref_mut()),
327 },
328 _ => unreachable!(),
329 }),
330 _ => unreachable!(),
331 },
332 ContainerType::MovableList => match content {
333 InnerContent::List(list) => JsonOpContent::MovableList(match list {
334 InnerListOp::Insert { slice, pos } => {
335 let mut values =
336 arena.get_values(slice.0.start as usize..slice.0.end as usize);
337 values.iter_mut().for_each(|x| {
338 if let LoroValue::Container(id) = x {
339 if id.is_normal() {
340 *id = register_container_id(
341 id.clone(),
342 peer_register.as_deref_mut(),
343 );
344 }
345 }
346 });
347 json::MovableListOp::Insert {
348 pos: *pos as u32,
349 value: values,
350 }
351 }
352 InnerListOp::Delete(DeleteSpanWithId {
353 id_start,
354 span: DeleteSpan { pos, signed_len },
355 }) => json::MovableListOp::Delete {
356 pos: *pos as i32,
357 len: *signed_len as i32,
358 start_id: register_id(id_start, peer_register.as_deref_mut()),
359 },
360 InnerListOp::Move {
361 from,
362 elem_id: from_id,
363 to,
364 } => json::MovableListOp::Move {
365 from: *from,
366 to: *to,
367 elem_id: register_idlp(from_id, peer_register.as_deref_mut()),
368 },
369 InnerListOp::Set { elem_id, value } => {
370 let value = if let LoroValue::Container(id) = value {
371 if id.is_normal() {
372 LoroValue::Container(register_container_id(
373 id.clone(),
374 peer_register.as_deref_mut(),
375 ))
376 } else {
377 value.clone()
378 }
379 } else {
380 value.clone()
381 };
382 json::MovableListOp::Set {
383 elem_id: register_idlp(elem_id, peer_register.as_deref_mut()),
384 value,
385 }
386 }
387 _ => unreachable!(),
388 }),
389 _ => unreachable!(),
390 },
391 ContainerType::Text => match content {
392 InnerContent::List(list) => JsonOpContent::Text(match list {
393 InnerListOp::InsertText {
394 slice,
395 unicode_start: _,
396 unicode_len: _,
397 pos,
398 } => {
399 let text = String::from_utf8(slice.as_bytes().to_vec()).unwrap();
400 json::TextOp::Insert { pos: *pos, text }
401 }
402 InnerListOp::Delete(DeleteSpanWithId {
403 id_start,
404 span: DeleteSpan { pos, signed_len },
405 }) => json::TextOp::Delete {
406 pos: *pos as i32,
407 len: *signed_len as i32,
408 start_id: register_id(id_start, peer_register.as_deref_mut()),
409 },
410 InnerListOp::StyleStart {
411 start,
412 end,
413 key,
414 value,
415 info,
416 } => json::TextOp::Mark {
417 start: *start,
418 end: *end,
419 style_key: key.to_string(),
420 style_value: value.clone(),
421 info: info.to_byte(),
422 },
423 InnerListOp::StyleEnd => json::TextOp::MarkEnd,
424 _ => unreachable!(),
425 }),
426 _ => unreachable!(),
427 },
428 ContainerType::Map => match content {
429 InnerContent::Map(MapSet { key, value }) => {
430 JsonOpContent::Map(if let Some(v) = value {
431 let value = if let LoroValue::Container(id) = v {
432 if id.is_normal() {
433 LoroValue::Container(register_container_id(
434 id.clone(),
435 peer_register.as_deref_mut(),
436 ))
437 } else {
438 v.clone()
439 }
440 } else {
441 v.clone()
442 };
443 json::MapOp::Insert {
444 key: key.to_string(),
445 value,
446 }
447 } else {
448 json::MapOp::Delete {
449 key: key.to_string(),
450 }
451 })
452 }
453
454 _ => unreachable!(),
455 },
456
457 ContainerType::Tree => match content {
458 InnerContent::Tree(op) => JsonOpContent::Tree(match &**op {
459 TreeOp::Create {
460 target,
461 parent,
462 position,
463 } => json::TreeOp::Create {
464 target: register_tree_id(target, peer_register.as_deref_mut()),
465 parent: parent.map(|p| register_tree_id(&p, peer_register.as_deref_mut())),
466 fractional_index: position.clone(),
467 },
468 TreeOp::Move {
469 target,
470 parent,
471 position,
472 } => json::TreeOp::Move {
473 target: register_tree_id(target, peer_register.as_deref_mut()),
474 parent: parent.map(|p| register_tree_id(&p, peer_register.as_deref_mut())),
475 fractional_index: position.clone(),
476 },
477 TreeOp::Delete { target } => json::TreeOp::Delete {
478 target: register_tree_id(target, peer_register.as_deref_mut()),
479 },
480 }),
481 _ => unreachable!(),
482 },
483 ContainerType::Unknown(_) => {
484 let InnerContent::Future(FutureInnerContent::Unknown { prop, value }) = content
485 else {
486 unreachable!();
487 };
488 JsonOpContent::Future(json::FutureOpWrapper {
489 prop: *prop,
490 value: json::FutureOp::Unknown((**value).clone()),
491 })
492 }
493 #[cfg(feature = "counter")]
494 ContainerType::Counter => {
495 let InnerContent::Future(f) = content else {
496 unreachable!()
497 };
498 match f {
499 FutureInnerContent::Counter(x) => {
500 JsonOpContent::Future(json::FutureOpWrapper {
501 prop: 0,
502 value: json::FutureOp::Counter(super::OwnedValue::F64(*x)),
503 })
504 }
505 _ => unreachable!(),
506 }
507 }
508 };
509 ops.push(json::JsonOp {
510 counter: *counter,
511 container,
512 content: op,
513 });
514 }
515 let c = json::JsonChange {
516 id: register_id(change.id, peer_register.as_deref_mut()),
517 ops,
518 deps: change
519 .deps
520 .iter()
521 .sorted()
523 .map(|id| register_id(&id, peer_register.as_deref_mut()))
524 .collect(),
525 lamport: *change.lamport,
526 timestamp: *change.timestamp,
527 msg: change.commit_msg.as_deref().map(|x| x.to_string()),
528 };
529 c
530}
531
532fn decode_changes(json: JsonSchema, arena: &SharedArena) -> LoroResult<Vec<Change>> {
533 let JsonSchema { peers, changes, .. } = json;
534 let mut ans = Vec::with_capacity(changes.len());
535 for json::JsonChange {
536 id,
537 timestamp,
538 deps,
539 lamport,
540 msg,
541 ops: json_ops,
542 } in changes
543 {
544 let id = convert_id(&id, &peers)?;
545 let mut ops: RleVec<[Op; 1]> = RleVec::new();
546 for op in json_ops {
547 ops.push(decode_op(op, arena, &peers)?);
548 }
549
550 let change = Change {
551 id,
552 timestamp,
553 deps: Frontiers::from_iter(
554 deps.into_iter()
555 .map(|id| convert_id(&id, &peers))
556 .collect::<LoroResult<Vec<_>>>()?,
557 ),
558 lamport,
559 ops,
560 commit_msg: msg.map(|x| x.into()),
561 };
562 ans.push(change);
563 }
564 Ok(ans)
565}
566
567fn decode_op(op: json::JsonOp, arena: &SharedArena, peers: &Option<Vec<PeerID>>) -> LoroResult<Op> {
568 let json::JsonOp {
569 counter,
570 container,
571 content,
572 } = op;
573 let container = convert_container_id(container, peers)?;
574 let idx = arena.register_container(&container);
575 let content = match container.container_type() {
576 ContainerType::Text => match content {
577 JsonOpContent::Text(text) => match text {
578 json::TextOp::Insert { pos, text } => {
579 let (slice, result) = arena.alloc_str_with_slice(&text);
580 InnerContent::List(InnerListOp::InsertText {
581 slice,
582 unicode_start: result.start as u32,
583 unicode_len: (result.end - result.start) as u32,
584 pos,
585 })
586 }
587 json::TextOp::Delete {
588 pos,
589 len,
590 start_id: id_start,
591 } => {
592 let id_start = convert_id(&id_start, peers)?;
593 InnerContent::List(InnerListOp::Delete(DeleteSpanWithId {
594 id_start,
595 span: DeleteSpan {
596 pos: pos as isize,
597 signed_len: len as isize,
598 },
599 }))
600 }
601 json::TextOp::Mark {
602 start,
603 end,
604 style_key,
605 style_value,
606 info,
607 } => InnerContent::List(InnerListOp::StyleStart {
608 start,
609 end,
610 key: style_key.into(),
611 value: style_value,
612 info: TextStyleInfoFlag::from_byte(info),
613 }),
614 json::TextOp::MarkEnd => InnerContent::List(InnerListOp::StyleEnd),
615 },
616 _ => {
617 return Err(LoroError::DecodeError(
618 "invalid op content for text container".into(),
619 ))
620 }
621 },
622 ContainerType::List => match content {
623 JsonOpContent::List(list) => match list {
624 json::ListOp::Insert {
625 pos,
626 value: mut values,
627 } => {
628 for v in values.iter_mut() {
629 if let LoroValue::Container(id) = v {
630 if id.is_normal() {
631 *id = convert_container_id(id.clone(), peers)?;
632 }
633 }
634 }
635 let range = arena.alloc_values(values.iter().cloned());
636 InnerContent::List(InnerListOp::Insert {
637 slice: SliceRange::new(range.start as u32..range.end as u32),
638 pos: pos as usize,
639 })
640 }
641 json::ListOp::Delete { pos, len, start_id } => {
642 InnerContent::List(InnerListOp::Delete(DeleteSpanWithId {
643 id_start: convert_id(&start_id, peers)?,
644 span: DeleteSpan {
645 pos: pos as isize,
646 signed_len: len as isize,
647 },
648 }))
649 }
650 },
651 _ => {
652 return Err(LoroError::DecodeError(
653 "invalid op content for list container".into(),
654 ))
655 }
656 },
657 ContainerType::MovableList => match content {
658 JsonOpContent::MovableList(list) => match list {
659 json::MovableListOp::Insert {
660 pos,
661 value: mut values,
662 } => {
663 for v in values.iter_mut() {
664 if let LoroValue::Container(id) = v {
665 if id.is_normal() {
666 *id = convert_container_id(id.clone(), peers)?;
667 }
668 }
669 }
670 let range = arena.alloc_values(values.iter().cloned());
671 InnerContent::List(InnerListOp::Insert {
672 slice: SliceRange::new(range.start as u32..range.end as u32),
673 pos: pos as usize,
674 })
675 }
676 json::MovableListOp::Delete { pos, len, start_id } => {
677 InnerContent::List(InnerListOp::Delete(DeleteSpanWithId {
678 id_start: convert_id(&start_id, peers)?,
679 span: DeleteSpan {
680 pos: pos as isize,
681 signed_len: len as isize,
682 },
683 }))
684 }
685 json::MovableListOp::Move {
686 from,
687 elem_id: from_id,
688 to,
689 } => {
690 let from_id = convert_idlp(&from_id, peers)?;
691 InnerContent::List(InnerListOp::Move {
692 from,
693 elem_id: from_id,
694 to,
695 })
696 }
697 json::MovableListOp::Set { elem_id, mut value } => {
698 let elem_id = convert_idlp(&elem_id, peers)?;
699 if let LoroValue::Container(id) = &mut value {
700 *id = convert_container_id(id.clone(), peers)?;
701 }
702 InnerContent::List(InnerListOp::Set { elem_id, value })
703 }
704 },
705 _ => {
706 return Err(LoroError::DecodeError(
707 "invalid op content for movable list container".into(),
708 ))
709 }
710 },
711 ContainerType::Map => match content {
712 JsonOpContent::Map(map) => match map {
713 json::MapOp::Insert { key, mut value } => {
714 if let LoroValue::Container(id) = &mut value {
715 *id = convert_container_id(id.clone(), peers)?;
716 }
717 InnerContent::Map(MapSet {
718 key: key.into(),
719 value: Some(value),
720 })
721 }
722 json::MapOp::Delete { key } => InnerContent::Map(MapSet {
723 key: key.into(),
724 value: None,
725 }),
726 },
727 _ => {
728 return Err(LoroError::DecodeError(
729 "invalid op content for map container".into(),
730 ))
731 }
732 },
733 ContainerType::Tree => match content {
734 JsonOpContent::Tree(tree) => match tree {
735 json::TreeOp::Create {
736 target,
737 parent,
738 fractional_index,
739 } => InnerContent::Tree(Arc::new(TreeOp::Create {
740 target: convert_tree_id(&target, peers)?,
741 parent: match parent {
742 Some(p) => Some(convert_tree_id(&p, peers)?),
743 None => None,
744 },
745 position: fractional_index,
746 })),
747 json::TreeOp::Move {
748 target,
749 parent,
750 fractional_index,
751 } => InnerContent::Tree(Arc::new(TreeOp::Move {
752 target: convert_tree_id(&target, peers)?,
753 parent: match parent {
754 Some(p) => Some(convert_tree_id(&p, peers)?),
755 None => None,
756 },
757 position: fractional_index,
758 })),
759 json::TreeOp::Delete { target } => InnerContent::Tree(Arc::new(TreeOp::Delete {
760 target: convert_tree_id(&target, peers)?,
761 })),
762 },
763 _ => {
764 return Err(LoroError::DecodeError(
765 "invalid op content for tree container".into(),
766 ))
767 }
768 },
769 ContainerType::Unknown(_) => match content {
770 JsonOpContent::Future(json::FutureOpWrapper {
771 prop,
772 value: json::FutureOp::Unknown(value),
773 }) => InnerContent::Future(FutureInnerContent::Unknown {
774 prop,
775 value: Box::new(value),
776 }),
777 _ => {
778 return Err(LoroError::DecodeError(
779 "invalid op content for unknown container".into(),
780 ))
781 }
782 },
783 #[cfg(feature = "counter")]
784 ContainerType::Counter => {
785 let JsonOpContent::Future(json::FutureOpWrapper { prop: _, value }) = content else {
786 return Err(LoroError::DecodeError(
787 "invalid op content for counter container".into(),
788 ));
789 };
790 use crate::encoding::OwnedValue;
791 match value {
792 json::FutureOp::Counter(OwnedValue::F64(c))
793 | json::FutureOp::Unknown(OwnedValue::F64(c)) => {
794 InnerContent::Future(FutureInnerContent::Counter(c))
795 }
796 json::FutureOp::Counter(OwnedValue::I64(c))
797 | json::FutureOp::Unknown(OwnedValue::I64(c)) => {
798 InnerContent::Future(FutureInnerContent::Counter(c as f64))
799 }
800 _ => {
801 return Err(LoroError::DecodeError(
802 "invalid counter op value type".into(),
803 ))
804 }
805 }
806 } };
808 Ok(Op {
809 counter,
810 container: idx,
811 content,
812 })
813}
814
815impl TryFrom<&str> for JsonSchema {
816 type Error = serde_json::Error;
817
818 fn try_from(value: &str) -> Result<Self, Self::Error> {
819 serde_json::from_str(value)
820 }
821}
822
823impl TryFrom<&String> for JsonSchema {
824 type Error = serde_json::Error;
825
826 fn try_from(value: &String) -> Result<Self, Self::Error> {
827 serde_json::from_str(value)
828 }
829}
830
831impl TryFrom<String> for JsonSchema {
832 type Error = serde_json::Error;
833
834 fn try_from(value: String) -> Result<Self, Self::Error> {
835 serde_json::from_str(&value)
836 }
837}
838
839pub mod json {
840 use crate::{
841 encoding::OwnedValue,
842 version::{Frontiers, VersionRange},
843 };
844 use fractional_index::FractionalIndex;
845 use loro_common::{ContainerID, Counter, IdLp, Lamport, LoroValue, PeerID, TreeID, ID};
846 use serde::{Deserialize, Serialize};
847 use std::ops::Range;
848
849 use super::{get_peer_from_peers, redact_value};
850
851 #[derive(Debug, Clone, Serialize, Deserialize)]
852 pub struct JsonSchema {
853 pub schema_version: u8,
854 #[serde(with = "self::serde_impl::frontiers")]
855 pub start_version: Frontiers,
856 #[serde(with = "self::serde_impl::peer_id")]
857 pub peers: Option<Vec<PeerID>>,
858 pub changes: Vec<JsonChange>,
859 }
860
861 #[derive(Debug, Clone, Serialize, Deserialize)]
862 pub struct JsonChange {
863 #[serde(with = "self::serde_impl::id")]
864 pub id: ID,
865 pub timestamp: i64,
866 #[serde(with = "self::serde_impl::deps")]
867 pub deps: Vec<ID>,
868 pub lamport: Lamport,
869 pub msg: Option<String>,
870 pub ops: Vec<JsonOp>,
871 }
872
873 impl JsonChange {
874 pub fn op_len(&self) -> usize {
875 let Some(last_op) = self.ops.last() else {
876 return 0;
877 };
878 (last_op.counter - self.id.counter) as usize + last_op.content.op_len()
879 }
880 }
881
882 #[derive(Debug, Clone)]
883 pub struct JsonOp {
884 pub content: JsonOpContent,
885 pub container: ContainerID,
886 pub counter: i32,
887 }
888
889 #[derive(Debug, Clone, Serialize, Deserialize)]
890 #[serde(untagged)]
891 pub enum JsonOpContent {
892 List(ListOp),
893 MovableList(MovableListOp),
894 Map(MapOp),
895 Text(TextOp),
896 Tree(TreeOp),
897 Future(FutureOpWrapper),
899 }
900
901 impl JsonOpContent {
902 pub fn op_len(&self) -> usize {
903 match self {
904 JsonOpContent::List(list_op) => list_op.op_len(),
905 JsonOpContent::MovableList(movable_list_op) => movable_list_op.op_len(),
906 JsonOpContent::Map(..) => 1,
907 JsonOpContent::Text(text_op) => text_op.op_len(),
908 JsonOpContent::Tree(..) => 1,
909 JsonOpContent::Future(..) => 1,
910 }
911 }
912 }
913
914 #[derive(Debug, Clone, Serialize, Deserialize)]
915 pub struct FutureOpWrapper {
916 #[serde(flatten)]
917 pub value: FutureOp,
918 pub prop: i32,
919 }
920
921 #[derive(Debug, Clone, Serialize, Deserialize)]
922 #[serde(tag = "type", rename_all = "snake_case")]
923 pub enum ListOp {
924 Insert {
925 pos: u32,
926 value: Vec<LoroValue>,
927 },
928 Delete {
929 pos: i32,
930 len: i32,
931 #[serde(with = "self::serde_impl::id")]
932 start_id: ID,
933 },
934 }
935
936 impl ListOp {
937 fn op_len(&self) -> usize {
938 match self {
939 ListOp::Insert { value: values, .. } => values.len(),
940 ListOp::Delete { len, .. } => (*len).unsigned_abs() as usize,
941 }
942 }
943 }
944
945 #[derive(Debug, Clone, Serialize, Deserialize)]
946 #[serde(tag = "type", rename_all = "snake_case")]
947 pub enum MovableListOp {
948 Insert {
949 pos: u32,
950 value: Vec<LoroValue>,
951 },
952 Delete {
953 pos: i32,
954 len: i32,
955 #[serde(with = "self::serde_impl::id")]
956 start_id: ID,
957 },
958 Move {
959 from: u32,
960 to: u32,
961 #[serde(with = "self::serde_impl::idlp")]
962 elem_id: IdLp,
963 },
964 Set {
965 #[serde(with = "self::serde_impl::idlp")]
966 elem_id: IdLp,
967 value: LoroValue,
968 },
969 }
970
971 impl MovableListOp {
972 fn op_len(&self) -> usize {
973 match self {
974 MovableListOp::Insert { value: values, .. } => values.len(),
975 MovableListOp::Delete { len, .. } => (*len).unsigned_abs() as usize,
976 MovableListOp::Move { .. } => 1,
977 MovableListOp::Set { .. } => 1,
978 }
979 }
980 }
981
982 #[derive(Debug, Clone, Serialize, Deserialize)]
983 #[serde(tag = "type", rename_all = "snake_case")]
984 pub enum MapOp {
985 Insert { key: String, value: LoroValue },
986 Delete { key: String },
987 }
988
989 #[derive(Debug, Clone, Serialize, Deserialize)]
990 #[serde(tag = "type", rename_all = "snake_case")]
991 pub enum TextOp {
992 Insert {
993 pos: u32,
994 text: String,
995 },
996 Delete {
997 pos: i32,
998 len: i32,
999 #[serde(with = "self::serde_impl::id")]
1000 start_id: ID,
1001 },
1002 Mark {
1003 start: u32,
1004 end: u32,
1005 style_key: String,
1006 style_value: LoroValue,
1007 info: u8,
1008 },
1009 MarkEnd,
1010 }
1011
1012 impl TextOp {
1013 fn op_len(&self) -> usize {
1014 match self {
1015 TextOp::Insert { text, .. } => text.chars().count(),
1016 TextOp::Delete { len, .. } => len.unsigned_abs() as usize,
1017 TextOp::Mark { .. } => 1,
1018 TextOp::MarkEnd => 1,
1019 }
1020 }
1021 }
1022
1023 #[derive(Debug, Clone, Serialize, Deserialize)]
1024 #[serde(tag = "type", rename_all = "snake_case")]
1025 pub enum TreeOp {
1026 Create {
1027 #[serde(with = "self::serde_impl::tree_id")]
1028 target: TreeID,
1029 #[serde(with = "self::serde_impl::option_tree_id")]
1030 parent: Option<TreeID>,
1031 #[serde(default, with = "self::serde_impl::fractional_index")]
1032 fractional_index: FractionalIndex,
1033 },
1034 Move {
1035 #[serde(with = "self::serde_impl::tree_id")]
1036 target: TreeID,
1037 #[serde(with = "self::serde_impl::option_tree_id")]
1038 parent: Option<TreeID>,
1039 #[serde(default, with = "self::serde_impl::fractional_index")]
1040 fractional_index: FractionalIndex,
1041 },
1042 Delete {
1043 #[serde(with = "self::serde_impl::tree_id")]
1044 target: TreeID,
1045 },
1046 }
1047
1048 #[derive(Debug, Clone, Serialize, Deserialize)]
1049 #[serde(tag = "type", rename_all = "snake_case")]
1050 pub enum FutureOp {
1051 #[cfg(feature = "counter")]
1052 Counter(OwnedValue),
1053 Unknown(OwnedValue),
1054 }
1055
1056 mod serde_impl {
1057
1058 use loro_common::{ContainerID, ContainerType};
1059 use serde::{
1060 de::{MapAccess, Visitor},
1061 ser::SerializeStruct,
1062 Deserialize, Deserializer, Serialize, Serializer,
1063 };
1064
1065 #[allow(unused_imports)]
1066 use crate::encoding::OwnedValue;
1067
1068 impl Serialize for super::JsonOp {
1069 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1070 where
1071 S: Serializer,
1072 {
1073 let mut s = serializer.serialize_struct("Op", 3)?;
1074 s.serialize_field("container", &self.container.to_string())?;
1075 s.serialize_field("content", &self.content)?;
1076 s.serialize_field("counter", &self.counter)?;
1077 s.end()
1078 }
1079 }
1080
1081 impl<'de> Deserialize<'de> for super::JsonOp {
1082 fn deserialize<D>(deserializer: D) -> Result<super::JsonOp, D::Error>
1083 where
1084 D: Deserializer<'de>,
1085 {
1086 struct __Visitor;
1087
1088 impl<'de> Visitor<'de> for __Visitor {
1089 type Value = super::JsonOp;
1090 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1091 formatter.write_str("struct Op")
1092 }
1093
1094 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1095 where
1096 A: MapAccess<'de>,
1097 {
1098 let (_key, container) = map
1099 .next_entry::<String, String>()?
1100 .ok_or_else(|| serde::de::Error::custom("missing container field"))?;
1101 let is_unknown = container.ends_with(')');
1102 let container = ContainerID::try_from(container.as_str())
1103 .map_err(|_| serde::de::Error::custom("invalid container id"))?;
1104 let op = if is_unknown {
1105 let (_key, op) = map
1106 .next_entry::<String, super::FutureOpWrapper>()?
1107 .ok_or_else(|| {
1108 serde::de::Error::custom(
1109 "missing op field for unknown container",
1110 )
1111 })?;
1112 super::JsonOpContent::Future(op)
1113 } else {
1114 match container.container_type() {
1115 ContainerType::List => {
1116 let (_key, op) = map
1117 .next_entry::<String, super::ListOp>()?
1118 .ok_or_else(|| {
1119 serde::de::Error::custom("missing op field for list")
1120 })?;
1121 super::JsonOpContent::List(op)
1122 }
1123 ContainerType::MovableList => {
1124 let (_key, op) = map
1125 .next_entry::<String, super::MovableListOp>()?
1126 .ok_or_else(|| {
1127 serde::de::Error::custom(
1128 "missing op field for movable list",
1129 )
1130 })?;
1131 super::JsonOpContent::MovableList(op)
1132 }
1133 ContainerType::Map => {
1134 let (_key, op) =
1135 map.next_entry::<String, super::MapOp>()?.ok_or_else(
1136 || serde::de::Error::custom("missing op field for map"),
1137 )?;
1138 super::JsonOpContent::Map(op)
1139 }
1140 ContainerType::Text => {
1141 let (_key, op) = map
1142 .next_entry::<String, super::TextOp>()?
1143 .ok_or_else(|| {
1144 serde::de::Error::custom("missing op field for text")
1145 })?;
1146 super::JsonOpContent::Text(op)
1147 }
1148 ContainerType::Tree => {
1149 let (_key, op) = map
1150 .next_entry::<String, super::TreeOp>()?
1151 .ok_or_else(|| {
1152 serde::de::Error::custom("missing op field for tree")
1153 })?;
1154 super::JsonOpContent::Tree(op)
1155 }
1156 #[cfg(feature = "counter")]
1157 ContainerType::Counter => {
1158 let (_key, value) = map
1159 .next_entry::<String, OwnedValue>()?
1160 .ok_or_else(|| {
1161 serde::de::Error::custom(
1162 "missing value field for counter",
1163 )
1164 })?;
1165 super::JsonOpContent::Future(super::FutureOpWrapper {
1166 prop: 0,
1167 value: super::FutureOp::Counter(value),
1168 })
1169 }
1170 _ => unreachable!(),
1171 }
1172 };
1173 let (_, counter) = map
1174 .next_entry::<String, i32>()?
1175 .ok_or_else(|| serde::de::Error::custom("missing counter field"))?;
1176 Ok(super::JsonOp {
1177 container,
1178 content: op,
1179 counter,
1180 })
1181 }
1182 }
1183 const FIELDS: &[&str] = &["container", "content", "counter"];
1184 deserializer.deserialize_struct("JsonOp", FIELDS, __Visitor)
1185 }
1186 }
1187
1188 pub mod id {
1189 use loro_common::ID;
1190 use serde::{Deserialize, Deserializer, Serializer};
1191
1192 pub fn serialize<S>(id: &ID, s: S) -> Result<S::Ok, S::Error>
1193 where
1194 S: Serializer,
1195 {
1196 s.serialize_str(&id.to_string())
1197 }
1198
1199 pub fn deserialize<'de, 'a, D>(d: D) -> Result<ID, D::Error>
1200 where
1201 D: Deserializer<'de>,
1202 {
1203 let str: String = Deserialize::deserialize(d)?;
1205 let id: ID = ID::try_from(str.as_str())
1206 .map_err(|_| serde::de::Error::custom("invalid ID format"))?;
1207 Ok(id)
1208 }
1209 }
1210
1211 pub mod frontiers {
1212 use itertools::Itertools;
1213 use loro_common::ID;
1214 use serde::{ser::SerializeMap, Deserializer, Serializer};
1215
1216 use crate::version::Frontiers;
1217
1218 pub fn serialize<S>(f: &Frontiers, s: S) -> Result<S::Ok, S::Error>
1219 where
1220 S: Serializer,
1221 {
1222 let mut map = s.serialize_map(Some(f.len()))?;
1223 for id in f
1224 .iter()
1225 .sorted()
1227 {
1228 map.serialize_entry(&id.peer.to_string(), &id.counter)?;
1229 }
1230 map.end()
1231 }
1232
1233 pub fn deserialize<'de, 'a, D>(d: D) -> Result<Frontiers, D::Error>
1234 where
1235 D: Deserializer<'de>,
1236 {
1237 struct __Visitor;
1238 impl<'de> serde::de::Visitor<'de> for __Visitor {
1239 type Value = Frontiers;
1240 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1241 formatter.write_str("a Frontiers")
1242 }
1243
1244 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1245 where
1246 A: serde::de::MapAccess<'de>,
1247 {
1248 let mut f = Frontiers::default();
1249 while let Some((k, v)) = map.next_entry::<String, i32>()? {
1250 f.push(ID::new(
1251 k.parse().map_err(|_| {
1252 serde::de::Error::custom("invalid peer id in frontiers")
1253 })?,
1254 v,
1255 ))
1256 }
1257 Ok(f)
1258 }
1259 }
1260 d.deserialize_map(__Visitor)
1261 }
1262 }
1263
1264 pub mod deps {
1265 use loro_common::ID;
1266 use serde::{Deserialize, Deserializer, Serializer};
1267
1268 pub fn serialize<S>(deps: &[ID], s: S) -> Result<S::Ok, S::Error>
1269 where
1270 S: Serializer,
1271 {
1272 s.collect_seq(deps.iter().map(|x| x.to_string()))
1273 }
1274
1275 pub fn deserialize<'de, 'a, D>(d: D) -> Result<Vec<ID>, D::Error>
1276 where
1277 D: Deserializer<'de>,
1278 {
1279 let deps: Vec<String> = Deserialize::deserialize(d)?;
1280 Ok(deps
1281 .into_iter()
1282 .map(|x| {
1283 ID::try_from(x.as_str())
1284 .map_err(|_| serde::de::Error::custom("invalid ID in deps"))
1285 })
1286 .collect::<Result<Vec<_>, _>>()?)
1287 }
1288 }
1289
1290 pub mod peer_id {
1291 use loro_common::PeerID;
1292 use serde::{Deserialize, Deserializer, Serializer};
1293
1294 pub fn serialize<S>(peers: &Option<Vec<PeerID>>, s: S) -> Result<S::Ok, S::Error>
1295 where
1296 S: Serializer,
1297 {
1298 match peers {
1299 Some(peers) => s.collect_seq(peers.iter().map(|x| x.to_string())),
1300 None => s.serialize_none(),
1301 }
1302 }
1303
1304 pub fn deserialize<'de, 'a, D>(d: D) -> Result<Option<Vec<PeerID>>, D::Error>
1305 where
1306 D: Deserializer<'de>,
1307 {
1308 let peers: Option<Vec<String>> = Deserialize::deserialize(d)?;
1309 Ok(peers
1310 .map(|x| {
1311 x.into_iter()
1312 .map(|x| {
1313 x.parse()
1314 .map_err(|_| serde::de::Error::custom("invalid peer id"))
1315 })
1316 .collect::<Result<Vec<_>, _>>()
1317 })
1318 .transpose()?)
1319 }
1320 }
1321
1322 pub mod idlp {
1323 use loro_common::IdLp;
1324 use serde::{Deserialize, Deserializer, Serializer};
1325
1326 pub fn serialize<S>(idlp: &IdLp, s: S) -> Result<S::Ok, S::Error>
1327 where
1328 S: Serializer,
1329 {
1330 s.serialize_str(&idlp.to_string())
1331 }
1332
1333 pub fn deserialize<'de, 'a, D>(d: D) -> Result<IdLp, D::Error>
1334 where
1335 D: Deserializer<'de>,
1336 {
1337 let str: String = Deserialize::deserialize(d)?;
1338 let id: IdLp = IdLp::try_from(str.as_str())
1339 .map_err(|_| serde::de::Error::custom("invalid IdLp format"))?;
1340 Ok(id)
1341 }
1342 }
1343
1344 pub mod tree_id {
1345 use loro_common::TreeID;
1346 use serde::{Deserialize, Deserializer, Serializer};
1347
1348 pub fn serialize<S>(id: &TreeID, s: S) -> Result<S::Ok, S::Error>
1349 where
1350 S: Serializer,
1351 {
1352 s.serialize_str(&id.to_string())
1353 }
1354
1355 pub fn deserialize<'de, 'a, D>(d: D) -> Result<TreeID, D::Error>
1356 where
1357 D: Deserializer<'de>,
1358 {
1359 let str: String = Deserialize::deserialize(d)?;
1360 let id: TreeID = TreeID::try_from(str.as_str())
1361 .map_err(|_| serde::de::Error::custom("invalid TreeID format"))?;
1362 Ok(id)
1363 }
1364 }
1365
1366 pub mod option_tree_id {
1367 use loro_common::TreeID;
1368 use serde::{Deserialize, Deserializer, Serializer};
1369
1370 pub fn serialize<S>(id: &Option<TreeID>, s: S) -> Result<S::Ok, S::Error>
1371 where
1372 S: Serializer,
1373 {
1374 match id {
1375 Some(id) => s.serialize_str(&id.to_string()),
1376 None => s.serialize_none(),
1377 }
1378 }
1379
1380 pub fn deserialize<'de, 'a, D>(d: D) -> Result<Option<TreeID>, D::Error>
1381 where
1382 D: Deserializer<'de>,
1383 {
1384 let str: Option<String> = Deserialize::deserialize(d)?;
1385 match str {
1386 Some(str) => {
1387 let id: TreeID = TreeID::try_from(str.as_str())
1388 .map_err(|_| serde::de::Error::custom("invalid TreeID format"))?;
1389 Ok(Some(id))
1390 }
1391 None => Ok(None),
1392 }
1393 }
1394 }
1395
1396 pub mod fractional_index {
1397 use fractional_index::FractionalIndex;
1398 use serde::{Deserialize, Deserializer, Serializer};
1399
1400 pub fn serialize<S>(fi: &FractionalIndex, s: S) -> Result<S::Ok, S::Error>
1401 where
1402 S: Serializer,
1403 {
1404 s.serialize_str(&fi.to_string())
1405 }
1406
1407 pub fn deserialize<'de, 'a, D>(d: D) -> Result<FractionalIndex, D::Error>
1408 where
1409 D: Deserializer<'de>,
1410 {
1411 let str: String = Deserialize::deserialize(d)?;
1412 if str.len() % 2 != 0 {
1413 return Err(serde::de::Error::custom(
1414 "invalid fractional index hex length",
1415 ));
1416 }
1417
1418 let mut bytes = Vec::with_capacity(str.len() / 2);
1419 for i in 0..str.len() / 2 {
1420 let byte = u8::from_str_radix(&str[i * 2..i * 2 + 2], 16).map_err(|e| {
1421 serde::de::Error::custom(format!("invalid fractional index hex: {e}"))
1422 })?;
1423 bytes.push(byte);
1424 }
1425
1426 Ok(FractionalIndex::from_bytes(bytes))
1427 }
1428 }
1429 }
1430
1431 #[derive(thiserror::Error, Debug, PartialEq, Eq)]
1432 pub enum RedactError {
1433 #[error("unknown operation type")]
1434 UnknownOperationType,
1435 #[error("invalid schema: {0}")]
1436 InvalidSchema(String),
1437 }
1438
1439 pub fn redact(json: &mut JsonSchema, range: VersionRange) -> Result<(), RedactError> {
1456 let peers = json.peers.clone();
1457 let mut errors = Vec::new();
1458 for change in json.changes.iter_mut() {
1459 let real_peer = get_peer_from_peers(&peers, change.id.peer)
1460 .map_err(|_| RedactError::InvalidSchema("peer index out of range".to_string()))?;
1461 let real_id = ID::new(real_peer, change.id.counter);
1462 if !range.has_overlap_with(real_id.to_span(change.op_len())) {
1463 continue;
1464 }
1465
1466 let redact_range = range.get(&real_peer).copied().unwrap();
1467 for op in change.ops.iter_mut() {
1468 if op.counter >= redact_range.1 {
1469 break;
1470 }
1471
1472 let len = op.content.op_len() as Counter;
1473 if op.counter + len <= redact_range.0 {
1474 continue;
1475 }
1476
1477 let result = redact_op(
1478 &mut op.content,
1479 (redact_range.0 - op.counter).max(0).min(len)
1480 ..(redact_range.1 - op.counter).max(0).min(len),
1481 );
1482 match result {
1483 Ok(()) => {}
1484 Err(e) => errors.push(e),
1485 }
1486 }
1487 }
1488
1489 if errors.is_empty() {
1490 Ok(())
1491 } else {
1492 Err(errors.pop().unwrap())
1493 }
1494 }
1495
1496 fn redact_op(op: &mut JsonOpContent, range: Range<Counter>) -> Result<(), RedactError> {
1497 match op {
1498 JsonOpContent::List(list_op) => {
1499 match list_op {
1500 ListOp::Insert { value: values, .. } => {
1501 for i in range {
1502 redact_value(&mut values[i as usize]);
1503 }
1504 }
1505 ListOp::Delete { .. } => {
1506 }
1508 }
1509 }
1510 JsonOpContent::MovableList(movable_list_op) => {
1511 match movable_list_op {
1512 MovableListOp::Insert { value: values, .. } => {
1513 for i in range {
1514 redact_value(&mut values[i as usize]);
1515 }
1516 }
1517 MovableListOp::Delete { .. } | MovableListOp::Move { .. } => {
1518 }
1520 MovableListOp::Set { value, .. } => {
1521 assert!(range.start == 0 && range.len() == 1);
1522 redact_value(value);
1523 }
1524 }
1525 }
1526 JsonOpContent::Map(map_op) => {
1527 match map_op {
1528 MapOp::Insert { value, .. } => {
1529 assert!(range.start == 0 && range.len() == 1);
1530 redact_value(value);
1531 }
1532 MapOp::Delete { .. } => {
1533 }
1535 }
1536 }
1537 JsonOpContent::Text(text_op) => {
1538 match text_op {
1539 TextOp::Insert { text, .. } => {
1540 let mut chars = vec![];
1541 for (i, c) in text.chars().enumerate() {
1542 if i < range.start as usize || i >= range.end as usize {
1543 chars.push(c);
1544 } else {
1545 chars.push("� ".chars().next().unwrap());
1546 }
1547 }
1548 *text = chars.into_iter().collect();
1549 }
1550 TextOp::Delete { .. } => {
1551 }
1553 TextOp::Mark { style_value, .. } => {
1554 assert!(range.start == 0 && range.len() == 1);
1555 *style_value = LoroValue::Null;
1556 }
1557 TextOp::MarkEnd => {
1558 }
1560 }
1561 }
1562 JsonOpContent::Tree(..) => {
1563 }
1565 JsonOpContent::Future(future_op_wrapper) => match &mut future_op_wrapper.value {
1566 #[cfg(feature = "counter")]
1567 FutureOp::Counter(owned_value) => {
1568 *owned_value = OwnedValue::I64(0);
1569 }
1570 FutureOp::Unknown(..) => {
1571 return Err(RedactError::UnknownOperationType);
1572 }
1573 },
1574 }
1575
1576 Ok(())
1577 }
1578}
1579
1580fn redact_value(v: &mut LoroValue) {
1581 match v {
1582 LoroValue::Container(_) => {}
1583 _ => *v = LoroValue::Null,
1584 }
1585}
1586
1587#[cfg(test)]
1588mod tests {
1589 use crate::{LoroDoc, VersionVector};
1590
1591 #[test]
1592 fn json_range_version() {
1593 let doc = LoroDoc::new_auto_commit();
1594 doc.set_peer_id(0).unwrap();
1595 let list = doc.get_list("list");
1596 list.insert(0, "a").unwrap();
1597 list.insert(0, "b").unwrap();
1598 list.insert(0, "c").unwrap();
1599 let json = doc.export_json_updates(
1600 &VersionVector::from_iter(vec![(0, 1)]),
1601 &VersionVector::from_iter(vec![(0, 2)]),
1602 true,
1603 );
1604 assert_eq!(json.changes[0].ops.len(), 1);
1605 let json = doc.export_json_updates(
1606 &VersionVector::from_iter(vec![(0, 0)]),
1607 &VersionVector::from_iter(vec![(0, 2)]),
1608 true,
1609 );
1610 assert_eq!(json.changes[0].ops.len(), 2);
1611 }
1612}