use std::{
collections::{BTreeMap, BTreeSet},
convert::TryInto,
hash::Hash,
};
use automerge::ReadDoc;
use serde::ser::{SerializeMap, SerializeSeq};
pub fn new_doc() -> automerge::AutoCommit {
let mut d = automerge::AutoCommit::new();
d.set_actor(automerge::ActorId::random());
d
}
pub fn new_doc_with_actor(actor: automerge::ActorId) -> automerge::AutoCommit {
let mut d = automerge::AutoCommit::new();
d.set_actor(actor);
d
}
pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) {
let a = automerge::ActorId::random();
let b = automerge::ActorId::random();
if a > b {
(b, a)
} else {
(a, b)
}
}
#[macro_export]
macro_rules! assert_doc {
($doc: expr, $expected: expr) => {{
use $crate::realize;
let realized = realize($doc);
let expected_obj = $expected.into();
if realized != expected_obj {
$crate::pretty_panic(expected_obj, realized)
}
}};
}
#[macro_export]
macro_rules! assert_obj {
($doc: expr, $obj_id: expr, $prop: expr, $expected: expr) => {{
use $crate::realize_prop;
let realized = realize_prop($doc, $obj_id, $prop);
let expected_obj = $expected.into();
if realized != expected_obj {
$crate::pretty_panic(expected_obj, realized)
}
}};
}
#[macro_export]
macro_rules! map {
(@inner { $($value:expr,)+ }) => { map!(@inner { $($value),+ }) };
(@inner { $($value:expr),* }) => {
{
use std::collections::BTreeSet;
use $crate::RealizedObject;
let mut inner: BTreeSet<RealizedObject> = BTreeSet::new();
$(
let _ = inner.insert($value.into());
)*
inner
}
};
($($key:expr => $inner:tt,)+) => { map!($($key => $inner),+) };
($($key:expr => $inner:tt),*) => {
{
use std::collections::{BTreeMap, BTreeSet};
use $crate::RealizedObject;
let mut _map: BTreeMap<String, BTreeSet<RealizedObject>> = ::std::collections::BTreeMap::new();
$(
let inner = map!(@inner $inner);
let _ = _map.insert($key.to_string(), inner);
)*
RealizedObject::Map(_map)
}
}
}
#[macro_export]
macro_rules! list {
(@single $($x:tt)*) => (());
(@count $($rest:tt),*) => (<[()]>::len(&[$(list!(@single $rest)),*]));
(@inner { $($value:expr,)+ }) => { list!(@inner { $($value),+ }) };
(@inner { $($value:expr),* }) => {
{
use std::collections::BTreeSet;
use $crate::RealizedObject;
let mut inner: BTreeSet<RealizedObject> = BTreeSet::new();
$(
let _ = inner.insert($value.into());
)*
inner
}
};
($($inner:tt,)+) => { list!($($inner),+) };
($($inner:tt),*) => {
{
use std::collections::BTreeSet;
let _cap = list!(@count $($inner),*);
let mut _list: Vec<BTreeSet<RealizedObject>> = Vec::new();
$(
let inner = list!(@inner $inner);
let _ = _list.push(inner);
)*
RealizedObject::Sequence(_list)
}
}
}
pub fn mk_counter(value: i64) -> automerge::ScalarValue {
automerge::ScalarValue::counter(value)
}
#[derive(Eq, Hash, PartialEq, Debug)]
pub struct ExportedOpId(String);
impl std::fmt::Display for ExportedOpId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(PartialEq, PartialOrd, Ord, Eq, Hash, Debug)]
pub enum RealizedObject {
Map(BTreeMap<String, BTreeSet<RealizedObject>>),
Sequence(Vec<BTreeSet<RealizedObject>>),
Value(OrdScalarValue),
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum OrdScalarValue {
Bytes(Vec<u8>),
Str(smol_str::SmolStr),
Int(i64),
Uint(u64),
F64(decorum::Total<f64>),
Counter(i64),
Timestamp(i64),
Boolean(bool),
Null,
Unknown { type_code: u8, bytes: Vec<u8> },
}
impl From<automerge::ScalarValue> for OrdScalarValue {
fn from(v: automerge::ScalarValue) -> Self {
match v {
automerge::ScalarValue::Bytes(v) => OrdScalarValue::Bytes(v),
automerge::ScalarValue::Str(v) => OrdScalarValue::Str(v),
automerge::ScalarValue::Int(v) => OrdScalarValue::Int(v),
automerge::ScalarValue::Uint(v) => OrdScalarValue::Uint(v),
automerge::ScalarValue::F64(v) => OrdScalarValue::F64(decorum::Total::from(v)),
automerge::ScalarValue::Counter(c) => OrdScalarValue::Counter(c.into()),
automerge::ScalarValue::Timestamp(v) => OrdScalarValue::Timestamp(v),
automerge::ScalarValue::Boolean(v) => OrdScalarValue::Boolean(v),
automerge::ScalarValue::Null => OrdScalarValue::Null,
automerge::ScalarValue::Unknown { type_code, bytes } => {
OrdScalarValue::Unknown { type_code, bytes }
}
}
}
}
impl From<&OrdScalarValue> for automerge::ScalarValue {
fn from(v: &OrdScalarValue) -> Self {
match v {
OrdScalarValue::Bytes(v) => automerge::ScalarValue::Bytes(v.clone()),
OrdScalarValue::Str(v) => automerge::ScalarValue::Str(v.clone()),
OrdScalarValue::Int(v) => automerge::ScalarValue::Int(*v),
OrdScalarValue::Uint(v) => automerge::ScalarValue::Uint(*v),
OrdScalarValue::F64(v) => automerge::ScalarValue::F64(v.into_inner()),
OrdScalarValue::Counter(v) => automerge::ScalarValue::counter(*v),
OrdScalarValue::Timestamp(v) => automerge::ScalarValue::Timestamp(*v),
OrdScalarValue::Boolean(v) => automerge::ScalarValue::Boolean(*v),
OrdScalarValue::Null => automerge::ScalarValue::Null,
OrdScalarValue::Unknown { type_code, bytes } => automerge::ScalarValue::Unknown {
type_code: *type_code,
bytes: bytes.to_vec(),
},
}
}
}
impl serde::Serialize for OrdScalarValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
OrdScalarValue::Bytes(v) => serializer.serialize_bytes(v),
OrdScalarValue::Str(v) => serializer.serialize_str(v.as_str()),
OrdScalarValue::Int(v) => serializer.serialize_i64(*v),
OrdScalarValue::Uint(v) => serializer.serialize_u64(*v),
OrdScalarValue::F64(v) => serializer.serialize_f64(v.into_inner()),
OrdScalarValue::Counter(v) => {
serializer.serialize_str(format!("Counter({})", v).as_str())
}
OrdScalarValue::Timestamp(v) => {
serializer.serialize_str(format!("Timestamp({})", v).as_str())
}
OrdScalarValue::Boolean(v) => serializer.serialize_bool(*v),
OrdScalarValue::Null => serializer.serialize_none(),
OrdScalarValue::Unknown { type_code, .. } => serializer
.serialize_str(format!("An unknown type with code {}", type_code).as_str()),
}
}
}
impl serde::Serialize for RealizedObject {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Map(kvs) => {
let mut map_ser = serializer.serialize_map(Some(kvs.len()))?;
for (k, vs) in kvs {
let vs_serded = vs.iter().collect::<Vec<&RealizedObject>>();
map_ser.serialize_entry(k, &vs_serded)?;
}
map_ser.end()
}
Self::Sequence(elems) => {
let mut list_ser = serializer.serialize_seq(Some(elems.len()))?;
for elem in elems {
let vs_serded = elem.iter().collect::<Vec<&RealizedObject>>();
list_ser.serialize_element(&vs_serded)?;
}
list_ser.end()
}
Self::Value(v) => v.serialize(serializer),
}
}
}
pub fn realize<R: ReadDoc>(doc: &R) -> RealizedObject {
realize_obj(doc, &automerge::ROOT, automerge::ObjType::Map)
}
pub fn realize_prop<R: ReadDoc, P: Into<automerge::Prop>>(
doc: &R,
obj_id: &automerge::ObjId,
prop: P,
) -> RealizedObject {
let (val, obj_id) = doc.get(obj_id, prop).unwrap().unwrap();
match val {
automerge::Value::Object(obj_type) => realize_obj(doc, &obj_id, obj_type),
automerge::Value::Scalar(v) => RealizedObject::Value(OrdScalarValue::from(v.into_owned())),
}
}
pub fn realize_obj<R: ReadDoc>(
doc: &R,
obj_id: &automerge::ObjId,
objtype: automerge::ObjType,
) -> RealizedObject {
match objtype {
automerge::ObjType::Map | automerge::ObjType::Table => {
let mut result = BTreeMap::new();
for key in doc.keys(obj_id) {
result.insert(key.clone(), realize_values(doc, obj_id, key));
}
RealizedObject::Map(result)
}
automerge::ObjType::List | automerge::ObjType::Text => {
let length = doc.length(obj_id);
let mut result = Vec::with_capacity(length);
for i in 0..length {
result.push(realize_values(doc, obj_id, i));
}
RealizedObject::Sequence(result)
}
}
}
fn realize_values<R: ReadDoc, K: Into<automerge::Prop>>(
doc: &R,
obj_id: &automerge::ObjId,
key: K,
) -> BTreeSet<RealizedObject> {
let mut values = BTreeSet::new();
for (value, objid) in doc.get_all(obj_id, key).unwrap() {
let realized = match value {
automerge::Value::Object(objtype) => realize_obj(doc, &objid, objtype),
automerge::Value::Scalar(v) => {
RealizedObject::Value(OrdScalarValue::from(v.into_owned()))
}
};
values.insert(realized);
}
values
}
impl<I: Into<RealizedObject>> From<BTreeMap<&str, BTreeSet<I>>> for RealizedObject {
fn from(values: BTreeMap<&str, BTreeSet<I>>) -> Self {
let intoed = values
.into_iter()
.map(|(k, v)| (k.to_string(), v.into_iter().map(|v| v.into()).collect()))
.collect();
RealizedObject::Map(intoed)
}
}
impl<I: Into<RealizedObject>> From<Vec<BTreeSet<I>>> for RealizedObject {
fn from(values: Vec<BTreeSet<I>>) -> Self {
RealizedObject::Sequence(
values
.into_iter()
.map(|v| v.into_iter().map(|v| v.into()).collect())
.collect(),
)
}
}
impl From<bool> for RealizedObject {
fn from(b: bool) -> Self {
RealizedObject::Value(OrdScalarValue::Boolean(b))
}
}
impl From<usize> for RealizedObject {
fn from(u: usize) -> Self {
let v = u.try_into().unwrap();
RealizedObject::Value(OrdScalarValue::Int(v))
}
}
impl From<u64> for RealizedObject {
fn from(v: u64) -> Self {
RealizedObject::Value(OrdScalarValue::Uint(v))
}
}
impl From<u32> for RealizedObject {
fn from(v: u32) -> Self {
RealizedObject::Value(OrdScalarValue::Uint(v.into()))
}
}
impl From<i64> for RealizedObject {
fn from(v: i64) -> Self {
RealizedObject::Value(OrdScalarValue::Int(v))
}
}
impl From<i32> for RealizedObject {
fn from(v: i32) -> Self {
RealizedObject::Value(OrdScalarValue::Int(v.into()))
}
}
impl From<automerge::ScalarValue> for RealizedObject {
fn from(s: automerge::ScalarValue) -> Self {
RealizedObject::Value(OrdScalarValue::from(s))
}
}
impl From<&str> for RealizedObject {
fn from(s: &str) -> Self {
RealizedObject::Value(OrdScalarValue::Str(smol_str::SmolStr::from(s)))
}
}
impl From<f64> for RealizedObject {
fn from(f: f64) -> Self {
RealizedObject::Value(OrdScalarValue::F64(f.into()))
}
}
impl From<Vec<u64>> for RealizedObject {
fn from(vals: Vec<u64>) -> Self {
RealizedObject::Sequence(
vals.into_iter()
.map(|i| {
let mut set = BTreeSet::new();
set.insert(i.into());
set
})
.collect(),
)
}
}
pub fn pretty_print(doc: &automerge::Automerge) {
println!("{}", serde_json::to_string_pretty(&realize(doc)).unwrap())
}
pub fn pretty_panic(expected_obj: RealizedObject, realized: RealizedObject) {
let serde_right = serde_json::to_string_pretty(&realized).unwrap();
let serde_left = serde_json::to_string_pretty(&expected_obj).unwrap();
panic!(
"documents didn't match\n expected\n{}\n got\n{}",
&serde_left, &serde_right
);
}