Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions crates/json-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
unreachable_patterns
)]

pub mod base64_serde;
use std::fmt::Display;

use serde_json::Value as JValue;
use std::error::Error;

pub mod base64_serde;

pub fn into_string(v: JValue) -> Option<String> {
if let JValue::String(s) = v {
Expand All @@ -47,6 +48,6 @@ pub fn into_array(v: JValue) -> Option<Vec<JValue>> {
}

/// Converts an error into IValue::String
pub fn err_as_value<E: Error>(err: E) -> JValue {
pub fn err_as_value<E: Display>(err: E) -> JValue {
JValue::String(format!("Error: {}", err))
}
11 changes: 0 additions & 11 deletions crates/local-vm/src/local_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,22 +314,11 @@ pub fn read_args(
let AVMOutcome {
data,
call_requests,
next_peer_pks,
..
} = local_vm
.call(&particle.script, particle_data, params, call_results)
.expect("execute & make particle");

println!(
"call_requests: {:?} next_peer_pks: {:?}",
call_requests, next_peer_pks
);

println!(
"data: {}",
serde_json::from_slice::<JValue>(data.as_slice()).unwrap()
);

particle_data = data;
call_results = <_>::default();

Expand Down
2 changes: 2 additions & 0 deletions particle-builtins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ itertools = "0.10.5"
bytesize = "1.1.0"
derivative = "2.2.0"

eyre = { workspace = true }

[dev-dependencies]
proptest = "1.0.0"
tempfile = "3.3.0"
66 changes: 10 additions & 56 deletions particle-builtins/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use humantime_serde::re::humantime::format_duration as pretty;
use libp2p::{core::Multiaddr, kad::kbucket::Key, kad::K_VALUE, PeerId};
use multihash::{Code, MultihashDigest, MultihashGeneric};
use parking_lot::{Mutex, RwLock};
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use serde_json::{json, Value as JValue};
use JValue::Array;

Expand All @@ -51,8 +51,10 @@ use server_config::ServicesConfig;
use crate::debug::fmt_custom_services;
use crate::error::HostClosureCallError;
use crate::error::HostClosureCallError::{DecodeBase58, DecodeUTF8};
use crate::func::{binary, unary};
use crate::identify::NodeInfo;
use crate::math;
use crate::outcome::{ok, wrap, wrap_unit};
use crate::{json, math};

#[derive(Derivative)]
#[derivative(Debug)]
Expand Down Expand Up @@ -236,6 +238,12 @@ where
("sig", "verify") => wrap(self.verify(args)),
("sig", "get_peer_id") => wrap(self.get_peer_id()),

("json", "obj") => wrap(json::obj(args)),
("json", "put") => wrap(json::put(args)),
("json", "puts") => wrap(json::puts(args)),
("json", "parse") => unary(args, |s: String| -> R<JValue, _> { json::parse(&s) }),
("json", "stringify") => unary(args, |v: JValue| -> R<String, _> { Ok(json::stringify(v)) }),

_ => FunctionOutcome::NotDefined { args, params: particle },
}
}
Expand Down Expand Up @@ -910,24 +918,6 @@ fn make_module_config(args: Args) -> Result<JValue, JError> {
Ok(config)
}

fn ok(v: JValue) -> FunctionOutcome {
FunctionOutcome::Ok(v)
}

fn wrap(r: Result<JValue, JError>) -> FunctionOutcome {
match r {
Ok(v) => FunctionOutcome::Ok(v),
Err(err) => FunctionOutcome::Err(err),
}
}

fn wrap_unit(r: Result<(), JError>) -> FunctionOutcome {
match r {
Ok(_) => FunctionOutcome::Empty,
Err(err) => FunctionOutcome::Err(err),
}
}

fn parse_from_str<T>(
field: &'static str,
mut args: &mut impl Iterator<Item = JValue>,
Expand Down Expand Up @@ -982,42 +972,6 @@ fn get_delay(delay: Option<Duration>, interval: Option<Duration>) -> Duration {
}
}

fn unary<X, Out, F>(args: Args, f: F) -> FunctionOutcome
where
X: for<'de> Deserialize<'de>,
Out: Serialize,
F: Fn(X) -> Result<Out, JError>,
{
if args.function_args.len() != 1 {
let err = format!("expected 1 arguments, got {}", args.function_args.len());
return FunctionOutcome::Err(JError::new(err));
}
let mut args = args.function_args.into_iter();

let x: X = Args::next("x", &mut args)?;
let out = f(x)?;
FunctionOutcome::Ok(json!(out))
}

fn binary<X, Y, Out, F>(args: Args, f: F) -> FunctionOutcome
where
X: for<'de> Deserialize<'de>,
Y: for<'de> Deserialize<'de>,
Out: Serialize,
F: Fn(X, Y) -> Result<Out, JError>,
{
if args.function_args.len() != 2 {
let err = format!("expected 2 arguments, got {}", args.function_args.len());
return FunctionOutcome::Err(JError::new(err));
}
let mut args = args.function_args.into_iter();

let x: X = Args::next("x", &mut args)?;
let y: Y = Args::next("y", &mut args)?;
let out = f(x, y)?;
FunctionOutcome::Ok(json!(out))
}

#[derive(thiserror::Error, Debug)]
enum ResolveVaultError {
#[error("Incorrect vault path `{1}`: doesn't belong to vault (`{2}`)")]
Expand Down
41 changes: 41 additions & 0 deletions particle-builtins/src/func.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use serde::{Deserialize, Serialize};
use serde_json::json;

use particle_args::{Args, JError};
use particle_execution::FunctionOutcome;

pub fn unary<X, Out, F>(args: Args, f: F) -> FunctionOutcome
where
X: for<'de> Deserialize<'de>,
Out: Serialize,
F: Fn(X) -> Result<Out, JError>,
{
if args.function_args.len() != 1 {
let err = format!("expected 1 arguments, got {}", args.function_args.len());
return FunctionOutcome::Err(JError::new(err));
}
let mut args = args.function_args.into_iter();

let x: X = Args::next("x", &mut args)?;
let out = f(x)?;
FunctionOutcome::Ok(json!(out))
}

pub fn binary<X, Y, Out, F>(args: Args, f: F) -> FunctionOutcome
where
X: for<'de> Deserialize<'de>,
Y: for<'de> Deserialize<'de>,
Out: Serialize,
F: Fn(X, Y) -> Result<Out, JError>,
{
if args.function_args.len() != 2 {
let err = format!("expected 2 arguments, got {}", args.function_args.len());
return FunctionOutcome::Err(JError::new(err));
}
let mut args = args.function_args.into_iter();

let x: X = Args::next("x", &mut args)?;
let y: Y = Args::next("y", &mut args)?;
let out = f(x, y)?;
FunctionOutcome::Ok(json!(out))
}
65 changes: 65 additions & 0 deletions particle-builtins/src/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use eyre::eyre;
use particle_args::{Args, JError};
use serde_json::Value as JValue;

fn insert_pairs(
mut object: serde_json::Map<String, JValue>,
args: &mut impl Iterator<Item = JValue>,
) -> Result<serde_json::Map<String, JValue>, JError> {
loop {
match (args.next(), args.next()) {
(Some(JValue::String(name)), Some(value)) => { object.insert(name, value); },
(Some(key), None) => return Err(JError::new(eyre!(
"Expected odd number of arguments, got even. No value for key '{}'",
key
).to_string())),
(Some(key), Some(value)) => return Err(JError::new(eyre!(
"All keys must be of type string. Key of the following pair is of invalid type: ({}, {})",
key,
value
).to_string())),
(None, _) => break,
}
}

Ok(object)
}

/// Constructs a JSON object from a list of key value pairs.
pub fn obj(args: Args) -> Result<JValue, JError> {
let mut args = args.function_args.into_iter();

let object = insert_pairs(<_>::default(), &mut args)?;

Ok(JValue::Object(object))
}

/// Inserts a value into a JSON object
pub fn put(args: Args) -> Result<JValue, JError> {
let mut args = args.function_args.into_iter();
let mut object: serde_json::Map<String, JValue> = Args::next("object", &mut args)?;
let key = Args::next("key", &mut args)?;
let value = Args::next("value", &mut args)?;

object.insert(key, value);

Ok(JValue::Object(object))
}

/// Inserts list of key value pairs into an object.
pub fn puts(args: Args) -> Result<JValue, JError> {
let mut args = args.function_args.into_iter();
let object = Args::next("object", &mut args)?;

let object = insert_pairs(object, &mut args)?;

Ok(JValue::Object(object))
}

pub fn parse(json: &str) -> Result<JValue, JError> {
serde_json::from_str(json).map_err(Into::into)
}

pub fn stringify(value: JValue) -> String {
value.to_string()
}
3 changes: 3 additions & 0 deletions particle-builtins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub use identify::NodeInfo;
mod builtins;
mod debug;
mod error;
mod func;
mod identify;
mod json;
mod math;
mod outcome;
mod particle_function;
21 changes: 21 additions & 0 deletions particle-builtins/src/outcome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use particle_args::JError;
use particle_execution::FunctionOutcome;
use serde_json::Value as JValue;

pub fn ok(v: JValue) -> FunctionOutcome {
FunctionOutcome::Ok(v)
}

pub fn wrap(r: Result<JValue, JError>) -> FunctionOutcome {
match r {
Ok(v) => FunctionOutcome::Ok(v),
Err(err) => FunctionOutcome::Err(err),
}
}

pub fn wrap_unit(r: Result<(), JError>) -> FunctionOutcome {
match r {
Ok(_) => FunctionOutcome::Empty,
Err(err) => FunctionOutcome::Err(err),
}
}
52 changes: 52 additions & 0 deletions particle-node/tests/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,58 @@ fn sig_verify_invalid_signature() {
}
}

#[test]
fn json_builtins() {
let result = exec_script(
r#"
(seq
(seq
;; create
(seq
(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
;; 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)
)
;; stringify and parse
(seq
(call relay ("json" "stringify") [outer_first] outer_first_string)
(call relay ("json" "parse") [outer_first_string] outer_first_parsed)
)
)
)
"#,
hashmap! {},
r"nested_first nested_second outer_first outer_second outer_first_string outer_first_parsed",
1,
).expect("execute script");

if let [nested_first, nested_second, outer_first, outer_second, outer_first_string, outer_first_parsed] =
result.as_slice()
{
let nf_expected = json!({"name": "nested_first", "num": 1});
let ns_expected = json!({"name": "nested_second", "num": 2});

let of_expected = json!({"name": "outer_first", "num": 0, "nested": nf_expected});
let os_expected = json!({"name": "outer_second", "num": 3, "nested": ns_expected });

assert_eq!(&nf_expected, nested_first);
assert_eq!(&ns_expected, nested_second);
assert_eq!(&of_expected, outer_first);
assert_eq!(&os_expected, outer_second);
assert_eq!(&of_expected.to_string(), outer_first_string);
assert_eq!(&of_expected, outer_first_parsed);
} else {
panic!("Result is of incorrect shape: {:?}", result);
}
}

fn binary(
service: &str,
func: &str,
Expand Down
5 changes: 1 addition & 4 deletions particle-node/tests/client_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@
use std::time::Duration;

use async_std::task::block_on;
use eyre::WrapErr;
use futures::channel::oneshot::channel;
use futures::future::BoxFuture;
use futures::FutureExt;
use maplit::hashmap;
use serde_json::json;
use serde_json::Value as JValue;

use connected_client::ConnectedClient;
use created_swarm::{make_swarms, CreatedSwarm};
use created_swarm::make_swarms;
use now_millis::now_ms;
use particle_execution::FunctionOutcome;
use particle_protocol::Particle;
Expand Down
1 change: 0 additions & 1 deletion particle-node/tests/network/loop_topology.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use serde_json::Value as JValue;

use connected_client::ConnectedClient;
use created_swarm::{add_print, make_swarms, CreatedSwarm};
use log_utils::enable_logs;

use super::join_stream;

Expand Down