Skip to content

Commit da16b8a

Browse files
authored
refactor: use better repr for container id (#144)
1 parent a40b5c6 commit da16b8a

File tree

8 files changed

+125
-121
lines changed

8 files changed

+125
-121
lines changed

Cargo.lock

-17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/loro-common/src/id.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,31 @@ use std::{
99

1010
impl Debug for ID {
1111
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12-
f.write_str(format!("c{}:{}", self.peer, self.counter).as_str())
12+
f.write_str(format!("{}@{}", self.counter, self.peer).as_str())
1313
}
1414
}
1515

1616
impl Display for ID {
1717
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18-
f.write_str(format!("{}@{}", self.counter, self.peer).as_str())
18+
f.write_str(format!("{}@{:X}", self.counter, self.peer).as_str())
1919
}
2020
}
2121

2222
impl TryFrom<&str> for ID {
2323
type Error = LoroError;
2424

2525
fn try_from(value: &str) -> Result<Self, Self::Error> {
26-
let splitted: Vec<_> = value.split('@').collect();
27-
if splitted.len() != 2 {
26+
if value.split('@').count() != 2 {
2827
return Err(LoroError::DecodeError("Invalid ID format".into()));
2928
}
3029

31-
let counter = splitted[0]
30+
let mut iter = value.split('@');
31+
let counter = iter
32+
.next()
33+
.unwrap()
3234
.parse::<Counter>()
3335
.map_err(|_| LoroError::DecodeError("Invalid ID format".into()))?;
34-
let client_id = splitted[1]
35-
.parse::<PeerID>()
36+
let client_id = u64::from_str_radix(iter.next().unwrap(), 16)
3637
.map_err(|_| LoroError::DecodeError("Invalid ID format".into()))?;
3738
Ok(ID {
3839
peer: client_id,

crates/loro-common/src/lib.rs

+80-27
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,13 @@ mod container {
158158
ContainerID::Root {
159159
name,
160160
container_type,
161-
} => f.write_fmt(format_args!("/{}:{}", name, container_type))?,
161+
} => f.write_fmt(format_args!("cid:root-{}:{}", name, container_type))?,
162162
ContainerID::Normal {
163163
peer,
164164
counter,
165165
container_type,
166166
} => f.write_fmt(format_args!(
167-
"{}:{}",
167+
"cid:{}:{}",
168168
ID::new(*peer, *counter),
169169
container_type
170170
))?,
@@ -176,24 +176,39 @@ mod container {
176176
impl TryFrom<&str> for ContainerID {
177177
type Error = ();
178178

179-
fn try_from(value: &str) -> Result<Self, Self::Error> {
180-
let mut parts = value.split(':');
181-
let id = parts.next().ok_or(())?;
182-
let container_type = parts.next().ok_or(())?;
183-
let container_type = ContainerType::try_from(container_type).map_err(|_| ())?;
184-
if let Some(id) = id.strip_prefix('/') {
179+
fn try_from(mut s: &str) -> Result<Self, Self::Error> {
180+
if !s.starts_with("cid:") {
181+
return Err(());
182+
}
183+
184+
s = &s[4..];
185+
if s.starts_with("root-") {
186+
// root container
187+
s = &s[5..];
188+
let split = s.rfind(':').ok_or(())?;
189+
if split == 0 {
190+
return Err(());
191+
}
192+
let kind = ContainerType::try_from(&s[split + 1..]).map_err(|_| ())?;
193+
let name = &s[..split];
185194
Ok(ContainerID::Root {
186-
name: id.into(),
187-
container_type,
195+
name: name.into(),
196+
container_type: kind,
188197
})
189198
} else {
190-
let mut parts = id.split('@');
191-
let counter = parts.next().ok_or(())?.parse().map_err(|_| ())?;
192-
let client = parts.next().ok_or(())?.parse().map_err(|_| ())?;
199+
let mut iter = s.split(':');
200+
let id = iter.next().ok_or(())?;
201+
let kind = iter.next().ok_or(())?;
202+
if iter.next().is_some() {
203+
return Err(());
204+
}
205+
206+
let id = ID::try_from(id).map_err(|_| ())?;
207+
let kind = ContainerType::try_from(kind).map_err(|_| ())?;
193208
Ok(ContainerID::Normal {
194-
counter,
195-
peer: client,
196-
container_type,
209+
peer: id.peer,
210+
counter: id.counter,
211+
container_type: kind,
197212
})
198213
}
199214
}
@@ -328,18 +343,19 @@ impl Display for TreeID {
328343
}
329344

330345
impl TryFrom<&str> for TreeID {
331-
type Error = ();
346+
type Error = LoroError;
332347
fn try_from(value: &str) -> Result<Self, Self::Error> {
333-
let mut parts = value.split('@');
334-
let counter = parts.next().ok_or(())?.parse().map_err(|_| ())?;
335-
let peer = parts.next().ok_or(())?.parse().map_err(|_| ())?;
336-
Ok(TreeID { peer, counter })
348+
let id = ID::try_from(value)?;
349+
Ok(TreeID {
350+
peer: id.peer,
351+
counter: id.counter,
352+
})
337353
}
338354
}
339355

340356
#[cfg(feature = "wasm")]
341357
pub mod wasm {
342-
use crate::TreeID;
358+
use crate::{LoroError, TreeID};
343359
use wasm_bindgen::JsValue;
344360
impl From<TreeID> for JsValue {
345361
fn from(value: TreeID) -> Self {
@@ -348,13 +364,50 @@ pub mod wasm {
348364
}
349365

350366
impl TryFrom<JsValue> for TreeID {
351-
type Error = ();
367+
type Error = LoroError;
352368
fn try_from(value: JsValue) -> Result<Self, Self::Error> {
353369
let id = value.as_string().unwrap();
354-
let mut parts = id.split('@');
355-
let counter = parts.next().ok_or(())?.parse().map_err(|_| ())?;
356-
let peer = parts.next().ok_or(())?.parse().map_err(|_| ())?;
357-
Ok(TreeID { peer, counter })
370+
TreeID::try_from(id.as_str())
358371
}
359372
}
360373
}
374+
375+
#[cfg(test)]
376+
mod test {
377+
use crate::ContainerID;
378+
379+
#[test]
380+
fn test_container_id_convert_to_and_from_str() {
381+
let id = ContainerID::Root {
382+
name: "name".into(),
383+
container_type: crate::ContainerType::Map,
384+
};
385+
let id_str = id.to_string();
386+
assert_eq!(id_str.as_str(), "cid:root-name:Map");
387+
assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
388+
389+
let id = ContainerID::Normal {
390+
counter: 10,
391+
peer: 255,
392+
container_type: crate::ContainerType::Map,
393+
};
394+
let id_str = id.to_string();
395+
assert_eq!(id_str.as_str(), "cid:10@FF:Map");
396+
assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
397+
398+
let id = ContainerID::try_from("cid:root-a:b:c:Tree").unwrap();
399+
assert_eq!(
400+
id,
401+
ContainerID::new_root("a:b:c", crate::ContainerType::Tree)
402+
);
403+
}
404+
405+
#[test]
406+
fn test_convert_invalid_container_id_str() {
407+
assert!(ContainerID::try_from("cid:root-:Map").is_err());
408+
assert!(ContainerID::try_from("cid:0@:Map").is_err());
409+
assert!(ContainerID::try_from("cid:@:Map").is_err());
410+
assert!(ContainerID::try_from("cid:x@0:Map").is_err());
411+
assert!(ContainerID::try_from("id:0@0:Map").is_err());
412+
}
413+
}

crates/loro-internal/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ append-only-bytes = { version = "0.1.12", features = ["u32_range"] }
3333
itertools = "0.11.0"
3434
enum_dispatch = "0.3.11"
3535
im = "15.1.0"
36-
jumprope = { version = "1.1.2", features = ["wchar_conversion"] }
3736
generic-btree = { version = "0.8.2" }
3837
miniz_oxide = "0.7.1"
3938
getrandom = "0.2.10"

crates/loro-internal/src/container.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ pub mod idx {
6767

6868
pub mod list;
6969
pub mod map;
70-
pub mod tree;
7170
pub mod richtext;
7271
pub(crate) mod text;
72+
pub mod tree;
7373

7474
use idx::ContainerIdx;
7575

@@ -183,19 +183,19 @@ mod test {
183183
fn container_id_convert() {
184184
let container_id = ContainerID::new_normal(ID::new(12, 12), ContainerType::List);
185185
let s = container_id.to_string();
186-
assert_eq!(s, "12@12:List");
186+
assert_eq!(s, "cid:12@C:List");
187187
let actual = ContainerID::try_from(s.as_str()).unwrap();
188188
assert_eq!(actual, container_id);
189189

190190
let container_id = ContainerID::new_root("123", ContainerType::Map);
191191
let s = container_id.to_string();
192-
assert_eq!(s, "/123:Map");
192+
assert_eq!(s, "cid:root-123:Map");
193193
let actual = ContainerID::try_from(s.as_str()).unwrap();
194194
assert_eq!(actual, container_id);
195195

196196
let container_id = ContainerID::new_root("kkk", ContainerType::Text);
197197
let s = container_id.to_string();
198-
assert_eq!(s, "/kkk:Text");
198+
assert_eq!(s, "cid:root-kkk:Text");
199199
let actual = ContainerID::try_from(s.as_str()).unwrap();
200200
assert_eq!(actual, container_id);
201201
}

crates/loro-wasm/src/lib.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ impl Loro {
164164
self.0.peer_id()
165165
}
166166

167+
/// Get peer id in hex string.
168+
#[wasm_bindgen(js_name = "peerIdStr", method, getter)]
169+
pub fn peer_id_str(&self) -> String {
170+
format!("{:X}", self.0.peer_id())
171+
}
172+
167173
#[wasm_bindgen(js_name = "getText")]
168174
pub fn get_text(&self, name: &str) -> JsResult<LoroText> {
169175
let text = self.0.get_text(name);
@@ -699,7 +705,7 @@ impl LoroTree {
699705
pub fn create(&mut self, parent: Option<JsTreeID>) -> JsResult<JsTreeID> {
700706
let id = if let Some(p) = parent {
701707
let parent: JsValue = p.into();
702-
self.0.create_and_mov_(parent.try_into().unwrap())?
708+
self.0.create_and_mov_(parent.try_into().unwrap_throw())?
703709
} else {
704710
self.0.create_()?
705711
};
@@ -797,9 +803,9 @@ impl LoroTree {
797803
const TYPES: &'static str = r#"
798804
export type ContainerType = "Text" | "Map" | "List"| "Tree";
799805
export type ContainerID =
800-
| `/${string}:${ContainerType}`
801-
| `${number}@${number}:${ContainerType}`;
802-
export type TreeID = `${number}@${number}`;
806+
| `cid:root-${string}:${ContainerType}`
807+
| `cid:${number}@${string}:${ContainerType}`;
808+
export type TreeID = `${number}@${string}`;
803809
804810
interface Loro {
805811
exportFrom(version?: Uint8Array): Uint8Array;

loro-js/src/index.ts

+8-52
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ export {
1111
import { Delta, PrelimMap } from "loro-wasm";
1212
import { PrelimText } from "loro-wasm";
1313
import { PrelimList } from "loro-wasm";
14-
import {
15-
ContainerID,
16-
Loro,
17-
LoroList,
18-
LoroMap,
19-
LoroText,
20-
} from "loro-wasm";
14+
import { ContainerID, Loro, LoroList, LoroMap, LoroText } from "loro-wasm";
2115

2216
export type { ContainerID, ContainerType } from "loro-wasm";
2317

@@ -95,27 +89,7 @@ interface Listener {
9589
const CONTAINER_TYPES = ["Map", "Text", "List"];
9690

9791
export function isContainerId(s: string): s is ContainerID {
98-
try {
99-
if (s.startsWith("/")) {
100-
const [_, type] = s.slice(1).split(":");
101-
if (!CONTAINER_TYPES.includes(type)) {
102-
return false;
103-
}
104-
} else {
105-
const [id, type] = s.split(":");
106-
if (!CONTAINER_TYPES.includes(type)) {
107-
return false;
108-
}
109-
110-
const [counter, client] = id.split("@");
111-
Number.parseInt(counter);
112-
Number.parseInt(client);
113-
}
114-
115-
return true;
116-
} catch (e) {
117-
return false;
118-
}
92+
return s.startsWith("cid:");
11993
}
12094

12195
export { Loro };
@@ -142,40 +116,22 @@ declare module "loro-wasm" {
142116

143117
get(index: number): Value;
144118
getTyped<Key extends keyof T & number>(loro: Loro, index: Key): T[Key];
145-
insertTyped<Key extends keyof T & number>(
146-
pos: Key,
147-
value: T[Key],
148-
): void;
119+
insertTyped<Key extends keyof T & number>(pos: Key, value: T[Key]): void;
149120
insert(pos: number, value: Value | Prelim): void;
150121
delete(pos: number, len: number): void;
151122
subscribe(txn: Loro, listener: Listener): number;
152123
}
153124

154125
interface LoroMap<T extends Record<string, any> = Record<string, any>> {
155-
insertContainer(
156-
key: string,
157-
container_type: "Map",
158-
): LoroMap;
159-
insertContainer(
160-
key: string,
161-
container_type: "List",
162-
): LoroList;
163-
insertContainer(
164-
key: string,
165-
container_type: "Text",
166-
): LoroText;
167-
insertContainer(
168-
key: string,
169-
container_type: string,
170-
): never;
126+
insertContainer(key: string, container_type: "Map"): LoroMap;
127+
insertContainer(key: string, container_type: "List"): LoroList;
128+
insertContainer(key: string, container_type: "Text"): LoroText;
129+
insertContainer(key: string, container_type: string): never;
171130

172131
get(key: string): Value;
173132
getTyped<Key extends keyof T & string>(txn: Loro, key: Key): T[Key];
174133
set(key: string, value: Value | Prelim): void;
175-
setTyped<Key extends keyof T & string>(
176-
key: Key,
177-
value: T[Key],
178-
): void;
134+
setTyped<Key extends keyof T & string>(key: Key, value: T[Key]): void;
179135
delete(key: string): void;
180136
subscribe(txn: Loro, listener: Listener): number;
181137
}

0 commit comments

Comments
 (0)