Skip to main content

loro_common/
lib.rs

1#![allow(unused_assignments)]
2
3use std::{fmt::Display, io::Write};
4
5use arbitrary::Arbitrary;
6use enum_as_inner::EnumAsInner;
7
8use nonmax::{NonMaxI32, NonMaxU32};
9use serde::{Deserialize, Serialize};
10
11mod error;
12mod id;
13mod internal_string;
14mod logging;
15mod macros;
16mod span;
17mod value;
18
19pub use error::{LoroEncodeError, LoroError, LoroResult, LoroTreeError};
20pub use internal_string::InternalString;
21pub use logging::log::*;
22#[doc(hidden)]
23pub use rustc_hash::FxHashMap;
24pub use span::*;
25pub use value::{
26    to_value, LoroBinaryValue, LoroListValue, LoroMapValue, LoroStringValue, LoroValue,
27};
28
29/// Unique id for each peer. It's a random u64 by default.
30pub type PeerID = u64;
31/// If it's the nth Op of a peer, the counter will be n.
32pub type Counter = i32;
33/// It's the [Lamport clock](https://en.wikipedia.org/wiki/Lamport_timestamp)
34pub type Lamport = u32;
35
36/// It's the unique ID of an Op represented by [PeerID] and [Counter].
37#[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
38pub struct ID {
39    pub peer: PeerID,
40    pub counter: Counter,
41}
42
43impl ID {
44    pub fn to_bytes(&self) -> [u8; 12] {
45        let mut bytes = [0; 12];
46        bytes[..8].copy_from_slice(&self.peer.to_be_bytes());
47        bytes[8..].copy_from_slice(&self.counter.to_be_bytes());
48        bytes
49    }
50
51    pub fn from_bytes(bytes: &[u8]) -> Self {
52        if bytes.len() != 12 {
53            panic!(
54                "Invalid ID bytes. Expected 12 bytes but got {} bytes",
55                bytes.len()
56            );
57        }
58
59        Self {
60            peer: u64::from_be_bytes(bytes[..8].try_into().unwrap()),
61            counter: i32::from_be_bytes(bytes[8..].try_into().unwrap()),
62        }
63    }
64}
65
66/// It's the unique ID of an Op represented by [PeerID] and [Counter].
67#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
68pub struct CompactId {
69    pub peer: PeerID,
70    pub counter: NonMaxI32,
71}
72
73/// Namespace prefix used to encode mergeable container IDs into Root container names.
74///
75/// The 🤝 ("handshake") sentinel mirrors Loro's `🦜:` brand convention
76/// (`crates/loro-common/src/value.rs::LORO_CONTAINER_ID_PREFIX`) and signals
77/// "two peers agreeing on the same cid." The trailing `:` separates the brand
78/// from the flattened path payload; the container kind is carried on the
79/// `Root` itself and is not duplicated in the name. `check_root_container_name`
80/// rejects user-created root names that start with this prefix.
81///
82/// **Cost of nesting mergeable maps inside mergeable maps**: the payload embeds
83/// the nearest non-mergeable map ancestor once, followed by escaped map keys.
84/// Names grow linearly with nesting depth × average key length and ride through
85/// every op header, snapshot, and event path.
86pub const MERGEABLE_NAMESPACE_PREFIX: &str = "🤝:";
87
88fn write_len_prefixed_segment(out: &mut Vec<u8>, bytes: &[u8]) {
89    leb128::write::unsigned(out, bytes.len() as u64).unwrap();
90    out.extend_from_slice(bytes);
91}
92
93fn push_mergeable_escaped(out: &mut String, segment: &str) {
94    for ch in segment.chars() {
95        match ch {
96            '\\' => out.push_str("\\\\"),
97            '>' => out.push_str("\\>"),
98            '/' => out.push_str("\\s"),
99            '\0' => out.push_str("\\0"),
100            _ => out.push(ch),
101        }
102    }
103}
104
105fn decode_mergeable_segment(segment: &str) -> Option<String> {
106    let mut out = String::with_capacity(segment.len());
107    let mut chars = segment.chars();
108    while let Some(ch) = chars.next() {
109        match ch {
110            '\\' => {
111                let escaped = chars.next()?;
112                match escaped {
113                    '\\' => out.push('\\'),
114                    '>' => out.push('>'),
115                    's' => out.push('/'),
116                    '0' => out.push('\0'),
117                    _ => return None,
118                }
119            }
120            '/' | '\0' => return None,
121            _ => out.push(ch),
122        }
123    }
124
125    Some(out)
126}
127
128fn find_first_mergeable_separator(payload: &str) -> Option<usize> {
129    let mut escaped = false;
130    for (idx, ch) in payload.char_indices() {
131        if escaped {
132            escaped = false;
133            continue;
134        }
135
136        match ch {
137            '\\' => escaped = true,
138            '>' => return Some(idx),
139            _ => {}
140        }
141    }
142
143    None
144}
145
146fn find_last_mergeable_separator(payload: &str) -> Option<usize> {
147    let mut last = None;
148    let mut escaped = false;
149    for (idx, ch) in payload.char_indices() {
150        if escaped {
151            escaped = false;
152            continue;
153        }
154
155        match ch {
156            '\\' => escaped = true,
157            '>' => last = Some(idx),
158            _ => {}
159        }
160    }
161
162    if escaped {
163        None
164    } else {
165        last
166    }
167}
168
169fn push_u64_base36(out: &mut String, mut value: u64) {
170    const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
171    if value == 0 {
172        out.push('0');
173        return;
174    }
175
176    let mut buf = [0u8; 13];
177    let mut idx = buf.len();
178    while value > 0 {
179        idx -= 1;
180        buf[idx] = DIGITS[(value % 36) as usize];
181        value /= 36;
182    }
183
184    out.push_str(std::str::from_utf8(&buf[idx..]).unwrap());
185}
186
187fn push_i32_base36(out: &mut String, value: i32) {
188    if value < 0 {
189        out.push('-');
190    }
191    let magnitude = if value < 0 {
192        (-(i64::from(value))) as u64
193    } else {
194        value as u64
195    };
196    push_u64_base36(out, magnitude);
197}
198
199fn parse_base36_digit(byte: u8) -> Option<u64> {
200    match byte {
201        b'0'..=b'9' => Some(u64::from(byte - b'0')),
202        b'a'..=b'z' => Some(u64::from(byte - b'a' + 10)),
203        _ => None,
204    }
205}
206
207fn parse_u64_base36(s: &str) -> Option<u64> {
208    if s.is_empty() {
209        return None;
210    }
211    if s.len() > 1 && s.starts_with('0') {
212        return None;
213    }
214
215    let mut value = 0u64;
216    for byte in s.bytes() {
217        value = value.checked_mul(36)?;
218        value = value.checked_add(parse_base36_digit(byte)?)?;
219    }
220    Some(value)
221}
222
223fn parse_i32_base36(s: &str) -> Option<i32> {
224    if let Some(rest) = s.strip_prefix('-') {
225        let value = parse_u64_base36(rest)?;
226        if value == 0 {
227            return None;
228        }
229        let signed = -(i64::try_from(value).ok()?);
230        i32::try_from(signed).ok()
231    } else {
232        let value = parse_u64_base36(s)?;
233        i32::try_from(value).ok()
234    }
235}
236
237fn parse_mergeable_base_parent(segment: &str) -> Option<ContainerID> {
238    let mut chars = segment.chars();
239    match chars.next()? {
240        '$' => {
241            let name = &segment['$'.len_utf8()..];
242            if !check_root_container_name(name) {
243                return None;
244            }
245            Some(ContainerID::Root {
246                name: name.into(),
247                container_type: ContainerType::Map,
248            })
249        }
250        '@' => {
251            let rest = &segment['@'.len_utf8()..];
252            let (peer, counter) = rest.split_once(':')?;
253            Some(ContainerID::Normal {
254                peer: parse_u64_base36(peer)?,
255                counter: parse_i32_base36(counter)?,
256                container_type: ContainerType::Map,
257            })
258        }
259        _ => None,
260    }
261}
262
263fn validate_mergeable_payload(payload: &str) -> Option<()> {
264    let separator = find_first_mergeable_separator(payload)?;
265    validate_mergeable_base_parent(&payload[..separator])?;
266    validate_mergeable_key_path(&payload[separator + '>'.len_utf8()..])
267}
268
269fn validate_mergeable_base_parent(segment: &str) -> Option<()> {
270    match segment.as_bytes().first().copied()? {
271        b'$' => validate_mergeable_root_base_parent(&segment['$'.len_utf8()..]),
272        b'@' => validate_mergeable_normal_base_parent(&segment['@'.len_utf8()..]),
273        _ => None,
274    }
275}
276
277fn validate_mergeable_root_base_parent(name: &str) -> Option<()> {
278    if name.is_empty() || name.starts_with(MERGEABLE_NAMESPACE_PREFIX) {
279        return None;
280    }
281
282    let mut chars = name.chars();
283    while let Some(ch) = chars.next() {
284        match ch {
285            '\\' => match chars.next()? {
286                '\\' | '>' => {}
287                // Root container names cannot contain slash or NUL after decoding.
288                's' | '0' => return None,
289                _ => return None,
290            },
291            '/' | '\0' => return None,
292            _ => {}
293        }
294    }
295
296    Some(())
297}
298
299fn validate_mergeable_normal_base_parent(segment: &str) -> Option<()> {
300    let (peer, counter) = segment.split_once(':')?;
301    parse_u64_base36(peer)?;
302    parse_i32_base36(counter)?;
303    Some(())
304}
305
306fn validate_mergeable_key_path(path: &str) -> Option<()> {
307    let mut chars = path.chars();
308    while let Some(ch) = chars.next() {
309        match ch {
310            '\\' => match chars.next()? {
311                '\\' | '>' | 's' | '0' => {}
312                _ => return None,
313            },
314            '/' | '\0' => return None,
315            _ => {}
316        }
317    }
318
319    Some(())
320}
321
322fn parse_mergeable_payload(payload: &str) -> Option<(ContainerID, String)> {
323    validate_mergeable_payload(payload)?;
324    let separator = find_last_mergeable_separator(payload)?;
325    let parent_payload = &payload[..separator];
326    let key = decode_mergeable_segment(&payload[separator + '>'.len_utf8()..])?;
327
328    let parent = if find_last_mergeable_separator(parent_payload).is_some() {
329        ContainerID::Root {
330            name: format!("{MERGEABLE_NAMESPACE_PREFIX}{parent_payload}").into(),
331            container_type: ContainerType::Map,
332        }
333    } else {
334        let parent_segment = decode_mergeable_segment(parent_payload)?;
335        parse_mergeable_base_parent(&parent_segment)?
336    };
337
338    Some((parent, key))
339}
340
341fn mergeable_payload_for_parent(parent: &ContainerID) -> Option<&str> {
342    let ContainerID::Root {
343        name,
344        container_type: ContainerType::Map,
345    } = parent
346    else {
347        return None;
348    };
349    let payload = name.as_str().strip_prefix(MERGEABLE_NAMESPACE_PREFIX)?;
350    validate_mergeable_payload(payload)?;
351    Some(payload)
352}
353
354fn push_mergeable_parent(out: &mut String, parent: &ContainerID) {
355    if let Some(payload) = mergeable_payload_for_parent(parent) {
356        out.push_str(payload);
357        return;
358    }
359
360    match parent {
361        ContainerID::Root { name, .. } => {
362            out.push('$');
363            push_mergeable_escaped(out, name.as_str());
364        }
365        ContainerID::Normal { peer, counter, .. } => {
366            out.push('@');
367            push_u64_base36(out, *peer);
368            out.push(':');
369            push_i32_base36(out, *counter);
370        }
371    }
372}
373
374/// Return whether the given name is a valid root container name.
375pub fn check_root_container_name(name: &str) -> bool {
376    !name.is_empty()
377        && !name.starts_with(MERGEABLE_NAMESPACE_PREFIX)
378        && name.char_indices().all(|(_, x)| x != '/' && x != '\0')
379}
380
381/// Binary marker stored in a parent map slot to activate a mergeable child.
382///
383/// This is a compact mergeable-child container ref, not the child cid itself. The child cid is
384/// derived deterministically from `(parent, key, kind)`; this value only records that the parent map
385/// slot currently activates that child kind.
386///
387/// Only this specially constructed binary value activates a mergeable child.
388/// Old clients that do not understand mergeable containers should see an inert binary scalar rather
389/// than a fake child container edge or a reserved-looking user string.
390pub const MERGEABLE_MARKER_MAGIC: [u8; 4] = [0x00, b'L', b'M', 0x01];
391
392const MERGEABLE_MARKER_DIGEST_LEN: usize = 3;
393const MERGEABLE_MARKER_LEN: usize = 4 + 1 + MERGEABLE_MARKER_DIGEST_LEN;
394const MERGEABLE_MARKER_CRC_DOMAIN: &[u8] = b"loro.mergeable.marker.v1";
395
396/// Build the [`LoroValue`] a parent map stores at a mergeable key for `container_type`.
397///
398/// Layout: `MAGIC[4] + KIND[1] + CRC24(parent_id, key, kind)[3]`.
399pub fn mergeable_marker(
400    parent: &ContainerID,
401    key: &str,
402    container_type: ContainerType,
403) -> LoroValue {
404    let mut marker = Vec::with_capacity(MERGEABLE_MARKER_LEN);
405    marker.extend_from_slice(&MERGEABLE_MARKER_MAGIC);
406    marker.push(container_type.to_u8());
407    let digest = mergeable_marker_crc24(parent, key, container_type);
408    marker.extend_from_slice(&digest);
409    LoroValue::Binary(marker.into())
410}
411
412/// Parse a parent map slot value back into the mergeable [`ContainerType`] it activates.
413///
414/// The marker is bound to `(parent, key, kind)`, so copying it to another map/key does not activate
415/// a mergeable child there. Malformed markers and arbitrary non-marker values are treated as
416/// ordinary user values.
417pub fn parse_mergeable_marker(
418    parent: &ContainerID,
419    key: &str,
420    value: &LoroValue,
421) -> Option<ContainerType> {
422    let LoroValue::Binary(bytes) = value else {
423        return None;
424    };
425    if bytes.len() != MERGEABLE_MARKER_LEN || !bytes.starts_with(&MERGEABLE_MARKER_MAGIC) {
426        return None;
427    }
428
429    let kind = ContainerType::try_from_u8(bytes[MERGEABLE_MARKER_MAGIC.len()]).ok()?;
430    if matches!(kind, ContainerType::Unknown(_)) {
431        return None;
432    }
433
434    let digest_start = MERGEABLE_MARKER_MAGIC.len() + 1;
435    let expected = mergeable_marker_crc24(parent, key, kind);
436    if &bytes[digest_start..] != expected.as_slice() {
437        return None;
438    }
439
440    Some(kind)
441}
442
443/// Translate a raw map slot value into the user-visible Container view for a Map at `parent`.
444///
445/// Mergeable child activation lives as a binary marker in the parent map's value table. This
446/// is the canonical conversion from that marker to the deterministic child cid; every read
447/// boundary (`MapHandler` getters, Map diff emission, local-event hints) goes through it so
448/// callers see the same shape as a regular child container.
449pub fn translate_mergeable_marker_value(
450    parent: &ContainerID,
451    key: &str,
452    value: LoroValue,
453) -> LoroValue {
454    match parse_mergeable_marker(parent, key, &value) {
455        Some(kind) => LoroValue::Container(ContainerID::new_mergeable(parent, key, kind)),
456        None => value,
457    }
458}
459
460fn mergeable_marker_crc24(parent: &ContainerID, key: &str, kind: ContainerType) -> [u8; 3] {
461    let mut input = Vec::new();
462    input.extend_from_slice(MERGEABLE_MARKER_CRC_DOMAIN);
463    write_len_prefixed_segment(&mut input, &parent.to_bytes());
464    write_len_prefixed_segment(&mut input, key.as_bytes());
465    input.push(kind.to_u8());
466
467    let crc = crc32(&input) & 0x00ff_ffff;
468    [
469        ((crc >> 16) & 0xff) as u8,
470        ((crc >> 8) & 0xff) as u8,
471        (crc & 0xff) as u8,
472    ]
473}
474
475fn crc32(bytes: &[u8]) -> u32 {
476    let mut crc = 0xffff_ffff_u32;
477    for &byte in bytes {
478        crc ^= u32::from(byte);
479        for _ in 0..8 {
480            let mask = (crc & 1).wrapping_neg();
481            crc = (crc >> 1) ^ (0xedb8_8320 & mask);
482        }
483    }
484    !crc
485}
486
487impl CompactId {
488    pub fn new(peer: PeerID, counter: Counter) -> Self {
489        Self {
490            peer,
491            counter: NonMaxI32::new(counter).unwrap(),
492        }
493    }
494
495    pub fn to_id(&self) -> ID {
496        ID {
497            peer: self.peer,
498            counter: self.counter.get(),
499        }
500    }
501
502    pub fn inc(&self, start: i32) -> CompactId {
503        Self {
504            peer: self.peer,
505            counter: NonMaxI32::new(start + self.counter.get()).unwrap(),
506        }
507    }
508}
509
510impl TryFrom<ID> for CompactId {
511    type Error = ID;
512
513    fn try_from(id: ID) -> Result<Self, ID> {
514        if id.counter == i32::MAX {
515            return Err(id);
516        }
517
518        Ok(Self::new(id.peer, id.counter))
519    }
520}
521
522/// It's the unique ID of an Op represented by [PeerID] and [Lamport] clock.
523/// It's used to define the total order of Ops.
524#[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord)]
525pub struct IdLp {
526    pub lamport: Lamport,
527    pub peer: PeerID,
528}
529
530impl IdLp {
531    pub fn compact(self) -> CompactIdLp {
532        CompactIdLp::new(self.peer, self.lamport)
533    }
534}
535
536/// It's the unique ID of an Op represented by [PeerID] and [Counter].
537#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
538pub struct CompactIdLp {
539    pub peer: PeerID,
540    pub lamport: NonMaxU32,
541}
542
543impl CompactIdLp {
544    pub fn new(peer: PeerID, lamport: Lamport) -> Self {
545        Self {
546            peer,
547            lamport: NonMaxU32::new(lamport).unwrap(),
548        }
549    }
550
551    pub fn to_id(&self) -> IdLp {
552        IdLp {
553            peer: self.peer,
554            lamport: self.lamport.get(),
555        }
556    }
557}
558
559impl TryFrom<IdLp> for CompactIdLp {
560    type Error = IdLp;
561
562    fn try_from(id: IdLp) -> Result<Self, IdLp> {
563        if id.lamport == u32::MAX {
564            return Err(id);
565        }
566
567        Ok(Self::new(id.peer, id.lamport))
568    }
569}
570
571/// It's the unique ID of an Op represented by [PeerID], [Lamport] clock and [Counter].
572#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
573pub struct IdFull {
574    pub peer: PeerID,
575    pub lamport: Lamport,
576    pub counter: Counter,
577}
578
579/// [ContainerID] includes the Op's [ID] and the type. So it's impossible to have
580/// the same [ContainerID] with conflict [ContainerType].
581///
582/// This structure is really cheap to clone.
583///
584/// String representation:
585///
586/// - Root Container: `/<name>:<type>`
587/// - Normal Container: `<counter>@<client>:<type>`
588///
589/// Note: It will be encoded into binary format, so the order of its fields should not be changed.
590#[derive(Hash, PartialEq, Eq, Clone, Serialize, Deserialize, EnumAsInner)]
591pub enum ContainerID {
592    /// Root container does not need an op to create. It can be created implicitly.
593    Root {
594        name: InternalString,
595        container_type: ContainerType,
596    },
597    Normal {
598        peer: PeerID,
599        counter: Counter,
600        container_type: ContainerType,
601    },
602}
603
604impl ContainerID {
605    pub fn encode<W: Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
606        match self {
607            Self::Root {
608                name,
609                container_type,
610            } => {
611                let first_byte = container_type.to_u8() | 0b10000000;
612                writer.write_all(&[first_byte])?;
613                leb128::write::unsigned(writer, name.len() as u64)?;
614                writer.write_all(name.as_bytes())?;
615            }
616            Self::Normal {
617                peer,
618                counter,
619                container_type,
620            } => {
621                let first_byte = container_type.to_u8();
622                writer.write_all(&[first_byte])?;
623                writer.write_all(&peer.to_le_bytes())?;
624                writer.write_all(&counter.to_le_bytes())?;
625            }
626        }
627
628        Ok(())
629    }
630
631    pub fn to_bytes(&self) -> Vec<u8> {
632        // normal need 13 bytes
633        let mut bytes = Vec::with_capacity(13);
634        self.encode(&mut bytes).unwrap();
635        bytes
636    }
637
638    pub fn from_bytes(bytes: &[u8]) -> Self {
639        Self::try_from_bytes(bytes).unwrap()
640    }
641
642    pub fn try_from_bytes(bytes: &[u8]) -> LoroResult<Self> {
643        if bytes.is_empty() {
644            return Err(LoroError::DecodeError(
645                "Decode container id failed".to_string().into_boxed_str(),
646            ));
647        }
648
649        let first_byte = bytes[0];
650        let container_type = ContainerType::try_from_u8(first_byte & 0b01111111)?;
651        let is_root = (first_byte & 0b10000000) != 0;
652
653        let mut reader = &bytes[1..];
654        match is_root {
655            true => {
656                let name_len = leb128::read::unsigned(&mut reader).map_err(|_| {
657                    LoroError::DecodeError(
658                        "Decode container id failed".to_string().into_boxed_str(),
659                    )
660                })?;
661                let name_len = usize::try_from(name_len).map_err(|_| {
662                    LoroError::DecodeError(
663                        "Decode container id failed".to_string().into_boxed_str(),
664                    )
665                })?;
666                if reader.len() != name_len {
667                    return Err(LoroError::DecodeError(
668                        "Decode container id failed".to_string().into_boxed_str(),
669                    ));
670                }
671
672                let name = std::str::from_utf8(&reader[..name_len]).map_err(|_| {
673                    LoroError::DecodeError(
674                        "Decode container id failed".to_string().into_boxed_str(),
675                    )
676                })?;
677                Ok(Self::Root {
678                    name: InternalString::from(name),
679                    container_type,
680                })
681            }
682            false => {
683                if reader.len() != 12 {
684                    return Err(LoroError::DecodeError(
685                        "Decode container id failed".to_string().into_boxed_str(),
686                    ));
687                }
688
689                let peer = PeerID::from_le_bytes(reader[..8].try_into().unwrap());
690                let counter = i32::from_le_bytes(reader[8..12].try_into().unwrap());
691                Ok(Self::Normal {
692                    peer,
693                    counter,
694                    container_type,
695                })
696            }
697        }
698    }
699
700    const LORO_CONTAINER_ID_PREFIX: &str = "🦜:";
701    pub fn to_loro_value_string(&self) -> String {
702        format!("{}{}", Self::LORO_CONTAINER_ID_PREFIX, self)
703    }
704
705    pub fn try_from_loro_value_string(s: &str) -> Option<Self> {
706        if let Some(s) = s.strip_prefix(Self::LORO_CONTAINER_ID_PREFIX) {
707            Self::try_from(s).ok()
708        } else {
709            None
710        }
711    }
712}
713
714impl std::fmt::Debug for ContainerID {
715    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
716        match self {
717            Self::Root {
718                name,
719                container_type,
720            } => {
721                write!(f, "Root(\"{name}\" {container_type:?})")
722            }
723            Self::Normal {
724                peer,
725                counter,
726                container_type,
727            } => {
728                write!(f, "Normal({container_type:?} {counter}@{peer})")
729            }
730        }
731    }
732}
733
734// TODO: add non_exhausted
735// Note: It will be encoded into binary format, so the order of its fields should not be changed.
736#[derive(Arbitrary, Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
737pub enum ContainerType {
738    Text,
739    Map,
740    List,
741    MovableList,
742    Tree,
743    #[cfg(feature = "counter")]
744    Counter,
745    Unknown(u8),
746}
747
748impl ContainerType {
749    #[cfg(feature = "counter")]
750    pub const ALL_TYPES: [ContainerType; 6] = [
751        ContainerType::Map,
752        ContainerType::List,
753        ContainerType::Text,
754        ContainerType::Tree,
755        ContainerType::MovableList,
756        ContainerType::Counter,
757    ];
758    #[cfg(not(feature = "counter"))]
759    pub const ALL_TYPES: [ContainerType; 5] = [
760        ContainerType::Map,
761        ContainerType::List,
762        ContainerType::Text,
763        ContainerType::Tree,
764        ContainerType::MovableList,
765    ];
766
767    pub fn default_value(&self) -> LoroValue {
768        match self {
769            ContainerType::Map => LoroValue::Map(Default::default()),
770            ContainerType::List => LoroValue::List(Default::default()),
771            ContainerType::Text => LoroValue::String(Default::default()),
772            ContainerType::Tree => LoroValue::List(Default::default()),
773            ContainerType::MovableList => LoroValue::List(Default::default()),
774            #[cfg(feature = "counter")]
775            ContainerType::Counter => LoroValue::Double(0.),
776            ContainerType::Unknown(_) => unreachable!(),
777        }
778    }
779
780    pub fn to_u8(self) -> u8 {
781        match self {
782            ContainerType::Map => 0,
783            ContainerType::List => 1,
784            ContainerType::Text => 2,
785            ContainerType::Tree => 3,
786            ContainerType::MovableList => 4,
787            #[cfg(feature = "counter")]
788            ContainerType::Counter => 5,
789            ContainerType::Unknown(k) => k,
790        }
791    }
792
793    pub fn try_from_u8(v: u8) -> LoroResult<Self> {
794        match v {
795            0 => Ok(ContainerType::Map),
796            1 => Ok(ContainerType::List),
797            2 => Ok(ContainerType::Text),
798            3 => Ok(ContainerType::Tree),
799            4 => Ok(ContainerType::MovableList),
800            #[cfg(feature = "counter")]
801            5 => Ok(ContainerType::Counter),
802            x => Ok(ContainerType::Unknown(x)),
803        }
804    }
805}
806
807#[derive(Serialize, Deserialize)]
808#[serde(rename = "ContainerType")]
809enum ContainerTypeSerdeRepr {
810    Text,
811    Map,
812    List,
813    MovableList,
814    Tree,
815    #[cfg(feature = "counter")]
816    Counter,
817    Unknown(u8),
818}
819
820// For some historical reason, we have another to_byte format for ContainerType,
821// it was used for serde of ContainerType
822fn historical_container_type_to_byte(c: ContainerType) -> u8 {
823    match c {
824        ContainerType::Text => 0,
825        ContainerType::Map => 1,
826        ContainerType::List => 2,
827        ContainerType::MovableList => 3,
828        ContainerType::Tree => 4,
829        #[cfg(feature = "counter")]
830        ContainerType::Counter => 5,
831        ContainerType::Unknown(k) => k,
832    }
833}
834
835fn historical_try_byte_to_container(byte: u8) -> ContainerType {
836    match byte {
837        0 => ContainerType::Text,
838        1 => ContainerType::Map,
839        2 => ContainerType::List,
840        3 => ContainerType::MovableList,
841        4 => ContainerType::Tree,
842        #[cfg(feature = "counter")]
843        5 => ContainerType::Counter,
844        _ => ContainerType::Unknown(byte),
845    }
846}
847
848impl From<ContainerType> for ContainerTypeSerdeRepr {
849    fn from(value: ContainerType) -> Self {
850        match value {
851            ContainerType::Text => Self::Text,
852            ContainerType::Map => Self::Map,
853            ContainerType::List => Self::List,
854            ContainerType::MovableList => Self::MovableList,
855            ContainerType::Tree => Self::Tree,
856            #[cfg(feature = "counter")]
857            ContainerType::Counter => Self::Counter,
858            ContainerType::Unknown(value) => Self::Unknown(value),
859        }
860    }
861}
862
863impl From<ContainerTypeSerdeRepr> for ContainerType {
864    fn from(value: ContainerTypeSerdeRepr) -> Self {
865        match value {
866            ContainerTypeSerdeRepr::Text => ContainerType::Text,
867            ContainerTypeSerdeRepr::Map => ContainerType::Map,
868            ContainerTypeSerdeRepr::List => ContainerType::List,
869            ContainerTypeSerdeRepr::MovableList => ContainerType::MovableList,
870            ContainerTypeSerdeRepr::Tree => ContainerType::Tree,
871            #[cfg(feature = "counter")]
872            ContainerTypeSerdeRepr::Counter => ContainerType::Counter,
873            ContainerTypeSerdeRepr::Unknown(value) => ContainerType::Unknown(value),
874        }
875    }
876}
877
878impl Serialize for ContainerType {
879    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
880    where
881        S: serde::Serializer,
882    {
883        if serializer.is_human_readable() {
884            ContainerTypeSerdeRepr::from(*self).serialize(serializer)
885        } else {
886            serializer.serialize_u8(historical_container_type_to_byte(*self))
887        }
888    }
889}
890
891impl<'de> Deserialize<'de> for ContainerType {
892    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
893    where
894        D: serde::Deserializer<'de>,
895    {
896        if deserializer.is_human_readable() {
897            let repr = ContainerTypeSerdeRepr::deserialize(deserializer)?;
898            Ok(repr.into())
899        } else {
900            let value = u8::deserialize(deserializer)?;
901            Ok(historical_try_byte_to_container(value))
902        }
903    }
904}
905
906pub type IdSpanVector = rustc_hash::FxHashMap<PeerID, CounterSpan>;
907
908mod container {
909    use super::*;
910
911    impl Display for ContainerType {
912        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
913            f.write_str(match self {
914                ContainerType::Map => "Map",
915                ContainerType::List => "List",
916                ContainerType::MovableList => "MovableList",
917                ContainerType::Text => "Text",
918                ContainerType::Tree => "Tree",
919                #[cfg(feature = "counter")]
920                ContainerType::Counter => "Counter",
921                ContainerType::Unknown(k) => return f.write_fmt(format_args!("Unknown({k})")),
922            })
923        }
924    }
925
926    impl Display for ContainerID {
927        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
928            match self {
929                ContainerID::Root {
930                    name,
931                    container_type,
932                } => f.write_fmt(format_args!("cid:root-{name}:{container_type}"))?,
933                ContainerID::Normal {
934                    peer,
935                    counter,
936                    container_type,
937                } => f.write_fmt(format_args!(
938                    "cid:{id}:{container_type}",
939                    id = ID::new(*peer, *counter),
940                    container_type = container_type
941                ))?,
942            };
943            Ok(())
944        }
945    }
946
947    impl TryFrom<&str> for ContainerID {
948        type Error = ();
949
950        fn try_from(mut s: &str) -> Result<Self, Self::Error> {
951            if !s.starts_with("cid:") {
952                return Err(());
953            }
954
955            s = &s[4..];
956            if s.starts_with("root-") {
957                // root container
958                s = &s[5..];
959                let split = s.rfind(':').ok_or(())?;
960                if split == 0 {
961                    return Err(());
962                }
963                let kind = ContainerType::try_from(&s[split + 1..]).map_err(|_| ())?;
964                let name = &s[..split];
965                Ok(ContainerID::Root {
966                    name: name.into(),
967                    container_type: kind,
968                })
969            } else {
970                let mut iter = s.split(':');
971                let id = iter.next().ok_or(())?;
972                let kind = iter.next().ok_or(())?;
973                if iter.next().is_some() {
974                    return Err(());
975                }
976
977                let id = ID::try_from(id).map_err(|_| ())?;
978                let kind = ContainerType::try_from(kind).map_err(|_| ())?;
979                Ok(ContainerID::Normal {
980                    peer: id.peer,
981                    counter: id.counter,
982                    container_type: kind,
983                })
984            }
985        }
986    }
987
988    impl ContainerID {
989        #[inline]
990        pub fn new_normal(id: ID, container_type: ContainerType) -> Self {
991            ContainerID::Normal {
992                peer: id.peer,
993                counter: id.counter,
994                container_type,
995            }
996        }
997
998        #[inline]
999        pub fn new_root(name: &str, container_type: ContainerType) -> Self {
1000            if !check_root_container_name(name) {
1001                panic!(
1002                    "Invalid root container name, it should not be empty or contain '/' or '\\0'"
1003                );
1004            } else {
1005                ContainerID::Root {
1006                    name: name.into(),
1007                    container_type,
1008                }
1009            }
1010        }
1011
1012        #[inline]
1013        pub fn name(&self) -> &InternalString {
1014            match self {
1015                ContainerID::Root { name, .. } => name,
1016                ContainerID::Normal { .. } => unreachable!(),
1017            }
1018        }
1019
1020        #[inline]
1021        pub fn container_type(&self) -> ContainerType {
1022            match self {
1023                ContainerID::Root { container_type, .. } => *container_type,
1024                ContainerID::Normal { container_type, .. } => *container_type,
1025            }
1026        }
1027
1028        pub fn is_unknown(&self) -> bool {
1029            matches!(self.container_type(), ContainerType::Unknown(_))
1030        }
1031
1032        /// Create a mergeable container ID for the given parent, key, and container type.
1033        ///
1034        /// The cid is a Root container with a reserved namespace prefix and a flattened
1035        /// map-path payload, so two peers calling this with the same arguments produce the
1036        /// identical `ContainerID`.
1037        ///
1038        /// The container kind is intentionally *not* encoded into the name: it already
1039        /// lives in `ContainerID::Root::container_type`, so encoding it twice would waste two
1040        /// bytes per cid, and `ContainerID` equality already keeps two `(parent, key)`
1041        /// mergeable cids of different kinds distinct.
1042        ///
1043        /// Only maps can be mergeable parents. The encoded payload records the nearest
1044        /// non-mergeable map ancestor (`$root-name` or `@peer:counter`) once, then appends
1045        /// escaped map keys separated by `>`. This keeps nested mergeable map names linear
1046        /// in path length instead of recursively embedding the full parent cid.
1047        pub fn new_mergeable(
1048            parent: &ContainerID,
1049            key: &str,
1050            container_type: ContainerType,
1051        ) -> Self {
1052            assert_eq!(
1053                parent.container_type(),
1054                ContainerType::Map,
1055                "mergeable child parent must be a map"
1056            );
1057            let parent_len_hint = match parent {
1058                ContainerID::Root { name, .. } => name.len(),
1059                ContainerID::Normal { .. } => 24,
1060            };
1061            let cap = MERGEABLE_NAMESPACE_PREFIX.len() + parent_len_hint + key.len() + 16;
1062            let mut name = String::with_capacity(cap);
1063            name.push_str(MERGEABLE_NAMESPACE_PREFIX);
1064            push_mergeable_parent(&mut name, parent);
1065            name.push('>');
1066            push_mergeable_escaped(&mut name, key);
1067
1068            Self::Root {
1069                name: name.into(),
1070                container_type,
1071            }
1072        }
1073
1074        /// Returns `true` if this is a valid mergeable container ID (i.e. created via
1075        /// `new_mergeable`).
1076        ///
1077        /// A Root whose name merely starts with the reserved mergeable prefix but does not
1078        /// decode to a valid path payload is an ordinary root, not a mergeable
1079        /// container. The check is structural; a fabricated `"🤝:not-valid"` Root is not
1080        /// treated as mergeable.
1081        ///
1082        /// Constant-time short-circuit when the name doesn't start with the mergeable prefix
1083        /// (most container ids). For names that do match the prefix, validates segment
1084        /// escaping and the compact base-parent encoding.
1085        pub fn is_mergeable(&self) -> bool {
1086            let Self::Root { name, .. } = self else {
1087                return false;
1088            };
1089            name.as_str()
1090                .strip_prefix(MERGEABLE_NAMESPACE_PREFIX)
1091                .and_then(validate_mergeable_payload)
1092                .is_some()
1093        }
1094
1095        /// Decode a mergeable container ID back into its `(parent, key, container_type)` components.
1096        ///
1097        /// Returns `None` if this is not a valid mergeable container ID. The returned
1098        /// `container_type` is the type carried on the `Root` itself; the encoded payload only
1099        /// carries `(parent, key)`.
1100        pub fn parse_mergeable(&self) -> Option<(ContainerID, String, ContainerType)> {
1101            let Self::Root {
1102                name,
1103                container_type,
1104            } = self
1105            else {
1106                return None;
1107            };
1108            let payload = name.strip_prefix(MERGEABLE_NAMESPACE_PREFIX)?;
1109            let (parent, key) = parse_mergeable_payload(payload)?;
1110            Some((parent, key, *container_type))
1111        }
1112    }
1113
1114    impl TryFrom<&str> for ContainerType {
1115        type Error = LoroError;
1116
1117        fn try_from(value: &str) -> Result<Self, Self::Error> {
1118            match value {
1119                "Map" | "map" => Ok(ContainerType::Map),
1120                "List" | "list" => Ok(ContainerType::List),
1121                "Text" | "text" => Ok(ContainerType::Text),
1122                "Tree" | "tree" => Ok(ContainerType::Tree),
1123                "MovableList" | "movableList" => Ok(ContainerType::MovableList),
1124                #[cfg(feature = "counter")]
1125                "Counter" | "counter" => Ok(ContainerType::Counter),
1126                a => {
1127                    if a.ends_with(')') {
1128                        let start = a.find('(').ok_or_else(|| {
1129                            LoroError::DecodeError(
1130                                format!("Invalid container type string \"{value}\"").into(),
1131                            )
1132                        })?;
1133                        let k = a[start+1..a.len() - 1].parse().map_err(|_| {
1134                            LoroError::DecodeError(
1135                    format!("Unknown container type \"{value}\". The valid options are Map|List|Text|Tree|MovableList.").into(),
1136                )
1137                        })?;
1138                        match ContainerType::try_from_u8(k) {
1139                            Ok(k) => Ok(k),
1140                            Err(_) => Ok(ContainerType::Unknown(k)),
1141                        }
1142                    } else {
1143                        Err(LoroError::DecodeError(
1144                    format!("Unknown container type \"{value}\". The valid options are Map|List|Text|Tree|MovableList.").into(),
1145                ))
1146                    }
1147                }
1148            }
1149        }
1150    }
1151}
1152
1153/// In movable tree, we use a specific [`TreeID`] to represent the root of **ALL** deleted tree node.
1154///
1155/// Deletion operation is equivalent to move target tree node to [`DELETED_TREE_ROOT`].
1156pub const DELETED_TREE_ROOT: TreeID = TreeID {
1157    peer: PeerID::MAX,
1158    counter: Counter::MAX,
1159};
1160
1161/// Each node of movable tree has a unique [`TreeID`] generated by Loro.
1162///
1163/// To further represent the metadata (a MapContainer) associated with each node,
1164/// we also use [`TreeID`] as [`ID`] portion of [`ContainerID`].
1165/// This not only allows for convenient association of metadata with each node,
1166/// but also ensures the uniqueness of the MapContainer.
1167///
1168/// Special ID:
1169/// - [`DELETED_TREE_ROOT`]: the root of all deleted nodes. To get it by [`TreeID::delete_root()`]
1170#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1171
1172pub struct TreeID {
1173    pub peer: PeerID,
1174    // TODO: can use a NonMax here
1175    pub counter: Counter,
1176}
1177
1178impl TreeID {
1179    #[inline(always)]
1180    pub fn new(peer: PeerID, counter: Counter) -> Self {
1181        Self { peer, counter }
1182    }
1183
1184    /// return [`DELETED_TREE_ROOT`]
1185    pub const fn delete_root() -> Self {
1186        DELETED_TREE_ROOT
1187    }
1188
1189    /// return `true` if the `TreeID` is deleted root
1190    pub fn is_deleted_root(&self) -> bool {
1191        self == &DELETED_TREE_ROOT
1192    }
1193
1194    pub fn from_id(id: ID) -> Self {
1195        Self {
1196            peer: id.peer,
1197            counter: id.counter,
1198        }
1199    }
1200
1201    pub fn id(&self) -> ID {
1202        ID {
1203            peer: self.peer,
1204            counter: self.counter,
1205        }
1206    }
1207
1208    pub fn associated_meta_container(&self) -> ContainerID {
1209        ContainerID::new_normal(self.id(), ContainerType::Map)
1210    }
1211}
1212
1213impl Display for TreeID {
1214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1215        self.id().fmt(f)
1216    }
1217}
1218
1219impl TryFrom<&str> for TreeID {
1220    type Error = LoroError;
1221    fn try_from(value: &str) -> Result<Self, Self::Error> {
1222        let id = ID::try_from(value)?;
1223        Ok(TreeID {
1224            peer: id.peer,
1225            counter: id.counter,
1226        })
1227    }
1228}
1229
1230#[cfg(feature = "wasm")]
1231pub mod wasm {
1232    use crate::{LoroError, TreeID};
1233    use wasm_bindgen::JsValue;
1234    impl From<TreeID> for JsValue {
1235        fn from(value: TreeID) -> Self {
1236            JsValue::from_str(&format!("{value}"))
1237        }
1238    }
1239
1240    impl TryFrom<JsValue> for TreeID {
1241        type Error = LoroError;
1242        fn try_from(value: JsValue) -> Result<Self, Self::Error> {
1243            let id = value.as_string().unwrap();
1244            TreeID::try_from(id.as_str())
1245        }
1246    }
1247}
1248
1249#[cfg(test)]
1250mod test {
1251    use crate::{
1252        mergeable_marker, parse_mergeable_marker, ContainerID, ContainerType, LoroValue, ID,
1253        MERGEABLE_MARKER_MAGIC,
1254    };
1255
1256    #[test]
1257    fn mergeable_marker_round_trips_every_kind() {
1258        let parent = ContainerID::new_root("state", ContainerType::Map);
1259        let key = "field";
1260        let kinds = [
1261            ContainerType::Map,
1262            ContainerType::List,
1263            ContainerType::Text,
1264            ContainerType::Tree,
1265            ContainerType::MovableList,
1266            #[cfg(feature = "counter")]
1267            ContainerType::Counter,
1268        ];
1269        for kind in kinds {
1270            assert_eq!(
1271                parse_mergeable_marker(&parent, key, &mergeable_marker(&parent, key, kind)),
1272                Some(kind),
1273                "marker value must parse back to {kind:?}"
1274            );
1275        }
1276    }
1277
1278    #[test]
1279    fn mergeable_marker_exact_layout() {
1280        let parent = ContainerID::new_root("state", ContainerType::Map);
1281        let value = mergeable_marker(&parent, "field", ContainerType::List);
1282        let LoroValue::Binary(bytes) = value else {
1283            panic!("mergeable marker must be a binary value");
1284        };
1285
1286        assert_eq!(bytes.len(), 8);
1287        assert_eq!(
1288            &bytes[..MERGEABLE_MARKER_MAGIC.len()],
1289            MERGEABLE_MARKER_MAGIC
1290        );
1291        assert_eq!(
1292            bytes[MERGEABLE_MARKER_MAGIC.len()],
1293            ContainerType::List.to_u8()
1294        );
1295    }
1296
1297    #[test]
1298    fn parse_mergeable_marker_rejects_non_markers() {
1299        let parent = ContainerID::new_root("state", ContainerType::Map);
1300
1301        // Plain user strings are never markers.
1302        assert_eq!(
1303            parse_mergeable_marker(&parent, "field", &LoroValue::String("Map".into())),
1304            None
1305        );
1306        assert_eq!(
1307            parse_mergeable_marker(&parent, "field", &LoroValue::String("not-a-marker".into())),
1308            None
1309        );
1310
1311        // Non-binary values are never markers.
1312        assert_eq!(
1313            parse_mergeable_marker(&parent, "field", &LoroValue::Double(1.0)),
1314            None
1315        );
1316        assert_eq!(
1317            parse_mergeable_marker(&parent, "field", &LoroValue::Null),
1318            None
1319        );
1320
1321        // Malformed binary values stay ordinary binary values.
1322        assert_eq!(
1323            parse_mergeable_marker(
1324                &parent,
1325                "field",
1326                &LoroValue::Binary(vec![0x00, b'L', b'M'].into()),
1327            ),
1328            None
1329        );
1330
1331        let mut wrong_magic = marker_bytes(mergeable_marker(&parent, "field", ContainerType::Map));
1332        wrong_magic[0] = 0xff;
1333        assert_eq!(
1334            parse_mergeable_marker(&parent, "field", &LoroValue::Binary(wrong_magic.into()),),
1335            None
1336        );
1337
1338        let marker = mergeable_marker(&parent, "field", ContainerType::Map);
1339        assert_eq!(
1340            parse_mergeable_marker(&parent, "other", &marker),
1341            None,
1342            "marker digest binds the marker to its map key"
1343        );
1344
1345        let other_parent = ContainerID::new_root("other", ContainerType::Map);
1346        assert_eq!(
1347            parse_mergeable_marker(&other_parent, "field", &marker),
1348            None,
1349            "marker digest binds the marker to its parent container"
1350        );
1351
1352        let mut wrong_digest = marker_bytes(marker);
1353        *wrong_digest.last_mut().unwrap() ^= 1;
1354        assert_eq!(
1355            parse_mergeable_marker(&parent, "field", &LoroValue::Binary(wrong_digest.into()),),
1356            None
1357        );
1358    }
1359
1360    #[test]
1361    fn is_mergeable_rejects_prefix_only_roots() {
1362        let missing_key = ContainerID::Root {
1363            name: "🤝:$state".into(),
1364            container_type: ContainerType::Map,
1365        };
1366        assert!(
1367            !missing_key.is_mergeable(),
1368            "payload without a key segment must not be mergeable"
1369        );
1370        assert!(missing_key.parse_mergeable().is_none());
1371
1372        let bad_escape = ContainerID::Root {
1373            name: "🤝:$state>not\\xvalid".into(),
1374            container_type: ContainerType::Map,
1375        };
1376        assert!(
1377            !bad_escape.is_mergeable(),
1378            "unknown escapes in the payload must not be mergeable"
1379        );
1380
1381        let parent = ContainerID::new_root("state", ContainerType::Map);
1382        let real = ContainerID::new_mergeable(&parent, "field", ContainerType::Map);
1383        assert!(
1384            real.is_mergeable(),
1385            "valid mergeable cid must remain mergeable"
1386        );
1387    }
1388
1389    /// A `🤝:` payload can be malformed in several ways. `parse_mergeable` returns
1390    /// `Option`, so such input must yield `None`, never panic.
1391    #[test]
1392    fn parse_mergeable_rejects_malformed_path_payloads() {
1393        for name in [
1394            "🤝:",
1395            "🤝:$>key",
1396            "🤝:%state>key",
1397            "🤝:@1>key",
1398            "🤝:@:1>key",
1399            "🤝:@1:zzzzzzzzzzzzzz>key",
1400            "🤝:@Z:0>key",
1401            "🤝:@001:0>key",
1402            "🤝:@1:00>key",
1403            "🤝:@1:-0>key",
1404            "🤝:$root\\sname>key",
1405            "🤝:$root\\0name>key",
1406            "🤝:$state>dangling\\",
1407            "🤝:$state>unknown\\xescape",
1408            "🤝:$state>raw/slash",
1409            "🤝:$state>raw\0nul",
1410        ] {
1411            let cid = ContainerID::Root {
1412                name: name.into(),
1413                container_type: ContainerType::Map,
1414            };
1415            assert_eq!(cid.parse_mergeable(), None, "payload {name:?}");
1416            assert!(!cid.is_mergeable(), "payload {name:?}");
1417        }
1418    }
1419
1420    #[test]
1421    fn parse_mergeable_marker_rejects_unknown_kind() {
1422        let parent = ContainerID::new_root("state", ContainerType::Map);
1423        let mut marker = marker_bytes(mergeable_marker(&parent, "field", ContainerType::Map));
1424        marker[MERGEABLE_MARKER_MAGIC.len()] = u8::MAX;
1425
1426        assert_eq!(
1427            parse_mergeable_marker(&parent, "field", &LoroValue::Binary(marker.into())),
1428            None
1429        );
1430    }
1431
1432    fn marker_bytes(value: LoroValue) -> Vec<u8> {
1433        let LoroValue::Binary(bytes) = value else {
1434            panic!("expected binary mergeable marker");
1435        };
1436        bytes.to_vec()
1437    }
1438
1439    #[test]
1440    fn test_container_id_convert_to_and_from_str() {
1441        let id = ContainerID::Root {
1442            name: "name".into(),
1443            container_type: crate::ContainerType::Map,
1444        };
1445        let id_str = id.to_string();
1446        assert_eq!(id_str.as_str(), "cid:root-name:Map");
1447        assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
1448
1449        let id = ContainerID::Normal {
1450            counter: 10,
1451            peer: 255,
1452            container_type: crate::ContainerType::Map,
1453        };
1454        let id_str = id.to_string();
1455        assert_eq!(id_str.as_str(), "cid:10@255:Map");
1456        assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
1457
1458        let id = ContainerID::try_from("cid:root-a:b:c:Tree").unwrap();
1459        assert_eq!(
1460            id,
1461            ContainerID::new_root("a:b:c", crate::ContainerType::Tree)
1462        );
1463    }
1464
1465    #[test]
1466    fn test_convert_invalid_container_id_str() {
1467        assert!(ContainerID::try_from("cid:root-:Map").is_err());
1468        assert!(ContainerID::try_from("cid:0@:Map").is_err());
1469        assert!(ContainerID::try_from("cid:@:Map").is_err());
1470        assert!(ContainerID::try_from("cid:x@0:Map").is_err());
1471        assert!(ContainerID::try_from("id:0@0:Map").is_err());
1472        assert!(ContainerID::try_from("cid:0@0:Unknown(6)").is_ok());
1473    }
1474
1475    #[test]
1476    fn test_container_id_encode_and_decode() {
1477        let id = ContainerID::new_normal(ID::new(1, 2), ContainerType::Map);
1478        let bytes = id.to_bytes();
1479        assert_eq!(ContainerID::from_bytes(&bytes), id);
1480
1481        let id = ContainerID::new_normal(ID::new(u64::MAX, i32::MAX), ContainerType::Text);
1482        let bytes = id.to_bytes();
1483        assert_eq!(ContainerID::from_bytes(&bytes), id);
1484
1485        let id = ContainerID::new_root("test_root", ContainerType::List);
1486        let bytes = id.to_bytes();
1487        assert_eq!(ContainerID::from_bytes(&bytes), id);
1488
1489        let id = ContainerID::new_normal(ID::new(0, 0), ContainerType::MovableList);
1490        let bytes = id.to_bytes();
1491        assert_eq!(ContainerID::from_bytes(&bytes), id);
1492
1493        let id = ContainerID::new_root(&"x".repeat(1024), ContainerType::Tree);
1494        let bytes = id.to_bytes();
1495        assert_eq!(ContainerID::from_bytes(&bytes), id);
1496
1497        #[cfg(feature = "counter")]
1498        {
1499            let id = ContainerID::new_normal(ID::new(42, 100), ContainerType::Counter);
1500            let bytes = id.to_bytes();
1501            assert_eq!(ContainerID::from_bytes(&bytes), id);
1502        }
1503
1504        let id = ContainerID::new_normal(ID::new(1, 1), ContainerType::Unknown(100));
1505        let bytes = id.to_bytes();
1506        assert_eq!(ContainerID::from_bytes(&bytes), id);
1507    }
1508
1509    #[test]
1510    fn container_id_try_from_bytes_rejects_trailing_bytes() {
1511        let normal = ContainerID::new_normal(ID::new(1, 2), ContainerType::Map);
1512        let mut normal_bytes = normal.to_bytes();
1513        normal_bytes.push(0);
1514        assert!(ContainerID::try_from_bytes(&normal_bytes).is_err());
1515
1516        let root = ContainerID::new_root("root", ContainerType::List);
1517        let mut root_bytes = root.to_bytes();
1518        root_bytes.push(0);
1519        assert!(ContainerID::try_from_bytes(&root_bytes).is_err());
1520    }
1521}