mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(y-octo): import y-octo monorepo (#11750)
This commit is contained in:
78
packages/common/y-octo/utils/src/codec.rs
Normal file
78
packages/common/y-octo/utils/src/codec.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
use lib0::encoding::Write;
|
||||
|
||||
fn test_var_uint_enc_dec(num: u64) {
|
||||
let mut buf1 = Vec::new();
|
||||
write_var_u64(&mut buf1, num).unwrap();
|
||||
|
||||
let mut buf2 = Vec::new();
|
||||
buf2.write_var(num);
|
||||
|
||||
{
|
||||
let (rest, decoded_num) = read_var_u64(&buf1).unwrap();
|
||||
assert_eq!(num, decoded_num);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
let (rest, decoded_num) = read_var_u64(&buf2).unwrap();
|
||||
assert_eq!(num, decoded_num);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_var_int_enc_dec(num: i32) {
|
||||
{
|
||||
let mut buf1: Vec<u8> = Vec::new();
|
||||
write_var_i32(&mut buf1, num).unwrap();
|
||||
|
||||
let (rest, decoded_num) = read_var_i32(&buf1).unwrap();
|
||||
assert_eq!(num, decoded_num);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
let mut buf2 = Vec::new();
|
||||
buf2.write_var(num);
|
||||
|
||||
let (rest, decoded_num) = read_var_i32(&buf2).unwrap();
|
||||
assert_eq!(num, decoded_num);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_var_uint_codec() {
|
||||
test_var_uint_enc_dec(0);
|
||||
test_var_uint_enc_dec(1);
|
||||
test_var_uint_enc_dec(127);
|
||||
test_var_uint_enc_dec(0b1000_0000);
|
||||
test_var_uint_enc_dec(0b1_0000_0000);
|
||||
test_var_uint_enc_dec(0b1_1111_1111);
|
||||
test_var_uint_enc_dec(0b10_0000_0000);
|
||||
test_var_uint_enc_dec(0b11_1111_1111);
|
||||
test_var_uint_enc_dec(0x7fff_ffff_ffff_ffff);
|
||||
test_var_uint_enc_dec(u64::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_var_int() {
|
||||
test_var_int_enc_dec(0);
|
||||
test_var_int_enc_dec(1);
|
||||
test_var_int_enc_dec(-1);
|
||||
test_var_int_enc_dec(63);
|
||||
test_var_int_enc_dec(-63);
|
||||
test_var_int_enc_dec(64);
|
||||
test_var_int_enc_dec(-64);
|
||||
test_var_int_enc_dec(i32::MAX);
|
||||
test_var_int_enc_dec(i32::MIN);
|
||||
test_var_int_enc_dec(((1 << 20) - 1) * 8);
|
||||
test_var_int_enc_dec(-((1 << 20) - 1) * 8);
|
||||
}
|
||||
}
|
||||
21
packages/common/y-octo/utils/src/doc.rs
Normal file
21
packages/common/y-octo/utils/src/doc.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use y_octo::Doc;
|
||||
use yrs::{Map, Transact};
|
||||
|
||||
#[test]
|
||||
fn test_basic_yrs_binary_compatibility() {
|
||||
let yrs_doc = yrs::Doc::new();
|
||||
|
||||
let map = yrs_doc.get_or_insert_map("abc");
|
||||
let mut trx = yrs_doc.transact_mut();
|
||||
map.insert(&mut trx, "a", 1);
|
||||
|
||||
let binary_from_yrs = trx.encode_update_v1();
|
||||
|
||||
let doc = Doc::try_from_binary_v1(&binary_from_yrs).unwrap();
|
||||
let binary = doc.encode_update_v1().unwrap();
|
||||
|
||||
assert_eq!(binary_from_yrs, binary);
|
||||
}
|
||||
}
|
||||
5
packages/common/y-octo/utils/src/doc_operation/mod.rs
Normal file
5
packages/common/y-octo/utils/src/doc_operation/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod types;
|
||||
pub mod yrs_op;
|
||||
|
||||
pub use types::*;
|
||||
pub use yrs_op::*;
|
||||
63
packages/common/y-octo/utils/src/doc_operation/types.rs
Normal file
63
packages/common/y-octo/utils/src/doc_operation/types.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use yrs::{ArrayRef, MapRef, TextRef, XmlFragmentRef, XmlTextRef};
|
||||
|
||||
pub const NEST_DATA_INSERT: &str = "insert";
|
||||
pub const NEST_DATA_DELETE: &str = "delete";
|
||||
pub const NEST_DATA_CLEAR: &str = "clear";
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Debug, arbitrary::Arbitrary)]
|
||||
pub enum OpType {
|
||||
HandleCurrent,
|
||||
CreateCRDTNestType,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Debug, arbitrary::Arbitrary)]
|
||||
pub enum NestDataOpType {
|
||||
Insert,
|
||||
Delete,
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, arbitrary::Arbitrary)]
|
||||
pub struct CRDTParam {
|
||||
pub op_type: OpType,
|
||||
pub new_nest_type: CRDTNestType,
|
||||
pub manipulate_source: ManipulateSource,
|
||||
pub insert_pos: InsertPos,
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
pub nest_data_op_type: NestDataOpType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, arbitrary::Arbitrary)]
|
||||
pub enum CRDTNestType {
|
||||
Array,
|
||||
Map,
|
||||
Text,
|
||||
XMLElement,
|
||||
XMLFragment,
|
||||
XMLText,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, arbitrary::Arbitrary)]
|
||||
pub enum ManipulateSource {
|
||||
NewNestTypeFromYDocRoot,
|
||||
CurrentNestType,
|
||||
NewNestTypeFromCurrent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, arbitrary::Arbitrary)]
|
||||
pub enum InsertPos {
|
||||
BEGIN,
|
||||
MID,
|
||||
END,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum YrsNestType {
|
||||
ArrayType(ArrayRef),
|
||||
MapType(MapRef),
|
||||
TextType(TextRef),
|
||||
XMLElementType(XmlFragmentRef),
|
||||
XMLFragmentType(XmlFragmentRef),
|
||||
XMLTextType(XmlTextRef),
|
||||
}
|
||||
172
packages/common/y-octo/utils/src/doc_operation/yrs_op/array.rs
Normal file
172
packages/common/y-octo/utils/src/doc_operation/yrs_op/array.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use phf::phf_map;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let array = match nest_input {
|
||||
YrsNestType::ArrayType(array) => array,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = array.len(&trx);
|
||||
let index = random_pick_num(len, ¶ms.insert_pos);
|
||||
array.insert(&mut trx, index, params.value);
|
||||
}
|
||||
|
||||
fn delete_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let array = match nest_input {
|
||||
YrsNestType::ArrayType(array) => array,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = array.len(&trx);
|
||||
if len >= 1 {
|
||||
let index = random_pick_num(len - 1, ¶ms.insert_pos);
|
||||
array.remove(&mut trx, index);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_op(doc: &yrs::Doc, nest_input: &YrsNestType, _params: CRDTParam) {
|
||||
let array = match nest_input {
|
||||
YrsNestType::ArrayType(array) => array,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = array.len(&trx);
|
||||
for _ in 0..len {
|
||||
array.remove(&mut trx, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub static ARRAY_OPS: TestOps = phf_map! {
|
||||
"insert" => insert_op,
|
||||
"delete" => delete_op,
|
||||
"clear" => clear_op,
|
||||
};
|
||||
|
||||
pub fn yrs_create_array_from_nest_type(
|
||||
doc: &yrs::Doc,
|
||||
current: &mut YrsNestType,
|
||||
insert_pos: &InsertPos,
|
||||
key: String,
|
||||
) -> Option<ArrayRef> {
|
||||
let cal_index = |len: u32| -> u32 {
|
||||
match insert_pos {
|
||||
InsertPos::BEGIN => 0,
|
||||
InsertPos::MID => len / 2,
|
||||
InsertPos::END => len,
|
||||
}
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let array_prelim = ArrayPrelim::default();
|
||||
match current {
|
||||
YrsNestType::ArrayType(array) => {
|
||||
let index = cal_index(array.len(&trx));
|
||||
Some(array.insert(&mut trx, index, array_prelim))
|
||||
}
|
||||
YrsNestType::MapType(map) => Some(map.insert(&mut trx, key, array_prelim)),
|
||||
YrsNestType::TextType(text) => {
|
||||
let str = text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
Some(text.insert_embed(&mut trx, byte_start_offset as u32, array_prelim))
|
||||
}
|
||||
YrsNestType::XMLTextType(xml_text) => {
|
||||
let str = xml_text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
Some(xml_text.insert_embed(&mut trx, byte_start_offset as u32, array_prelim))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yrs::Doc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gen_array_ref_ops() {
|
||||
let doc = Doc::new();
|
||||
let array_ref = doc.get_or_insert_array("test_array");
|
||||
|
||||
let ops_registry = OpsRegistry::new();
|
||||
|
||||
let mut params = CRDTParam {
|
||||
op_type: OpType::CreateCRDTNestType,
|
||||
new_nest_type: CRDTNestType::Array,
|
||||
manipulate_source: ManipulateSource::NewNestTypeFromYDocRoot,
|
||||
insert_pos: InsertPos::BEGIN,
|
||||
key: String::from("test_key"),
|
||||
value: String::from("test_value"),
|
||||
nest_data_op_type: NestDataOpType::Insert,
|
||||
};
|
||||
|
||||
ops_registry.operate_yrs_nest_type(
|
||||
&doc,
|
||||
YrsNestType::ArrayType(array_ref.clone()),
|
||||
params.clone(),
|
||||
);
|
||||
assert_eq!(array_ref.len(&doc.transact()), 1);
|
||||
params.nest_data_op_type = NestDataOpType::Delete;
|
||||
ops_registry.operate_yrs_nest_type(
|
||||
&doc,
|
||||
YrsNestType::ArrayType(array_ref.clone()),
|
||||
params.clone(),
|
||||
);
|
||||
assert_eq!(array_ref.len(&doc.transact()), 0);
|
||||
|
||||
params.nest_data_op_type = NestDataOpType::Clear;
|
||||
ops_registry.operate_yrs_nest_type(
|
||||
&doc,
|
||||
YrsNestType::ArrayType(array_ref.clone()),
|
||||
params.clone(),
|
||||
);
|
||||
assert_eq!(array_ref.len(&doc.transact()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_yrs_create_array_from_nest_type() {
|
||||
let doc = Doc::new();
|
||||
let array_ref = doc.get_or_insert_array("test_array");
|
||||
let key = String::from("test_key");
|
||||
|
||||
let new_array_ref = yrs_create_array_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::ArrayType(array_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(new_array_ref.is_some());
|
||||
|
||||
let map_ref = doc.get_or_insert_map("test_map");
|
||||
let new_array_ref = yrs_create_array_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::MapType(map_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(new_array_ref.is_some());
|
||||
|
||||
let text_ref = doc.get_or_insert_text("test_text");
|
||||
let new_array_ref = yrs_create_array_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::TextType(text_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(new_array_ref.is_some());
|
||||
}
|
||||
}
|
||||
168
packages/common/y-octo/utils/src/doc_operation/yrs_op/map.rs
Normal file
168
packages/common/y-octo/utils/src/doc_operation/yrs_op/map.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
use phf::phf_map;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let map = match nest_input {
|
||||
YrsNestType::MapType(map) => map,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
map.insert(&mut trx, params.key, params.value);
|
||||
}
|
||||
|
||||
fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let map = match nest_input {
|
||||
YrsNestType::MapType(map) => map,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let rand_key = {
|
||||
let trx = doc.transact_mut();
|
||||
let mut iter = map.iter(&trx);
|
||||
let len = map.len(&trx) as usize;
|
||||
let skip_step = if len <= 1 {
|
||||
0
|
||||
} else {
|
||||
random_pick_num((len - 1) as u32, ¶ms.insert_pos)
|
||||
};
|
||||
|
||||
iter
|
||||
.nth(skip_step as usize)
|
||||
.map(|(key, _value)| key.to_string())
|
||||
};
|
||||
|
||||
if let Some(key) = rand_key {
|
||||
let mut trx = doc.transact_mut();
|
||||
map.remove(&mut trx, &key).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_op(doc: &yrs::Doc, nest_input: &YrsNestType, _params: CRDTParam) {
|
||||
let map = match nest_input {
|
||||
YrsNestType::MapType(map) => map,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
map.clear(&mut trx);
|
||||
}
|
||||
|
||||
pub static MAP_OPS: TestOps = phf_map! {
|
||||
"insert" => insert_op,
|
||||
"delete" => remove_op,
|
||||
"clear" => clear_op,
|
||||
};
|
||||
|
||||
pub fn yrs_create_map_from_nest_type(
|
||||
doc: &yrs::Doc,
|
||||
current: &mut YrsNestType,
|
||||
insert_pos: &InsertPos,
|
||||
key: String,
|
||||
) -> Option<MapRef> {
|
||||
let cal_index = |len: u32| -> u32 {
|
||||
match insert_pos {
|
||||
InsertPos::BEGIN => 0,
|
||||
InsertPos::MID => len / 2,
|
||||
InsertPos::END => len,
|
||||
}
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let map_prelim = MapPrelim::from([("deepkey".to_owned(), "deepvalue")]);
|
||||
match current {
|
||||
YrsNestType::ArrayType(array) => {
|
||||
let index = cal_index(array.len(&trx));
|
||||
Some(array.insert(&mut trx, index, map_prelim))
|
||||
}
|
||||
YrsNestType::MapType(map) => Some(map.insert(&mut trx, key, map_prelim)),
|
||||
YrsNestType::TextType(text) => {
|
||||
let str = text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
Some(text.insert_embed(&mut trx, byte_start_offset as u32, map_prelim))
|
||||
}
|
||||
YrsNestType::XMLTextType(xml_text) => {
|
||||
let str = xml_text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
Some(xml_text.insert_embed(&mut trx, byte_start_offset as u32, map_prelim))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yrs::Doc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gen_map_ref_ops() {
|
||||
let doc = Doc::new();
|
||||
let map_ref = doc.get_or_insert_map("test_map");
|
||||
|
||||
let ops_registry = OpsRegistry::new();
|
||||
|
||||
let mut params = CRDTParam {
|
||||
op_type: OpType::CreateCRDTNestType,
|
||||
new_nest_type: CRDTNestType::Map,
|
||||
manipulate_source: ManipulateSource::NewNestTypeFromYDocRoot,
|
||||
insert_pos: InsertPos::BEGIN,
|
||||
key: String::from("test_key"),
|
||||
value: String::from("test_value"),
|
||||
nest_data_op_type: NestDataOpType::Insert,
|
||||
};
|
||||
|
||||
ops_registry.operate_yrs_nest_type(&doc, YrsNestType::MapType(map_ref.clone()), params.clone());
|
||||
assert_eq!(map_ref.len(&doc.transact()), 1);
|
||||
params.nest_data_op_type = NestDataOpType::Delete;
|
||||
ops_registry.operate_yrs_nest_type(&doc, YrsNestType::MapType(map_ref.clone()), params.clone());
|
||||
assert_eq!(map_ref.len(&doc.transact()), 0);
|
||||
|
||||
params.nest_data_op_type = NestDataOpType::Clear;
|
||||
ops_registry.operate_yrs_nest_type(&doc, YrsNestType::MapType(map_ref.clone()), params.clone());
|
||||
assert_eq!(map_ref.len(&doc.transact()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_yrs_create_map_from_nest_type() {
|
||||
let doc = Doc::new();
|
||||
let map_ref = doc.get_or_insert_map("test_map");
|
||||
let key = String::from("test_key");
|
||||
|
||||
let new_map_ref = yrs_create_map_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::MapType(map_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(new_map_ref.is_some());
|
||||
|
||||
let map_ref = doc.get_or_insert_map("test_map");
|
||||
let new_map_ref = yrs_create_map_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::MapType(map_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(new_map_ref.is_some());
|
||||
|
||||
let text_ref = doc.get_or_insert_text("test_text");
|
||||
let new_map_ref = yrs_create_map_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::TextType(text_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(new_map_ref.is_some());
|
||||
}
|
||||
}
|
||||
193
packages/common/y-octo/utils/src/doc_operation/yrs_op/mod.rs
Normal file
193
packages/common/y-octo/utils/src/doc_operation/yrs_op/mod.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
pub mod array;
|
||||
pub mod map;
|
||||
pub mod text;
|
||||
pub mod xml_element;
|
||||
pub mod xml_fragment;
|
||||
pub mod xml_text;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use array::*;
|
||||
use map::*;
|
||||
use text::*;
|
||||
use xml_element::*;
|
||||
use xml_fragment::*;
|
||||
use xml_text::*;
|
||||
use yrs::{
|
||||
Array, ArrayPrelim, ArrayRef, Doc, GetString, Map, MapPrelim, MapRef, Text, TextPrelim, TextRef,
|
||||
Transact, XmlFragment, XmlTextPrelim, XmlTextRef,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
type TestOp = fn(doc: &Doc, nest_input: &YrsNestType, params: CRDTParam) -> ();
|
||||
type TestOps = phf::Map<&'static str, TestOp>;
|
||||
|
||||
pub struct OpsRegistry<'a>(HashMap<CRDTNestType, &'a TestOps>);
|
||||
|
||||
impl Default for OpsRegistry<'_> {
|
||||
fn default() -> Self {
|
||||
OpsRegistry::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl OpsRegistry<'_> {
|
||||
pub fn new() -> Self {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(CRDTNestType::Map, &MAP_OPS);
|
||||
map.insert(CRDTNestType::Array, &ARRAY_OPS);
|
||||
map.insert(CRDTNestType::Text, &TEXT_OPS);
|
||||
map.insert(CRDTNestType::XMLElement, &XML_ELEMENT_OPS);
|
||||
map.insert(CRDTNestType::XMLText, &XML_TEXT_OPS);
|
||||
map.insert(CRDTNestType::XMLFragment, &XML_FRAGMENT_OPS);
|
||||
|
||||
OpsRegistry(map)
|
||||
}
|
||||
|
||||
pub fn get_ops(&self, crdt_nest_type: &CRDTNestType) -> &TestOps {
|
||||
match crdt_nest_type {
|
||||
CRDTNestType::Map => self.0.get(&CRDTNestType::Map).unwrap(),
|
||||
CRDTNestType::Array => self.0.get(&CRDTNestType::Array).unwrap(),
|
||||
CRDTNestType::Text => self.0.get(&CRDTNestType::Text).unwrap(),
|
||||
CRDTNestType::XMLElement => self.0.get(&CRDTNestType::XMLElement).unwrap(),
|
||||
CRDTNestType::XMLFragment => self.0.get(&CRDTNestType::XMLFragment).unwrap(),
|
||||
CRDTNestType::XMLText => self.0.get(&CRDTNestType::XMLText).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ops_from_yrs_nest_type(&self, yrs_nest_type: &YrsNestType) -> &TestOps {
|
||||
match yrs_nest_type {
|
||||
YrsNestType::MapType(_) => self.get_ops(&CRDTNestType::Map),
|
||||
YrsNestType::ArrayType(_) => self.get_ops(&CRDTNestType::Array),
|
||||
YrsNestType::TextType(_) => self.get_ops(&CRDTNestType::Text),
|
||||
YrsNestType::XMLElementType(_) => self.get_ops(&CRDTNestType::XMLElement),
|
||||
YrsNestType::XMLTextType(_) => self.get_ops(&CRDTNestType::XMLText),
|
||||
YrsNestType::XMLFragmentType(_) => self.get_ops(&CRDTNestType::XMLFragment),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operate_yrs_nest_type(
|
||||
&self,
|
||||
doc: &yrs::Doc,
|
||||
cur_crdt_nest_type: YrsNestType,
|
||||
crdt_param: CRDTParam,
|
||||
) {
|
||||
let ops = self.get_ops_from_yrs_nest_type(&cur_crdt_nest_type);
|
||||
ops
|
||||
.get(match &crdt_param.nest_data_op_type {
|
||||
NestDataOpType::Insert => NEST_DATA_INSERT,
|
||||
NestDataOpType::Delete => NEST_DATA_DELETE,
|
||||
NestDataOpType::Clear => NEST_DATA_CLEAR,
|
||||
})
|
||||
.unwrap()(doc, &cur_crdt_nest_type, crdt_param)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn yrs_create_nest_type_from_root(
|
||||
doc: &yrs::Doc,
|
||||
target_type: CRDTNestType,
|
||||
key: &str,
|
||||
) -> YrsNestType {
|
||||
match target_type {
|
||||
CRDTNestType::Array => YrsNestType::ArrayType(doc.get_or_insert_array(key)),
|
||||
CRDTNestType::Map => YrsNestType::MapType(doc.get_or_insert_map(key)),
|
||||
CRDTNestType::Text => YrsNestType::TextType(doc.get_or_insert_text(key)),
|
||||
CRDTNestType::XMLElement => YrsNestType::XMLElementType(doc.get_or_insert_xml_fragment(key)),
|
||||
CRDTNestType::XMLFragment => YrsNestType::XMLFragmentType(doc.get_or_insert_xml_fragment(key)),
|
||||
CRDTNestType::XMLText => {
|
||||
YrsNestType::XMLTextType((AsRef::<XmlTextRef>::as_ref(&doc.get_or_insert_text(key))).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_nest_type_from_root(doc: &mut Doc, crdt_param: &CRDTParam) -> Option<YrsNestType> {
|
||||
match crdt_param.new_nest_type {
|
||||
CRDTNestType::Array => Some(yrs_create_nest_type_from_root(
|
||||
doc,
|
||||
CRDTNestType::Array,
|
||||
crdt_param.key.as_str(),
|
||||
)),
|
||||
CRDTNestType::Map => Some(yrs_create_nest_type_from_root(
|
||||
doc,
|
||||
CRDTNestType::Map,
|
||||
crdt_param.key.as_str(),
|
||||
)),
|
||||
CRDTNestType::Text => Some(yrs_create_nest_type_from_root(
|
||||
doc,
|
||||
CRDTNestType::Text,
|
||||
crdt_param.key.as_str(),
|
||||
)),
|
||||
CRDTNestType::XMLText => Some(yrs_create_nest_type_from_root(
|
||||
doc,
|
||||
CRDTNestType::XMLText,
|
||||
crdt_param.key.as_str(),
|
||||
)),
|
||||
CRDTNestType::XMLElement => Some(yrs_create_nest_type_from_root(
|
||||
doc,
|
||||
CRDTNestType::XMLElement,
|
||||
crdt_param.key.as_str(),
|
||||
)),
|
||||
CRDTNestType::XMLFragment => Some(yrs_create_nest_type_from_root(
|
||||
doc,
|
||||
CRDTNestType::XMLFragment,
|
||||
crdt_param.key.as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_nest_type_from_nest_type(
|
||||
doc: &mut Doc,
|
||||
crdt_param: CRDTParam,
|
||||
nest_type: &mut YrsNestType,
|
||||
) -> Option<YrsNestType> {
|
||||
match crdt_param.new_nest_type {
|
||||
CRDTNestType::Array => {
|
||||
yrs_create_array_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key)
|
||||
.map(YrsNestType::ArrayType)
|
||||
}
|
||||
CRDTNestType::Map => {
|
||||
yrs_create_map_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key)
|
||||
.map(YrsNestType::MapType)
|
||||
}
|
||||
CRDTNestType::Text => {
|
||||
yrs_create_text_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key)
|
||||
.map(YrsNestType::TextType)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_pick_num(len: u32, insert_pos: &InsertPos) -> u32 {
|
||||
match insert_pos {
|
||||
InsertPos::BEGIN => 0,
|
||||
InsertPos::MID => len / 2,
|
||||
InsertPos::END => len,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ops_registry_new() {
|
||||
let ops_registry = OpsRegistry::new();
|
||||
assert_eq!(ops_registry.0.len(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_registry_get_ops() {
|
||||
let ops_registry = OpsRegistry::new();
|
||||
let ops = ops_registry.get_ops(&CRDTNestType::Array);
|
||||
assert!(!ops.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_registry_get_ops_from_yrs_nest_type() {
|
||||
let doc = yrs::Doc::new();
|
||||
let array = doc.get_or_insert_array("array");
|
||||
let ops_registry = OpsRegistry::new();
|
||||
let ops = ops_registry.get_ops_from_yrs_nest_type(&YrsNestType::ArrayType(array));
|
||||
assert!(!ops.is_empty());
|
||||
}
|
||||
}
|
||||
180
packages/common/y-octo/utils/src/doc_operation/yrs_op/text.rs
Normal file
180
packages/common/y-octo/utils/src/doc_operation/yrs_op/text.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use phf::phf_map;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let text = match nest_input {
|
||||
YrsNestType::TextType(text) => text,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
|
||||
let str = text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, ¶ms.insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
text.insert(&mut trx, byte_start_offset as u32, ¶ms.value);
|
||||
}
|
||||
|
||||
fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let text = match nest_input {
|
||||
YrsNestType::TextType(text) => text,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
|
||||
let str = text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
if len < 1 {
|
||||
return;
|
||||
}
|
||||
let index = random_pick_num(len - 1, ¶ms.insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
let char_byte_len = str.chars().nth(index).unwrap().len_utf8();
|
||||
text.remove_range(&mut trx, byte_start_offset as u32, char_byte_len as u32);
|
||||
}
|
||||
|
||||
fn clear_op(doc: &yrs::Doc, nest_input: &YrsNestType, _params: CRDTParam) {
|
||||
let text = match nest_input {
|
||||
YrsNestType::TextType(text) => text,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
|
||||
let str = text.get_string(&trx);
|
||||
let byte_len = str.chars().fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
text.remove_range(&mut trx, 0, byte_len as u32);
|
||||
}
|
||||
|
||||
pub static TEXT_OPS: TestOps = phf_map! {
|
||||
"insert" => insert_op,
|
||||
"delete" => remove_op,
|
||||
"clear" => clear_op,
|
||||
};
|
||||
|
||||
pub fn yrs_create_text_from_nest_type(
|
||||
doc: &yrs::Doc,
|
||||
current: &mut YrsNestType,
|
||||
insert_pos: &InsertPos,
|
||||
key: String,
|
||||
) -> Option<TextRef> {
|
||||
let cal_index_closure = |len: u32| -> u32 { random_pick_num(len, insert_pos) };
|
||||
let mut trx = doc.transact_mut();
|
||||
let text_prelim = TextPrelim::new("");
|
||||
match current {
|
||||
YrsNestType::ArrayType(array) => {
|
||||
let index = cal_index_closure(array.len(&trx));
|
||||
Some(array.insert(&mut trx, index, text_prelim))
|
||||
}
|
||||
YrsNestType::MapType(map) => Some(map.insert(&mut trx, key, text_prelim)),
|
||||
YrsNestType::TextType(text) => {
|
||||
let str = text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
Some(text.insert_embed(&mut trx, byte_start_offset as u32, text_prelim))
|
||||
}
|
||||
YrsNestType::XMLTextType(xml_text) => {
|
||||
let str = xml_text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
Some(xml_text.insert_embed(&mut trx, byte_start_offset as u32, text_prelim))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gen_array_ref_ops() {
|
||||
let doc = Doc::new();
|
||||
let text_ref = doc.get_or_insert_text("test_text");
|
||||
|
||||
let ops_registry = OpsRegistry::new();
|
||||
|
||||
let mut params = CRDTParam {
|
||||
op_type: OpType::CreateCRDTNestType,
|
||||
new_nest_type: CRDTNestType::Text,
|
||||
manipulate_source: ManipulateSource::NewNestTypeFromYDocRoot,
|
||||
insert_pos: InsertPos::BEGIN,
|
||||
key: String::from("test_key"),
|
||||
value: String::from("test_value"),
|
||||
nest_data_op_type: NestDataOpType::Insert,
|
||||
};
|
||||
|
||||
ops_registry.operate_yrs_nest_type(
|
||||
&doc,
|
||||
YrsNestType::TextType(text_ref.clone()),
|
||||
params.clone(),
|
||||
);
|
||||
assert_eq!(text_ref.len(&doc.transact()), 10);
|
||||
params.nest_data_op_type = NestDataOpType::Delete;
|
||||
ops_registry.operate_yrs_nest_type(
|
||||
&doc,
|
||||
YrsNestType::TextType(text_ref.clone()),
|
||||
params.clone(),
|
||||
);
|
||||
assert_eq!(text_ref.len(&doc.transact()), 9);
|
||||
|
||||
params.nest_data_op_type = NestDataOpType::Clear;
|
||||
ops_registry.operate_yrs_nest_type(
|
||||
&doc,
|
||||
YrsNestType::TextType(text_ref.clone()),
|
||||
params.clone(),
|
||||
);
|
||||
assert_eq!(text_ref.len(&doc.transact()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_yrs_create_array_from_nest_type() {
|
||||
let doc = Doc::new();
|
||||
let array_ref = doc.get_or_insert_array("test_array");
|
||||
let key = String::from("test_key");
|
||||
|
||||
let next_text_ref = yrs_create_text_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::ArrayType(array_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(next_text_ref.is_some());
|
||||
|
||||
let map_ref = doc.get_or_insert_map("test_map");
|
||||
let next_text_ref = yrs_create_text_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::MapType(map_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(next_text_ref.is_some());
|
||||
|
||||
let text_ref = doc.get_or_insert_text("test_text");
|
||||
let next_text_ref = yrs_create_text_from_nest_type(
|
||||
&doc,
|
||||
&mut YrsNestType::TextType(text_ref.clone()),
|
||||
&InsertPos::BEGIN,
|
||||
key.clone(),
|
||||
);
|
||||
assert!(next_text_ref.is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use phf::phf_map;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let xml_element = match nest_input {
|
||||
YrsNestType::XMLElementType(xml_element) => xml_element,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = xml_element.len(&trx);
|
||||
let index = random_pick_num(len, ¶ms.insert_pos);
|
||||
xml_element.insert(&mut trx, index, XmlTextPrelim::new(params.value));
|
||||
}
|
||||
|
||||
fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let xml_element = match nest_input {
|
||||
YrsNestType::XMLElementType(xml_element) => xml_element,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = xml_element.len(&trx);
|
||||
if len >= 1 {
|
||||
let index = random_pick_num(len - 1, ¶ms.insert_pos);
|
||||
xml_element.remove_range(&mut trx, index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_op(doc: &yrs::Doc, nest_input: &YrsNestType, _params: CRDTParam) {
|
||||
let xml_element = match nest_input {
|
||||
YrsNestType::XMLElementType(xml_element) => xml_element,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = xml_element.len(&trx);
|
||||
for _ in 0..len {
|
||||
xml_element.remove_range(&mut trx, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub static XML_ELEMENT_OPS: TestOps = phf_map! {
|
||||
"insert" => insert_op,
|
||||
"delete" => remove_op,
|
||||
"clear" => clear_op,
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
use phf::phf_map;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let xml_fragment = match nest_input {
|
||||
YrsNestType::XMLFragmentType(xml_fragment) => xml_fragment,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = xml_fragment.len(&trx);
|
||||
let index = random_pick_num(len, ¶ms.insert_pos);
|
||||
xml_fragment.insert(&mut trx, index, XmlTextPrelim::new(params.value));
|
||||
}
|
||||
|
||||
fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let xml_fragment = match nest_input {
|
||||
YrsNestType::XMLFragmentType(xml_fragment) => xml_fragment,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = xml_fragment.len(&trx);
|
||||
if len >= 1 {
|
||||
let index = random_pick_num(len - 1, ¶ms.insert_pos);
|
||||
xml_fragment.remove_range(&mut trx, index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_op(doc: &yrs::Doc, nest_input: &YrsNestType, _params: CRDTParam) {
|
||||
let xml_fragment = match nest_input {
|
||||
YrsNestType::XMLFragmentType(xml_fragment) => xml_fragment,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
let len = xml_fragment.len(&trx);
|
||||
for _ in 0..len {
|
||||
xml_fragment.remove_range(&mut trx, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub static XML_FRAGMENT_OPS: TestOps = phf_map! {
|
||||
"insert" => insert_op,
|
||||
"delete" => remove_op,
|
||||
"clear" => clear_op,
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
use phf::phf_map;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let xml_text = match nest_input {
|
||||
YrsNestType::XMLTextType(xml_text) => xml_text,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
|
||||
let str = xml_text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
let index = random_pick_num(len, ¶ms.insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
xml_text.insert(&mut trx, byte_start_offset as u32, ¶ms.value);
|
||||
}
|
||||
|
||||
fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) {
|
||||
let xml_text = match nest_input {
|
||||
YrsNestType::XMLTextType(xml_text) => xml_text,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
|
||||
let str = xml_text.get_string(&trx);
|
||||
let len = str.chars().fold(0, |acc, _| acc + 1);
|
||||
if len < 1 {
|
||||
return;
|
||||
}
|
||||
let index = random_pick_num(len - 1, ¶ms.insert_pos) as usize;
|
||||
let byte_start_offset = str
|
||||
.chars()
|
||||
.take(index)
|
||||
.fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
let char_byte_len = str.chars().nth(index).unwrap().len_utf8();
|
||||
xml_text.remove_range(&mut trx, byte_start_offset as u32, char_byte_len as u32);
|
||||
}
|
||||
|
||||
fn clear_op(doc: &yrs::Doc, nest_input: &YrsNestType, _params: CRDTParam) {
|
||||
let xml_text = match nest_input {
|
||||
YrsNestType::XMLTextType(xml_text) => xml_text,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut trx = doc.transact_mut();
|
||||
|
||||
let str = xml_text.get_string(&trx);
|
||||
let byte_len = str.chars().fold(0, |acc, ch| acc + ch.len_utf8());
|
||||
|
||||
xml_text.remove_range(&mut trx, 0, byte_len as u32);
|
||||
}
|
||||
|
||||
pub static XML_TEXT_OPS: TestOps = phf_map! {
|
||||
"insert" => insert_op,
|
||||
"delete" => remove_op,
|
||||
"clear" => clear_op,
|
||||
};
|
||||
7
packages/common/y-octo/utils/src/lib.rs
Normal file
7
packages/common/y-octo/utils/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod doc;
|
||||
|
||||
#[cfg(feature = "fuzz")]
|
||||
pub mod doc_operation;
|
||||
|
||||
#[cfg(feature = "fuzz")]
|
||||
pub use doc_operation::*;
|
||||
Reference in New Issue
Block a user