Skip to main content

loro_internal/encoding/
json_schema.rs

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            // Make sure the order is deterministic
522            .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        } // Note: The Future Type need try to parse Op from the unknown content
807    };
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        // #[serde(with = "self::serde_impl::future_op")]
898        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                // NOTE: https://github.com/serde-rs/serde/issues/2467    we use String here
1204                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                    // Make sure the order is deterministic
1226                    .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    /// Redacts sensitive content within the specified range by replacing it with default values.
1440    ///
1441    /// This method applies the following redaction rules:
1442    ///
1443    /// - Preserves delete and move operations without changes
1444    /// - Replaces text insertion content with the Unicode replacement character (U+FFFD)
1445    /// - Substitutes list and map insert values with `LoroValue::Null`
1446    /// - Maintains child container creation operations
1447    /// - Replaces text mark values with `LoroValue::Null`
1448    /// - Preserves map insertion and text annotation keys
1449    /// - Resets counter operations to zero
1450    /// - Leaves unknown operation types (from future Loro versions) unchanged
1451    ///
1452    /// This approach ensures sensitive data removal while preserving the document's overall
1453    /// structure. Redacted documents maintain seamless collaboration capabilities with both
1454    /// redacted and non-redacted versions.
1455    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                        // Delete op won't be changed
1507                    }
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                        // Delete and move ops won't be changed
1519                    }
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                        // Delete op won't be changed
1534                    }
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                        // Delete op won't be changed
1552                    }
1553                    TextOp::Mark { style_value, .. } => {
1554                        assert!(range.start == 0 && range.len() == 1);
1555                        *style_value = LoroValue::Null;
1556                    }
1557                    TextOp::MarkEnd => {
1558                        // MarkEnd won't be changed
1559                    }
1560                }
1561            }
1562            JsonOpContent::Tree(..) => {
1563                // Creation of child container won't be changed
1564            }
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}