From 3539f677308b833b2a9897d50a660b5ce038ad98 Mon Sep 17 00:00:00 2001 From: folex <0xdxdy@gmail.com> Date: Thu, 2 Feb 2023 14:57:00 +0700 Subject: [PATCH] feat(builtins): json.obj_pairs, json.puts_pairs to build JSON objects from array of pairs (#1434) * feat(builtins): impl json obj_array & puts_array * feat(builtins): obj_array => obj_pairs * feat(builtins): fix puts_pairs * feat(builtins): log run-console logs to debug * chore: fix formatting * feat!(errors): format errors via Debug * feat!(errors): format errors via Display AND Debug * chore: fix tests * chore: add tests on json obj_pairs puts_pairs --- crates/json-utils/src/lib.rs | 6 +- crates/libp2p/src/serde.rs | 110 +----------------- crates/log-utils/src/lib.rs | 4 +- crates/particle-args/src/args_error.rs | 5 + crates/particle-node-tests/tests/builtin.rs | 96 ++++++++++++++- .../tests/script_storage.rs | 5 +- crates/particle-node-tests/tests/spells.rs | 29 +++-- particle-builtins/src/builtins.rs | 4 + particle-builtins/src/json.rs | 43 ++++++- 9 files changed, 166 insertions(+), 136 deletions(-) diff --git a/crates/json-utils/src/lib.rs b/crates/json-utils/src/lib.rs index 2765e37709..89f027348b 100644 --- a/crates/json-utils/src/lib.rs +++ b/crates/json-utils/src/lib.rs @@ -25,8 +25,6 @@ unreachable_patterns )] -use std::fmt::Display; - use serde_json::Value as JValue; pub mod base64_serde; @@ -48,6 +46,6 @@ pub fn into_array(v: JValue) -> Option> { } /// Converts an error into IValue::String -pub fn err_as_value(err: E) -> JValue { - JValue::String(format!("Error: {err}")) +pub fn err_as_value(err: E) -> JValue { + JValue::String(format!("Error: {err}\n{err:?}")) } diff --git a/crates/libp2p/src/serde.rs b/crates/libp2p/src/serde.rs index abfc883a9e..0ac1c7c43b 100644 --- a/crates/libp2p/src/serde.rs +++ b/crates/libp2p/src/serde.rs @@ -39,75 +39,10 @@ pub mod peerid_serializer { } } -// waiting for https://github.com/serde-rs/serde/issues/723 that will make UX better -pub mod provider_serializer { - use libp2p::{core::Multiaddr, PeerId}; - use serde::{ - de::{SeqAccess, Visitor}, - ser::SerializeSeq, - Deserializer, Serializer, - }; - use std::{fmt, str::FromStr}; - - pub fn serialize(value: &[(Multiaddr, PeerId)], serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(2 * value.len()))?; - for (multiaddr, peerid) in value { - seq.serialize_element(multiaddr)?; - seq.serialize_element(&peerid.to_base58())?; - } - seq.end() - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - struct VecVisitor; - impl<'de> Visitor<'de> for VecVisitor { - type Value = Vec<(Multiaddr, PeerId)>; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("[Multiaddr, PeerId]") - } - - fn visit_seq>(self, mut seq: A) -> Result { - let mut vec = Vec::new(); - - let mut raw_multiaddr = seq.next_element::()?; - while raw_multiaddr.is_some() { - let multiaddr = raw_multiaddr.unwrap(); - let peer_id_bytes = seq.next_element::()?; - - if let Some(peer_id) = peer_id_bytes { - let peer_id = PeerId::from_str(&peer_id).map_err(|e| { - serde::de::Error::custom(format!( - "peer id deserialization failed for {e:?}" - )) - })?; - vec.push((multiaddr, peer_id)); - } else { - // Multiaddr deserialization's been successfull, but PeerId hasn't - return a error - return Err(serde::de::Error::custom("failed to deserialize PeerId")); - } - - raw_multiaddr = seq.next_element::()?; - } - - Ok(vec) - } - } - - deserializer.deserialize_seq(VecVisitor {}) - } -} - #[cfg(test)] mod tests { use crate::RandomPeerId; - use libp2p::{core::Multiaddr, PeerId}; + use libp2p::PeerId; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -180,47 +115,4 @@ mod tests { ); assert_eq!(deserialized_test.unwrap(), test); } - - #[test] - fn providers() { - use super::provider_serializer; - use std::net::Ipv4Addr; - - #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] - struct Test { - #[serde(with = "provider_serializer")] - providers: Vec<(Multiaddr, PeerId)>, - } - - let mut providers = Vec::new(); - let mut test_peer_ids = Vec::new(); - providers.push(( - Multiaddr::from(Ipv4Addr::new(0, 0, 0, 0)), - PeerId::from_str("QmY28NSCefB532XbERtnKHadexGuNzAfYnh5fJk6qhLsSi").unwrap(), - )); - - for i in 1..=255 { - let peer_id = RandomPeerId::random(); - - providers.push((Multiaddr::from(Ipv4Addr::new(i, i, i, i)), peer_id)); - test_peer_ids.push(peer_id); - } - - let test = Test { providers }; - - let serialized_test = serde_json::to_value(test.clone()); - assert!( - serialized_test.is_ok(), - "failed to serialize test struct: {}", - serialized_test.err().unwrap() - ); - - let deserialized_test = serde_json::from_value::(serialized_test.unwrap()); - assert!( - deserialized_test.is_ok(), - "failed to deserialize test struct: {}", - deserialized_test.err().unwrap() - ); - assert_eq!(deserialized_test.unwrap(), test); - } } diff --git a/crates/log-utils/src/lib.rs b/crates/log-utils/src/lib.rs index 337319deab..9e1e2277a5 100644 --- a/crates/log-utils/src/lib.rs +++ b/crates/log-utils/src/lib.rs @@ -19,11 +19,11 @@ pub fn enable_logs() { use log::LevelFilter::*; - std::env::set_var("WASM_LOG", "trace"); + std::env::set_var("WASM_LOG", "info"); env_logger::builder() .format_timestamp_millis() - .filter_level(log::LevelFilter::Off) + .filter_level(log::LevelFilter::Info) .filter(Some("script_storage"), Trace) .filter(Some("script_storage"), Trace) .filter(Some("sorcerer"), Trace) diff --git a/crates/particle-args/src/args_error.rs b/crates/particle-args/src/args_error.rs index 7cb68a436c..7ece26310c 100644 --- a/crates/particle-args/src/args_error.rs +++ b/crates/particle-args/src/args_error.rs @@ -16,6 +16,7 @@ use json_utils::err_as_value; +use eyre::Report; use serde_json::{json, Value as JValue}; use std::borrow::Cow; use std::fmt::{Display, Formatter}; @@ -66,6 +67,10 @@ impl JError { pub fn new(msg: impl AsRef) -> Self { Self(json!(msg.as_ref())) } + + pub fn from_eyre(err: Report) -> Self { + JError(err_as_value(err)) + } } impl From for JValue { diff --git a/crates/particle-node-tests/tests/builtin.rs b/crates/particle-node-tests/tests/builtin.rs index 2fe69807e7..bad405c90b 100644 --- a/crates/particle-node-tests/tests/builtin.rs +++ b/crates/particle-node-tests/tests/builtin.rs @@ -1502,13 +1502,97 @@ fn json_builtins() { (call relay ("json" "obj") ["name" "nested_first" "num" 1] nested_first) (call relay ("json" "obj") ["name" "nested_second" "num" 2] nested_second) ) - (call relay ("json" "obj") ["name" "outer_first" "num" 0 "nested" nested_first] outer_first) + (seq + (call relay ("json" "obj") ["name" "outer_first" "num" 0 "nested" nested_first] outer_first) + (seq + (seq + (seq + (new $single-pair + (seq + (seq + (ap "name" $single-pair) + (ap "outer_first" $single-pair) + ) + (seq + (canon relay $single-pair #single-pair-1) + (ap #single-pair-1 $pairs) + ) + ) + ) + (new $single-pair + (seq + (seq + (ap "num" $single-pair) + (ap 0 $single-pair) + ) + (seq + (canon relay $single-pair #single-pair-2) + (ap #single-pair-2 $pairs) + ) + ) + ) + ) + (seq + (new $single-pair + (seq + (seq + (ap "nested" $single-pair) + (ap nested_first $single-pair) + ) + (seq + (canon relay $single-pair #single-pair-3) + (ap #single-pair-3 $pairs) + ) + ) + ) + (canon relay $pairs #pairs) + ) + ) + (call relay ("json" "obj_pairs") [#pairs] outer_first_pairs) + ) + ) ) (seq ;; modify (seq - (call relay ("json" "put") [outer_first "nested" nested_second] outer_tmp_second) - (call relay ("json" "puts") [outer_tmp_second "name" "outer_second" "num" 3] outer_second) + (seq + (call relay ("json" "put") [outer_first "nested" nested_second] outer_tmp_second) + (call relay ("json" "puts") [outer_tmp_second "name" "outer_second" "num" 3] outer_second) + ) + (new $puts-pairs + (seq + (seq + (new $single-pair + (seq + (seq + (ap "name" $single-pair) + (ap "outer_second" $single-pair) + ) + (seq + (canon relay $single-pair #single-pair-4) + (ap #single-pair-4 $puts-pairs) + ) + ) + ) + (new $single-pair + (seq + (seq + (ap "num" $single-pair) + (ap 3 $single-pair) + ) + (seq + (canon relay $single-pair #single-pair-5) + (ap #single-pair-5 $puts-pairs) + ) + ) + ) + ) + (seq + (canon relay $puts-pairs #puts-pairs) + (call relay ("json" "puts_pairs") [outer_tmp_second #puts-pairs] outer_second_pairs) + ) + ) + ) ) ;; stringify and parse (seq @@ -1519,11 +1603,11 @@ fn json_builtins() { ) "#, hashmap! {}, - r"nested_first nested_second outer_first outer_second outer_first_string outer_first_parsed", + r"nested_first nested_second outer_first outer_second outer_first_string outer_first_parsed outer_first_pairs outer_second_pairs", 1, ).expect("execute script"); - if let [nested_first, nested_second, outer_first, outer_second, outer_first_string, outer_first_parsed] = + if let [nested_first, nested_second, outer_first, outer_second, outer_first_string, outer_first_parsed, outer_first_pairs, outer_second_pairs] = result.as_slice() { let nf_expected = json!({"name": "nested_first", "num": 1}); @@ -1535,7 +1619,9 @@ fn json_builtins() { assert_eq!(&nf_expected, nested_first); assert_eq!(&ns_expected, nested_second); assert_eq!(&of_expected, outer_first); + assert_eq!(&of_expected, outer_first_pairs); assert_eq!(&os_expected, outer_second); + assert_eq!(&os_expected, outer_second_pairs); assert_eq!(&of_expected.to_string(), outer_first_string); assert_eq!(&of_expected, outer_first_parsed); } else { diff --git a/crates/particle-node-tests/tests/script_storage.rs b/crates/particle-node-tests/tests/script_storage.rs index a39dabd020..d15bc66370 100644 --- a/crates/particle-node-tests/tests/script_storage.rs +++ b/crates/particle-node-tests/tests/script_storage.rs @@ -693,6 +693,9 @@ fn add_script_from_vault_wrong_vault() { .as_slice() { let expected_error_prefix = "Local service error, ret_code is 1, error message is '\"Error: Incorrect vault path `/tmp/vault/another-particle-id/script"; - assert!(error_msg.starts_with(expected_error_prefix)); + assert!( + error_msg.starts_with(expected_error_prefix), + "expected:\n{expected_error_prefix}\ngot:\n{error_msg}" + ); } } diff --git a/crates/particle-node-tests/tests/spells.rs b/crates/particle-node-tests/tests/spells.rs index dc01883693..7b1cba0408 100644 --- a/crates/particle-node-tests/tests/spells.rs +++ b/crates/particle-node-tests/tests/spells.rs @@ -25,6 +25,7 @@ use serde_json::{json, Value as JValue}; use connected_client::ConnectedClient; use created_swarm::make_swarms; use fluence_spell_dtos::trigger_config::TriggerConfig; +use log_utils::enable_logs; use service_modules::load_module; use spell_event_bus::api::{TriggerInfo, TriggerInfoAqua, MAX_PERIOD_SEC}; use test_utils::create_service; @@ -414,8 +415,11 @@ fn spell_install_fail_end_sec_past() { .unwrap() .as_slice() { - let msg = "Local service error, ret_code is 1, error message is '\"Error: invalid config: end_sec is less than start_sec or in the past\"'"; - assert!(error_msg.starts_with(msg)); + let expected = "Local service error, ret_code is 1, error message is '\"Error: invalid config: end_sec is less than start_sec or in the past"; + assert!( + error_msg.starts_with(expected), + "expected:\n{expected}\ngot:\n{error_msg}" + ); } } @@ -423,6 +427,8 @@ fn spell_install_fail_end_sec_past() { // In this case we don't schedule a spell and return error. #[test] fn spell_install_fail_end_sec_before_start() { + enable_logs(); + let swarms = make_swarms(1); let mut client = ConnectedClient::connect_to(swarms[0].multiaddr.clone()) .wrap_err("connect client") @@ -463,8 +469,11 @@ fn spell_install_fail_end_sec_before_start() { .unwrap() .as_slice() { - let msg = "Local service error, ret_code is 1, error message is '\"Error: invalid config: end_sec is less than start_sec or in the past\"'"; - assert!(error_msg.starts_with(msg)); + let expected = "Local service error, ret_code is 1, error message is '\"Error: invalid config: end_sec is less than start_sec or in the past"; + assert!( + error_msg.starts_with(expected), + "expected:\n{expected}\ngot:\n{error_msg}" + ); } } @@ -601,10 +610,10 @@ fn spell_remove_spell_as_service() { .unwrap() .as_slice() { - let msg_end = "cannot call function 'remove_service': cannot remove a spell\"'"; + let expected = "cannot call function 'remove_service': cannot remove a spell"; assert!( - msg.ends_with(msg_end), - "should end with `{msg_end}`, given msg `{msg}`" + msg.contains(expected), + "should contain `{expected}`, given msg `{msg}`" ); } } @@ -644,10 +653,10 @@ fn spell_remove_service_as_spell() { .unwrap() .as_slice() { - let msg_end = "cannot call function 'remove_spell': the service isn't a spell\"'"; + let expected = "cannot call function 'remove_spell': the service isn't a spell"; assert!( - msg.ends_with(msg_end), - "should end with `{msg_end}`, given msg `{msg}`" + msg.contains(expected), + "should contain `{expected}`, given msg `{msg}`" ); } } diff --git a/particle-builtins/src/builtins.rs b/particle-builtins/src/builtins.rs index 70155608f5..36939b003a 100644 --- a/particle-builtins/src/builtins.rs +++ b/particle-builtins/src/builtins.rs @@ -255,6 +255,10 @@ where ("json", "puts") => wrap(json::puts(args)), ("json", "parse") => unary(args, |s: String| -> R { json::parse(&s) }), ("json", "stringify") => unary(args, |v: JValue| -> R { Ok(json::stringify(v)) }), + ("json", "obj_pairs") => unary(args, |vs: Vec<(String, JValue)>| -> R { json::obj_from_pairs(vs) }), + ("json", "puts_pairs") => binary(args, |obj: JValue, vs: Vec<(String, JValue)>| -> R { json::puts_from_pairs(obj, vs) }), + + ("run-console", "print") => wrap_unit(Ok(log::debug!(target: "run-console", "{}", json!(args.function_args)))), _ => FunctionOutcome::NotDefined { args, params: particle }, } diff --git a/particle-builtins/src/json.rs b/particle-builtins/src/json.rs index 54ce9c5e4a..77715c423e 100644 --- a/particle-builtins/src/json.rs +++ b/particle-builtins/src/json.rs @@ -1,8 +1,8 @@ -use eyre::eyre; +use eyre::{eyre, Context}; use particle_args::{Args, JError}; use serde_json::Value as JValue; -fn insert_pairs( +fn obj_from_iter( mut object: serde_json::Map, args: &mut impl Iterator, ) -> Result, JError> { @@ -29,11 +29,26 @@ fn insert_pairs( pub fn obj(args: Args) -> Result { let mut args = args.function_args.into_iter(); - let object = insert_pairs(<_>::default(), &mut args)?; + let object = obj_from_iter(<_>::default(), &mut args)?; Ok(JValue::Object(object)) } +/// Constructs a JSON object from a list of key value pairs. +pub fn obj_from_pairs( + values: impl IntoIterator, +) -> Result { + let map = values.into_iter().fold( + >::default(), + |mut acc, (k, v)| { + acc.insert(k, v); + acc + }, + ); + + Ok(JValue::Object(map)) +} + /// Inserts a value into a JSON object pub fn put(args: Args) -> Result { let mut args = args.function_args.into_iter(); @@ -51,13 +66,31 @@ pub fn puts(args: Args) -> Result { let mut args = args.function_args.into_iter(); let object = Args::next("object", &mut args)?; - let object = insert_pairs(object, &mut args)?; + let object = obj_from_iter(object, &mut args)?; Ok(JValue::Object(object)) } +/// Inserts list of key value pairs into an object. +pub fn puts_from_pairs( + object: JValue, + values: impl IntoIterator, +) -> Result { + if let JValue::Object(map) = object.clone() { + let map = values.into_iter().fold(map, |mut acc, (k, v)| { + acc.insert(k, v); + acc + }); + Ok(JValue::Object(map)) + } else { + Err(JError::new(format!("expected json object, got {object}"))) + } +} + pub fn parse(json: &str) -> Result { - serde_json::from_str(json).map_err(Into::into) + serde_json::from_str(json) + .context(format!("error parsing json {json}")) + .map_err(JError::from_eyre) } pub fn stringify(value: JValue) -> String {