Skip to content

Commit

Permalink
feat: impl json object (#128)
Browse files Browse the repository at this point in the history
* impl json object

* add test and fix clippy

* fmt

* add unmarshal json value

* updat ecomments

* rollback shared libraries

* fix to return error instead of unreachable
  • Loading branch information
beer-1 authored Sep 20, 2024
1 parent 44a7cff commit d4b03a1
Show file tree
Hide file tree
Showing 17 changed files with 501 additions and 46 deletions.
5 changes: 1 addition & 4 deletions crates/e2e-move-tests/src/tests/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -814,10 +814,7 @@ fn biguint_bigdecimal() {
vec![acc],
str::parse(entry).unwrap(),
vec![],
vec![
r#""100""#.to_string(),
r#""100""#.to_string(),
],
vec![r#""100""#.to_string(), r#""100""#.to_string()],
)
.unwrap();

Expand Down
6 changes: 4 additions & 2 deletions crates/e2e-move-tests/src/tests/move_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@

use crate::test_utils::mock_chain::{BlankAPIImpl, BlankTableViewImpl};

use initia_move_gas::{InitiaGasParameters, InitialGasSchedule, MiscGasParameters, NativeGasParameters};
use initia_move_compiler::unit_test_factory::InitiaUnitTestFactory;
use initia_move_gas::{
InitiaGasParameters, InitialGasSchedule, MiscGasParameters, NativeGasParameters,
};
use initia_move_natives::{
account::NativeAccountContext, all_natives, block::NativeBlockContext, code::NativeCodeContext,
cosmos::NativeCosmosContext, event::NativeEventContext, oracle::NativeOracleContext,
query::NativeQueryContext, staking::NativeStakingContext, table::NativeTableContext,
transaction_context::NativeTransactionContext,
};
use initia_move_types::metadata;
use initia_move_compiler::unit_test_factory::InitiaUnitTestFactory;

use move_cli::base::test::{run_move_unit_tests_with_factory, UnitTestResult};
use move_core_types::effects::ChangeSet;
Expand Down
3 changes: 2 additions & 1 deletion crates/e2e-move-tests/src/tests/view_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ fn test_view_output() {
module: module_name,
name: struct_name,
type_args: vec![]
})).to_string(),
}))
.to_string(),
"{\"arg\":\"hello world\",\"type_arg\":\"u256\"}".to_string()
)])
.into_inner(),
Expand Down
28 changes: 23 additions & 5 deletions crates/json/src/json_to_move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,20 @@ fn convert_json_value_to_move_value<S: StructResolver, R: ResourceResolver>(
.ok_or_else(deserialization_error)?;
let full_name = format!("{}::{}", st.module.short_str_lossless(), st.name);
match full_name.as_str() {
// JSONValue and JSONObject are not supported as entry function arguments
//
// "0x1::json::JSONValue" => MoveValue::vector_u8(
// serde_json::to_vec(&json_val).map_err(deserialization_error_with_msg)?,
// ),
// "0x1::json::JSONObject" => {
// let json_obj = json_val.as_object().ok_or_else(deserialization_error)?.to_owned();
// let elems = json_obj.into_iter().map(|(k, v)| {
// let key = k.into_bytes();
// let value = serde_json::to_vec(&v).map_err(deserialization_error_with_msg)?;
// Ok(MoveValue::Struct(MoveStruct::new(vec![MoveValue::vector_u8(key), MoveValue::vector_u8(value)])))
// }).collect::<VMResult<Vec<_>>>()?;
// MoveValue::Vector(elems)
// },
"0x1::string::String" => MoveValue::vector_u8(
json_val.as_str().ok_or_else(deserialization_error)?.into(),
),
Expand Down Expand Up @@ -780,10 +794,9 @@ mod json_arg_testing {
#[test]
fn test_deserialize_json_args_big_uint() {
let mut mock_state = mock_state();
mock_state.structs.insert(
StructNameIndex(0),
Arc::new(for_test("biguint", "BigUint")),
);
mock_state
.structs
.insert(StructNameIndex(0), Arc::new(for_test("biguint", "BigUint")));

let state_view = StateViewImpl::new(&mock_state);

Expand Down Expand Up @@ -823,7 +836,12 @@ mod json_arg_testing {

assert_eq!(
result,
bcs::to_bytes(&BigUint::from_u128(1234567u128 * (1e14 as u128)).unwrap().to_bytes_le()).unwrap()
bcs::to_bytes(
&BigUint::from_u128(1234567u128 * (1e14 as u128))
.unwrap()
.to_bytes_le()
)
.unwrap()
);

// invalid negative
Expand Down
98 changes: 98 additions & 0 deletions crates/json/src/json_to_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@ pub fn convert_json_value_to_value(
let full_name =
format!("{}::{}", type_.module_id().short_str_lossless(), type_.name);
match full_name.as_str() {
"0x1::json::JSONValue" => Value::struct_(Struct::pack(vec![Value::vector_u8(
serde_json::to_vec(&json_val).map_err(deserialization_error_with_msg)?,
)])),
"0x1::json::JSONObject" => {
let json_obj = json_val
.as_object()
.ok_or_else(deserialization_error)?
.to_owned();
let elems = json_obj
.into_iter()
.map(|(k, v)| {
let key = k.into_bytes();
let value = serde_json::to_vec(&v)
.map_err(deserialization_error_with_msg)?;
Ok(Value::struct_(Struct::pack(vec![
Value::vector_u8(key),
Value::vector_u8(value),
])))
})
.collect::<VMResult<Vec<_>>>()?;
Value::struct_(Struct::pack(vec![Value::vector_for_testing_only(elems)]))
}
"0x1::string::String" => Value::struct_(Struct::pack(vec![Value::vector_u8(
json_val
.as_str()
Expand Down Expand Up @@ -598,4 +620,80 @@ mod json_arg_testing {
let arg = b"\"-123.4567\"";
_ = deserialize_json_to_value(&layout, arg).unwrap_err();
}

#[test]
fn test_deserialize_json_to_value_json_value() {
let layout = MoveTypeLayout::Struct(MoveStructLayout::with_types(
StructTag {
address: AccountAddress::ONE,
module: ident_str!("json").into(),
name: ident_str!("JSONValue").into(),
type_args: vec![],
},
vec![MoveFieldLayout {
name: ident_str!("value").into(),
layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)),
}],
));

let arg = b"\"123\"";
let result = deserialize_json_to_value(&layout, arg).unwrap();
assert!(result
.equals(&Value::struct_(Struct::pack(vec![Value::vector_u8(
b"\"123\"".to_vec()
)])))
.unwrap());
}

#[test]
fn test_deserialize_json_to_value_json_object() {
let layout = MoveTypeLayout::Struct(MoveStructLayout::with_types(
StructTag {
address: AccountAddress::ONE,
module: ident_str!("json").into(),
name: ident_str!("JSONObject").into(),
type_args: vec![],
},
vec![MoveFieldLayout {
name: ident_str!("elems").into(),
layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Struct(
MoveStructLayout::with_types(
StructTag {
address: AccountAddress::ONE,
module: ident_str!("json").into(),
name: ident_str!("Element").into(),
type_args: vec![],
},
vec![
MoveFieldLayout {
name: ident_str!("key").into(),
layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)),
},
MoveFieldLayout {
name: ident_str!("value").into(),
layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)),
},
],
),
))),
}],
));

let arg = b"{\"key1\": \"value1\", \"key2\": \"value2\"}";
let result = deserialize_json_to_value(&layout, arg).unwrap();
assert!(result
.equals(&Value::struct_(Struct::pack(vec![
Value::vector_for_testing_only(vec![
Value::struct_(Struct::pack(vec![
Value::vector_u8(b"key1".to_vec()),
Value::vector_u8(b"\"value1\"".to_vec())
])),
Value::struct_(Struct::pack(vec![
Value::vector_u8(b"key2".to_vec()),
Value::vector_u8(b"\"value2\"".to_vec())
]))
])
])))
.unwrap());
}
}
163 changes: 162 additions & 1 deletion crates/json/src/move_to_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ fn convert_move_value_to_json_value(val: &MoveValue, depth: usize) -> VMResult<J
// check the struct type is string
// if yes, then convert move value to json string
// else, execute convert function recursively
if is_utf8_string(type_) {
if is_json_value(type_) {
convert_json_value_to_json_value(&fields[0].1)
} else if is_json_object(type_) {
convert_json_object_to_json_value(&fields[0].1)
} else if is_utf8_string(type_) {
convert_string_to_json_value(&fields[0].1)
} else if is_biguint(type_) {
convert_biguint_to_json_value(&fields[0].1)
Expand Down Expand Up @@ -124,6 +128,51 @@ fn convert_move_value_to_json_value(val: &MoveValue, depth: usize) -> VMResult<J
}
}

fn bytes_from_move_value(val: &MoveValue) -> VMResult<Vec<u8>> {
match val {
MoveValue::Vector(bytes_val) => bytes_val
.iter()
.map(|byte_val| match byte_val {
MoveValue::U8(byte) => Ok(*byte),
_ => Err(deserialization_error_with_msg("Expected U8 in vector")),
})
.collect::<VMResult<Vec<u8>>>(),
_ => Err(deserialization_error_with_msg("Expected vector of U8s")),
}
}

fn convert_json_value_to_json_value(val: &MoveValue) -> VMResult<JSONValue> {
let bz = bytes_from_move_value(val)?;
serde_json::from_slice(&bz).map_err(deserialization_error_with_msg)
}

fn convert_json_object_to_json_value(val: &MoveValue) -> VMResult<JSONValue> {
let elems = match val {
MoveValue::Vector(elems) => elems
.iter()
.map(|elem| match elem {
MoveValue::Struct(
MoveStruct::WithTypes { type_: _, fields }
| MoveStruct::WithFields(fields)
| MoveStruct::WithVariantFields(_, _, fields),
) => {
let key =
std::str::from_utf8(&bytes_from_move_value(&fields.first().unwrap().1)?)
.map_err(deserialization_error_with_msg)?
.to_string();
let val = convert_json_value_to_json_value(&fields.get(1).unwrap().1)?;

Ok((key, val))
}
_ => unreachable!(),
})
.collect::<VMResult<Map<_, _>>>()?,
_ => unreachable!(),
};

Ok(JSONValue::Object(elems))
}

fn convert_string_to_json_value(val: &MoveValue) -> VMResult<JSONValue> {
let bz: Vec<u8> = match val {
MoveValue::Vector(bytes_val) => bytes_val
Expand Down Expand Up @@ -232,6 +281,18 @@ fn convert_object_to_json_value(val: &MoveValue) -> VMResult<JSONValue> {
}

// check functions
fn is_json_value(type_: &StructTag) -> bool {
type_.address == CORE_CODE_ADDRESS
&& type_.module.as_str() == "json"
&& type_.name.as_str() == "JSONValue"
}

fn is_json_object(type_: &StructTag) -> bool {
type_.address == CORE_CODE_ADDRESS
&& type_.module.as_str() == "json"
&& type_.name.as_str() == "JSONObject"
}

fn is_utf8_string(type_: &StructTag) -> bool {
type_.address == CORE_CODE_ADDRESS
&& type_.module.as_str() == "string"
Expand Down Expand Up @@ -453,5 +514,105 @@ mod move_to_json_tests {
});
let val = convert_move_value_to_json_value(&mv, 1).unwrap();
assert_eq!(val, json!(addr.to_hex_literal()));

// json value
let mv = MoveValue::Struct(MoveStruct::WithTypes {
type_: StructTag {
address: CORE_CODE_ADDRESS,
module: ident_str!("json").into(),
name: ident_str!("JSONValue").into(),
type_args: vec![],
},
fields: vec![(
ident_str!("val").into(),
MoveValue::Vector(vec![
MoveValue::U8(34),
MoveValue::U8(109),
MoveValue::U8(111),
MoveValue::U8(118),
MoveValue::U8(101),
MoveValue::U8(34),
]),
)],
});
let val = convert_move_value_to_json_value(&mv, 1).unwrap();
assert_eq!(val, json!("move"));

// json object
let mv = MoveValue::Struct(MoveStruct::WithTypes {
type_: StructTag {
address: CORE_CODE_ADDRESS,
module: ident_str!("json").into(),
name: ident_str!("JSONObject").into(),
type_args: vec![],
},
fields: vec![(
ident_str!("elems").into(),
MoveValue::Vector(vec![
MoveValue::Struct(MoveStruct::WithTypes {
type_: StructTag {
address: CORE_CODE_ADDRESS,
module: ident_str!("json").into(),
name: ident_str!("Element").into(),
type_args: vec![],
},
fields: vec![
(
ident_str!("key").into(),
MoveValue::Vector(vec![
MoveValue::U8(109),
MoveValue::U8(111),
MoveValue::U8(118),
MoveValue::U8(101),
]),
),
(
ident_str!("value").into(),
MoveValue::Vector(vec![
MoveValue::U8(34),
MoveValue::U8(109),
MoveValue::U8(111),
MoveValue::U8(118),
MoveValue::U8(101),
MoveValue::U8(34),
]),
),
],
}),
MoveValue::Struct(MoveStruct::WithTypes {
type_: StructTag {
address: CORE_CODE_ADDRESS,
module: ident_str!("json").into(),
name: ident_str!("Element").into(),
type_args: vec![],
},
fields: vec![
(
ident_str!("key").into(),
MoveValue::Vector(vec![MoveValue::U8(102)]),
),
(
ident_str!("value").into(),
MoveValue::Vector(vec![
MoveValue::U8(110),
MoveValue::U8(117),
MoveValue::U8(108),
MoveValue::U8(108),
]),
),
],
}),
]),
)],
});

let val = convert_move_value_to_json_value(&mv, 1).unwrap();
assert_eq!(
val,
json!({
"move": json!("move"),
"f": json!(null),
})
);
}
}
Loading

0 comments on commit d4b03a1

Please sign in to comment.