marine/crates/it-json-serde/src/json_to_ivalues.rs

245 lines
8.4 KiB
Rust

/*
* Copyright 2022 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::IValue;
use crate::IType;
use super::ITJsonSeDeError::De;
use super::JsonResult;
use crate::MRecordTypes;
use serde_json::Value as JValue;
use wasmer_it::NEVec;
use std::collections::HashMap;
use std::iter::ExactSizeIterator;
/// Convert json to an array of ivalues according to the supplied argument types.
pub fn json_to_ivalues<'a, 'b>(
json_args: JValue,
arg_types: impl Iterator<Item = (&'a String, &'a IType)> + ExactSizeIterator,
record_types: &'b MRecordTypes,
) -> JsonResult<Vec<IValue>> {
let ivalues = match json_args {
JValue::Object(json_map) => json_map_to_ivalues(json_map, arg_types, record_types)?,
JValue::Array(json_array) => {
json_array_to_ivalues(json_array, arg_types.map(|arg| arg.1), record_types)?
}
JValue::Null => json_null_to_ivalues(arg_types)?,
json_value => json_value_to_ivalues(json_value, arg_types)?,
};
Ok(ivalues)
}
/// Convert json map to an array of ivalues according to the supplied argument types.
fn json_map_to_ivalues<'a, 'b>(
mut json_map: serde_json::Map<String, JValue>,
arg_types: impl Iterator<Item = (&'a String, &'a IType)>,
record_types: &'b MRecordTypes,
) -> JsonResult<Vec<IValue>> {
let mut iargs = Vec::new();
for (arg_name, arg_type) in arg_types {
let json_value = json_map
.remove(arg_name)
.ok_or_else(|| De(format!("missing argument with name {}", arg_name)))?;
let iarg = jvalue_to_ivalue(json_value, arg_type, record_types)?;
iargs.push(iarg);
}
if !json_map.is_empty() {
return Err(De(format!(
"function requires {} arguments, {} provided",
iargs.len(),
iargs.len() + json_map.len()
)));
}
Ok(iargs)
}
/// Convert json array to an array of ivalues according to the supplied argument types.
fn json_array_to_ivalues<'a, 'b>(
json_array: Vec<JValue>,
arg_types: impl Iterator<Item = &'a IType> + ExactSizeIterator,
record_types: &'b MRecordTypes,
) -> JsonResult<Vec<IValue>> {
if json_array.len() != arg_types.len() {
return Err(De(format!(
"function requires {} arguments, {} provided",
arg_types.len(),
json_array.len()
)));
}
let iargs = json_array
.into_iter()
.zip(arg_types)
.map(|(json_value, arg_type)| jvalue_to_ivalue(json_value, arg_type, record_types))
.collect::<JsonResult<Vec<_>>>()?;
Ok(iargs)
}
/// Convert json value (Number, String or Bool) to an array of ivalues according to the supplied argument types.
fn json_value_to_ivalues<'a>(
json_value: JValue,
mut arg_types: impl Iterator<Item = (&'a String, &'a IType)> + ExactSizeIterator,
) -> JsonResult<Vec<IValue>> {
if arg_types.len() != 1 {
return Err(De(format!(
"called function has the following signature: '{:?}', and it isn't suitable for an argument '{:?}' provided",
arg_types.collect::<Vec<_>>(),
json_value,
)));
}
// unwrap is safe here because iterator size's been checked
let arg_type = arg_types.next().unwrap().1;
let ivalue = jvalue_to_ivalue(json_value, arg_type, &HashMap::new())?;
Ok(vec![ivalue])
}
/// Convert json Null to an empty array of ivalues.
fn json_null_to_ivalues<'a>(
arg_types: impl Iterator<Item = (&'a String, &'a IType)> + ExactSizeIterator,
) -> JsonResult<Vec<IValue>> {
if arg_types.len() != 0 {
return Err(De(format!(
"the called function has the following signature: {:?}, but no arguments is provided",
arg_types.collect::<Vec<_>>()
)));
}
Ok(vec![])
}
/// Convert one JValue to an array of ivalues according to the supplied argument type.
fn jvalue_to_ivalue(jvalue: JValue, ty: &IType, record_types: &MRecordTypes) -> JsonResult<IValue> {
macro_rules! to_ivalue(
($json_value:expr, $ty:ident) => {
{
let value = match $json_value {
// if there is an array with only one element try to implicitly flatten it,
// this is needed mostly because jsonpath lib returns Vec<&JValue> and
// could be changed in future
JValue::Array(mut json_array) if json_array.len() == 1 => {
serde_json::from_value(json_array.remove(0))
},
jvalue => serde_json::from_value(jvalue),
}.map_err(|e|
De(format!("error {:?} occurred while deserializing function arguments",e))
)?;
Ok(IValue::$ty(value))
}
}
);
match ty {
IType::Boolean => to_ivalue!(jvalue, Boolean),
IType::S8 => to_ivalue!(jvalue, S8),
IType::S16 => to_ivalue!(jvalue, S16),
IType::S32 => to_ivalue!(jvalue, S32),
IType::S64 => to_ivalue!(jvalue, S64),
IType::U8 => to_ivalue!(jvalue, U8),
IType::U16 => to_ivalue!(jvalue, U16),
IType::U32 => to_ivalue!(jvalue, U32),
IType::U64 => to_ivalue!(jvalue, U64),
IType::F32 => to_ivalue!(jvalue, F32),
IType::F64 => to_ivalue!(jvalue, F64),
IType::String => to_ivalue!(jvalue, String),
IType::ByteArray => {
let value = match jvalue {
JValue::Array(json_array) => {
let iargs = json_array
.into_iter()
.map(|json_value| jvalue_to_ivalue(json_value, &IType::U8, record_types))
.collect::<JsonResult<Vec<_>>>()?;
Ok(iargs)
}
_ => Err(De(format!("expected bytearray, got {:?}", jvalue))),
}?;
Ok(IValue::Array(value))
}
IType::Array(value_type) => {
let value = match jvalue {
JValue::Array(json_array) => {
let iargs = json_array
.into_iter()
.map(|json_value| jvalue_to_ivalue(json_value, value_type, record_types))
.collect::<JsonResult<Vec<_>>>()?;
Ok(iargs)
}
_ => Err(De(format!(
"expected array of {:?} types, got {:?}",
value_type, jvalue
))),
}?;
Ok(IValue::Array(value))
}
IType::I32 => to_ivalue!(jvalue, I32),
IType::I64 => to_ivalue!(jvalue, I64),
IType::Record(record_type_id) => {
let value = json_record_type_to_ivalue(jvalue, record_type_id, record_types)?;
Ok(IValue::Record(value))
}
}
}
#[allow(clippy::ptr_arg)]
/// Convert JValue of array or object types to an IValue record type.
// TODO: after introducing new Record type wrapper change the result type
fn json_record_type_to_ivalue(
json_value: JValue,
record_type_id: &u64,
record_types: &MRecordTypes,
) -> JsonResult<NEVec<IValue>> {
let record_type = record_types.get(record_type_id).ok_or_else(|| {
De(format!(
"record with type id `{}` wasn't found",
record_type_id
))
})?;
match json_value {
JValue::Object(json_map) => Ok(NEVec::new(json_map_to_ivalues(
json_map,
record_type
.fields
.iter()
.map(|field| (&field.name, &field.ty)),
record_types,
)?)
.unwrap()),
JValue::Array(json_array) => Ok(NEVec::new(json_array_to_ivalues(
json_array,
record_type.fields.iter().map(|field| (&field.ty)),
record_types,
)?)
.unwrap()),
_ => Err(De(format!(
"record with type id `{}` should be encoded as array or map of fields",
record_type_id
))),
}
}