1use automerge::hydrate;
4use automerge::transaction::Transactable;
5use serde_json::Value;
6
7fn insert_value_into_map<'a>(
9 tx: &mut automerge::transaction::Transaction<'a>,
10 parent: &automerge::ObjId,
11 key: &str,
12 value: &Value,
13) -> Result<(), automerge::AutomergeError> {
14 match value {
15 Value::String(s) => {
16 let text_id = tx.put_object(parent, key, automerge::ObjType::Text)?;
18 tx.splice_text(&text_id, 0, 0, s.as_str())?;
19 }
20 Value::Number(n) => {
21 if let Some(i) = n.as_i64() {
22 tx.put(parent, key, i)?;
23 } else if let Some(f) = n.as_f64() {
24 tx.put(parent, key, f)?;
25 }
26 }
27 Value::Bool(b) => {
28 tx.put(parent, key, *b)?;
29 }
30 Value::Null => {
31 tx.put(parent, key, ())?;
32 }
33 Value::Object(map) => {
34 let obj_id = tx.put_object(parent, key, automerge::ObjType::Map)?;
35 for (nested_key, nested_val) in map {
36 insert_value_into_map(tx, &obj_id, nested_key.as_str(), nested_val)?;
37 }
38 }
39 Value::Array(arr) => {
40 let list_id = tx.put_object(parent, key, automerge::ObjType::List)?;
41 for (i, item) in arr.iter().enumerate() {
42 insert_value_into_list(tx, &list_id, i, item)?;
43 }
44 }
45 }
46 Ok(())
47}
48
49fn insert_value_into_list<'a>(
51 tx: &mut automerge::transaction::Transaction<'a>,
52 parent: &automerge::ObjId,
53 index: usize,
54 value: &Value,
55) -> Result<(), automerge::AutomergeError> {
56 match value {
57 Value::String(s) => {
58 let text_id = tx.insert_object(parent, index, automerge::ObjType::Text)?;
60 tx.splice_text(&text_id, 0, 0, s.as_str())?;
61 }
62 Value::Number(n) => {
63 if let Some(i) = n.as_i64() {
64 tx.insert(parent, index, i)?;
65 } else if let Some(f) = n.as_f64() {
66 tx.insert(parent, index, f)?;
67 }
68 }
69 Value::Bool(b) => {
70 tx.insert(parent, index, *b)?;
71 }
72 Value::Null => {
73 tx.insert(parent, index, ())?;
74 }
75 Value::Object(map) => {
76 let obj_id = tx.insert_object(parent, index, automerge::ObjType::Map)?;
77 for (nested_key, nested_val) in map {
78 insert_value_into_map(tx, &obj_id, nested_key.as_str(), nested_val)?;
79 }
80 }
81 Value::Array(arr) => {
82 let list_id = tx.insert_object(parent, index, automerge::ObjType::List)?;
83 for (i, item) in arr.iter().enumerate() {
84 insert_value_into_list(tx, &list_id, i, item)?;
85 }
86 }
87 }
88 Ok(())
89}
90
91pub fn populate_automerge_from_json<'a>(
93 tx: &mut automerge::transaction::Transaction<'a>,
94 obj_id: automerge::ObjId,
95 value: &Value,
96) -> Result<(), automerge::AutomergeError> {
97 let Value::Object(map) = value else {
98 let value_type = match value {
99 Value::Null => "Null",
100 Value::Bool(_) => "Bool",
101 Value::Number(_) => "Number",
102 Value::String(_) => "String",
103 Value::Array(_) => "Array",
104 Value::Object(_) => unreachable!(),
105 };
106
107 return Err(automerge::AutomergeError::InvalidValueType {
108 expected: "Object".to_string(),
109 unexpected: format!("{} as document root", value_type),
110 });
111 };
112
113 for (key, val) in map {
114 insert_value_into_map(tx, &obj_id, key.as_str(), val)?;
115 }
116
117 Ok(())
118}
119
120pub fn hydrate_to_json(value: &hydrate::Value) -> Value {
122 match value {
123 hydrate::Value::Scalar(s) => scalar_to_json(s),
124 hydrate::Value::Map(m) => {
125 let mut map = serde_json::Map::new();
126 for (key, map_value) in m.iter() {
127 map.insert(key.to_string(), hydrate_to_json(&map_value.value));
128 }
129 Value::Object(map)
130 }
131 hydrate::Value::List(l) => {
132 Value::Array(l.iter().map(|list_value| hydrate_to_json(&list_value.value)).collect())
133 }
134 hydrate::Value::Text(t) => Value::String(t.to_string()),
135 }
136}
137
138fn scalar_to_json(s: &automerge::ScalarValue) -> Value {
139 use automerge::ScalarValue;
140 match s {
141 ScalarValue::Bytes(b) => {
142 Value::Array(b.iter().map(|v| Value::Number((*v).into())).collect())
143 }
144 ScalarValue::Str(s) => Value::String(s.to_string()),
145 ScalarValue::Int(i) => Value::Number((*i).into()),
146 ScalarValue::Uint(u) => Value::Number((*u).into()),
147 ScalarValue::F64(f) => {
148 serde_json::Number::from_f64(*f).map(Value::Number).unwrap_or(Value::Null)
149 }
150 ScalarValue::Counter(c) => Value::Number(i64::from(c).into()),
151 ScalarValue::Timestamp(t) => Value::Number((*t).into()),
152 ScalarValue::Boolean(b) => Value::Bool(*b),
153 ScalarValue::Null => Value::Null,
154 ScalarValue::Unknown { type_code, bytes } => Value::Object(serde_json::Map::from_iter([
155 ("type_code".to_string(), Value::Number((*type_code).into())),
156 (
157 "bytes".to_string(),
158 Value::Array(bytes.iter().map(|b| Value::Number((*b).into())).collect()),
159 ),
160 ])),
161 }
162}
163
164#[cfg(all(test, feature = "property-tests"))]
165mod tests {
166 use super::*;
167 use crate::common_test::roundtrip_json;
168 use crate::v1::notebook::ModelNotebook;
169 use automerge::Automerge;
170 use test_strategy::proptest;
171
172 #[proptest(cases = 64)]
174 fn model_notebook_roundtrips_through_automerge(notebook: ModelNotebook) {
175 let json = serde_json::to_value(¬ebook.0).expect("serialize to JSON");
176 let result = roundtrip_json(&json);
177 proptest::prop_assert_eq!(json, result);
178 }
179
180 #[proptest(cases = 64)]
182 fn non_object_root_is_rejected(value: bool) {
183 let json = Value::Bool(value);
184 let mut doc = Automerge::new();
185 let result = doc.transact(|tx| populate_automerge_from_json(tx, automerge::ROOT, &json));
186 proptest::prop_assert!(result.is_err());
187 }
188}