From 46db061a4662414875faf20ace592ffc1056a49c Mon Sep 17 00:00:00 2001 From: Pawel Iwan Date: Mon, 21 Nov 2022 09:19:54 +0100 Subject: [PATCH 1/5] feat: insert with parents for Document --- dpp/src/document/mod.rs | 14 +- dpp/src/util/json_path.rs | 2 + .../util/json_value/insert_with_parents.rs | 268 ++++++++++++++++++ .../util/{json_value.rs => json_value/mod.rs} | 65 ++++- 4 files changed, 338 insertions(+), 11 deletions(-) create mode 100644 dpp/src/util/json_value/insert_with_parents.rs rename dpp/src/util/{json_value.rs => json_value/mod.rs} (91%) diff --git a/dpp/src/document/mod.rs b/dpp/src/document/mod.rs index 5a677e7d..e025463a 100644 --- a/dpp/src/document/mod.rs +++ b/dpp/src/document/mod.rs @@ -56,6 +56,16 @@ pub struct Document { } impl Document { + pub fn from_json( + mut json_object: JsonValue, + data_contract: DataContract, + ) -> Result { + json_object.replace_identifier_paths(IDENTIFIER_FIELDS, ReplaceWith::Bytes); + // now we need to get the all informations related to the + + todo!() + } + pub fn from_raw_document( mut raw_document: JsonValue, data_contract: DataContract, @@ -181,8 +191,8 @@ impl Document { Ok(hash(self.to_buffer()?)) } - pub fn set_value(&mut self, property: &str, value: JsonValue) -> Result<(), ProtocolError> { - Ok(self.data.insert(property.to_string(), value)?) + pub fn set_value(&mut self, path: &str, value: JsonValue) -> Result<(), ProtocolError> { + Ok(self.data.insert_with_parents(path, value)?) } /// Retrieves field specified by path diff --git a/dpp/src/util/json_path.rs b/dpp/src/util/json_path.rs index 2404636a..6d3d5170 100644 --- a/dpp/src/util/json_path.rs +++ b/dpp/src/util/json_path.rs @@ -94,6 +94,8 @@ fn try_parse_indexed_field(step: &str) -> Result<(String, usize), anyhow::Error> #[cfg(test)] mod test { + use std::convert::TryInto; + use super::*; #[test] diff --git a/dpp/src/util/json_value/insert_with_parents.rs b/dpp/src/util/json_value/insert_with_parents.rs new file mode 100644 index 00000000..fa725593 --- /dev/null +++ b/dpp/src/util/json_value/insert_with_parents.rs @@ -0,0 +1,268 @@ +use super::JsonValueExt; +use crate::util::json_path::JsonPathStep; +use anyhow::{bail, Context}; +use serde_json::Value; + +/// Inserts the value specified by the json path. If intermediate object doesn't exist, crates a one. +/// If `Value::Null` is encountered while traversing the path, they are replaced with the required structure. +pub(super) fn insert_with_parents( + data: &mut Value, + json_path: &[JsonPathStep], + value: Value, +) -> Result<(), anyhow::Error> { + let mut current_level = data; + let last_index = json_path.len() - 1; + + for (i, key) in json_path.iter().enumerate() { + match key { + JsonPathStep::Index(json_index) => { + if i == last_index { + if current_level.is_null() { + *current_level = Value::Array(Default::default()); + } + fill_empty_indexes(current_level, *json_index); + insert_into_array(current_level, *json_index, value).with_context(|| { + format!("failed inserting on position {json_index} into {current_level:#?}") + })?; + break; + } + + if current_level.get(json_index).is_none() { + if current_level.is_null() { + *current_level = Value::Array(Default::default()); + } + fill_empty_indexes(current_level, *json_index); + current_level.push(new_value_based_on_next_step(&json_path[i + 1]))?; + } + + let new_level = current_level.get_mut(json_index).unwrap(); + current_level = new_level; + } + + JsonPathStep::Key(key) => { + if i == last_index { + if current_level.is_null() { + *current_level = Value::Object(Default::default()); + } + current_level.insert(key.to_string(), value)?; + break; + } + + if current_level.get(key).is_none() { + if current_level.is_null() { + *current_level = Value::Object(Default::default()); + } + current_level.insert( + key.to_string(), + new_value_based_on_next_step(&json_path[i + 1]), + )?; + } + let new_level = current_level.get_mut(key).unwrap(); + current_level = new_level; + } + } + } + + Ok(()) +} + +fn insert_into_array( + maybe_array: &mut Value, + position: usize, + value: Value, +) -> Result<(), anyhow::Error> { + match maybe_array.as_array_mut() { + Some(ref mut array) => { + if position >= array.len() { + array.push(value) + } else { + array[position] = value; + } + Ok(()) + } + None => bail!("expected array"), + } +} + +fn fill_empty_indexes(current_level: &mut Value, i: usize) { + let index = i as i64; + if let Some(array) = current_level.as_array_mut() { + let positions_to_fill = index - (array.len() as i64 - 1) - 1; + array.extend((0..positions_to_fill).map(|_| Value::Null)); + } +} + +fn new_value_based_on_next_step(next_step: &JsonPathStep) -> Value { + match next_step { + JsonPathStep::Index(_) => Value::Array(Default::default()), + JsonPathStep::Key(_) => Value::Object(Default::default()), + } +} + +#[cfg(test)] +mod test_set { + use serde_json::json; + + use super::*; + + #[test] + fn set_onto_nil_when_object_given() { + let mut data = Value::Null; + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("b".to_string()), + JsonPathStep::Key("c".to_string()), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data["a"]["b"]["c"], json!("alpha")) + } + + #[test] + fn set_onto_nil_when_array_given() { + let mut data = Value::Null; + let keys = [JsonPathStep::Index(0)]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data[0], json!("alpha")) + } + + #[test] + fn set_new_value_only_maps() { + let mut data = Value::Object(Default::default()); + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("b".to_string()), + JsonPathStep::Key("c".to_string()), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + assert_eq!(data["a"]["b"]["c"], json!("alpha")) + } + + #[test] + fn set_value_with_array() { + let mut data = Value::Object(Default::default()); + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("b".to_string()), + JsonPathStep::Index(0), + JsonPathStep::Key("c".to_string()), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data["a"]["b"][0]["c"], json!("alpha")) + } + + #[test] + fn set_value_with_array_padding() { + let mut data = Value::Object(Default::default()); + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("b".to_string()), + JsonPathStep::Index(3), + JsonPathStep::Key("c".to_string()), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data["a"]["b"][0]["c"], Value::Null); + assert_eq!(data["a"]["b"][1]["c"], Value::Null); + assert_eq!(data["a"]["b"][2]["c"], Value::Null); + assert_eq!(data["a"]["b"][3]["c"], json!("alpha")); + } + + #[test] + fn test_set_the_existing_path() { + let mut data = json!({ + "a": { + "b" : vec![ Value::Null, Value::Null] + } + }); + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("b".to_string()), + JsonPathStep::Index(1), + JsonPathStep::Key("c".to_string()), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data["a"]["b"][1]["c"], json!("alpha")); + } + + #[test] + fn set_the_existing_root_path() { + let mut data = json!({ + "a": {} + }); + let keys = [JsonPathStep::Key("a".to_string())]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data["a"], json!("alpha")); + } + + #[test] + fn errors_if_existing_path_has_different_types() { + let mut data = json!({ + "a": { + "b" : "some_string" + } + }); + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("b".to_string()), + JsonPathStep::Key("c".to_string()), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")) + .expect_err("error should be returned"); + } + + #[test] + fn replace_if_existing_object_has_different_type() { + let mut data = json!({ + "a": { + "b" : { "c": "bravo"} + } + }); + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("b".to_string()), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data["a"]["b"], json!("alpha")); + } + + #[test] + fn replace_if_existing_object_has_different_type_in_array() { + let mut data = json!({ "a": [json!("already_taken")] }); + let keys = [JsonPathStep::Key("a".to_string()), JsonPathStep::Index(0)]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + + assert_eq!(data["a"][0], json!("alpha")); + } + + #[test] + fn error_if_try_set_the_array_index_when_object_is_not_array() { + let mut data = json!({ "a": { + "not_array" : { + "c" :{}, + } + } }); + let keys = [ + JsonPathStep::Key("a".to_string()), + JsonPathStep::Key("not_array".to_string()), + JsonPathStep::Index(0), + ]; + + insert_with_parents(&mut data, &keys, json!("alpha")).expect_err("inserting error"); + } +} diff --git a/dpp/src/util/json_value.rs b/dpp/src/util/json_value/mod.rs similarity index 91% rename from dpp/src/util/json_value.rs rename to dpp/src/util/json_value/mod.rs index 5cb109b4..6d178184 100644 --- a/dpp/src/util/json_value.rs +++ b/dpp/src/util/json_value/mod.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeMap, convert::TryInto}; use anyhow::{anyhow, bail}; use log::trace; use serde::de::DeserializeOwned; -use serde_json::{Number, Value as JsonValue}; +use serde_json::{json, Number, Value as JsonValue}; use crate::util::deserializer; use crate::{ @@ -16,6 +16,9 @@ use super::{ string_encoding::Encoding, }; +mod insert_with_parents; +use insert_with_parents::*; + const PROPERTY_CONTENT_MEDIA_TYPE: &str = "contentMediaType"; const PROPERTY_PROTOCOL_VERSION: &str = "protocolVersion"; @@ -76,6 +79,10 @@ pub trait JsonValueExt { property_name: &str, protocol_bytes: &[u8], ) -> Result<(), ProtocolError>; + + /// Insert value under the path. Path is dot-separated string. i.e `properties[0].id`. If parents don't + /// exists they will be created + fn insert_with_parents(&mut self, path: &str, value: JsonValue) -> Result<(), anyhow::Error>; } impl JsonValueExt for JsonValue { @@ -271,14 +278,16 @@ impl JsonValueExt for JsonValue { for raw_path in paths { let mut to_replace = get_value_mut(raw_path, self); match to_replace { - Some(ref mut v) => replace_identifier(v, with).map_err(|err| { - anyhow!( - "unable replace the {:?} with {:?}: '{}'", - raw_path, - with, - err - ) - })?, + Some(ref mut v) => { + replace_identifier(v, with).map_err(|err| { + anyhow!( + "unable replace the {:?} with {:?}: '{}'", + raw_path, + with, + err + ) + })?; + } None => { trace!("path '{}' is not found, replacing to {:?} ", raw_path, with) } @@ -347,6 +356,17 @@ impl JsonValueExt for JsonValue { _ => bail!("the Json Value isn't a map: {:?}", self), } } + + /// Insert value under the path. Path is dot-separated string. i.e `properties[0].id` + fn insert_with_parents( + &mut self, + string_path: &str, + value: JsonValue, + ) -> Result<(), anyhow::Error> { + let path_literal: JsonPathLiteral = string_path.into(); + let path: JsonPath = path_literal.try_into().unwrap(); + insert_with_parents(self, &path, value) + } } /// replaces the Identifiers specified in binary_properties with Bytes or Base58 @@ -563,4 +583,31 @@ mod test { let result = identifiers_to(&binary_properties, &mut document, ReplaceWith::Bytes); assert_error_contains!(result, "Identifier must be 32 bytes long"); } + + #[test] + fn insert_with_parents() { + let mut document = json!({ + "root" : { + "from" : { + "id": "123", + "message": "text_message", + }, + } + }); + + document + .insert_with_parents("root.to.new_field", json!("new_value")) + .expect("no errors"); + document + .insert_with_parents("root.array[0].new_field", json!("new_value")) + .expect("no errors"); + + assert_eq!(document["root"]["from"]["id"], json!("123")); + assert_eq!(document["root"]["from"]["message"], json!("text_message")); + assert_eq!(document["root"]["to"]["new_field"], json!("new_value")); + assert_eq!( + document["root"]["array"][0]["new_field"], + json!("new_value") + ); + } } From bfcf51a2ce8a56fca7db64a2dc8c6f34ae16bb68 Mon Sep 17 00:00:00 2001 From: Pawel Iwan Date: Mon, 21 Nov 2022 09:31:06 +0100 Subject: [PATCH 2/5] remove todo --- dpp/src/document/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/dpp/src/document/mod.rs b/dpp/src/document/mod.rs index e025463a..e2edc78d 100644 --- a/dpp/src/document/mod.rs +++ b/dpp/src/document/mod.rs @@ -56,16 +56,6 @@ pub struct Document { } impl Document { - pub fn from_json( - mut json_object: JsonValue, - data_contract: DataContract, - ) -> Result { - json_object.replace_identifier_paths(IDENTIFIER_FIELDS, ReplaceWith::Bytes); - // now we need to get the all informations related to the - - todo!() - } - pub fn from_raw_document( mut raw_document: JsonValue, data_contract: DataContract, From 3d80e4018752f72a20248f69e1130244a33bf27a Mon Sep 17 00:00:00 2001 From: Pawel Iwan Date: Mon, 21 Nov 2022 09:48:39 +0100 Subject: [PATCH 3/5] setter and getter for data --- dpp/src/document/mod.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/dpp/src/document/mod.rs b/dpp/src/document/mod.rs index e2edc78d..939546d5 100644 --- a/dpp/src/document/mod.rs +++ b/dpp/src/document/mod.rs @@ -181,7 +181,10 @@ impl Document { Ok(hash(self.to_buffer()?)) } - pub fn set_value(&mut self, path: &str, value: JsonValue) -> Result<(), ProtocolError> { + /// Set the value under given path. + /// The path supports syntax from `lodash` JS lib. Example: "root.people[0].name". + /// If parents are not present they will be automatically created + pub fn set(&mut self, path: &str, value: JsonValue) -> Result<(), ProtocolError> { Ok(self.data.insert_with_parents(path, value)?) } @@ -192,6 +195,16 @@ impl Document { Err(_) => None, } } + + /// Get the Document's data + pub fn get_data(&self) -> &JsonValue { + &self.data + } + + /// Set the Document's data + pub fn set_data(&mut self, data: JsonValue) { + self.data = data; + } } #[cfg(test)] From 944ecbd02ae09907a1eac3251f7ccbbe270945e1 Mon Sep 17 00:00:00 2001 From: Pawel Iwan Date: Tue, 22 Nov 2022 13:52:26 +0100 Subject: [PATCH 4/5] adress the comments --- dpp/src/document/mod.rs | 2 +- ...rt_with_parents.rs => insert_with_path.rs} | 25 +++++++++---------- dpp/src/util/json_value/mod.rs | 14 +++++------ 3 files changed, 20 insertions(+), 21 deletions(-) rename dpp/src/util/json_value/{insert_with_parents.rs => insert_with_path.rs} (88%) diff --git a/dpp/src/document/mod.rs b/dpp/src/document/mod.rs index 939546d5..fc06448c 100644 --- a/dpp/src/document/mod.rs +++ b/dpp/src/document/mod.rs @@ -185,7 +185,7 @@ impl Document { /// The path supports syntax from `lodash` JS lib. Example: "root.people[0].name". /// If parents are not present they will be automatically created pub fn set(&mut self, path: &str, value: JsonValue) -> Result<(), ProtocolError> { - Ok(self.data.insert_with_parents(path, value)?) + Ok(self.data.insert_with_path(path, value)?) } /// Retrieves field specified by path diff --git a/dpp/src/util/json_value/insert_with_parents.rs b/dpp/src/util/json_value/insert_with_path.rs similarity index 88% rename from dpp/src/util/json_value/insert_with_parents.rs rename to dpp/src/util/json_value/insert_with_path.rs index fa725593..b3684adb 100644 --- a/dpp/src/util/json_value/insert_with_parents.rs +++ b/dpp/src/util/json_value/insert_with_path.rs @@ -5,7 +5,7 @@ use serde_json::Value; /// Inserts the value specified by the json path. If intermediate object doesn't exist, crates a one. /// If `Value::Null` is encountered while traversing the path, they are replaced with the required structure. -pub(super) fn insert_with_parents( +pub(super) fn insert_with_path( data: &mut Value, json_path: &[JsonPathStep], value: Value, @@ -114,7 +114,7 @@ mod test_set { JsonPathStep::Key("c".to_string()), ]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"]["b"]["c"], json!("alpha")) } @@ -124,7 +124,7 @@ mod test_set { let mut data = Value::Null; let keys = [JsonPathStep::Index(0)]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data[0], json!("alpha")) } @@ -138,7 +138,7 @@ mod test_set { JsonPathStep::Key("c".to_string()), ]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"]["b"]["c"], json!("alpha")) } @@ -152,7 +152,7 @@ mod test_set { JsonPathStep::Key("c".to_string()), ]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"]["b"][0]["c"], json!("alpha")) } @@ -167,7 +167,7 @@ mod test_set { JsonPathStep::Key("c".to_string()), ]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"]["b"][0]["c"], Value::Null); assert_eq!(data["a"]["b"][1]["c"], Value::Null); @@ -189,7 +189,7 @@ mod test_set { JsonPathStep::Key("c".to_string()), ]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"]["b"][1]["c"], json!("alpha")); } @@ -201,7 +201,7 @@ mod test_set { }); let keys = [JsonPathStep::Key("a".to_string())]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"], json!("alpha")); } @@ -219,8 +219,7 @@ mod test_set { JsonPathStep::Key("c".to_string()), ]; - insert_with_parents(&mut data, &keys, json!("alpha")) - .expect_err("error should be returned"); + insert_with_path(&mut data, &keys, json!("alpha")).expect_err("error should be returned"); } #[test] @@ -235,7 +234,7 @@ mod test_set { JsonPathStep::Key("b".to_string()), ]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"]["b"], json!("alpha")); } @@ -245,7 +244,7 @@ mod test_set { let mut data = json!({ "a": [json!("already_taken")] }); let keys = [JsonPathStep::Key("a".to_string()), JsonPathStep::Index(0)]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect("no errors"); + insert_with_path(&mut data, &keys, json!("alpha")).expect("no errors"); assert_eq!(data["a"][0], json!("alpha")); } @@ -263,6 +262,6 @@ mod test_set { JsonPathStep::Index(0), ]; - insert_with_parents(&mut data, &keys, json!("alpha")).expect_err("inserting error"); + insert_with_path(&mut data, &keys, json!("alpha")).expect_err("inserting error"); } } diff --git a/dpp/src/util/json_value/mod.rs b/dpp/src/util/json_value/mod.rs index 6d178184..ba1d9c0b 100644 --- a/dpp/src/util/json_value/mod.rs +++ b/dpp/src/util/json_value/mod.rs @@ -16,8 +16,8 @@ use super::{ string_encoding::Encoding, }; -mod insert_with_parents; -use insert_with_parents::*; +mod insert_with_path; +use insert_with_path::*; const PROPERTY_CONTENT_MEDIA_TYPE: &str = "contentMediaType"; const PROPERTY_PROTOCOL_VERSION: &str = "protocolVersion"; @@ -82,7 +82,7 @@ pub trait JsonValueExt { /// Insert value under the path. Path is dot-separated string. i.e `properties[0].id`. If parents don't /// exists they will be created - fn insert_with_parents(&mut self, path: &str, value: JsonValue) -> Result<(), anyhow::Error>; + fn insert_with_path(&mut self, path: &str, value: JsonValue) -> Result<(), anyhow::Error>; } impl JsonValueExt for JsonValue { @@ -358,14 +358,14 @@ impl JsonValueExt for JsonValue { } /// Insert value under the path. Path is dot-separated string. i.e `properties[0].id` - fn insert_with_parents( + fn insert_with_path( &mut self, string_path: &str, value: JsonValue, ) -> Result<(), anyhow::Error> { let path_literal: JsonPathLiteral = string_path.into(); let path: JsonPath = path_literal.try_into().unwrap(); - insert_with_parents(self, &path, value) + insert_with_path(self, &path, value) } } @@ -596,10 +596,10 @@ mod test { }); document - .insert_with_parents("root.to.new_field", json!("new_value")) + .insert_with_path("root.to.new_field", json!("new_value")) .expect("no errors"); document - .insert_with_parents("root.array[0].new_field", json!("new_value")) + .insert_with_path("root.array[0].new_field", json!("new_value")) .expect("no errors"); assert_eq!(document["root"]["from"]["id"], json!("123")); From c2b4ab2633ea3f931a3800ec395557d05e2b4f93 Mon Sep 17 00:00:00 2001 From: Pawel Iwan Date: Tue, 22 Nov 2022 16:23:28 +0100 Subject: [PATCH 5/5] fix test in drive --- drive/src/drive/document/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drive/src/drive/document/update.rs b/drive/src/drive/document/update.rs index 230badc5..24b3d728 100644 --- a/drive/src/drive/document/update.rs +++ b/drive/src/drive/document/update.rs @@ -2299,7 +2299,7 @@ mod tests { // Update the document in a second document - .set_value("name", Value::String("Ivaaaaaaaaaan!".to_string())) + .set("name", Value::String("Ivaaaaaaaaaan!".to_string())) .expect("should change name"); let document_cbor = document.to_cbor().expect("should encode to cbor");