use crate::analysis::analysed_type::{list, option, record, tuple, variant};
use crate::analysis::{
AnalysedResourceId, AnalysedResourceMode, AnalysedType, NameOptionTypePair, NameTypePair,
TypeEnum, TypeFlags, TypeHandle, TypeList, TypeOption, TypeRecord, TypeResult, TypeTuple,
TypeVariant,
};
use crate::json::ValueAndTypeJsonExtensions;
use crate::{IntoValueAndType, Value, ValueAndType};
use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive};
use serde_json::{Number, Value as JsonValue};
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
impl ValueAndTypeJsonExtensions for ValueAndType {
fn parse_with_type(json_val: &JsonValue, typ: &AnalysedType) -> Result<Self, Vec<String>> {
match typ {
AnalysedType::Bool(_) => get_bool(json_val),
AnalysedType::S8(_) => get_s8(json_val),
AnalysedType::U8(_) => get_u8(json_val),
AnalysedType::S16(_) => get_s16(json_val),
AnalysedType::U16(_) => get_u16(json_val),
AnalysedType::S32(_) => get_s32(json_val),
AnalysedType::U32(_) => get_u32(json_val),
AnalysedType::S64(_) => get_s64(json_val),
AnalysedType::U64(_) => get_u64(json_val),
AnalysedType::F64(_) => get_f64(json_val),
AnalysedType::F32(_) => get_f32(json_val),
AnalysedType::Chr(_) => get_char(json_val),
AnalysedType::Str(_) => get_string(json_val),
AnalysedType::Enum(TypeEnum { cases, .. }) => get_enum(json_val, cases),
AnalysedType::Flags(TypeFlags { names, .. }) => get_flag(json_val, names),
AnalysedType::List(TypeList { inner, .. }) => get_list(json_val, inner),
AnalysedType::Option(TypeOption { inner, .. }) => get_option(json_val, inner),
AnalysedType::Result(TypeResult { ok, err, .. }) => get_result(json_val, ok, err),
AnalysedType::Record(TypeRecord { fields, .. }) => get_record(json_val, fields),
AnalysedType::Variant(TypeVariant { cases, .. }) => get_variant(json_val, cases),
AnalysedType::Tuple(TypeTuple { items, .. }) => get_tuple(json_val, items),
AnalysedType::Handle(TypeHandle {
resource_id, mode, ..
}) => get_handle(json_val, *resource_id, mode.clone()),
}
}
fn to_json_value(&self) -> Result<JsonValue, String> {
match (&self.typ, &self.value) {
(AnalysedType::Bool(_), Value::Bool(bool_val)) => Ok(JsonValue::Bool(*bool_val)),
(AnalysedType::S8(_), Value::S8(value)) => Ok(JsonValue::Number(Number::from(*value))),
(AnalysedType::U8(_), Value::U8(value)) => Ok(JsonValue::Number(Number::from(*value))),
(AnalysedType::S16(_), Value::S16(value)) => {
Ok(JsonValue::Number(Number::from(*value)))
}
(AnalysedType::U16(_), Value::U16(value)) => {
Ok(JsonValue::Number(Number::from(*value)))
}
(AnalysedType::S32(_), Value::S32(value)) => {
Ok(JsonValue::Number(Number::from(*value)))
}
(AnalysedType::U32(_), Value::U32(value)) => {
Ok(JsonValue::Number(Number::from(*value)))
}
(AnalysedType::S64(_), Value::S64(value)) => {
Ok(JsonValue::Number(Number::from(*value)))
}
(AnalysedType::U64(_), Value::U64(value)) => {
Ok(JsonValue::Number(Number::from(*value)))
}
(AnalysedType::F32(_), Value::F32(value)) => Ok(JsonValue::Number(
Number::from_f64(*value as f64)
.ok_or_else(|| "Failed to encode f32 as JSON number".to_string())?,
)),
(AnalysedType::F64(_), Value::F64(value)) => Ok(JsonValue::Number(
Number::from_f64(*value)
.ok_or_else(|| "Failed to encode f64 as JSON number".to_string())?,
)),
(AnalysedType::Chr(_), Value::Char(value)) => {
Ok(JsonValue::Number(Number::from(*value as u32)))
}
(AnalysedType::Str(_), Value::String(value)) => Ok(JsonValue::String(value.clone())),
(AnalysedType::Enum(TypeEnum { cases, .. }), Value::Enum(value)) => {
if let Some(case) = cases.get(*value as usize) {
Ok(JsonValue::String(case.clone()))
} else {
Err(format!("Invalid enum index '{value}'"))
}
}
(AnalysedType::Flags(TypeFlags { names, .. }), Value::Flags(value)) => {
let values: Vec<JsonValue> = value
.iter()
.zip(names)
.filter_map(|(enabled, name)| {
if *enabled {
Some(JsonValue::String(name.clone()))
} else {
None
}
})
.collect();
Ok(JsonValue::Array(values))
}
(AnalysedType::Option(TypeOption { inner, .. }), Value::Option(value)) => {
if let Some(inner_value) = value {
let inner_vnt = ValueAndType::new((**inner_value).clone(), (**inner).clone());
inner_vnt.to_json_value()
} else {
Ok(JsonValue::Null)
}
}
(AnalysedType::Tuple(TypeTuple { items, .. }), Value::Tuple(values)) => {
let item_jsons = items
.iter()
.zip(values)
.map(|(item_type, item_value)| {
let item_vnt = ValueAndType::new(item_value.clone(), item_type.clone());
item_vnt.to_json_value()
})
.collect::<Result<Vec<_>, _>>()?;
Ok(JsonValue::Array(item_jsons))
}
(AnalysedType::List(TypeList { inner, .. }), Value::List(values)) => {
let item_jsons = values
.iter()
.map(|item_value| {
let item_vnt = ValueAndType::new(item_value.clone(), (**inner).clone());
item_vnt.to_json_value()
})
.collect::<Result<Vec<_>, _>>()?;
Ok(JsonValue::Array(item_jsons))
}
(AnalysedType::Record(TypeRecord { fields, .. }), Value::Record(field_values)) => {
let fields = fields
.iter()
.zip(field_values)
.map(|(field_type, field_value)| {
let field_vnt =
ValueAndType::new(field_value.clone(), field_type.typ.clone());
field_vnt
.to_json_value()
.map(|json| (field_type.name.clone(), json))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(JsonValue::Object(fields.into_iter().collect()))
}
(
AnalysedType::Variant(TypeVariant { cases, .. }),
Value::Variant {
case_idx,
case_value,
},
) => {
if let Some(case) = cases.get(*case_idx as usize) {
let mut map = serde_json::Map::new();
match &case.typ {
Some(case_typ) => {
if let Some(value) = case_value {
let value_vnt =
ValueAndType::new((**value).clone(), case_typ.clone());
map.insert(case.name.clone(), value_vnt.to_json_value()?);
} else {
map.insert(case.name.clone(), JsonValue::Null);
}
}
None => {
map.insert(case.name.clone(), JsonValue::Null);
}
}
Ok(JsonValue::Object(map))
} else {
Err(format!("Invalid variant index '{case_idx}'"))
}
}
(AnalysedType::Result(TypeResult { ok, err, .. }), Value::Result(result)) => {
match result {
Ok(None) => Ok(JsonValue::Object(
vec![("ok".to_string(), JsonValue::Null)]
.into_iter()
.collect(),
)),
Ok(Some(ok_value)) => {
if let Some(ok_type) = ok {
let ok_vnt =
ValueAndType::new((**ok_value).clone(), (**ok_type).clone());
Ok(JsonValue::Object(
vec![("ok".to_string(), ok_vnt.to_json_value()?)]
.into_iter()
.collect(),
))
} else {
Err("Missing ok value in Result".to_string())
}
}
Err(None) => Ok(JsonValue::Object(
vec![("err".to_string(), JsonValue::Null)]
.into_iter()
.collect(),
)),
Err(Some(err_value)) => {
if let Some(err_type) = err {
let err_vnt =
ValueAndType::new((**err_value).clone(), (**err_type).clone());
Ok(JsonValue::Object(
vec![("err".to_string(), err_vnt.to_json_value()?)]
.into_iter()
.collect(),
))
} else {
Err("Missing err value in Result".to_string())
}
}
}
}
(AnalysedType::Handle(TypeHandle { .. }), Value::Handle { uri, resource_id }) => {
Ok(JsonValue::String(format!("{uri}/{resource_id}")))
}
_ => Err(format!(
"Type and value mismatch (type is {:?}, value is {:?})",
self.typ, self.value
)),
}
}
}
fn get_bool(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
match json {
JsonValue::Bool(bool_val) => Ok(bool_val.into_value_and_type()),
_ => {
let type_description = type_description(json);
Err(vec![format!("expected bool, found {}", type_description)])
}
}
}
fn get_s8(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_i8(i8::MIN).expect("Failed to convert i8::MIN to BigDecimal"),
BigDecimal::from_i8(i8::MAX).expect("Failed to convert i8::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_i8()
.ok_or_else(|| vec!["Failed to convert number to i8".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_u8(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_u8(u8::MIN).expect("Failed to convert u8::MIN to BigDecimal"),
BigDecimal::from_u8(u8::MAX).expect("Failed to convert u8::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_u8()
.ok_or_else(|| vec!["Failed to convert number to u8".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_s16(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_i16(i16::MIN).expect("Failed to convert i16::MIN to BigDecimal"),
BigDecimal::from_i16(i16::MAX).expect("Failed to convert i16::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_i16()
.ok_or_else(|| vec!["Failed to convert number to i16".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_u16(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_u16(u16::MIN).expect("Failed to convert u16::MIN to BigDecimal"),
BigDecimal::from_u16(u16::MAX).expect("Failed to convert u16::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_u16()
.ok_or_else(|| vec!["Failed to convert number to u16".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_s32(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_i32(i32::MIN).expect("Failed to convert i32::MIN to BigDecimal"),
BigDecimal::from_i32(i32::MAX).expect("Failed to convert i32::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_i32()
.ok_or_else(|| vec!["Failed to convert number to i32".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_u32(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_u32(u32::MIN).expect("Failed to convert u32::MIN to BigDecimal"),
BigDecimal::from_u32(u32::MAX).expect("Failed to convert u32::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_u32()
.ok_or_else(|| vec!["Failed to convert number to u32".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_s64(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_i64(i64::MIN).expect("Failed to convert i64::MIN to BigDecimal"),
BigDecimal::from_i64(i64::MAX).expect("Failed to convert i64::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_i64()
.ok_or_else(|| vec!["Failed to convert number to i64".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_f32(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
ensure_range(
json,
BigDecimal::from_f32(f32::MIN).expect("Failed to convert f32::MIN to BigDecimal"),
BigDecimal::from_f32(f32::MAX).expect("Failed to convert f32::MAX to BigDecimal"),
)
.and_then(|num| {
num.to_f32()
.ok_or_else(|| vec!["Failed to convert number to f32".to_string()])
})
.map(|num| num.into_value_and_type())
}
fn get_f64(json_val: &JsonValue) -> Result<ValueAndType, Vec<String>> {
let num = get_big_decimal(json_val)?;
let num: f64 = num
.to_string()
.parse()
.map_err(|err| vec![format!("Failed to convert number to f64: {err}")])?;
Ok(num.into_value_and_type())
}
fn get_string(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
if let Some(str_value) = json.as_str() {
Ok(str_value.to_string().into_value_and_type())
} else {
let type_description = type_description(json);
Err(vec![format!("expected string, found {}", type_description)])
}
}
fn get_char(json: &JsonValue) -> Result<ValueAndType, Vec<String>> {
if let Some(num_u64) = json.as_u64() {
if num_u64 > u32::MAX as u64 {
Err(vec![format!(
"The value {} is too large to be converted to a char",
num_u64
)])
} else if let Some(ch) = char::from_u32(num_u64 as u32) {
Ok(ch.into_value_and_type())
} else {
Err(vec![format!(
"The value {} cannot be converted to a char",
num_u64
)])
}
} else {
let type_description = type_description(json);
Err(vec![format!("expected char, found {}", type_description)])
}
}
fn get_tuple(input_json: &JsonValue, types: &[AnalysedType]) -> Result<ValueAndType, Vec<String>> {
let json_array = input_json.as_array().ok_or(vec![format!(
"Input {} is not an array representing tuple",
input_json
)])?;
if json_array.len() != types.len() {
return Err(vec![format!(
"The length of types in template is not equal to the length of tuple (array) in {}",
input_json,
)]);
}
let mut errors: Vec<String> = vec![];
let mut vals: Vec<Value> = vec![];
let mut tpes: Vec<AnalysedType> = vec![];
for (json, tpe) in json_array.iter().zip(types.iter()) {
match ValueAndType::parse_with_type(json, tpe) {
Ok(result) => {
vals.push(result.value);
tpes.push(result.typ);
}
Err(errs) => errors.extend(errs),
}
}
if errors.is_empty() {
Ok(ValueAndType::new(Value::Tuple(vals), tuple(tpes)))
} else {
Err(errors)
}
}
fn get_option(input_json: &JsonValue, tpe: &AnalysedType) -> Result<ValueAndType, Vec<String>> {
match input_json.as_null() {
Some(_) => Ok(ValueAndType::new(Value::Option(None), option(tpe.clone()))),
None => ValueAndType::parse_with_type(input_json, tpe).map(|result| {
ValueAndType::new(
Value::Option(Some(Box::new(result.value))),
option(tpe.clone()),
)
}),
}
}
fn get_list(input_json: &JsonValue, tpe: &AnalysedType) -> Result<ValueAndType, Vec<String>> {
let json_array = input_json
.as_array()
.ok_or(vec![format!("Input {} is not an array", input_json)])?;
let mut errors: Vec<String> = vec![];
let mut vals: Vec<Value> = vec![];
for json in json_array {
match ValueAndType::parse_with_type(json, tpe) {
Ok(result) => vals.push(result.value),
Err(errs) => errors.extend(errs),
}
}
if errors.is_empty() {
Ok(ValueAndType::new(Value::List(vals), list(tpe.clone())))
} else {
Err(errors)
}
}
fn get_enum(input_json: &JsonValue, names: &[String]) -> Result<ValueAndType, Vec<String>> {
let input_enum_value = input_json
.as_str()
.ok_or(vec![format!("Input {} is not string", input_json)])?;
let enum_value = names
.iter()
.position(|n| n == input_enum_value)
.ok_or_else(|| {
vec![format!(
"Invalid input {}. Valid values are {}",
input_enum_value,
names.join(",")
)]
})?;
Ok(ValueAndType::new(
Value::Enum(enum_value as u32),
AnalysedType::Enum(TypeEnum {
name: None,
owner: None,
cases: names.to_vec(),
}),
))
}
#[allow(clippy::type_complexity)]
fn get_result(
input_json: &JsonValue,
ok_type: &Option<Box<AnalysedType>>,
err_type: &Option<Box<AnalysedType>>,
) -> Result<ValueAndType, Vec<String>> {
fn validate(
typ: &Option<Box<AnalysedType>>,
input_json: &JsonValue,
) -> Result<Option<Box<Value>>, Vec<String>> {
if let Some(typ) = typ {
ValueAndType::parse_with_type(input_json, typ).map(|v| Some(Box::new(v.value)))
} else if input_json.is_null() {
Ok(None)
} else {
Err(vec![
"The type of ok is absent, but some JSON value was provided".to_string(),
])
}
}
match input_json.get("ok") {
Some(value) => {
let value = validate(ok_type, value)?;
Ok(ValueAndType::new(
Value::Result(Ok(value)),
AnalysedType::Result(TypeResult {
ok: ok_type.clone(),
err: err_type.clone(),
name: None,
owner: None,
}),
))
}
None => match input_json.get("err") {
Some(value) => {
let value = validate(err_type, value)?;
Ok(ValueAndType::new(
Value::Result(Err(value)),
AnalysedType::Result(TypeResult {
ok: ok_type.clone(),
err: err_type.clone(),
name: None,
owner: None,
}),
))
}
None => Err(vec![
"Failed to retrieve either ok value or err value".to_string(),
]),
},
}
}
fn get_record(
input_json: &JsonValue,
name_type_pairs: &[NameTypePair],
) -> Result<ValueAndType, Vec<String>> {
let json_map = input_json.as_object().ok_or(vec![format!(
"The input {} is not a json object",
input_json
)])?;
let mut errors: Vec<String> = vec![];
let mut vals: Vec<Value> = vec![];
for NameTypePair { name, typ } in name_type_pairs {
if let Some(json_value) = json_map.get(name) {
match ValueAndType::parse_with_type(json_value, typ) {
Ok(result) => vals.push(result.value),
Err(value_errors) => errors.extend(
value_errors
.iter()
.map(|err| format!("invalid value for key {name}: {err}"))
.collect::<Vec<_>>(),
),
}
} else {
match typ {
AnalysedType::Option(_) => {
vals.push(Value::Option(None));
}
_ => errors.push(format!("key '{name}' not found")),
}
}
}
if errors.is_empty() {
Ok(ValueAndType::new(
Value::Record(vals),
record(name_type_pairs.to_vec()),
))
} else {
Err(errors)
}
}
fn get_flag(input_json: &JsonValue, names: &[String]) -> Result<ValueAndType, Vec<String>> {
let json_array = input_json
.as_array()
.ok_or(vec![format!("Input {} is not an array", input_json)])?;
let mut errors: Vec<String> = vec![];
let mut vals: HashSet<String> = HashSet::new();
for json in json_array.iter() {
let flag: String = json
.as_str()
.map(|x| x.to_string())
.or_else(|| json.as_bool().map(|b| b.to_string()))
.or_else(|| json.as_number().map(|n| n.to_string()))
.ok_or(vec![format!(
"Input {} is not a string or boolean or number",
json
)])?;
if names.contains(&flag) {
vals.insert(flag);
} else {
errors.push(format!(
"Invalid input {}. Valid values are {}",
flag,
names.join(",")
));
}
}
if errors.is_empty() {
let mut bitmap = vec![false; names.len()];
for (i, name) in names.iter().enumerate() {
bitmap[i] = vals.contains(name);
}
Ok(ValueAndType::new(
Value::Flags(bitmap),
AnalysedType::Flags(TypeFlags {
names: names.to_vec(),
name: None,
owner: None,
}),
))
} else {
Err(errors)
}
}
fn get_variant(
input_json: &JsonValue,
types: &[NameOptionTypePair],
) -> Result<ValueAndType, Vec<String>> {
let mut possible_mapping_indexed: HashMap<&String, &Option<AnalysedType>> = HashMap::new();
for NameOptionTypePair {
name,
typ: optional_type,
} in types.iter()
{
possible_mapping_indexed.insert(name, optional_type);
}
let json_obj = input_json
.as_object()
.ok_or(vec![format!("Input {} is not an object", input_json)])?;
let (key, json) = if json_obj.is_empty() {
Err(vec!["Zero variants in in the input".to_string()])
} else {
Ok(json_obj.iter().next().unwrap())
}?;
let case_idx = types
.iter()
.position(|pair| pair.name == *key)
.ok_or_else(|| vec![format!("Unknown key {key} in the variant")])?
as u32;
match possible_mapping_indexed.get(key) {
Some(Some(tpe)) => {
let result = ValueAndType::parse_with_type(json, tpe)?;
Ok(ValueAndType::new(
Value::Variant {
case_idx,
case_value: Some(Box::new(result.value)),
},
variant(types.to_vec()),
))
}
Some(None) if json.is_null() => Ok(ValueAndType::new(
Value::Variant {
case_idx,
case_value: None,
},
variant(types.to_vec()),
)),
Some(None) => Err(vec![format!("Unit variant {key} has non-null JSON value")]),
None => Err(vec![format!("Unknown key {key} in the variant")]),
}
}
fn get_handle(
value: &JsonValue,
id: AnalysedResourceId,
resource_mode: AnalysedResourceMode,
) -> Result<ValueAndType, Vec<String>> {
match value.as_str() {
Some(str) => {
let parts: Vec<&str> = str.split('/').collect();
if parts.len() >= 2 {
match u64::from_str(parts[parts.len() - 1]) {
Ok(resource_id) => {
let uri = parts[0..(parts.len() - 1)].join("/");
Ok(ValueAndType::new(
Value::Handle { resource_id, uri },
AnalysedType::Handle(TypeHandle {
resource_id: id,
mode: resource_mode,
name: None,
owner: None,
}),
))
}
Err(err) => Err(vec![format!(
"Failed to parse resource-id section of the handle value: {}",
err
)]),
}
} else {
Err(vec![format!(
"expected handle, represented by a worker-url/resource-id string, found {}",
str
)])
}
}
None => Err(vec![format!(
"expected handle, represented by a worker-url/resource-id string, found {}",
type_description(value)
)]),
}
}
fn type_description(value: &JsonValue) -> &'static str {
match value {
JsonValue::Null => "null",
JsonValue::Bool(_) => "boolean",
JsonValue::Number(_) => "number",
JsonValue::String(_) => "string",
JsonValue::Array(_) => "list",
JsonValue::Object(_) => "record",
}
}
fn ensure_range(
value: &JsonValue,
min: BigDecimal,
max: BigDecimal,
) -> Result<BigDecimal, Vec<String>> {
let num = get_big_decimal(value)?;
if num >= min && num <= max {
Ok(num)
} else {
Err(vec![format!(
"value {} is not within the range of {} to {}",
value, min, max
)])
}
}
fn get_big_decimal(value: &JsonValue) -> Result<BigDecimal, Vec<String>> {
match value {
JsonValue::Number(num) => {
if let Ok(f64) = BigDecimal::from_str(num.to_string().as_str()) {
Ok(f64)
} else {
Err(vec![format!("cannot convert {} to f64", num)])
}
}
_ => {
let type_description = type_description(value);
Err(vec![format!("expected number, found {}", type_description)])
}
}
}
fn get_u64(value: &JsonValue) -> Result<ValueAndType, Vec<String>> {
match value {
JsonValue::Number(num) => {
if let Some(u64) = num.as_u64() {
Ok(u64.into_value_and_type())
} else {
Err(vec![format!("Cannot convert {} to u64", num)])
}
}
_ => {
let type_description = type_description(value);
Err(vec![format!("expected u64, found {}", type_description)])
}
}
}
#[cfg(test)]
mod tests {
use test_r::test;
use std::collections::HashSet;
use crate::analysis::AnalysedType;
use crate::analysis::analysed_type::{
bool, case, chr, r#enum, f32, f64, field, flags, list, option, record, result, s8, s16,
s32, s64, str, tuple, u8, u16, u32, u64, variant,
};
use proptest::prelude::*;
use serde_json::{Number, Value as JsonValue};
use crate::json::ValueAndTypeJsonExtensions;
use crate::{Value, ValueAndType};
fn validate_function_result(
val: Value,
expected_type: &AnalysedType,
) -> Result<JsonValue, Vec<String>> {
ValueAndType::new(val, expected_type.clone())
.to_json_value()
.map_err(|s| vec![s])
}
fn validate_function_parameter(
json: &JsonValue,
expected_type: &AnalysedType,
) -> Result<Value, Vec<String>> {
match ValueAndType::parse_with_type(json, expected_type) {
Ok(result) => Ok(result.value),
Err(err) => Err(err),
}
}
proptest! {
#[test]
fn test_u8_param(value: u8) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &u8());
prop_assert_eq!(result, Ok(Value::U8(value)));
}
#[test]
fn test_u16_param(value: u16) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &u16());
prop_assert_eq!(result, Ok(Value::U16(value)));
}
#[test]
fn test_u32_param(value: u32) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &u32());
prop_assert_eq!(result, Ok(Value::U32(value)));
}
#[test]
fn test_u64_param(value: u64) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &u64());
prop_assert_eq!(result, Ok(Value::U64(value)));
}
#[test]
fn test_s8_param(value: i8) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &s8());
prop_assert_eq!(result, Ok(Value::S8(value)));
}
#[test]
fn test_s16_param(value: i16) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &s16());
prop_assert_eq!(result, Ok(Value::S16(value)));
}
#[test]
fn test_s32_param(value: i32) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &s32());
prop_assert_eq!(result, Ok(Value::S32(value)));
}
#[test]
fn test_s64_param(value: i64) {
let json = JsonValue::Number(Number::from(value));
let result = validate_function_parameter(&json, &s64());
prop_assert_eq!(result, Ok(Value::S64(value)));
}
#[test]
fn test_f32_param(value: f32) {
let json = JsonValue::Number(Number::from_f64(value as f64).unwrap());
let result = validate_function_parameter(&json, &f32());
prop_assert_eq!(result, Ok(Value::F32(value)));
}
#[test]
fn test_f64_param(value: f64) {
let json = JsonValue::Number(Number::from_f64(value).unwrap());
let result = validate_function_parameter(&json, &f64());
prop_assert_eq!(result, Ok(Value::F64(value)));
}
#[test]
fn test_char_param(value: char) {
let json = JsonValue::Number(Number::from(value as u32));
let result = validate_function_parameter(&json, &chr());
prop_assert_eq!(result, Ok(Value::Char(value)));
}
#[test]
fn test_string_param(value: String) {
let json = JsonValue::String(value.clone());
let result = validate_function_parameter(&json, &str());
prop_assert_eq!(result, Ok(Value::String(value)));
}
#[test]
fn test_list_u8_param(value: Vec<u8>) {
let json = JsonValue::Array(value.iter().map(|v| JsonValue::Number(Number::from(*v))).collect());
let result = validate_function_parameter(&json, &list(u8()));
prop_assert_eq!(result, Ok(Value::List(value.into_iter().map(Value::U8).collect())));
}
#[test]
fn test_list_list_u64_param(value: Vec<Vec<u64>>) {
let json = JsonValue::Array(value.iter().map(|v| JsonValue::Array(v.iter().map(|n| JsonValue::Number(Number::from(*n))).collect())).collect());
let result = validate_function_parameter(&json, &list(list(u64())));
prop_assert_eq!(result, Ok(Value::List(value.into_iter().map(|v| Value::List(v.into_iter().map(Value::U64).collect())).collect())));
}
#[test]
fn test_tuple_int_char_string_param(value: (i32, char, String)) {
let json = JsonValue::Array(
vec![
JsonValue::Number(Number::from(value.0)),
JsonValue::Number(Number::from(value.1 as u32)),
JsonValue::String(value.2.clone()),
]);
let result = validate_function_parameter(&json, &tuple(vec![
s32(),
chr(),
str(),
]));
prop_assert_eq!(result, Ok(Value::Tuple(
vec![
Value::S32(value.0),
Value::Char(value.1),
Value::String(value.2),
])));
}
#[test]
fn test_record_bool_fields_param(value in
any::<Vec<(String, bool)>>().prop_filter("Keys are distinct", |pairs|
pairs.iter().map(|(k, _)| k).collect::<HashSet<_>>().len() == pairs.len())
) {
let json = JsonValue::Object(
value.iter().map(|(k, v)| (k.clone(), JsonValue::Bool(*v))).collect());
let result = validate_function_parameter(&json, &record(
value.iter().map(|(k, _)| field(k, bool())).collect()
));
prop_assert_eq!(result, Ok(Value::Record(
value.iter().map(|(_, v)| Value::Bool(*v)).collect())));
}
#[test]
fn test_flags_param(value in
any::<Vec<(String, bool)>>().prop_filter("Keys are distinct", |pairs|
pairs.iter().map(|(k, _)| k).collect::<HashSet<_>>().len() == pairs.len())
) {
let enabled: Vec<String> = value.iter().filter(|(_, v)| *v).map(|(k, _)| k.clone()).collect();
let json = JsonValue::Array(enabled.iter().map(|v| JsonValue::String(v.clone())).collect());
let result = validate_function_parameter(&json, &flags(&value.iter().map(|(k, _)| k.as_str()).collect::<Vec<&str>>()));
prop_assert_eq!(result, Ok(Value::Flags(
value.iter().map(|(_, v)| *v).collect())
));
}
#[test]
fn test_enum_param((names, idx) in (any::<HashSet<String>>().prop_filter("Name list is non empty", |names| !names.is_empty()), any::<usize>())) {
let names: Vec<String> = names.into_iter().collect();
let idx = idx % names.len();
let json = JsonValue::String(names[idx].clone());
let result = validate_function_parameter(&json, &r#enum(&names.iter().map(|s| s.as_str()).collect::<Vec<&str>>()));
prop_assert_eq!(result, Ok(Value::Enum(idx as u32)));
}
#[test]
fn test_option_string_param(value: Option<String>) {
let json = match &value {
Some(v) => JsonValue::String(v.clone()),
None => JsonValue::Null,
};
let result = validate_function_parameter(&json, &option(str()));
prop_assert_eq!(result, Ok(Value::Option(value.map(|v| Box::new(Value::String(v))))));
}
#[test]
fn test_result_option_s32_string_param(value: Result<Option<i32>, String>) {
let json = match &value {
Ok(None) => JsonValue::Object(vec![("ok".to_string(), JsonValue::Null)].into_iter().collect()),
Ok(Some(v)) => JsonValue::Object(vec![("ok".to_string(), JsonValue::Number(Number::from(*v)))].into_iter().collect()),
Err(e) => JsonValue::Object(vec![("err".to_string(), JsonValue::String(e.clone()))].into_iter().collect()),
};
let result = validate_function_parameter(&json, &result(option(s32()), str()));
prop_assert_eq!(result, Ok(Value::Result(
match value {
Ok(None) => Ok(Some(Box::new(Value::Option(None)))),
Ok(Some(v)) => Ok(Some(Box::new(Value::Option(Some(Box::new(Value::S32(v))))))),
Err(e) => Err(Some(Box::new(Value::String(e)))),
}
)));
}
#[test]
fn test_variant_u8tuple_string_param(first: (u32, u32), second: String, discriminator in 0i32..1i32) {
let json = match discriminator {
0 => JsonValue::Object(vec![
("first".to_string(), JsonValue::Array(vec![
JsonValue::Number(Number::from(first.0)),
JsonValue::Number(Number::from(first.1)),
])),
].into_iter().collect()),
1 => JsonValue::Object(vec![
("second".to_string(), JsonValue::String(second.clone())),
].into_iter().collect()),
_ => panic!("Invalid discriminator value"),
};
let result = validate_function_parameter(&json, &variant(vec![
case("first", tuple(vec![u32(), u32()])),
case("second", str()),
]));
prop_assert_eq!(result, Ok(Value::Variant {
case_idx: discriminator as u32,
case_value: match discriminator {
0 => Some(Box::new(Value::Tuple(vec![Value::U32(first.0), Value::U32(first.1)]))),
1 => Some(Box::new(Value::String(second))),
_ => panic!("Invalid discriminator value"),
}
}));
}
#[test]
fn test_u8_result(value: u8) {
let result = Value::U8(value);
let expected_type = u8();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_u16_result(value: u16) {
let result = Value::U16(value);
let expected_type = u16();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_u32_result(value: u32) {
let result = Value::U32(value);
let expected_type = u32();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_u64_result(value: u64) {
let result = Value::U64(value);
let expected_type = u64();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_s8_result(value: i8) {
let result = Value::S8(value);
let expected_type = s8();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_s16_result(value: i16) {
let result = Value::S16(value);
let expected_type = s16();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_s32_result(value: i32) {
let result = Value::S32(value);
let expected_type = s32();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_s64_result(value: i64) {
let result = Value::S64(value);
let expected_type = s64();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value))));
}
#[test]
fn test_f32_result(value: f32) {
let result = Value::F32(value);
let expected_type = f32();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from_f64(value as f64).unwrap())));
}
#[test]
fn test_f64_result(value: f64) {
let result = Value::F64(value);
let expected_type = f64();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from_f64(value).unwrap())));
}
#[test]
fn test_char_result(value: char) {
let result = Value::Char(value);
let expected_type = chr();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Number(Number::from(value as u32))));
}
#[test]
fn test_string_result(value: String) {
let result = Value::String(value.clone());
let expected_type = str();
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::String(value)));
}
#[test]
fn test_list_i32_result(value: Vec<i32>) {
let result = Value::List(value.iter().map(|v| Value::S32(*v)).collect());
let expected_type = list(s32());
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Array(value.into_iter().map(|v| JsonValue::Number(Number::from(v))).collect())));
}
#[test]
fn test_tuple_string_bool_result(value: (String, bool)) {
let result = Value::Tuple(vec![Value::String(value.0.clone()), Value::Bool(value.1)]);
let expected_type = tuple(vec![str(), bool()]);
let json = validate_function_result(result, &expected_type);
prop_assert_eq!(json, Ok(JsonValue::Array(vec![JsonValue::String(value.0), JsonValue::Bool(value.1)])));
}
#[test]
fn test_record_list_u8_fields(value in any::<Vec<(String, Vec<u8>)>>().prop_filter("Keys are distinct", |pairs|
pairs.iter().map(|(k, _)| k).collect::<HashSet<_>>().len() == pairs.len())
) {
let result = Value::Record(
value.iter().map(|(_, v)| Value::List(v.iter().map(|n| Value::U8(*n)).collect())).collect());
let expected_type = record(
value.iter().map(|(k, _)| field(k, list(u8()))).collect()
);
let json = validate_function_result(result, &expected_type);
let expected_json = JsonValue::Object(
value.iter().map(|(k, v)| (k.clone(), JsonValue::Array(v.iter().map(|n| JsonValue::Number(Number::from(*n))).collect()))).collect());
prop_assert_eq!(json, Ok(expected_json));
}
#[test]
fn test_flags_result(pairs in
any::<Vec<(String, bool)>>().prop_filter("Keys are distinct", |pairs|
pairs.iter().map(|(k, _)| k).collect::<HashSet<_>>().len() == pairs.len())
) {
let enabled: Vec<String> = pairs.iter().filter(|(_, v)| *v).map(|(k, _)| k.clone()).collect();
let value = Value::Flags(pairs.iter().map(|(_, v)| *v).collect());
let result = validate_function_result(value, &flags(&pairs.iter().map(|(k, _)| k.as_str()).collect::<Vec<&str>>()));
prop_assert_eq!(result, Ok(
JsonValue::Array(enabled.iter().map(|v| JsonValue::String(v.clone())).collect())
));
}
#[test]
fn test_enum_result((names, idx) in (any::<HashSet<String>>().prop_filter("Name list is non empty", |names| !names.is_empty()), any::<usize>())) {
let names: Vec<String> = names.into_iter().collect();
let idx = idx % names.len();
let value = Value::Enum(idx as u32);
let result = validate_function_result(value, &r#enum(&names.iter().map(|s| s.as_str()).collect::<Vec<&str>>()));
prop_assert_eq!(result, Ok(JsonValue::String(names[idx].clone())));
}
#[test]
fn test_option_string_result(opt: Option<String>) {
let value = Value::Option(opt.clone().map(|v| Box::new(Value::String(v))));
let result = validate_function_result(value, &option(str()));
let json = match opt {
Some(str) => Ok(JsonValue::String(str)),
None => Ok(JsonValue::Null),
};
prop_assert_eq!(result, json);
}
#[test]
fn test_variant_u8tuple_string_result(first: (u32, u32), second: String, discriminator in 0i32..1i32) {
let value = Value::Variant {
case_idx: discriminator as u32,
case_value: match discriminator {
0 => Some(Box::new(Value::Tuple(vec![Value::U32(first.0), Value::U32(first.1)]))),
1 => Some(Box::new(Value::String(second.clone()))),
_ => panic!("Invalid discriminator value"),
}
};
let result = validate_function_result(value, &variant(vec![
case("first", tuple(vec![u32(), u32()])),
case("second", str()),
]));
let json = match discriminator {
0 => JsonValue::Object(vec![
("first".to_string(), JsonValue::Array(vec![
JsonValue::Number(Number::from(first.0)),
JsonValue::Number(Number::from(first.1)),
])),
].into_iter().collect()),
1 => JsonValue::Object(vec![
("second".to_string(), JsonValue::String(second)),
].into_iter().collect()),
_ => panic!("Invalid discriminator value"),
};
prop_assert_eq!(result, Ok(json));
}
}
#[test]
fn json_null_works_as_none() {
let json = JsonValue::Null;
let result = validate_function_parameter(&json, &option(str()));
assert_eq!(result, Ok(Value::Option(None)));
}
#[test]
fn missing_field_works_as_none() {
let json = JsonValue::Object(
vec![("x".to_string(), JsonValue::String("a".to_string()))]
.into_iter()
.collect(),
);
let result = validate_function_parameter(
&json,
&record(vec![field("x", str()), field("y", option(str()))]),
);
assert_eq!(
result,
Ok(Value::Record(vec![
Value::String("a".to_string()),
Value::Option(None),
]))
);
}
}