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, Counter, HasCounterSpan, HasId, IdLp, IdSpan, LoroError,
21    LoroResult, 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    if id_span.counter.end <= 0 {
79        return vec![];
80    }
81    id_span.counter.start = id_span.counter.start.max(0);
82    id_span.counter.end = id_span.counter.end.max(0);
83
84    let end = oplog.vv().get(&id_span.peer).copied().unwrap_or(0);
85    if id_span.counter.start >= end {
86        return vec![];
87    }
88
89    id_span.counter.end = id_span.counter.end.min(end);
90    let mut diff_changes: Vec<Either<BlockChangeRef, Change>> = Vec::new();
91    while id_span.counter.start < id_span.counter.end {
92        let Some(change) = oplog.get_change_at(id_span.id_start()) else {
93            break;
94        };
95        let ctr_end = change.ctr_end();
96        if change.id.counter >= id_span.counter.start && change.ctr_end() <= id_span.counter.end {
97            diff_changes.push(Either::Left(change));
98        } else {
99            let start = if change.id.counter < id_span.counter.start {
100                (id_span.counter.start - change.id.counter) as usize
101            } else {
102                0
103            };
104
105            let end = if change.ctr_end() > id_span.counter.end {
106                (id_span.counter.end - change.id.counter) as usize
107            } else {
108                change.atom_len()
109            };
110
111            diff_changes.push(Either::Right(change.slice(start, end)));
112        }
113
114        id_span.counter.start = ctr_end;
115    }
116
117    encode_changes(&diff_changes, &oplog.arena, None)
118}
119
120pub(crate) fn decode_json_changes(
121    json: JsonSchema,
122    arena: &SharedArena,
123) -> LoroResult<Vec<Change>> {
124    decode_changes(json, arena)
125}
126
127fn init_encode<'s, 'a: 's>(
128    oplog: &'a OpLog,
129    start_vv: &VersionVector,
130    end_vv: &VersionVector,
131) -> Vec<Either<BlockChangeRef, Change>> {
132    let mut diff_changes: Vec<Either<BlockChangeRef, Change>> = Vec::new();
133    for change in oplog.iter_changes_peer_by_peer(start_vv, end_vv) {
134        let start_cnt = start_vv.get(&change.id.peer).copied().unwrap_or(0);
135        let end_cnt = end_vv.get(&change.id.peer).copied().unwrap_or(0);
136        if change.id.counter < start_cnt {
137            if change.ctr_end() <= start_cnt {
138                continue;
139            }
140
141            let offset = start_cnt - change.id.counter;
142            let to = change
143                .atom_len()
144                .min((end_cnt - change.id.counter) as usize);
145            diff_changes.push(Either::Right(change.slice(offset as usize, to)));
146        } else if change.id.counter + change.atom_len() as i32 > end_cnt {
147            let len = end_cnt - change.id.counter;
148            diff_changes.push(Either::Right(change.slice(0, len as usize)));
149        } else {
150            diff_changes.push(Either::Left(change));
151        }
152    }
153    diff_changes.sort_by_key(|x| match x {
154        Either::Left(c) => c.lamport,
155        Either::Right(c) => c.lamport,
156    });
157    diff_changes
158}
159
160fn register_id(id: &ID, peer_register: Option<&mut ValueRegister<PeerID>>) -> ID {
161    let peer = match peer_register {
162        Some(peer_register) => peer_register.register(&id.peer) as PeerID,
163        None => id.peer,
164    };
165    ID::new(peer as PeerID, id.counter)
166}
167
168fn register_idlp(idlp: &IdLp, peer_register: Option<&mut ValueRegister<PeerID>>) -> IdLp {
169    let peer = match peer_register {
170        Some(peer_register) => peer_register.register(&idlp.peer) as PeerID,
171        None => idlp.peer,
172    };
173    IdLp {
174        peer,
175        lamport: idlp.lamport,
176    }
177}
178
179fn register_tree_id(tree: &TreeID, peer_register: Option<&mut ValueRegister<PeerID>>) -> TreeID {
180    TreeID {
181        peer: match peer_register {
182            Some(peer_register) => peer_register.register(&tree.peer) as PeerID,
183            None => tree.peer,
184        },
185        counter: tree.counter,
186    }
187}
188
189fn register_container_id(
190    container: ContainerID,
191    peer_register: Option<&mut ValueRegister<PeerID>>,
192) -> ContainerID {
193    match container {
194        ContainerID::Normal {
195            peer,
196            counter,
197            container_type,
198        } => ContainerID::Normal {
199            peer: match peer_register {
200                Some(peer_register) => peer_register.register(&peer) as PeerID,
201                None => peer,
202            },
203            counter,
204            container_type,
205        },
206        r => r,
207    }
208}
209
210fn convert_container_id(
211    container: ContainerID,
212    peers: &Option<Vec<PeerID>>,
213) -> LoroResult<ContainerID> {
214    match container {
215        ContainerID::Normal {
216            peer,
217            counter,
218            container_type,
219        } => Ok(ContainerID::Normal {
220            peer: get_peer_from_peers(peers, peer)?,
221            counter,
222            container_type,
223        }),
224        r => Ok(r),
225    }
226}
227
228pub(crate) fn get_peer_from_peers(peers: &Option<Vec<PeerID>>, peer: PeerID) -> LoroResult<PeerID> {
229    match peers {
230        Some(peers) => peers.get(peer as usize).copied().ok_or_else(|| {
231            LoroError::DecodeError("peer index out of range in compressed peer table".into())
232        }),
233        None => Ok(peer),
234    }
235}
236
237fn convert_id(id: &ID, peers: &Option<Vec<PeerID>>) -> LoroResult<ID> {
238    Ok(ID {
239        peer: get_peer_from_peers(peers, id.peer)?,
240        counter: id.counter,
241    })
242}
243
244fn convert_idlp(idlp: &IdLp, peers: &Option<Vec<PeerID>>) -> LoroResult<IdLp> {
245    Ok(IdLp {
246        lamport: idlp.lamport,
247        peer: get_peer_from_peers(peers, idlp.peer)?,
248    })
249}
250
251fn convert_tree_id(tree: &TreeID, peers: &Option<Vec<PeerID>>) -> LoroResult<TreeID> {
252    Ok(TreeID {
253        peer: get_peer_from_peers(peers, tree.peer)?,
254        counter: tree.counter,
255    })
256}
257pub(crate) fn encode_change_to_json(change: ChangeRef<'_>, arena: &SharedArena) -> JsonSchema {
258    let f = change.deps.clone();
259    let changes = vec![encode_change(change, arena, None)];
260    JsonSchema {
261        changes,
262        schema_version: SCHEMA_VERSION,
263        peers: None,
264        start_version: f,
265    }
266}
267
268fn encode_changes(
269    diff_changes: &[Either<BlockChangeRef, Change>],
270    arena: &SharedArena,
271    mut peer_register: Option<&mut ValueRegister<PeerID>>,
272) -> Vec<json::JsonChange> {
273    let mut changes = Vec::with_capacity(diff_changes.len());
274    for change in diff_changes.iter() {
275        let change: &Change = match change {
276            Either::Left(c) => c,
277            Either::Right(c) => c,
278        };
279        let c = encode_change(
280            ChangeRef::from_change(change),
281            arena,
282            peer_register.as_deref_mut(),
283        );
284        changes.push(c);
285    }
286    changes
287}
288
289pub(crate) fn encode_change(
290    change: ChangeRef<'_, Op>,
291    arena: &SharedArena,
292    mut peer_register: Option<&mut ValueRegister<PeerID>>,
293) -> JsonChange {
294    let mut ops = Vec::with_capacity(change.ops.len());
295    for Op {
296        counter,
297        container,
298        content,
299    } in change.ops.iter()
300    {
301        let mut container = arena.get_container_id(*container).unwrap();
302        if container.is_normal() {
303            container = register_container_id(container, peer_register.as_deref_mut());
304        }
305        let op = match container.container_type() {
306            ContainerType::List => match content {
307                InnerContent::List(list) => JsonOpContent::List(match list {
308                    InnerListOp::Insert { slice, pos } => {
309                        let mut values =
310                            arena.get_values(slice.0.start as usize..slice.0.end as usize);
311                        values.iter_mut().for_each(|x| {
312                            if let LoroValue::Container(id) = x {
313                                if id.is_normal() {
314                                    *id = register_container_id(
315                                        id.clone(),
316                                        peer_register.as_deref_mut(),
317                                    );
318                                }
319                            }
320                        });
321                        json::ListOp::Insert {
322                            pos: *pos as u32,
323                            value: values,
324                        }
325                    }
326                    InnerListOp::Delete(DeleteSpanWithId {
327                        id_start,
328                        span: DeleteSpan { pos, signed_len },
329                    }) => json::ListOp::Delete {
330                        pos: *pos as i32,
331                        len: *signed_len as i32,
332                        start_id: register_id(id_start, peer_register.as_deref_mut()),
333                    },
334                    _ => unreachable!(),
335                }),
336                _ => unreachable!(),
337            },
338            ContainerType::MovableList => match content {
339                InnerContent::List(list) => JsonOpContent::MovableList(match list {
340                    InnerListOp::Insert { slice, pos } => {
341                        let mut values =
342                            arena.get_values(slice.0.start as usize..slice.0.end as usize);
343                        values.iter_mut().for_each(|x| {
344                            if let LoroValue::Container(id) = x {
345                                if id.is_normal() {
346                                    *id = register_container_id(
347                                        id.clone(),
348                                        peer_register.as_deref_mut(),
349                                    );
350                                }
351                            }
352                        });
353                        json::MovableListOp::Insert {
354                            pos: *pos as u32,
355                            value: values,
356                        }
357                    }
358                    InnerListOp::Delete(DeleteSpanWithId {
359                        id_start,
360                        span: DeleteSpan { pos, signed_len },
361                    }) => json::MovableListOp::Delete {
362                        pos: *pos as i32,
363                        len: *signed_len as i32,
364                        start_id: register_id(id_start, peer_register.as_deref_mut()),
365                    },
366                    InnerListOp::Move {
367                        from,
368                        elem_id: from_id,
369                        to,
370                    } => json::MovableListOp::Move {
371                        from: *from,
372                        to: *to,
373                        elem_id: register_idlp(from_id, peer_register.as_deref_mut()),
374                    },
375                    InnerListOp::Set { elem_id, value } => {
376                        let value = if let LoroValue::Container(id) = value {
377                            if id.is_normal() {
378                                LoroValue::Container(register_container_id(
379                                    id.clone(),
380                                    peer_register.as_deref_mut(),
381                                ))
382                            } else {
383                                value.clone()
384                            }
385                        } else {
386                            value.clone()
387                        };
388                        json::MovableListOp::Set {
389                            elem_id: register_idlp(elem_id, peer_register.as_deref_mut()),
390                            value,
391                        }
392                    }
393                    _ => unreachable!(),
394                }),
395                _ => unreachable!(),
396            },
397            ContainerType::Text => match content {
398                InnerContent::List(list) => JsonOpContent::Text(match list {
399                    InnerListOp::InsertText {
400                        slice,
401                        unicode_start: _,
402                        unicode_len: _,
403                        pos,
404                    } => {
405                        let text = String::from_utf8(slice.as_bytes().to_vec()).unwrap();
406                        json::TextOp::Insert { pos: *pos, text }
407                    }
408                    InnerListOp::Delete(DeleteSpanWithId {
409                        id_start,
410                        span: DeleteSpan { pos, signed_len },
411                    }) => json::TextOp::Delete {
412                        pos: *pos as i32,
413                        len: *signed_len as i32,
414                        start_id: register_id(id_start, peer_register.as_deref_mut()),
415                    },
416                    InnerListOp::StyleStart {
417                        start,
418                        end,
419                        key,
420                        value,
421                        info,
422                    } => json::TextOp::Mark {
423                        start: *start,
424                        end: *end,
425                        style_key: key.to_string(),
426                        style_value: value.clone(),
427                        info: info.to_byte(),
428                    },
429                    InnerListOp::StyleEnd => json::TextOp::MarkEnd,
430                    _ => unreachable!(),
431                }),
432                _ => unreachable!(),
433            },
434            ContainerType::Map => match content {
435                InnerContent::Map(MapSet { key, value }) => {
436                    JsonOpContent::Map(if let Some(v) = value {
437                        let value = if let LoroValue::Container(id) = v {
438                            if id.is_normal() {
439                                LoroValue::Container(register_container_id(
440                                    id.clone(),
441                                    peer_register.as_deref_mut(),
442                                ))
443                            } else {
444                                v.clone()
445                            }
446                        } else {
447                            v.clone()
448                        };
449                        json::MapOp::Insert {
450                            key: key.to_string(),
451                            value,
452                        }
453                    } else {
454                        json::MapOp::Delete {
455                            key: key.to_string(),
456                        }
457                    })
458                }
459
460                _ => unreachable!(),
461            },
462
463            ContainerType::Tree => match content {
464                InnerContent::Tree(op) => JsonOpContent::Tree(match &**op {
465                    TreeOp::Create {
466                        target,
467                        parent,
468                        position,
469                    } => json::TreeOp::Create {
470                        target: register_tree_id(target, peer_register.as_deref_mut()),
471                        parent: parent.map(|p| register_tree_id(&p, peer_register.as_deref_mut())),
472                        fractional_index: position.clone(),
473                    },
474                    TreeOp::Move {
475                        target,
476                        parent,
477                        position,
478                    } => json::TreeOp::Move {
479                        target: register_tree_id(target, peer_register.as_deref_mut()),
480                        parent: parent.map(|p| register_tree_id(&p, peer_register.as_deref_mut())),
481                        fractional_index: position.clone(),
482                    },
483                    TreeOp::Delete { target } => json::TreeOp::Delete {
484                        target: register_tree_id(target, peer_register.as_deref_mut()),
485                    },
486                }),
487                _ => unreachable!(),
488            },
489            ContainerType::Unknown(_) => {
490                let InnerContent::Future(FutureInnerContent::Unknown { prop, value }) = content
491                else {
492                    unreachable!();
493                };
494                JsonOpContent::Future(json::FutureOpWrapper {
495                    prop: *prop,
496                    value: json::FutureOp::Unknown((**value).clone()),
497                })
498            }
499            #[cfg(feature = "counter")]
500            ContainerType::Counter => {
501                let InnerContent::Future(f) = content else {
502                    unreachable!()
503                };
504                match f {
505                    FutureInnerContent::Counter(x) => {
506                        JsonOpContent::Future(json::FutureOpWrapper {
507                            prop: 0,
508                            value: json::FutureOp::Counter(super::OwnedValue::F64(*x)),
509                        })
510                    }
511                    _ => unreachable!(),
512                }
513            }
514        };
515        ops.push(json::JsonOp {
516            counter: *counter,
517            container,
518            content: op,
519        });
520    }
521    let c = json::JsonChange {
522        id: register_id(change.id, peer_register.as_deref_mut()),
523        ops,
524        deps: change
525            .deps
526            .iter()
527            // Make sure the order is deterministic
528            .sorted()
529            .map(|id| register_id(&id, peer_register.as_deref_mut()))
530            .collect(),
531        lamport: *change.lamport,
532        timestamp: *change.timestamp,
533        msg: change.commit_msg.as_deref().map(|x| x.to_string()),
534    };
535    c
536}
537
538fn decode_changes(json: JsonSchema, arena: &SharedArena) -> LoroResult<Vec<Change>> {
539    let JsonSchema {
540        schema_version,
541        start_version,
542        peers,
543        changes,
544    } = json;
545    if schema_version != SCHEMA_VERSION {
546        return Err(LoroError::DecodeError(
547            format!(
548                "unsupported json schema version: expected {SCHEMA_VERSION}, got {schema_version}"
549            )
550            .into_boxed_str(),
551        ));
552    }
553    validate_json_frontiers(&start_version)?;
554
555    let mut ans = Vec::with_capacity(changes.len());
556    for json::JsonChange {
557        id,
558        timestamp,
559        deps,
560        lamport,
561        msg,
562        ops: json_ops,
563    } in changes
564    {
565        let id = convert_id(&id, &peers)?;
566        validate_json_id_counter(id, "change id")?;
567        let mut ops: RleVec<[Op; 1]> = RleVec::new();
568        let mut expected_counter = id.counter;
569        if json_ops.is_empty() {
570            return Err(LoroError::DecodeError(
571                "invalid json change: change must contain at least one op".into(),
572            ));
573        }
574
575        for op in json_ops {
576            let next_counter = validate_json_op_counter(expected_counter, &op)?;
577            let op = decode_op(op, arena, &peers)?;
578            validate_json_op_created_container_ids(id.peer, &op, arena)?;
579            ops.push(op);
580            expected_counter = next_counter;
581        }
582
583        let deps = deps
584            .into_iter()
585            .map(|id| convert_id(&id, &peers))
586            .collect::<LoroResult<Vec<_>>>()?;
587        for dep in &deps {
588            validate_json_id_counter(*dep, "dependency id")?;
589        }
590
591        let change = Change {
592            id,
593            timestamp,
594            deps: Frontiers::from_iter(deps),
595            lamport,
596            ops,
597            commit_msg: msg.map(|x| x.into()),
598        };
599        ans.push(change);
600    }
601    Ok(ans)
602}
603
604fn validate_json_frontiers(frontiers: &Frontiers) -> LoroResult<()> {
605    for id in frontiers.iter() {
606        validate_json_id_counter(id, "start version id")?;
607    }
608
609    Ok(())
610}
611
612fn validate_json_id_counter(id: ID, name: &str) -> LoroResult<()> {
613    if id.counter < 0 {
614        return Err(LoroError::DecodeError(
615            format!("invalid json counter: {name} counter must be non-negative").into_boxed_str(),
616        ));
617    }
618
619    Ok(())
620}
621
622fn validate_json_tree_id_counter(id: &TreeID, name: &str) -> LoroResult<()> {
623    if id.counter < 0 {
624        return Err(LoroError::DecodeError(
625            format!("invalid json counter: {name} counter must be non-negative").into_boxed_str(),
626        ));
627    }
628
629    Ok(())
630}
631
632fn validate_json_container_id_counter(id: &ContainerID, name: &str) -> LoroResult<()> {
633    if let ContainerID::Normal { counter, .. } = id {
634        if *counter < 0 {
635            return Err(LoroError::DecodeError(
636                format!("invalid json counter: {name} counter must be non-negative")
637                    .into_boxed_str(),
638            ));
639        }
640    }
641
642    Ok(())
643}
644
645fn validate_json_op_counter(expected_counter: Counter, op: &json::JsonOp) -> LoroResult<Counter> {
646    let op_len = op.content.op_len();
647    if op_len == 0 {
648        return Err(LoroError::DecodeError(
649            "invalid json op counter: op length must be greater than zero".into(),
650        ));
651    }
652
653    if expected_counter < 0 || op.counter < 0 {
654        return Err(LoroError::DecodeError(
655            "invalid json op counter: op counters must be non-negative".into(),
656        ));
657    }
658
659    let op_len = Counter::try_from(op_len).map_err(|_| {
660        LoroError::DecodeError("invalid json op counter: op length is too large".into())
661    })?;
662    if op.counter != expected_counter {
663        return Err(LoroError::DecodeError(
664            "invalid json op counter: op counters must be contiguous with the change id".into(),
665        ));
666    }
667
668    expected_counter
669        .checked_add(op_len)
670        .ok_or_else(|| LoroError::DecodeError("invalid json op counter: counter overflow".into()))
671}
672
673fn validate_json_op_created_container_ids(
674    peer: PeerID,
675    op: &Op,
676    arena: &SharedArena,
677) -> LoroResult<()> {
678    match &op.content {
679        InnerContent::List(list) => match list {
680            InnerListOp::Insert { slice, .. } => {
681                for (offset, value) in arena.iter_value_slice(slice.to_range()).enumerate() {
682                    let LoroValue::Container(id) = value else {
683                        validate_json_value_has_no_container_refs(&value)?;
684                        continue;
685                    };
686                    let offset = Counter::try_from(offset).map_err(|_| {
687                        LoroError::DecodeError(
688                            "invalid json container id: list offset is too large".into(),
689                        )
690                    })?;
691                    let counter = op.counter.checked_add(offset).ok_or_else(|| {
692                        LoroError::DecodeError("invalid json container id: counter overflow".into())
693                    })?;
694                    validate_json_created_container_id(&id, ID::new(peer, counter))?;
695                }
696            }
697            InnerListOp::Set { value, .. } => {
698                if let LoroValue::Container(id) = value {
699                    validate_json_created_container_id(id, ID::new(peer, op.counter))?;
700                } else {
701                    validate_json_value_has_no_container_refs(value)?;
702                }
703            }
704            InnerListOp::Move { .. }
705            | InnerListOp::InsertText { .. }
706            | InnerListOp::Delete(_)
707            | InnerListOp::StyleEnd => {}
708            InnerListOp::StyleStart { value, .. } => {
709                validate_json_value_has_no_container_refs(value)?;
710            }
711        },
712        InnerContent::Map(map) => {
713            if let Some(value) = &map.value {
714                if let LoroValue::Container(id) = value {
715                    validate_json_created_container_id(id, ID::new(peer, op.counter))?;
716                } else {
717                    validate_json_value_has_no_container_refs(value)?;
718                }
719            }
720        }
721        InnerContent::Tree(tree) => {
722            if let TreeOp::Create { target, .. } = tree.as_ref() {
723                validate_json_tree_create_target(target, ID::new(peer, op.counter))?;
724            }
725        }
726        InnerContent::Future(_) => {}
727    }
728
729    Ok(())
730}
731
732fn validate_json_value_has_no_container_refs(value: &LoroValue) -> LoroResult<()> {
733    let mut stack = vec![value];
734    while let Some(value) = stack.pop() {
735        match value {
736            LoroValue::Container(_) => {
737                return Err(LoroError::DecodeError(
738                    "invalid json container id: container values must not be nested".into(),
739                ));
740            }
741            LoroValue::List(list) => {
742                stack.extend(list.iter());
743            }
744            LoroValue::Map(map) => {
745                stack.extend(map.values());
746            }
747            LoroValue::Null
748            | LoroValue::Bool(_)
749            | LoroValue::Double(_)
750            | LoroValue::I64(_)
751            | LoroValue::Binary(_)
752            | LoroValue::String(_) => {}
753        }
754    }
755
756    Ok(())
757}
758
759fn validate_json_created_container_id(id: &ContainerID, expected_id: ID) -> LoroResult<()> {
760    let ContainerID::Normal { peer, counter, .. } = id else {
761        return Err(LoroError::DecodeError(
762            "invalid json container id: created child containers must be normal".into(),
763        ));
764    };
765
766    if *peer != expected_id.peer || *counter != expected_id.counter {
767        return Err(LoroError::DecodeError(
768            "invalid json container id: created child container id must match the op id".into(),
769        ));
770    }
771
772    Ok(())
773}
774
775fn validate_json_tree_create_target(target: &TreeID, expected_id: ID) -> LoroResult<()> {
776    if target.peer != expected_id.peer || target.counter != expected_id.counter {
777        return Err(LoroError::DecodeError(
778            "invalid json tree target: tree create target must match the op id".into(),
779        ));
780    }
781
782    Ok(())
783}
784
785fn decode_op(op: json::JsonOp, arena: &SharedArena, peers: &Option<Vec<PeerID>>) -> LoroResult<Op> {
786    let json::JsonOp {
787        counter,
788        container,
789        content,
790    } = op;
791    let container = convert_container_id(container, peers)?;
792    validate_json_container_id_counter(&container, "op container")?;
793    let idx = arena.register_container(&container);
794    let content = match container.container_type() {
795        ContainerType::Text => match content {
796            JsonOpContent::Text(text) => match text {
797                json::TextOp::Insert { pos, text } => {
798                    let (slice, result) = arena.alloc_str_with_slice(&text);
799                    InnerContent::List(InnerListOp::InsertText {
800                        slice,
801                        unicode_start: result.start as u32,
802                        unicode_len: (result.end - result.start) as u32,
803                        pos,
804                    })
805                }
806                json::TextOp::Delete {
807                    pos,
808                    len,
809                    start_id: id_start,
810                } => {
811                    let id_start = convert_id(&id_start, peers)?;
812                    validate_json_id_counter(id_start, "text delete start id")?;
813                    InnerContent::List(InnerListOp::Delete(DeleteSpanWithId {
814                        id_start,
815                        span: DeleteSpan {
816                            pos: pos as isize,
817                            signed_len: len as isize,
818                        },
819                    }))
820                }
821                json::TextOp::Mark {
822                    start,
823                    end,
824                    style_key,
825                    style_value,
826                    info,
827                } => InnerContent::List(InnerListOp::StyleStart {
828                    start,
829                    end,
830                    key: style_key.into(),
831                    value: style_value,
832                    info: TextStyleInfoFlag::from_byte(info),
833                }),
834                json::TextOp::MarkEnd => InnerContent::List(InnerListOp::StyleEnd),
835            },
836            _ => {
837                return Err(LoroError::DecodeError(
838                    "invalid op content for text container".into(),
839                ))
840            }
841        },
842        ContainerType::List => match content {
843            JsonOpContent::List(list) => match list {
844                json::ListOp::Insert {
845                    pos,
846                    value: mut values,
847                } => {
848                    for v in values.iter_mut() {
849                        if let LoroValue::Container(id) = v {
850                            if id.is_normal() {
851                                *id = convert_container_id(id.clone(), peers)?;
852                            }
853                        }
854                    }
855                    let range = arena.alloc_values(values.iter().cloned());
856                    InnerContent::List(InnerListOp::Insert {
857                        slice: SliceRange::new(range.start as u32..range.end as u32),
858                        pos: pos as usize,
859                    })
860                }
861                json::ListOp::Delete { pos, len, start_id } => {
862                    let start_id = convert_id(&start_id, peers)?;
863                    validate_json_id_counter(start_id, "list delete start id")?;
864                    InnerContent::List(InnerListOp::Delete(DeleteSpanWithId {
865                        id_start: start_id,
866                        span: DeleteSpan {
867                            pos: pos as isize,
868                            signed_len: len as isize,
869                        },
870                    }))
871                }
872            },
873            _ => {
874                return Err(LoroError::DecodeError(
875                    "invalid op content for list container".into(),
876                ))
877            }
878        },
879        ContainerType::MovableList => match content {
880            JsonOpContent::MovableList(list) => match list {
881                json::MovableListOp::Insert {
882                    pos,
883                    value: mut values,
884                } => {
885                    for v in values.iter_mut() {
886                        if let LoroValue::Container(id) = v {
887                            if id.is_normal() {
888                                *id = convert_container_id(id.clone(), peers)?;
889                            }
890                        }
891                    }
892                    let range = arena.alloc_values(values.iter().cloned());
893                    InnerContent::List(InnerListOp::Insert {
894                        slice: SliceRange::new(range.start as u32..range.end as u32),
895                        pos: pos as usize,
896                    })
897                }
898                json::MovableListOp::Delete { pos, len, start_id } => {
899                    let start_id = convert_id(&start_id, peers)?;
900                    validate_json_id_counter(start_id, "movable list delete start id")?;
901                    InnerContent::List(InnerListOp::Delete(DeleteSpanWithId {
902                        id_start: start_id,
903                        span: DeleteSpan {
904                            pos: pos as isize,
905                            signed_len: len as isize,
906                        },
907                    }))
908                }
909                json::MovableListOp::Move {
910                    from,
911                    elem_id: from_id,
912                    to,
913                } => {
914                    let from_id = convert_idlp(&from_id, peers)?;
915                    InnerContent::List(InnerListOp::Move {
916                        from,
917                        elem_id: from_id,
918                        to,
919                    })
920                }
921                json::MovableListOp::Set { elem_id, mut value } => {
922                    let elem_id = convert_idlp(&elem_id, peers)?;
923                    if let LoroValue::Container(id) = &mut value {
924                        *id = convert_container_id(id.clone(), peers)?;
925                    }
926                    InnerContent::List(InnerListOp::Set { elem_id, value })
927                }
928            },
929            _ => {
930                return Err(LoroError::DecodeError(
931                    "invalid op content for movable list container".into(),
932                ))
933            }
934        },
935        ContainerType::Map => match content {
936            JsonOpContent::Map(map) => match map {
937                json::MapOp::Insert { key, mut value } => {
938                    if let LoroValue::Container(id) = &mut value {
939                        *id = convert_container_id(id.clone(), peers)?;
940                    }
941                    InnerContent::Map(MapSet {
942                        key: key.into(),
943                        value: Some(value),
944                    })
945                }
946                json::MapOp::Delete { key } => InnerContent::Map(MapSet {
947                    key: key.into(),
948                    value: None,
949                }),
950            },
951            _ => {
952                return Err(LoroError::DecodeError(
953                    "invalid op content for map container".into(),
954                ))
955            }
956        },
957        ContainerType::Tree => match content {
958            JsonOpContent::Tree(tree) => match tree {
959                json::TreeOp::Create {
960                    target,
961                    parent,
962                    fractional_index,
963                } => {
964                    let target = convert_tree_id(&target, peers)?;
965                    validate_json_tree_id_counter(&target, "tree create target")?;
966                    let parent = match parent {
967                        Some(p) => {
968                            let parent = convert_tree_id(&p, peers)?;
969                            validate_json_tree_id_counter(&parent, "tree create parent")?;
970                            Some(parent)
971                        }
972                        None => None,
973                    };
974                    InnerContent::Tree(Arc::new(TreeOp::Create {
975                        target,
976                        parent,
977                        position: fractional_index,
978                    }))
979                }
980                json::TreeOp::Move {
981                    target,
982                    parent,
983                    fractional_index,
984                } => {
985                    let target = convert_tree_id(&target, peers)?;
986                    validate_json_tree_id_counter(&target, "tree move target")?;
987                    let parent = match parent {
988                        Some(p) => {
989                            let parent = convert_tree_id(&p, peers)?;
990                            validate_json_tree_id_counter(&parent, "tree move parent")?;
991                            Some(parent)
992                        }
993                        None => None,
994                    };
995                    InnerContent::Tree(Arc::new(TreeOp::Move {
996                        target,
997                        parent,
998                        position: fractional_index,
999                    }))
1000                }
1001                json::TreeOp::Delete { target } => {
1002                    let target = convert_tree_id(&target, peers)?;
1003                    validate_json_tree_id_counter(&target, "tree delete target")?;
1004                    InnerContent::Tree(Arc::new(TreeOp::Delete { target }))
1005                }
1006            },
1007            _ => {
1008                return Err(LoroError::DecodeError(
1009                    "invalid op content for tree container".into(),
1010                ))
1011            }
1012        },
1013        ContainerType::Unknown(_) => match content {
1014            JsonOpContent::Future(json::FutureOpWrapper {
1015                prop,
1016                value: json::FutureOp::Unknown(value),
1017            }) => InnerContent::Future(FutureInnerContent::Unknown {
1018                prop,
1019                value: Box::new(value),
1020            }),
1021            _ => {
1022                return Err(LoroError::DecodeError(
1023                    "invalid op content for unknown container".into(),
1024                ))
1025            }
1026        },
1027        #[cfg(feature = "counter")]
1028        ContainerType::Counter => {
1029            let JsonOpContent::Future(json::FutureOpWrapper { prop: _, value }) = content else {
1030                return Err(LoroError::DecodeError(
1031                    "invalid op content for counter container".into(),
1032                ));
1033            };
1034            use crate::encoding::OwnedValue;
1035            match value {
1036                json::FutureOp::Counter(OwnedValue::F64(c))
1037                | json::FutureOp::Unknown(OwnedValue::F64(c)) => {
1038                    InnerContent::Future(FutureInnerContent::Counter(c))
1039                }
1040                json::FutureOp::Counter(OwnedValue::I64(c))
1041                | json::FutureOp::Unknown(OwnedValue::I64(c)) => {
1042                    InnerContent::Future(FutureInnerContent::Counter(c as f64))
1043                }
1044                _ => {
1045                    return Err(LoroError::DecodeError(
1046                        "invalid counter op value type".into(),
1047                    ))
1048                }
1049            }
1050        } // Note: The Future Type need try to parse Op from the unknown content
1051    };
1052    Ok(Op {
1053        counter,
1054        container: idx,
1055        content,
1056    })
1057}
1058
1059impl TryFrom<&str> for JsonSchema {
1060    type Error = serde_json::Error;
1061
1062    fn try_from(value: &str) -> Result<Self, Self::Error> {
1063        serde_json::from_str(value)
1064    }
1065}
1066
1067impl TryFrom<&String> for JsonSchema {
1068    type Error = serde_json::Error;
1069
1070    fn try_from(value: &String) -> Result<Self, Self::Error> {
1071        serde_json::from_str(value)
1072    }
1073}
1074
1075impl TryFrom<String> for JsonSchema {
1076    type Error = serde_json::Error;
1077
1078    fn try_from(value: String) -> Result<Self, Self::Error> {
1079        serde_json::from_str(&value)
1080    }
1081}
1082
1083pub mod json {
1084    use crate::{
1085        encoding::OwnedValue,
1086        version::{Frontiers, VersionRange},
1087    };
1088    use fractional_index::FractionalIndex;
1089    use loro_common::{ContainerID, Counter, IdLp, Lamport, LoroValue, PeerID, TreeID, ID};
1090    use serde::{Deserialize, Serialize};
1091    use std::ops::Range;
1092
1093    use super::{get_peer_from_peers, redact_value};
1094
1095    #[derive(Debug, Clone, Serialize, Deserialize)]
1096    pub struct JsonSchema {
1097        pub schema_version: u8,
1098        #[serde(with = "self::serde_impl::frontiers")]
1099        pub start_version: Frontiers,
1100        #[serde(with = "self::serde_impl::peer_id")]
1101        pub peers: Option<Vec<PeerID>>,
1102        pub changes: Vec<JsonChange>,
1103    }
1104
1105    #[derive(Debug, Clone, Serialize, Deserialize)]
1106    pub struct JsonChange {
1107        #[serde(with = "self::serde_impl::id")]
1108        pub id: ID,
1109        pub timestamp: i64,
1110        #[serde(with = "self::serde_impl::deps")]
1111        pub deps: Vec<ID>,
1112        pub lamport: Lamport,
1113        pub msg: Option<String>,
1114        pub ops: Vec<JsonOp>,
1115    }
1116
1117    impl JsonChange {
1118        pub fn checked_op_len(&self) -> Option<usize> {
1119            let Some(last_op) = self.ops.last() else {
1120                return Some(0);
1121            };
1122            if last_op.counter < self.id.counter {
1123                return None;
1124            }
1125
1126            let counter_delta = last_op.counter.checked_sub(self.id.counter)?;
1127            let counter_delta = usize::try_from(counter_delta).ok()?;
1128            counter_delta.checked_add(last_op.content.op_len())
1129        }
1130
1131        pub fn op_len(&self) -> usize {
1132            self.checked_op_len().unwrap_or(0)
1133        }
1134    }
1135
1136    #[derive(Debug, Clone)]
1137    pub struct JsonOp {
1138        pub content: JsonOpContent,
1139        pub container: ContainerID,
1140        pub counter: i32,
1141    }
1142
1143    #[derive(Debug, Clone, Serialize, Deserialize)]
1144    #[serde(untagged)]
1145    pub enum JsonOpContent {
1146        List(ListOp),
1147        MovableList(MovableListOp),
1148        Map(MapOp),
1149        Text(TextOp),
1150        Tree(TreeOp),
1151        // #[serde(with = "self::serde_impl::future_op")]
1152        Future(FutureOpWrapper),
1153    }
1154
1155    impl JsonOpContent {
1156        pub fn op_len(&self) -> usize {
1157            match self {
1158                JsonOpContent::List(list_op) => list_op.op_len(),
1159                JsonOpContent::MovableList(movable_list_op) => movable_list_op.op_len(),
1160                JsonOpContent::Map(..) => 1,
1161                JsonOpContent::Text(text_op) => text_op.op_len(),
1162                JsonOpContent::Tree(..) => 1,
1163                JsonOpContent::Future(..) => 1,
1164            }
1165        }
1166    }
1167
1168    #[derive(Debug, Clone, Serialize, Deserialize)]
1169    pub struct FutureOpWrapper {
1170        #[serde(flatten)]
1171        pub value: FutureOp,
1172        pub prop: i32,
1173    }
1174
1175    #[derive(Debug, Clone, Serialize, Deserialize)]
1176    #[serde(tag = "type", rename_all = "snake_case")]
1177    pub enum ListOp {
1178        Insert {
1179            pos: u32,
1180            value: Vec<LoroValue>,
1181        },
1182        Delete {
1183            pos: i32,
1184            len: i32,
1185            #[serde(with = "self::serde_impl::id")]
1186            start_id: ID,
1187        },
1188    }
1189
1190    impl ListOp {
1191        fn op_len(&self) -> usize {
1192            match self {
1193                ListOp::Insert { value: values, .. } => values.len(),
1194                ListOp::Delete { len, .. } => (*len).unsigned_abs() as usize,
1195            }
1196        }
1197    }
1198
1199    #[derive(Debug, Clone, Serialize, Deserialize)]
1200    #[serde(tag = "type", rename_all = "snake_case")]
1201    pub enum MovableListOp {
1202        Insert {
1203            pos: u32,
1204            value: Vec<LoroValue>,
1205        },
1206        Delete {
1207            pos: i32,
1208            len: i32,
1209            #[serde(with = "self::serde_impl::id")]
1210            start_id: ID,
1211        },
1212        Move {
1213            from: u32,
1214            to: u32,
1215            #[serde(with = "self::serde_impl::idlp")]
1216            elem_id: IdLp,
1217        },
1218        Set {
1219            #[serde(with = "self::serde_impl::idlp")]
1220            elem_id: IdLp,
1221            value: LoroValue,
1222        },
1223    }
1224
1225    impl MovableListOp {
1226        fn op_len(&self) -> usize {
1227            match self {
1228                MovableListOp::Insert { value: values, .. } => values.len(),
1229                MovableListOp::Delete { len, .. } => (*len).unsigned_abs() as usize,
1230                MovableListOp::Move { .. } => 1,
1231                MovableListOp::Set { .. } => 1,
1232            }
1233        }
1234    }
1235
1236    #[derive(Debug, Clone, Serialize, Deserialize)]
1237    #[serde(tag = "type", rename_all = "snake_case")]
1238    pub enum MapOp {
1239        Insert { key: String, value: LoroValue },
1240        Delete { key: String },
1241    }
1242
1243    #[derive(Debug, Clone, Serialize, Deserialize)]
1244    #[serde(tag = "type", rename_all = "snake_case")]
1245    pub enum TextOp {
1246        Insert {
1247            pos: u32,
1248            text: String,
1249        },
1250        Delete {
1251            pos: i32,
1252            len: i32,
1253            #[serde(with = "self::serde_impl::id")]
1254            start_id: ID,
1255        },
1256        Mark {
1257            start: u32,
1258            end: u32,
1259            style_key: String,
1260            style_value: LoroValue,
1261            info: u8,
1262        },
1263        MarkEnd,
1264    }
1265
1266    impl TextOp {
1267        fn op_len(&self) -> usize {
1268            match self {
1269                TextOp::Insert { text, .. } => text.chars().count(),
1270                TextOp::Delete { len, .. } => len.unsigned_abs() as usize,
1271                TextOp::Mark { .. } => 1,
1272                TextOp::MarkEnd => 1,
1273            }
1274        }
1275    }
1276
1277    #[derive(Debug, Clone, Serialize, Deserialize)]
1278    #[serde(tag = "type", rename_all = "snake_case")]
1279    pub enum TreeOp {
1280        Create {
1281            #[serde(with = "self::serde_impl::tree_id")]
1282            target: TreeID,
1283            #[serde(with = "self::serde_impl::option_tree_id")]
1284            parent: Option<TreeID>,
1285            #[serde(default, with = "self::serde_impl::fractional_index")]
1286            fractional_index: FractionalIndex,
1287        },
1288        Move {
1289            #[serde(with = "self::serde_impl::tree_id")]
1290            target: TreeID,
1291            #[serde(with = "self::serde_impl::option_tree_id")]
1292            parent: Option<TreeID>,
1293            #[serde(default, with = "self::serde_impl::fractional_index")]
1294            fractional_index: FractionalIndex,
1295        },
1296        Delete {
1297            #[serde(with = "self::serde_impl::tree_id")]
1298            target: TreeID,
1299        },
1300    }
1301
1302    #[derive(Debug, Clone, Serialize, Deserialize)]
1303    #[serde(tag = "type", rename_all = "snake_case")]
1304    pub enum FutureOp {
1305        #[cfg(feature = "counter")]
1306        Counter(OwnedValue),
1307        Unknown(OwnedValue),
1308    }
1309
1310    mod serde_impl {
1311
1312        use loro_common::{ContainerID, ContainerType};
1313        use serde::{
1314            de::{Error, IgnoredAny, MapAccess, Visitor},
1315            ser::SerializeStruct,
1316            Deserialize, Deserializer, Serialize, Serializer,
1317        };
1318
1319        #[allow(unused_imports)]
1320        use crate::encoding::OwnedValue;
1321
1322        impl Serialize for super::JsonOp {
1323            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1324            where
1325                S: Serializer,
1326            {
1327                let mut s = serializer.serialize_struct("Op", 3)?;
1328                s.serialize_field("container", &self.container.to_string())?;
1329                s.serialize_field("content", &self.content)?;
1330                s.serialize_field("counter", &self.counter)?;
1331                s.end()
1332            }
1333        }
1334
1335        impl<'de> Deserialize<'de> for super::JsonOp {
1336            fn deserialize<D>(deserializer: D) -> Result<super::JsonOp, D::Error>
1337            where
1338                D: Deserializer<'de>,
1339            {
1340                struct __Visitor;
1341
1342                impl<'de> Visitor<'de> for __Visitor {
1343                    type Value = super::JsonOp;
1344                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1345                        formatter.write_str("struct Op")
1346                    }
1347
1348                    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1349                    where
1350                        A: MapAccess<'de>,
1351                    {
1352                        let mut container = None;
1353                        let mut content = None;
1354                        let mut counter = None;
1355
1356                        while let Some(key) = map.next_key::<String>()? {
1357                            match key.as_str() {
1358                                "container" => {
1359                                    if container.is_some() {
1360                                        return Err(A::Error::duplicate_field("container"));
1361                                    }
1362                                    let value = map.next_value::<String>()?;
1363                                    container =
1364                                        Some(ContainerID::try_from(value.as_str()).map_err(
1365                                            |_| A::Error::custom("invalid container id"),
1366                                        )?);
1367                                }
1368                                "content" => {
1369                                    if content.is_some() {
1370                                        return Err(A::Error::duplicate_field("content"));
1371                                    }
1372                                    content = Some(map.next_value::<serde_json::Value>()?);
1373                                }
1374                                "counter" => {
1375                                    if counter.is_some() {
1376                                        return Err(A::Error::duplicate_field("counter"));
1377                                    }
1378                                    counter = Some(map.next_value::<i32>()?);
1379                                }
1380                                _ => {
1381                                    let _ = map.next_value::<IgnoredAny>()?;
1382                                }
1383                            }
1384                        }
1385
1386                        let container =
1387                            container.ok_or_else(|| A::Error::missing_field("container"))?;
1388                        let content = content.ok_or_else(|| A::Error::missing_field("content"))?;
1389                        let op = json_op_content_from_value(&container, content)?;
1390                        let counter = counter.ok_or_else(|| A::Error::missing_field("counter"))?;
1391                        Ok(super::JsonOp {
1392                            container,
1393                            content: op,
1394                            counter,
1395                        })
1396                    }
1397                }
1398                const FIELDS: &[&str] = &["container", "content", "counter"];
1399                deserializer.deserialize_struct("JsonOp", FIELDS, __Visitor)
1400            }
1401        }
1402
1403        fn json_op_content_from_value<E: Error>(
1404            container: &ContainerID,
1405            value: serde_json::Value,
1406        ) -> Result<super::JsonOpContent, E> {
1407            match container.container_type() {
1408                ContainerType::List => serde_json::from_value(value)
1409                    .map(super::JsonOpContent::List)
1410                    .map_err(E::custom),
1411                ContainerType::MovableList => serde_json::from_value(value)
1412                    .map(super::JsonOpContent::MovableList)
1413                    .map_err(E::custom),
1414                ContainerType::Map => serde_json::from_value(value)
1415                    .map(super::JsonOpContent::Map)
1416                    .map_err(E::custom),
1417                ContainerType::Text => serde_json::from_value(value)
1418                    .map(super::JsonOpContent::Text)
1419                    .map_err(E::custom),
1420                ContainerType::Tree => serde_json::from_value(value)
1421                    .map(super::JsonOpContent::Tree)
1422                    .map_err(E::custom),
1423                ContainerType::Unknown(_) => serde_json::from_value(value)
1424                    .map(super::JsonOpContent::Future)
1425                    .map_err(E::custom),
1426                #[cfg(feature = "counter")]
1427                ContainerType::Counter => {
1428                    match serde_json::from_value::<super::FutureOpWrapper>(value.clone()) {
1429                        Ok(op) => Ok(super::JsonOpContent::Future(op)),
1430                        Err(_) => {
1431                            let value =
1432                                serde_json::from_value::<OwnedValue>(value).map_err(E::custom)?;
1433                            Ok(super::JsonOpContent::Future(super::FutureOpWrapper {
1434                                prop: 0,
1435                                value: super::FutureOp::Counter(value),
1436                            }))
1437                        }
1438                    }
1439                }
1440            }
1441        }
1442
1443        pub mod id {
1444            use loro_common::ID;
1445            use serde::{Deserialize, Deserializer, Serializer};
1446
1447            pub fn serialize<S>(id: &ID, s: S) -> Result<S::Ok, S::Error>
1448            where
1449                S: Serializer,
1450            {
1451                s.serialize_str(&id.to_string())
1452            }
1453
1454            pub fn deserialize<'de, 'a, D>(d: D) -> Result<ID, D::Error>
1455            where
1456                D: Deserializer<'de>,
1457            {
1458                // NOTE: https://github.com/serde-rs/serde/issues/2467    we use String here
1459                let str: String = Deserialize::deserialize(d)?;
1460                let id: ID = ID::try_from(str.as_str())
1461                    .map_err(|_| serde::de::Error::custom("invalid ID format"))?;
1462                Ok(id)
1463            }
1464        }
1465
1466        pub mod frontiers {
1467            use itertools::Itertools;
1468            use loro_common::ID;
1469            use serde::{ser::SerializeMap, Deserializer, Serializer};
1470
1471            use crate::version::Frontiers;
1472
1473            pub fn serialize<S>(f: &Frontiers, s: S) -> Result<S::Ok, S::Error>
1474            where
1475                S: Serializer,
1476            {
1477                let mut map = s.serialize_map(Some(f.len()))?;
1478                for id in f
1479                    .iter()
1480                    // Make sure the order is deterministic
1481                    .sorted()
1482                {
1483                    map.serialize_entry(&id.peer.to_string(), &id.counter)?;
1484                }
1485                map.end()
1486            }
1487
1488            pub fn deserialize<'de, 'a, D>(d: D) -> Result<Frontiers, D::Error>
1489            where
1490                D: Deserializer<'de>,
1491            {
1492                struct __Visitor;
1493                impl<'de> serde::de::Visitor<'de> for __Visitor {
1494                    type Value = Frontiers;
1495                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1496                        formatter.write_str("a Frontiers")
1497                    }
1498
1499                    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1500                    where
1501                        A: serde::de::MapAccess<'de>,
1502                    {
1503                        let mut f = Frontiers::default();
1504                        while let Some((k, v)) = map.next_entry::<String, i32>()? {
1505                            f.push(ID::new(
1506                                k.parse().map_err(|_| {
1507                                    serde::de::Error::custom("invalid peer id in frontiers")
1508                                })?,
1509                                v,
1510                            ))
1511                        }
1512                        Ok(f)
1513                    }
1514                }
1515                d.deserialize_map(__Visitor)
1516            }
1517        }
1518
1519        pub mod deps {
1520            use loro_common::ID;
1521            use serde::{Deserialize, Deserializer, Serializer};
1522
1523            pub fn serialize<S>(deps: &[ID], s: S) -> Result<S::Ok, S::Error>
1524            where
1525                S: Serializer,
1526            {
1527                s.collect_seq(deps.iter().map(|x| x.to_string()))
1528            }
1529
1530            pub fn deserialize<'de, 'a, D>(d: D) -> Result<Vec<ID>, D::Error>
1531            where
1532                D: Deserializer<'de>,
1533            {
1534                let deps: Vec<String> = Deserialize::deserialize(d)?;
1535                deps.into_iter()
1536                    .map(|x| {
1537                        ID::try_from(x.as_str())
1538                            .map_err(|_| serde::de::Error::custom("invalid ID in deps"))
1539                    })
1540                    .collect::<Result<Vec<_>, _>>()
1541            }
1542        }
1543
1544        pub mod peer_id {
1545            use loro_common::PeerID;
1546            use serde::{Deserialize, Deserializer, Serializer};
1547
1548            pub fn serialize<S>(peers: &Option<Vec<PeerID>>, s: S) -> Result<S::Ok, S::Error>
1549            where
1550                S: Serializer,
1551            {
1552                match peers {
1553                    Some(peers) => s.collect_seq(peers.iter().map(|x| x.to_string())),
1554                    None => s.serialize_none(),
1555                }
1556            }
1557
1558            pub fn deserialize<'de, 'a, D>(d: D) -> Result<Option<Vec<PeerID>>, D::Error>
1559            where
1560                D: Deserializer<'de>,
1561            {
1562                let peers: Option<Vec<String>> = Deserialize::deserialize(d)?;
1563                peers
1564                    .map(|x| {
1565                        x.into_iter()
1566                            .map(|x| {
1567                                x.parse()
1568                                    .map_err(|_| serde::de::Error::custom("invalid peer id"))
1569                            })
1570                            .collect::<Result<Vec<_>, _>>()
1571                    })
1572                    .transpose()
1573            }
1574        }
1575
1576        pub mod idlp {
1577            use loro_common::IdLp;
1578            use serde::{Deserialize, Deserializer, Serializer};
1579
1580            pub fn serialize<S>(idlp: &IdLp, s: S) -> Result<S::Ok, S::Error>
1581            where
1582                S: Serializer,
1583            {
1584                s.serialize_str(&idlp.to_string())
1585            }
1586
1587            pub fn deserialize<'de, 'a, D>(d: D) -> Result<IdLp, D::Error>
1588            where
1589                D: Deserializer<'de>,
1590            {
1591                let str: String = Deserialize::deserialize(d)?;
1592                let id: IdLp = IdLp::try_from(str.as_str())
1593                    .map_err(|_| serde::de::Error::custom("invalid IdLp format"))?;
1594                Ok(id)
1595            }
1596        }
1597
1598        pub mod tree_id {
1599            use loro_common::TreeID;
1600            use serde::{Deserialize, Deserializer, Serializer};
1601
1602            pub fn serialize<S>(id: &TreeID, s: S) -> Result<S::Ok, S::Error>
1603            where
1604                S: Serializer,
1605            {
1606                s.serialize_str(&id.to_string())
1607            }
1608
1609            pub fn deserialize<'de, 'a, D>(d: D) -> Result<TreeID, D::Error>
1610            where
1611                D: Deserializer<'de>,
1612            {
1613                let str: String = Deserialize::deserialize(d)?;
1614                let id: TreeID = TreeID::try_from(str.as_str())
1615                    .map_err(|_| serde::de::Error::custom("invalid TreeID format"))?;
1616                Ok(id)
1617            }
1618        }
1619
1620        pub mod option_tree_id {
1621            use loro_common::TreeID;
1622            use serde::{Deserialize, Deserializer, Serializer};
1623
1624            pub fn serialize<S>(id: &Option<TreeID>, s: S) -> Result<S::Ok, S::Error>
1625            where
1626                S: Serializer,
1627            {
1628                match id {
1629                    Some(id) => s.serialize_str(&id.to_string()),
1630                    None => s.serialize_none(),
1631                }
1632            }
1633
1634            pub fn deserialize<'de, 'a, D>(d: D) -> Result<Option<TreeID>, D::Error>
1635            where
1636                D: Deserializer<'de>,
1637            {
1638                let str: Option<String> = Deserialize::deserialize(d)?;
1639                match str {
1640                    Some(str) => {
1641                        let id: TreeID = TreeID::try_from(str.as_str())
1642                            .map_err(|_| serde::de::Error::custom("invalid TreeID format"))?;
1643                        Ok(Some(id))
1644                    }
1645                    None => Ok(None),
1646                }
1647            }
1648        }
1649
1650        pub mod fractional_index {
1651            use fractional_index::FractionalIndex;
1652            use serde::{Deserialize, Deserializer, Serializer};
1653
1654            pub fn serialize<S>(fi: &FractionalIndex, s: S) -> Result<S::Ok, S::Error>
1655            where
1656                S: Serializer,
1657            {
1658                s.serialize_str(&fi.to_string())
1659            }
1660
1661            pub fn deserialize<'de, 'a, D>(d: D) -> Result<FractionalIndex, D::Error>
1662            where
1663                D: Deserializer<'de>,
1664            {
1665                let str: String = Deserialize::deserialize(d)?;
1666                if !str.len().is_multiple_of(2) {
1667                    return Err(serde::de::Error::custom(
1668                        "invalid fractional index hex length",
1669                    ));
1670                }
1671
1672                let mut bytes = Vec::with_capacity(str.len() / 2);
1673                for i in 0..str.len() / 2 {
1674                    let byte = u8::from_str_radix(&str[i * 2..i * 2 + 2], 16).map_err(|e| {
1675                        serde::de::Error::custom(format!("invalid fractional index hex: {e}"))
1676                    })?;
1677                    bytes.push(byte);
1678                }
1679
1680                Ok(FractionalIndex::from_bytes(bytes))
1681            }
1682        }
1683    }
1684
1685    #[derive(thiserror::Error, Debug, PartialEq, Eq)]
1686    pub enum RedactError {
1687        #[error("unknown operation type")]
1688        UnknownOperationType,
1689        #[error("invalid schema: {0}")]
1690        InvalidSchema(String),
1691    }
1692
1693    fn invalid_schema(message: impl Into<String>) -> RedactError {
1694        RedactError::InvalidSchema(message.into())
1695    }
1696
1697    fn checked_redact_op_len(op: &JsonOp) -> Result<Counter, RedactError> {
1698        let len = op.content.op_len();
1699        if len == 0 {
1700            return Err(invalid_schema("op length must be greater than zero"));
1701        }
1702
1703        Counter::try_from(len).map_err(|_| invalid_schema("op length is too large"))
1704    }
1705
1706    fn validate_redactable_change(change: &JsonChange) -> Result<usize, RedactError> {
1707        if change.id.counter < 0 {
1708            return Err(invalid_schema("change id counter must be non-negative"));
1709        }
1710
1711        let mut expected_counter = change.id.counter;
1712        for op in &change.ops {
1713            if op.counter < 0 {
1714                return Err(invalid_schema("op counter must be non-negative"));
1715            }
1716            if op.counter != expected_counter {
1717                return Err(invalid_schema(
1718                    "op counters must be contiguous with the change id",
1719                ));
1720            }
1721
1722            let len = checked_redact_op_len(op)?;
1723            expected_counter = expected_counter
1724                .checked_add(len)
1725                .ok_or_else(|| invalid_schema("op counter overflow"))?;
1726        }
1727
1728        let len = expected_counter
1729            .checked_sub(change.id.counter)
1730            .ok_or_else(|| invalid_schema("change counter overflow"))?;
1731        usize::try_from(len).map_err(|_| invalid_schema("change length is too large"))
1732    }
1733
1734    /// Redacts sensitive content within the specified range by replacing it with default values.
1735    ///
1736    /// This method applies the following redaction rules:
1737    ///
1738    /// - Preserves delete and move operations without changes
1739    /// - Replaces text insertion content with the Unicode replacement character (U+FFFD)
1740    /// - Substitutes list and map insert values with `LoroValue::Null`
1741    /// - Maintains child container creation operations
1742    /// - Replaces text mark values with `LoroValue::Null`
1743    /// - Preserves map insertion and text annotation keys
1744    /// - Resets counter operations to zero
1745    /// - Leaves unknown operation types (from future Loro versions) unchanged
1746    ///
1747    /// This approach ensures sensitive data removal while preserving the document's overall
1748    /// structure. Redacted documents maintain seamless collaboration capabilities with both
1749    /// redacted and non-redacted versions.
1750    pub fn redact(json: &mut JsonSchema, range: VersionRange) -> Result<(), RedactError> {
1751        let peers = json.peers.clone();
1752        let mut errors = Vec::new();
1753        for change in json.changes.iter_mut() {
1754            let real_peer = get_peer_from_peers(&peers, change.id.peer)
1755                .map_err(|_| RedactError::InvalidSchema("peer index out of range".to_string()))?;
1756            let real_id = ID::new(real_peer, change.id.counter);
1757            let change_len = validate_redactable_change(change)?;
1758            if !range.has_overlap_with(real_id.to_span(change_len)) {
1759                continue;
1760            }
1761
1762            let redact_range = range.get(&real_peer).copied().unwrap();
1763            for op in change.ops.iter_mut() {
1764                if op.counter >= redact_range.1 {
1765                    break;
1766                }
1767
1768                let len = checked_redact_op_len(op)?;
1769                let op_end = op
1770                    .counter
1771                    .checked_add(len)
1772                    .ok_or_else(|| invalid_schema("op counter overflow"))?;
1773                if op_end <= redact_range.0 {
1774                    continue;
1775                }
1776
1777                let result = redact_op(
1778                    &mut op.content,
1779                    redact_range.0.saturating_sub(op.counter).max(0).min(len)
1780                        ..redact_range.1.saturating_sub(op.counter).max(0).min(len),
1781                );
1782                match result {
1783                    Ok(()) => {}
1784                    Err(e) => errors.push(e),
1785                }
1786            }
1787        }
1788
1789        if errors.is_empty() {
1790            Ok(())
1791        } else {
1792            Err(errors.pop().unwrap())
1793        }
1794    }
1795
1796    fn redact_op(op: &mut JsonOpContent, range: Range<Counter>) -> Result<(), RedactError> {
1797        match op {
1798            JsonOpContent::List(list_op) => {
1799                match list_op {
1800                    ListOp::Insert { value: values, .. } => {
1801                        for i in range {
1802                            redact_value(&mut values[i as usize]);
1803                        }
1804                    }
1805                    ListOp::Delete { .. } => {
1806                        // Delete op won't be changed
1807                    }
1808                }
1809            }
1810            JsonOpContent::MovableList(movable_list_op) => {
1811                match movable_list_op {
1812                    MovableListOp::Insert { value: values, .. } => {
1813                        for i in range {
1814                            redact_value(&mut values[i as usize]);
1815                        }
1816                    }
1817                    MovableListOp::Delete { .. } | MovableListOp::Move { .. } => {
1818                        // Delete and move ops won't be changed
1819                    }
1820                    MovableListOp::Set { value, .. } => {
1821                        assert!(range.start == 0 && range.len() == 1);
1822                        redact_value(value);
1823                    }
1824                }
1825            }
1826            JsonOpContent::Map(map_op) => {
1827                match map_op {
1828                    MapOp::Insert { value, .. } => {
1829                        assert!(range.start == 0 && range.len() == 1);
1830                        redact_value(value);
1831                    }
1832                    MapOp::Delete { .. } => {
1833                        // Delete op won't be changed
1834                    }
1835                }
1836            }
1837            JsonOpContent::Text(text_op) => {
1838                match text_op {
1839                    TextOp::Insert { text, .. } => {
1840                        let mut chars = vec![];
1841                        for (i, c) in text.chars().enumerate() {
1842                            if i < range.start as usize || i >= range.end as usize {
1843                                chars.push(c);
1844                            } else {
1845                                chars.push("� ".chars().next().unwrap());
1846                            }
1847                        }
1848                        *text = chars.into_iter().collect();
1849                    }
1850                    TextOp::Delete { .. } => {
1851                        // Delete op won't be changed
1852                    }
1853                    TextOp::Mark { style_value, .. } => {
1854                        assert!(range.start == 0 && range.len() == 1);
1855                        *style_value = LoroValue::Null;
1856                    }
1857                    TextOp::MarkEnd => {
1858                        // MarkEnd won't be changed
1859                    }
1860                }
1861            }
1862            JsonOpContent::Tree(..) => {
1863                // Creation of child container won't be changed
1864            }
1865            JsonOpContent::Future(future_op_wrapper) => match &mut future_op_wrapper.value {
1866                #[cfg(feature = "counter")]
1867                FutureOp::Counter(owned_value) => {
1868                    *owned_value = OwnedValue::I64(0);
1869                }
1870                FutureOp::Unknown(..) => {
1871                    return Err(RedactError::UnknownOperationType);
1872                }
1873            },
1874        }
1875
1876        Ok(())
1877    }
1878}
1879
1880fn redact_value(v: &mut LoroValue) {
1881    match v {
1882        LoroValue::Container(_) => {}
1883        _ => *v = LoroValue::Null,
1884    }
1885}
1886
1887#[cfg(test)]
1888mod tests {
1889    use crate::{LoroDoc, VersionVector};
1890
1891    #[test]
1892    fn json_range_version() {
1893        let doc = LoroDoc::new_auto_commit();
1894        doc.set_peer_id(0).unwrap();
1895        let list = doc.get_list("list");
1896        list.insert(0, "a").unwrap();
1897        list.insert(0, "b").unwrap();
1898        list.insert(0, "c").unwrap();
1899        let json = doc.export_json_updates(
1900            &VersionVector::from_iter(vec![(0, 1)]),
1901            &VersionVector::from_iter(vec![(0, 2)]),
1902            true,
1903        );
1904        assert_eq!(json.changes[0].ops.len(), 1);
1905        let json = doc.export_json_updates(
1906            &VersionVector::from_iter(vec![(0, 0)]),
1907            &VersionVector::from_iter(vec![(0, 2)]),
1908            true,
1909        );
1910        assert_eq!(json.changes[0].ops.len(), 2);
1911    }
1912}