From da100669605baae8b19eeff458f01783358ea449 Mon Sep 17 00:00:00 2001 From: Afsal Thaj Date: Sat, 30 Mar 2024 16:31:25 +1100 Subject: [PATCH 1/6] Avoid new data types and simplify logic --- wasm-rpc/src/json.rs | 151 +++++++++------------------ wasm-rpc/src/type_annotated_value.rs | 151 +++++++++++++++++---------- 2 files changed, 144 insertions(+), 158 deletions(-) diff --git a/wasm-rpc/src/json.rs b/wasm-rpc/src/json.rs index 68cb34c7..fa4cf8fa 100644 --- a/wasm-rpc/src/json.rs +++ b/wasm-rpc/src/json.rs @@ -15,41 +15,32 @@ use golem_wasm_ast::analysis::{AnalysedFunctionParameter, AnalysedFunctionResult}; use serde_json::{Number, Value as JsonValue}; -use crate::{TypeAnnotatedValue, TypeAnnotatedValueResult, Value}; +use crate::{TypeAnnotatedValue, Value}; pub fn function_parameters( value: &JsonValue, expected_parameters: &[AnalysedFunctionParameter], ) -> Result, Vec> { - let parameters = value - .as_array() - .ok_or(vec!["Expecting an array for fn_params".to_string()])?; + let typed_values = TypeAnnotatedValue::from_function_parameters(value, expected_parameters)?; - let mut results = vec![]; let mut errors = vec![]; + let mut values = vec![]; - if parameters.len() == expected_parameters.len() { - for (json, fp) in parameters.iter().zip(expected_parameters.iter()) { - match TypeAnnotatedValue::from_json_value(json, &fp.typ) { - Ok(result) => match Value::try_from(result) { - Ok(value) => results.push(value), - Err(err) => errors.push(err), - }, - Err(err) => errors.extend(err), + for typed_value in typed_values { + match Value::try_from(typed_value) { + Ok(value) => { + values.push(value); + } + Err(err) => { + errors.push(err); } } + } - if errors.is_empty() { - Ok(results) - } else { - Err(errors) - } + if errors.is_empty() { + Ok(values) } else { - Err(vec![format!( - "Unexpected number of parameters (got {}, expected: {})", - parameters.len(), - expected_parameters.len() - )]) + Err(errors) } } @@ -57,96 +48,57 @@ pub fn function_result( values: Vec, expected_types: &[AnalysedFunctionResult], ) -> Result> { - TypeAnnotatedValueResult::from_values(values, expected_types).map(|result| match result { - TypeAnnotatedValueResult::WithoutNames(values) => JsonValue::Array( - values - .into_iter() - .map(|v| JsonFunctionResult::from(v).0) - .collect(), - ), - TypeAnnotatedValueResult::WithNames(values) => { - let mut map = serde_json::Map::new(); - for (name, value) in values { - map.insert(name, JsonFunctionResult::from(value).0); - } - JsonValue::Object(map) - } - }) + TypeAnnotatedValue::from_function_results(values, expected_types) + .map(|result| Json::from(result).0) } -pub struct JsonFunctionResult(pub serde_json::Value); +pub struct Json(pub serde_json::Value); -impl From for JsonFunctionResult { +impl From for Json { fn from(value: TypeAnnotatedValue) -> Self { match value { - TypeAnnotatedValue::Bool(bool) => JsonFunctionResult(serde_json::Value::Bool(bool)), - TypeAnnotatedValue::Flags { typ: _, values } => JsonFunctionResult(JsonValue::Array( + TypeAnnotatedValue::Bool(bool) => Json(serde_json::Value::Bool(bool)), + TypeAnnotatedValue::Flags { typ: _, values } => Json(JsonValue::Array( values.into_iter().map(JsonValue::String).collect(), )), - TypeAnnotatedValue::S8(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } - TypeAnnotatedValue::U8(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } - TypeAnnotatedValue::S16(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } - TypeAnnotatedValue::U16(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } - TypeAnnotatedValue::S32(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } - TypeAnnotatedValue::U32(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } - TypeAnnotatedValue::S64(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } - TypeAnnotatedValue::U64(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value))) - } + TypeAnnotatedValue::S8(value) => Json(JsonValue::Number(Number::from(value))), + TypeAnnotatedValue::U8(value) => Json(JsonValue::Number(Number::from(value))), + TypeAnnotatedValue::S16(value) => Json(JsonValue::Number(Number::from(value))), + TypeAnnotatedValue::U16(value) => Json(JsonValue::Number(Number::from(value))), + TypeAnnotatedValue::S32(value) => Json(JsonValue::Number(Number::from(value))), + TypeAnnotatedValue::U32(value) => Json(JsonValue::Number(Number::from(value))), + TypeAnnotatedValue::S64(value) => Json(JsonValue::Number(Number::from(value))), + TypeAnnotatedValue::U64(value) => Json(JsonValue::Number(Number::from(value))), TypeAnnotatedValue::F32(value) => { - JsonFunctionResult(JsonValue::Number(Number::from_f64(value as f64).unwrap())) + Json(JsonValue::Number(Number::from_f64(value as f64).unwrap())) } TypeAnnotatedValue::F64(value) => { - JsonFunctionResult(JsonValue::Number(Number::from_f64(value).unwrap())) - } - TypeAnnotatedValue::Chr(value) => { - JsonFunctionResult(JsonValue::Number(Number::from(value as u32))) - } - TypeAnnotatedValue::Str(value) => JsonFunctionResult(JsonValue::String(value)), - TypeAnnotatedValue::Enum { typ: _, value } => { - JsonFunctionResult(JsonValue::String(value)) + Json(JsonValue::Number(Number::from_f64(value).unwrap())) } + TypeAnnotatedValue::Chr(value) => Json(JsonValue::Number(Number::from(value as u32))), + TypeAnnotatedValue::Str(value) => Json(JsonValue::String(value)), + TypeAnnotatedValue::Enum { typ: _, value } => Json(JsonValue::String(value)), TypeAnnotatedValue::Option { typ: _, value } => match value { - Some(value) => JsonFunctionResult::from(*value), - None => JsonFunctionResult(JsonValue::Null), + Some(value) => Json::from(*value), + None => Json(JsonValue::Null), }, TypeAnnotatedValue::Tuple { typ: _, value } => { - let values: Vec = value - .into_iter() - .map(JsonFunctionResult::from) - .map(|v| v.0) - .collect(); - JsonFunctionResult(JsonValue::Array(values)) + let values: Vec = + value.into_iter().map(Json::from).map(|v| v.0).collect(); + Json(JsonValue::Array(values)) } TypeAnnotatedValue::List { typ: _, values } => { - let values: Vec = values - .into_iter() - .map(JsonFunctionResult::from) - .map(|v| v.0) - .collect(); - JsonFunctionResult(JsonValue::Array(values)) + let values: Vec = + values.into_iter().map(Json::from).map(|v| v.0).collect(); + Json(JsonValue::Array(values)) } TypeAnnotatedValue::Record { typ: _, value } => { let mut map = serde_json::Map::new(); for (key, value) in value { - map.insert(key, JsonFunctionResult::from(value).0); + map.insert(key, Json::from(value).0); } - JsonFunctionResult(JsonValue::Object(map)) + Json(JsonValue::Object(map)) } TypeAnnotatedValue::Variant { @@ -158,11 +110,11 @@ impl From for JsonFunctionResult { map.insert( case_name, case_value - .map(|x| JsonFunctionResult::from(*x)) + .map(|x| Json::from(*x)) .map(|v| v.0) .unwrap_or(JsonValue::Null), ); - JsonFunctionResult(JsonValue::Object(map)) + Json(JsonValue::Object(map)) } TypeAnnotatedValue::Result { @@ -176,7 +128,7 @@ impl From for JsonFunctionResult { map.insert( "ok".to_string(), ok_value - .map(|x| JsonFunctionResult::from(*x)) + .map(|x| Json::from(*x)) .map(|v| v.0) .unwrap_or(JsonValue::Null), ); @@ -185,13 +137,13 @@ impl From for JsonFunctionResult { map.insert( "err".to_string(), err_value - .map(|x| JsonFunctionResult::from(*x)) + .map(|x| Json::from(*x)) .map(|v| v.0) .unwrap_or(JsonValue::Null), ); } } - JsonFunctionResult(JsonValue::Object(map)) + Json(JsonValue::Object(map)) } TypeAnnotatedValue::Handle { @@ -199,14 +151,14 @@ impl From for JsonFunctionResult { resource_mode: _, uri, resource_id, - } => JsonFunctionResult(JsonValue::String(format!("{}/{}", uri.value, resource_id))), + } => Json(JsonValue::String(format!("{}/{}", uri.value, resource_id))), } } } #[cfg(test)] mod tests { - use crate::json::JsonFunctionResult; + use crate::json::Json; use crate::{TypeAnnotatedValue, Value}; use golem_wasm_ast::analysis::AnalysedType; use proptest::prelude::*; @@ -217,8 +169,7 @@ mod tests { val: Value, expected_type: &AnalysedType, ) -> Result> { - TypeAnnotatedValue::from_value(&val, expected_type) - .map(|result| JsonFunctionResult::from(result).0) + TypeAnnotatedValue::from_value(&val, expected_type).map(|result| Json::from(result).0) } fn validate_function_parameter( diff --git a/wasm-rpc/src/type_annotated_value.rs b/wasm-rpc/src/type_annotated_value.rs index cf00c025..38ffa5ce 100644 --- a/wasm-rpc/src/type_annotated_value.rs +++ b/wasm-rpc/src/type_annotated_value.rs @@ -4,7 +4,8 @@ use std::convert::{TryFrom, TryInto}; use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; use golem_wasm_ast::analysis::{ - AnalysedFunctionResult, AnalysedResourceId, AnalysedResourceMode, AnalysedType, + AnalysedFunctionParameter, AnalysedFunctionResult, AnalysedResourceId, AnalysedResourceMode, + AnalysedType, }; use crate::{Uri, Value, WitValue}; @@ -14,7 +15,7 @@ use serde_json::value::Value as JsonValue; use std::ops::Deref; use std::str::FromStr; -#[derive(Clone, Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum TypeAnnotatedValue { Bool(bool), S8(i8), @@ -72,6 +73,96 @@ pub enum TypeAnnotatedValue { } impl TypeAnnotatedValue { + pub fn from_function_parameters( + value: &JsonValue, + expected_parameters: &[AnalysedFunctionParameter], + ) -> Result, Vec> { + let parameters = value + .as_array() + .ok_or(vec!["Expecting an array for fn_params".to_string()])?; + + let mut results = vec![]; + let mut errors = vec![]; + + if parameters.len() == expected_parameters.len() { + for (json, fp) in parameters.iter().zip(expected_parameters.iter()) { + match TypeAnnotatedValue::from_json_value(json, &fp.typ) { + Ok(result) => results.push(result), + Err(err) => errors.extend(err), + } + } + + if errors.is_empty() { + Ok(results) + } else { + Err(errors) + } + } else { + Err(vec![format!( + "Unexpected number of parameters (got {}, expected: {})", + parameters.len(), + expected_parameters.len() + )]) + } + } + + pub fn from_function_results( + values: Vec, + expected_types: &[AnalysedFunctionResult], + ) -> Result> { + if values.len() != expected_types.len() { + Err(vec![format!( + "Unexpected number of result values (got {}, expected: {})", + values.len(), + expected_types.len() + )]) + } else { + let mut results = vec![]; + let mut errors = vec![]; + + for (value, expected) in values.into_iter().zip(expected_types.iter()) { + let result = TypeAnnotatedValue::from_value(&value, &expected.typ); + + match result { + Ok(value) => { + results.push((value, expected.typ.clone())); + } + Err(err) => errors.extend(err), + } + } + + let all_without_names = expected_types.iter().all(|t| t.name.is_none()); + + if all_without_names { + Ok(TypeAnnotatedValue::Tuple { + typ: results.iter().map(|(_, typ)| typ.clone()).collect(), + value: results.into_iter().map(|(v, _)| v).collect(), + }) + } else { + let mut named_typs: Vec<(String, AnalysedType)> = vec![]; + let mut named_values: Vec<(String, TypeAnnotatedValue)> = vec![]; + + for (index, ((typed_value, typ), expected)) in + results.into_iter().zip(expected_types.iter()).enumerate() + { + let name = if let Some(name) = &expected.name { + name.clone() + } else { + index.to_string() + }; + + named_typs.push((name.clone(), typ.clone())); + named_values.push((name, typed_value)); + } + + Ok(TypeAnnotatedValue::Record { + typ: named_typs, + value: named_values, + }) + } + } + } + pub fn from_value( val: &Value, analysed_type: &AnalysedType, @@ -946,62 +1037,6 @@ impl TryFrom for WitValue { } } -pub enum TypeAnnotatedValueResult { - WithoutNames(Vec), - WithNames(Vec<(String, TypeAnnotatedValue)>), -} - -impl TypeAnnotatedValueResult { - pub fn from_values( - values: Vec, - expected_types: &[AnalysedFunctionResult], - ) -> Result> { - if values.len() != expected_types.len() { - Err(vec![format!( - "Unexpected number of result values (got {}, expected: {})", - values.len(), - expected_types.len() - )]) - } else { - let mut results = vec![]; - let mut errors = vec![]; - - for (value, expected) in values.into_iter().zip(expected_types.iter()) { - let result = TypeAnnotatedValue::from_value(&value, &expected.typ); - - match result { - Ok(value) => results.push(value), - Err(err) => errors.extend(err), - } - } - - let all_without_names = expected_types.iter().all(|t| t.name.is_none()); - - if all_without_names { - Ok(TypeAnnotatedValueResult::WithoutNames(results)) - } else { - let mapped_values = results - .iter() - .zip(expected_types.iter()) - .enumerate() - .map(|(idx, (type_annotated_value, result_def))| { - ( - if let Some(name) = &result_def.name { - name.clone() - } else { - idx.to_string() - }, - type_annotated_value.clone(), - ) - }) - .collect::>(); - - Ok(TypeAnnotatedValueResult::WithNames(mapped_values)) - } - } - } -} - impl TryFrom for Value { type Error = String; From 10763a242a91bc60a72d38986fa610fd0f6242fe Mon Sep 17 00:00:00 2001 From: Afsal Thaj Date: Sat, 30 Mar 2024 17:41:34 +1100 Subject: [PATCH 2/6] Impl Display for Json --- wasm-rpc/src/json.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wasm-rpc/src/json.rs b/wasm-rpc/src/json.rs index fa4cf8fa..a4e4f844 100644 --- a/wasm-rpc/src/json.rs +++ b/wasm-rpc/src/json.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; use golem_wasm_ast::analysis::{AnalysedFunctionParameter, AnalysedFunctionResult}; use serde_json::{Number, Value as JsonValue}; @@ -54,6 +55,12 @@ pub fn function_result( pub struct Json(pub serde_json::Value); +impl Display for Json { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + impl From for Json { fn from(value: TypeAnnotatedValue) -> Self { match value { From 1451accd80c15f41ec0b9ff7bc9c533c611a81c8 Mon Sep 17 00:00:00 2001 From: Afsal Thaj Date: Sat, 30 Mar 2024 19:17:25 +1100 Subject: [PATCH 3/6] Allow reference to type annotated value to obtain its type --- wasm-rpc/src/json.rs | 2 +- wasm-rpc/src/text.rs | 2 +- wasm-rpc/src/type_annotated_value.rs | 32 ++++++++++++++++++---------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/wasm-rpc/src/json.rs b/wasm-rpc/src/json.rs index a4e4f844..681ae12c 100644 --- a/wasm-rpc/src/json.rs +++ b/wasm-rpc/src/json.rs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::Display; use golem_wasm_ast::analysis::{AnalysedFunctionParameter, AnalysedFunctionResult}; use serde_json::{Number, Value as JsonValue}; +use std::fmt::Display; use crate::{TypeAnnotatedValue, Value}; diff --git a/wasm-rpc/src/text.rs b/wasm-rpc/src/text.rs index fdb198f0..1993a899 100644 --- a/wasm-rpc/src/text.rs +++ b/wasm-rpc/src/text.rs @@ -120,7 +120,7 @@ impl WasmType for AnalysedType { impl WasmValue for TypeAnnotatedValue { type Type = AnalysedType; fn ty(&self) -> Self::Type { - AnalysedType(golem_wasm_ast::analysis::AnalysedType::from(self.clone())) + AnalysedType(golem_wasm_ast::analysis::AnalysedType::from(self)) } fn make_bool(val: bool) -> Self { diff --git a/wasm-rpc/src/type_annotated_value.rs b/wasm-rpc/src/type_annotated_value.rs index 38ffa5ce..df98b889 100644 --- a/wasm-rpc/src/type_annotated_value.rs +++ b/wasm-rpc/src/type_annotated_value.rs @@ -988,8 +988,8 @@ fn get_string(input_json: &JsonValue) -> Result> { } } -impl From for AnalysedType { - fn from(value: TypeAnnotatedValue) -> Self { +impl From<&TypeAnnotatedValue> for AnalysedType { + fn from(value: &TypeAnnotatedValue) -> Self { match value { TypeAnnotatedValue::Bool(_) => AnalysedType::Bool, TypeAnnotatedValue::S8(_) => AnalysedType::S8, @@ -1004,28 +1004,38 @@ impl From for AnalysedType { TypeAnnotatedValue::F64(_) => AnalysedType::F64, TypeAnnotatedValue::Chr(_) => AnalysedType::Chr, TypeAnnotatedValue::Str(_) => AnalysedType::Str, - TypeAnnotatedValue::List { typ, values: _ } => AnalysedType::List(Box::new(typ)), - TypeAnnotatedValue::Tuple { typ, value: _ } => AnalysedType::Tuple(typ), - TypeAnnotatedValue::Record { typ, value: _ } => AnalysedType::Record(typ), - TypeAnnotatedValue::Flags { typ, values: _ } => AnalysedType::Flags(typ), - TypeAnnotatedValue::Enum { typ, value: _ } => AnalysedType::Enum(typ), - TypeAnnotatedValue::Option { typ, value: _ } => AnalysedType::Option(Box::new(typ)), + TypeAnnotatedValue::List { typ, values: _ } => { + AnalysedType::List(Box::new(typ.clone())) + } + TypeAnnotatedValue::Tuple { typ, value: _ } => AnalysedType::Tuple(typ.clone()), + TypeAnnotatedValue::Record { typ, value: _ } => AnalysedType::Record(typ.clone()), + TypeAnnotatedValue::Flags { typ, values: _ } => AnalysedType::Flags(typ.clone()), + TypeAnnotatedValue::Enum { typ, value: _ } => AnalysedType::Enum(typ.clone()), + TypeAnnotatedValue::Option { typ, value: _ } => { + AnalysedType::Option(Box::new(typ.clone())) + } TypeAnnotatedValue::Result { ok, error, value: _, - } => AnalysedType::Result { ok, error }, + } => AnalysedType::Result { + ok: ok.clone(), + error: error.clone(), + }, TypeAnnotatedValue::Handle { id, resource_mode, uri: _, resource_id: _, - } => AnalysedType::Resource { id, resource_mode }, + } => AnalysedType::Resource { + id: id.clone(), + resource_mode: resource_mode.clone(), + }, TypeAnnotatedValue::Variant { typ, case_name: _, case_value: _, - } => AnalysedType::Variant(typ), + } => AnalysedType::Variant(typ.clone()), } } } From 512ce6d6a105c78cf0836c9e3eaa8f0fada7416d Mon Sep 17 00:00:00 2001 From: Afsal Thaj Date: Sat, 30 Mar 2024 19:39:16 +1100 Subject: [PATCH 4/6] Cleanup --- wasm-rpc/src/json.rs | 637 +++++++++++++++++++++++++- wasm-rpc/src/text.rs | 2 +- wasm-rpc/src/type_annotated_value.rs | 661 +-------------------------- 3 files changed, 632 insertions(+), 668 deletions(-) diff --git a/wasm-rpc/src/json.rs b/wasm-rpc/src/json.rs index 681ae12c..81fde58c 100644 --- a/wasm-rpc/src/json.rs +++ b/wasm-rpc/src/json.rs @@ -12,17 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -use golem_wasm_ast::analysis::{AnalysedFunctionParameter, AnalysedFunctionResult}; +use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; +use golem_wasm_ast::analysis::{ + AnalysedFunctionParameter, AnalysedFunctionResult, AnalysedResourceId, AnalysedResourceMode, + AnalysedType, +}; use serde_json::{Number, Value as JsonValue}; +use std::collections::HashMap; use std::fmt::Display; +use std::str::FromStr; -use crate::{TypeAnnotatedValue, Value}; +use crate::{TypeAnnotatedValue, Uri, Value}; pub fn function_parameters( value: &JsonValue, expected_parameters: &[AnalysedFunctionParameter], ) -> Result, Vec> { - let typed_values = TypeAnnotatedValue::from_function_parameters(value, expected_parameters)?; + let typed_values = function_parameters_typed(value, expected_parameters)?; let mut errors = vec![]; let mut values = vec![]; @@ -45,12 +51,629 @@ pub fn function_parameters( } } +pub fn function_parameters_typed( + value: &JsonValue, + expected_parameters: &[AnalysedFunctionParameter], +) -> Result, Vec> { + let parameters = value + .as_array() + .ok_or(vec!["Expecting an array for fn_params".to_string()])?; + + let mut results = vec![]; + let mut errors = vec![]; + + if parameters.len() == expected_parameters.len() { + for (json, fp) in parameters.iter().zip(expected_parameters.iter()) { + match get_typed_value_from_json(json, &fp.typ) { + Ok(result) => results.push(result), + Err(err) => errors.extend(err), + } + } + + if errors.is_empty() { + Ok(results) + } else { + Err(errors) + } + } else { + Err(vec![format!( + "Unexpected number of parameters (got {}, expected: {})", + parameters.len(), + expected_parameters.len() + )]) + } +} + pub fn function_result( values: Vec, expected_types: &[AnalysedFunctionResult], ) -> Result> { - TypeAnnotatedValue::from_function_results(values, expected_types) - .map(|result| Json::from(result).0) + function_result_typed(values, expected_types).map(|result| Json::from(result).0) +} + +pub fn function_result_typed( + values: Vec, + expected_types: &[AnalysedFunctionResult], +) -> Result> { + if values.len() != expected_types.len() { + Err(vec![format!( + "Unexpected number of result values (got {}, expected: {})", + values.len(), + expected_types.len() + )]) + } else { + let mut results = vec![]; + let mut errors = vec![]; + + for (value, expected) in values.into_iter().zip(expected_types.iter()) { + let result = TypeAnnotatedValue::from_value(&value, &expected.typ); + + match result { + Ok(value) => { + results.push((value, expected.typ.clone())); + } + Err(err) => errors.extend(err), + } + } + + let all_without_names = expected_types.iter().all(|t| t.name.is_none()); + + if all_without_names { + Ok(TypeAnnotatedValue::Tuple { + typ: results.iter().map(|(_, typ)| typ.clone()).collect(), + value: results.into_iter().map(|(v, _)| v).collect(), + }) + } else { + let mut named_typs: Vec<(String, AnalysedType)> = vec![]; + let mut named_values: Vec<(String, TypeAnnotatedValue)> = vec![]; + + for (index, ((typed_value, typ), expected)) in + results.into_iter().zip(expected_types.iter()).enumerate() + { + let name = if let Some(name) = &expected.name { + name.clone() + } else { + index.to_string() + }; + + named_typs.push((name.clone(), typ.clone())); + named_values.push((name, typed_value)); + } + + Ok(TypeAnnotatedValue::Record { + typ: named_typs, + value: named_values, + }) + } + } +} + +pub fn get_typed_value_from_json( + json_val: &JsonValue, + analysed_type: &AnalysedType, +) -> Result> { + match analysed_type { + 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(names) => get_enum(json_val, names), + AnalysedType::Flags(names) => get_flag(json_val, names), + AnalysedType::List(elem) => get_list(json_val, elem), + AnalysedType::Option(elem) => get_option(json_val, elem), + AnalysedType::Result { ok, error } => get_result(json_val, ok, error), + AnalysedType::Record(fields) => get_record(json_val, fields), + AnalysedType::Variant(cases) => get_variant(json_val, cases), + AnalysedType::Tuple(elems) => get_tuple(json_val, elems), + AnalysedType::Resource { id, resource_mode } => { + get_handle(json_val, id.clone(), resource_mode.clone()) + } + } +} + +fn get_bool(json: &JsonValue) -> Result> { + match json { + JsonValue::Bool(bool_val) => Ok(TypeAnnotatedValue::Bool(*bool_val)), + _ => { + let type_description = type_description(json); + Err(vec![format!( + "Expected function parameter type is Boolean. But found {}", + type_description + )]) + } + } +} + +fn get_s8(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::S8(num.to_i8().expect("Failed to convert BigDecimal to i8"))) +} + +fn get_u8(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::U8(num.to_u8().expect("Failed to convert BigDecimal to u8"))) +} + +fn get_s16(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::S16(num.to_i16().expect("Failed to convert BigDecimal to i16"))) +} + +fn get_u16(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::U16(num.to_u16().expect("Failed to convert BigDecimal to u16"))) +} + +fn get_s32(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::S32(num.to_i32().expect("Failed to convert BigDecimal to i32"))) +} + +fn get_u32(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::U32(num.to_u32().expect("Failed to convert BigDecimal to u32"))) +} + +fn get_s64(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::S64(num.to_i64().expect("Failed to convert BigDecimal to i64"))) +} + +fn get_f32(json: &JsonValue) -> Result> { + 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"), + ) + .map(|num| TypeAnnotatedValue::F32(num.to_f32().expect("Failed to convert BigDecimal to f32"))) +} + +fn get_f64(json_val: &JsonValue) -> Result> { + let num = get_big_decimal(json_val)?; + let value = TypeAnnotatedValue::F64( + num.to_string() + .parse() + .map_err(|err| vec![format!("Failed to parse f64: {}", err)])?, + ); + Ok(value) +} + +fn get_string(json: &JsonValue) -> Result> { + if let Some(str_value) = json.as_str() { + // If the JSON value is a string, return it + Ok(TypeAnnotatedValue::Str(str_value.to_string())) + } else { + // If the JSON value is not a string, return an error with type information + let type_description = type_description(json); + Err(vec![format!( + "Expected function parameter type is String. But found {}", + type_description + )]) + } +} + +fn get_char(json: &JsonValue) -> Result> { + 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 { + char::from_u32(num_u64 as u32) + .ok_or(vec![format!( + "The value {} is not a valid unicode character", + num_u64 + )]) + .map(TypeAnnotatedValue::Chr) + } + } else { + let type_description = type_description(json); + + Err(vec![format!( + "Expected function parameter type is Char. But found {}", + type_description + )]) + } +} + +fn get_tuple( + input_json: &JsonValue, + types: &[AnalysedType], +) -> Result> { + 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 = vec![]; + let mut vals: Vec = vec![]; + + for (json, tpe) in json_array.iter().zip(types.iter()) { + match get_typed_value_from_json(json, tpe) { + Ok(result) => vals.push(result), + Err(errs) => errors.extend(errs), + } + } + + if errors.is_empty() { + Ok(TypeAnnotatedValue::Tuple { + typ: types.to_vec(), + value: vals, + }) + } else { + Err(errors) + } +} + +fn get_option( + input_json: &JsonValue, + tpe: &AnalysedType, +) -> Result> { + match input_json.as_null() { + Some(_) => Ok(TypeAnnotatedValue::Option { + typ: tpe.clone(), + value: None, + }), + + None => { + get_typed_value_from_json(input_json, tpe).map(|result| TypeAnnotatedValue::Option { + typ: tpe.clone(), + value: Some(Box::new(result)), + }) + } + } +} + +fn get_list(input_json: &JsonValue, tpe: &AnalysedType) -> Result> { + let json_array = input_json + .as_array() + .ok_or(vec![format!("Input {} is not an array", input_json)])?; + + let mut errors: Vec = vec![]; + let mut vals: Vec = vec![]; + + for json in json_array { + match get_typed_value_from_json(json, tpe) { + Ok(result) => vals.push(result), + Err(errs) => errors.extend(errs), + } + } + + if errors.is_empty() { + Ok(TypeAnnotatedValue::List { + typ: tpe.clone(), + values: vals, + }) + } else { + Err(errors) + } +} + +fn get_enum(input_json: &JsonValue, names: &[String]) -> Result> { + let input_enum_value = input_json + .as_str() + .ok_or(vec![format!("Input {} is not string", input_json)])?; + + if names.contains(&input_enum_value.to_string()) { + Ok(TypeAnnotatedValue::Enum { + typ: names.to_vec(), + value: input_enum_value.to_string(), + }) + } else { + Err(vec![format!( + "Invalid input {}. Valid values are {}", + input_enum_value, + names.join(",") + )]) + } +} + +#[allow(clippy::type_complexity)] +fn get_result( + input_json: &JsonValue, + ok_type: &Option>, + err_type: &Option>, +) -> Result> { + fn validate( + typ: &Option>, + input_json: &JsonValue, + ) -> Result>, Vec> { + if let Some(typ) = typ { + get_typed_value_from_json(input_json, typ).map(|v| Some(Box::new(v))) + } 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) => Ok(TypeAnnotatedValue::Result { + ok: ok_type.clone(), + error: err_type.clone(), + value: Ok(validate(ok_type, value)?), + }), + None => match input_json.get("err") { + Some(value) => Ok(TypeAnnotatedValue::Result { + ok: ok_type.clone(), + error: err_type.clone(), + value: Err(validate(err_type, value)?), + }), + None => Err(vec![ + "Failed to retrieve either ok value or err value".to_string() + ]), + }, + } +} + +fn get_record( + input_json: &JsonValue, + name_type_pairs: &[(String, AnalysedType)], +) -> Result> { + let json_map = input_json.as_object().ok_or(vec![format!( + "The input {} is not a json object", + input_json + )])?; + + let mut errors: Vec = vec![]; + let mut vals: Vec<(String, TypeAnnotatedValue)> = vec![]; + + for (name, tpe) in name_type_pairs { + if let Some(json_value) = json_map.get(name) { + match get_typed_value_from_json(json_value, tpe) { + Ok(result) => vals.push((name.clone(), result)), + Err(value_errors) => errors.extend( + value_errors + .iter() + .map(|err| format!("Invalid value for the key {}. Error: {}", name, err)) + .collect::>(), + ), + } + } else { + match tpe { + AnalysedType::Option(_) => vals.push(( + name.clone(), + TypeAnnotatedValue::Option { + typ: tpe.clone(), + value: None, + }, + )), + _ => errors.push(format!("Key '{}' not found in json_map", name)), + } + } + } + + if errors.is_empty() { + Ok(TypeAnnotatedValue::Record { + typ: name_type_pairs.to_vec(), + value: vals, + }) + } else { + Err(errors) + } +} + +fn get_flag(input_json: &JsonValue, names: &[String]) -> Result> { + let json_array = input_json + .as_array() + .ok_or(vec![format!("Input {} is not an array", input_json)])?; + + let mut errors: Vec = vec![]; + let mut vals: Vec = vec![]; + + 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.push(flag); + } else { + errors.push(format!( + "Invalid input {}. Valid values are {}", + flag, + names.join(",") + )); + } + } + + if errors.is_empty() { + Ok(TypeAnnotatedValue::Flags { + typ: names.to_vec(), + values: vals, + }) + } else { + Err(errors) + } +} + +fn get_variant( + input_json: &JsonValue, + types: &[(String, Option)], +) -> Result> { + let mut possible_mapping_indexed: HashMap<&String, &Option> = HashMap::new(); + + for (name, 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()) + }?; + + match possible_mapping_indexed.get(key) { + Some(Some(tpe)) => { + get_typed_value_from_json(json, tpe).map(|result| TypeAnnotatedValue::Variant { + typ: types.to_vec(), + case_name: key.clone(), + case_value: Some(Box::new(result)), + }) + } + Some(None) if json.is_null() => Ok(TypeAnnotatedValue::Variant { + typ: types.to_vec(), + case_name: key.clone(), + case_value: None, + }), + 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> { + match value.as_str() { + Some(str) => { + // not assuming much about the url format, just checking it ends with a / + 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(TypeAnnotatedValue::Handle { id, resource_mode, uri: Uri { value: uri }, resource_id }) + } + Err(err) => { + Err(vec![format!("Failed to parse resource-id section of the handle value: {}", err)]) + } + } + } else { + Err(vec![format!( + "Expected function parameter type is Handle, represented by a worker-url/resource-id string. But found {}", + str + )]) + } + } + None => Err(vec![format!( + "Expected function parameter type is Handle, represented by a worker-url/resource-id string. But 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(_) => "Array", + JsonValue::Object(_) => "Object", + } +} + +fn ensure_range( + value: &JsonValue, + min: BigDecimal, + max: BigDecimal, +) -> Result> { + 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> { + 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 function parameter type is BigDecimal. But found {}", + type_description + )]) + } + } +} + +fn get_u64(value: &JsonValue) -> Result> { + match value { + JsonValue::Number(num) => { + if let Some(u64) = num.as_u64() { + Ok(TypeAnnotatedValue::U64(u64)) + } else { + Err(vec![format!("Cannot convert {} to u64", num)]) + } + } + _ => { + let type_description = type_description(value); + Err(vec![format!( + "Expected function parameter type is u64. But found {}", + type_description + )]) + } + } } pub struct Json(pub serde_json::Value); @@ -165,7 +788,7 @@ impl From for Json { #[cfg(test)] mod tests { - use crate::json::Json; + use crate::json::{get_typed_value_from_json, Json}; use crate::{TypeAnnotatedValue, Value}; use golem_wasm_ast::analysis::AnalysedType; use proptest::prelude::*; @@ -183,7 +806,7 @@ mod tests { json: &JsonValue, expected_type: &AnalysedType, ) -> Result> { - match TypeAnnotatedValue::from_json_value(json, expected_type) { + match get_typed_value_from_json(json, expected_type) { Ok(result) => match Value::try_from(result) { Ok(value) => Ok(value), Err(err) => Err(vec![err]), diff --git a/wasm-rpc/src/text.rs b/wasm-rpc/src/text.rs index 1993a899..f5700949 100644 --- a/wasm-rpc/src/text.rs +++ b/wasm-rpc/src/text.rs @@ -528,7 +528,7 @@ mod tests { let s = to_string(&typed_value).unwrap(); let round_trip_value: TypeAnnotatedValue = - from_str(&super::AnalysedType(AnalysedType::from(typed_value)), &s).unwrap(); + from_str(&super::AnalysedType(AnalysedType::from(&typed_value)), &s).unwrap(); let result: Value = round_trip_value.try_into().unwrap(); assert_eq!(value, result); } diff --git a/wasm-rpc/src/type_annotated_value.rs b/wasm-rpc/src/type_annotated_value.rs index df98b889..fcc5868b 100644 --- a/wasm-rpc/src/type_annotated_value.rs +++ b/wasm-rpc/src/type_annotated_value.rs @@ -1,19 +1,10 @@ -use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; -use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; - -use golem_wasm_ast::analysis::{ - AnalysedFunctionParameter, AnalysedFunctionResult, AnalysedResourceId, AnalysedResourceMode, - AnalysedType, -}; +use golem_wasm_ast::analysis::{AnalysedResourceId, AnalysedResourceMode, AnalysedType}; use crate::{Uri, Value, WitValue}; -use serde_json::value::Value as JsonValue; - use std::ops::Deref; -use std::str::FromStr; #[derive(Debug, Clone, PartialEq)] pub enum TypeAnnotatedValue { @@ -73,96 +64,6 @@ pub enum TypeAnnotatedValue { } impl TypeAnnotatedValue { - pub fn from_function_parameters( - value: &JsonValue, - expected_parameters: &[AnalysedFunctionParameter], - ) -> Result, Vec> { - let parameters = value - .as_array() - .ok_or(vec!["Expecting an array for fn_params".to_string()])?; - - let mut results = vec![]; - let mut errors = vec![]; - - if parameters.len() == expected_parameters.len() { - for (json, fp) in parameters.iter().zip(expected_parameters.iter()) { - match TypeAnnotatedValue::from_json_value(json, &fp.typ) { - Ok(result) => results.push(result), - Err(err) => errors.extend(err), - } - } - - if errors.is_empty() { - Ok(results) - } else { - Err(errors) - } - } else { - Err(vec![format!( - "Unexpected number of parameters (got {}, expected: {})", - parameters.len(), - expected_parameters.len() - )]) - } - } - - pub fn from_function_results( - values: Vec, - expected_types: &[AnalysedFunctionResult], - ) -> Result> { - if values.len() != expected_types.len() { - Err(vec![format!( - "Unexpected number of result values (got {}, expected: {})", - values.len(), - expected_types.len() - )]) - } else { - let mut results = vec![]; - let mut errors = vec![]; - - for (value, expected) in values.into_iter().zip(expected_types.iter()) { - let result = TypeAnnotatedValue::from_value(&value, &expected.typ); - - match result { - Ok(value) => { - results.push((value, expected.typ.clone())); - } - Err(err) => errors.extend(err), - } - } - - let all_without_names = expected_types.iter().all(|t| t.name.is_none()); - - if all_without_names { - Ok(TypeAnnotatedValue::Tuple { - typ: results.iter().map(|(_, typ)| typ.clone()).collect(), - value: results.into_iter().map(|(v, _)| v).collect(), - }) - } else { - let mut named_typs: Vec<(String, AnalysedType)> = vec![]; - let mut named_values: Vec<(String, TypeAnnotatedValue)> = vec![]; - - for (index, ((typed_value, typ), expected)) in - results.into_iter().zip(expected_types.iter()).enumerate() - { - let name = if let Some(name) = &expected.name { - name.clone() - } else { - index.to_string() - }; - - named_typs.push((name.clone(), typ.clone())); - named_values.push((name, typed_value)); - } - - Ok(TypeAnnotatedValue::Record { - typ: named_typs, - value: named_values, - }) - } - } - } - pub fn from_value( val: &Value, analysed_type: &AnalysedType, @@ -426,566 +327,6 @@ impl TypeAnnotatedValue { }, } } - - pub fn from_json_value( - json_val: &JsonValue, - analysed_type: &AnalysedType, - ) -> Result> { - match analysed_type { - AnalysedType::Bool => TypeAnnotatedValue::get_bool(json_val), - AnalysedType::S8 => TypeAnnotatedValue::get_s8(json_val), - AnalysedType::U8 => TypeAnnotatedValue::get_u8(json_val), - AnalysedType::S16 => TypeAnnotatedValue::get_s16(json_val), - AnalysedType::U16 => TypeAnnotatedValue::get_u16(json_val), - AnalysedType::S32 => TypeAnnotatedValue::get_s32(json_val), - AnalysedType::U32 => TypeAnnotatedValue::get_u32(json_val), - AnalysedType::S64 => TypeAnnotatedValue::get_s64(json_val), - AnalysedType::U64 => TypeAnnotatedValue::get_u64(json_val), - AnalysedType::F64 => TypeAnnotatedValue::get_f64(json_val), - AnalysedType::F32 => TypeAnnotatedValue::get_f32(json_val), - AnalysedType::Chr => TypeAnnotatedValue::get_char(json_val), - AnalysedType::Str => TypeAnnotatedValue::get_string(json_val), - AnalysedType::Enum(names) => TypeAnnotatedValue::get_enum(json_val, names), - AnalysedType::Flags(names) => TypeAnnotatedValue::get_flag(json_val, names), - AnalysedType::List(elem) => TypeAnnotatedValue::get_list(json_val, elem), - AnalysedType::Option(elem) => TypeAnnotatedValue::get_option(json_val, elem), - AnalysedType::Result { ok, error } => { - TypeAnnotatedValue::get_result(json_val, ok, error) - } - AnalysedType::Record(fields) => TypeAnnotatedValue::get_record(json_val, fields), - AnalysedType::Variant(cases) => TypeAnnotatedValue::get_variant(json_val, cases), - AnalysedType::Tuple(elems) => TypeAnnotatedValue::get_tuple(json_val, elems), - AnalysedType::Resource { id, resource_mode } => { - TypeAnnotatedValue::get_handle(json_val, id.clone(), resource_mode.clone()) - } - } - } - - fn get_bool(json: &JsonValue) -> Result> { - match json { - JsonValue::Bool(bool_val) => Ok(TypeAnnotatedValue::Bool(*bool_val)), - _ => { - let type_description = type_description(json); - Err(vec![format!( - "Expected function parameter type is Boolean. But found {}", - type_description - )]) - } - } - } - - fn get_s8(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| TypeAnnotatedValue::S8(num.to_i8().expect("Failed to convert BigDecimal to i8"))) - } - - fn get_u8(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| TypeAnnotatedValue::U8(num.to_u8().expect("Failed to convert BigDecimal to u8"))) - } - - fn get_s16(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| { - TypeAnnotatedValue::S16(num.to_i16().expect("Failed to convert BigDecimal to i16")) - }) - } - - fn get_u16(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| { - TypeAnnotatedValue::U16(num.to_u16().expect("Failed to convert BigDecimal to u16")) - }) - } - - fn get_s32(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| { - TypeAnnotatedValue::S32(num.to_i32().expect("Failed to convert BigDecimal to i32")) - }) - } - - fn get_u32(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| { - TypeAnnotatedValue::U32(num.to_u32().expect("Failed to convert BigDecimal to u32")) - }) - } - - fn get_s64(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| { - TypeAnnotatedValue::S64(num.to_i64().expect("Failed to convert BigDecimal to i64")) - }) - } - - fn get_f32(json: &JsonValue) -> Result> { - 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"), - ) - .map(|num| { - TypeAnnotatedValue::F32(num.to_f32().expect("Failed to convert BigDecimal to f32")) - }) - } - - fn get_f64(json_val: &JsonValue) -> Result> { - let num = bigdecimal(json_val)?; - let value = TypeAnnotatedValue::F64( - num.to_string() - .parse() - .map_err(|err| vec![format!("Failed to parse f64: {}", err)])?, - ); - Ok(value) - } - - fn get_string(json: &JsonValue) -> Result> { - get_string(json).map(TypeAnnotatedValue::Str) - } - - fn get_u64(json: &JsonValue) -> Result> { - get_u64(json).map(TypeAnnotatedValue::U64) - } - - fn get_char(json: &JsonValue) -> Result> { - get_char(json).map(TypeAnnotatedValue::Chr) - } - - fn get_tuple( - input_json: &JsonValue, - types: &[AnalysedType], - ) -> Result> { - 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 = vec![]; - let mut vals: Vec = vec![]; - - for (json, tpe) in json_array.iter().zip(types.iter()) { - match TypeAnnotatedValue::from_json_value(json, tpe) { - Ok(result) => vals.push(result), - Err(errs) => errors.extend(errs), - } - } - - if errors.is_empty() { - Ok(TypeAnnotatedValue::Tuple { - typ: types.to_vec(), - value: vals, - }) - } else { - Err(errors) - } - } - - fn get_option( - input_json: &JsonValue, - tpe: &AnalysedType, - ) -> Result> { - match input_json.as_null() { - Some(_) => Ok(TypeAnnotatedValue::Option { - typ: tpe.clone(), - value: None, - }), - - None => TypeAnnotatedValue::from_json_value(input_json, tpe).map(|result| { - TypeAnnotatedValue::Option { - typ: tpe.clone(), - value: Some(Box::new(result)), - } - }), - } - } - - fn get_list( - input_json: &JsonValue, - tpe: &AnalysedType, - ) -> Result> { - let json_array = input_json - .as_array() - .ok_or(vec![format!("Input {} is not an array", input_json)])?; - - let mut errors: Vec = vec![]; - let mut vals: Vec = vec![]; - - for json in json_array { - match TypeAnnotatedValue::from_json_value(json, tpe) { - Ok(result) => vals.push(result), - Err(errs) => errors.extend(errs), - } - } - - if errors.is_empty() { - Ok(TypeAnnotatedValue::List { - typ: tpe.clone(), - values: vals, - }) - } else { - Err(errors) - } - } - - fn get_enum( - input_json: &JsonValue, - names: &[String], - ) -> Result> { - let input_enum_value = input_json - .as_str() - .ok_or(vec![format!("Input {} is not string", input_json)])?; - - if names.contains(&input_enum_value.to_string()) { - Ok(TypeAnnotatedValue::Enum { - typ: names.to_vec(), - value: input_enum_value.to_string(), - }) - } else { - Err(vec![format!( - "Invalid input {}. Valid values are {}", - input_enum_value, - names.join(",") - )]) - } - } - - #[allow(clippy::type_complexity)] - fn get_result( - input_json: &JsonValue, - ok_type: &Option>, - err_type: &Option>, - ) -> Result> { - fn validate( - typ: &Option>, - input_json: &JsonValue, - ) -> Result>, Vec> { - if let Some(typ) = typ { - TypeAnnotatedValue::from_json_value(input_json, typ).map(|v| Some(Box::new(v))) - } 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) => Ok(TypeAnnotatedValue::Result { - ok: ok_type.clone(), - error: err_type.clone(), - value: Ok(validate(ok_type, value)?), - }), - None => match input_json.get("err") { - Some(value) => Ok(TypeAnnotatedValue::Result { - ok: ok_type.clone(), - error: err_type.clone(), - value: Err(validate(err_type, value)?), - }), - None => Err(vec![ - "Failed to retrieve either ok value or err value".to_string() - ]), - }, - } - } - - fn get_record( - input_json: &JsonValue, - name_type_pairs: &[(String, AnalysedType)], - ) -> Result> { - let json_map = input_json.as_object().ok_or(vec![format!( - "The input {} is not a json object", - input_json - )])?; - - let mut errors: Vec = vec![]; - let mut vals: Vec<(String, TypeAnnotatedValue)> = vec![]; - - for (name, tpe) in name_type_pairs { - if let Some(json_value) = json_map.get(name) { - match TypeAnnotatedValue::from_json_value(json_value, tpe) { - Ok(result) => vals.push((name.clone(), result)), - Err(value_errors) => errors.extend( - value_errors - .iter() - .map(|err| { - format!("Invalid value for the key {}. Error: {}", name, err) - }) - .collect::>(), - ), - } - } else { - match tpe { - AnalysedType::Option(_) => vals.push(( - name.clone(), - TypeAnnotatedValue::Option { - typ: tpe.clone(), - value: None, - }, - )), - _ => errors.push(format!("Key '{}' not found in json_map", name)), - } - } - } - - if errors.is_empty() { - Ok(TypeAnnotatedValue::Record { - typ: name_type_pairs.to_vec(), - value: vals, - }) - } else { - Err(errors) - } - } - - fn get_flag( - input_json: &JsonValue, - names: &[String], - ) -> Result> { - let json_array = input_json - .as_array() - .ok_or(vec![format!("Input {} is not an array", input_json)])?; - - let mut errors: Vec = vec![]; - let mut vals: Vec = vec![]; - - 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.push(flag); - } else { - errors.push(format!( - "Invalid input {}. Valid values are {}", - flag, - names.join(",") - )); - } - } - - if errors.is_empty() { - Ok(TypeAnnotatedValue::Flags { - typ: names.to_vec(), - values: vals, - }) - } else { - Err(errors) - } - } - - fn get_variant( - input_json: &JsonValue, - types: &[(String, Option)], - ) -> Result> { - let mut possible_mapping_indexed: HashMap<&String, &Option> = HashMap::new(); - - for (name, 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()) - }?; - - match possible_mapping_indexed.get(key) { - Some(Some(tpe)) => TypeAnnotatedValue::from_json_value(json, tpe).map(|result| { - TypeAnnotatedValue::Variant { - typ: types.to_vec(), - case_name: key.clone(), - case_value: Some(Box::new(result)), - } - }), - Some(None) if json.is_null() => Ok(TypeAnnotatedValue::Variant { - typ: types.to_vec(), - case_name: key.clone(), - case_value: None, - }), - 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> { - match value.as_str() { - Some(str) => { - // not assuming much about the url format, just checking it ends with a / - 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(TypeAnnotatedValue::Handle { id, resource_mode, uri: Uri { value: uri }, resource_id }) - } - Err(err) => { - Err(vec![format!("Failed to parse resource-id section of the handle value: {}", err)]) - } - } - } else { - Err(vec![format!( - "Expected function parameter type is Handle, represented by a worker-url/resource-id string. But found {}", - str - )]) - } - } - None => Err(vec![format!( - "Expected function parameter type is Handle, represented by a worker-url/resource-id string. But 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(_) => "Array", - JsonValue::Object(_) => "Object", - } -} -fn ensure_range( - value: &JsonValue, - min: BigDecimal, - max: BigDecimal, -) -> Result> { - let num = bigdecimal(value)?; - if num >= min && num <= max { - Ok(num) - } else { - Err(vec![format!( - "value {} is not within the range of {} to {}", - value, min, max - )]) - } -} -fn bigdecimal(value: &JsonValue) -> Result> { - 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 function parameter type is BigDecimal. But found {}", - type_description - )]) - } - } -} - -fn get_u64(value: &JsonValue) -> Result> { - match value { - JsonValue::Number(num) => { - if let Some(u64) = num.as_u64() { - Ok(u64) - } else { - Err(vec![format!("Cannot convert {} to u64", num)]) - } - } - _ => { - let type_description = type_description(value); - Err(vec![format!( - "Expected function parameter type is u64. But found {}", - type_description - )]) - } - } -} -fn get_char(json: &JsonValue) -> Result> { - 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 { - char::from_u32(num_u64 as u32).ok_or(vec![format!( - "The value {} is not a valid unicode character", - num_u64 - )]) - } - } else { - let type_description = type_description(json); - - Err(vec![format!( - "Expected function parameter type is Char. But found {}", - type_description - )]) - } -} - -fn get_string(input_json: &JsonValue) -> Result> { - if let Some(str_value) = input_json.as_str() { - // If the JSON value is a string, return it - Ok(str_value.to_string()) - } else { - // If the JSON value is not a string, return an error with type information - let type_description = type_description(input_json); - Err(vec![format!( - "Expected function parameter type is String. But found {}", - type_description - )]) - } } impl From<&TypeAnnotatedValue> for AnalysedType { From 7e93b8a0e6746ba6bb1e4ef37831ffe3b2e50b47 Mon Sep 17 00:00:00 2001 From: Afsal Thaj Date: Sat, 30 Mar 2024 20:18:32 +1100 Subject: [PATCH 5/6] Cleanup --- wasm-rpc/src/json.rs | 199 +++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 104 deletions(-) diff --git a/wasm-rpc/src/json.rs b/wasm-rpc/src/json.rs index 81fde58c..4b67c855 100644 --- a/wasm-rpc/src/json.rs +++ b/wasm-rpc/src/json.rs @@ -19,7 +19,6 @@ use golem_wasm_ast::analysis::{ }; use serde_json::{Number, Value as JsonValue}; use std::collections::HashMap; -use std::fmt::Display; use std::str::FromStr; use crate::{TypeAnnotatedValue, Uri, Value}; @@ -88,7 +87,7 @@ pub fn function_result( values: Vec, expected_types: &[AnalysedFunctionResult], ) -> Result> { - function_result_typed(values, expected_types).map(|result| Json::from(result).0) + function_result_typed(values, expected_types).map(|result| get_json_from_typed_value(&result)) } pub fn function_result_typed( @@ -676,119 +675,110 @@ fn get_u64(value: &JsonValue) -> Result> { } } -pub struct Json(pub serde_json::Value); - -impl Display for Json { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for Json { - fn from(value: TypeAnnotatedValue) -> Self { - match value { - TypeAnnotatedValue::Bool(bool) => Json(serde_json::Value::Bool(bool)), - TypeAnnotatedValue::Flags { typ: _, values } => Json(JsonValue::Array( - values.into_iter().map(JsonValue::String).collect(), - )), - TypeAnnotatedValue::S8(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::U8(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::S16(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::U16(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::S32(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::U32(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::S64(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::U64(value) => Json(JsonValue::Number(Number::from(value))), - TypeAnnotatedValue::F32(value) => { - Json(JsonValue::Number(Number::from_f64(value as f64).unwrap())) - } - TypeAnnotatedValue::F64(value) => { - Json(JsonValue::Number(Number::from_f64(value).unwrap())) - } - TypeAnnotatedValue::Chr(value) => Json(JsonValue::Number(Number::from(value as u32))), - TypeAnnotatedValue::Str(value) => Json(JsonValue::String(value)), - TypeAnnotatedValue::Enum { typ: _, value } => Json(JsonValue::String(value)), - TypeAnnotatedValue::Option { typ: _, value } => match value { - Some(value) => Json::from(*value), - None => Json(JsonValue::Null), - }, - TypeAnnotatedValue::Tuple { typ: _, value } => { - let values: Vec = - value.into_iter().map(Json::from).map(|v| v.0).collect(); - Json(JsonValue::Array(values)) - } - TypeAnnotatedValue::List { typ: _, values } => { - let values: Vec = - values.into_iter().map(Json::from).map(|v| v.0).collect(); - Json(JsonValue::Array(values)) - } +pub fn get_json_from_typed_value(typed_value: &TypeAnnotatedValue) -> JsonValue { + match typed_value { + TypeAnnotatedValue::Bool(bool) => JsonValue::Bool(*bool), + TypeAnnotatedValue::Flags { typ: _, values } => JsonValue::Array( + values + .iter() + .map(|x| JsonValue::String(x.clone())) + .collect(), + ), + TypeAnnotatedValue::S8(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::U8(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::S16(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::U16(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::S32(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::U32(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::S64(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::U64(value) => JsonValue::Number(Number::from(*value)), + TypeAnnotatedValue::F32(value) => { + JsonValue::Number(Number::from_f64(*value as f64).unwrap()) + } + TypeAnnotatedValue::F64(value) => JsonValue::Number(Number::from_f64(*value).unwrap()), + TypeAnnotatedValue::Chr(value) => JsonValue::Number(Number::from(*value as u32)), + TypeAnnotatedValue::Str(value) => JsonValue::String(value.clone()), + TypeAnnotatedValue::Enum { typ: _, value } => JsonValue::String(value.clone()), + TypeAnnotatedValue::Option { typ: _, value } => match value { + Some(value) => get_json_from_typed_value(value), + None => JsonValue::Null, + }, + TypeAnnotatedValue::Tuple { typ: _, value } => { + let values: Vec = + value.iter().map(get_json_from_typed_value).collect(); + JsonValue::Array(values) + } + TypeAnnotatedValue::List { typ: _, values } => { + let values: Vec = + values.iter().map(get_json_from_typed_value).collect(); + JsonValue::Array(values) + } - TypeAnnotatedValue::Record { typ: _, value } => { - let mut map = serde_json::Map::new(); - for (key, value) in value { - map.insert(key, Json::from(value).0); - } - Json(JsonValue::Object(map)) + TypeAnnotatedValue::Record { typ: _, value } => { + let mut map = serde_json::Map::new(); + for (key, value) in value { + map.insert(key.clone(), get_json_from_typed_value(value)); } + JsonValue::Object(map) + } - TypeAnnotatedValue::Variant { - typ: _, - case_name, - case_value, - } => { - let mut map = serde_json::Map::new(); - map.insert( - case_name, - case_value - .map(|x| Json::from(*x)) - .map(|v| v.0) - .unwrap_or(JsonValue::Null), - ); - Json(JsonValue::Object(map)) - } + TypeAnnotatedValue::Variant { + typ: _, + case_name, + case_value, + } => { + let mut map = serde_json::Map::new(); + map.insert( + case_name.clone(), + case_value + .clone() + .map(|x| get_json_from_typed_value(&*x)) + .unwrap_or(JsonValue::Null), + ); + JsonValue::Object(map) + } - TypeAnnotatedValue::Result { - ok: _, - error: _, - value, - } => { - let mut map = serde_json::Map::new(); - match value { - Ok(ok_value) => { - map.insert( - "ok".to_string(), - ok_value - .map(|x| Json::from(*x)) - .map(|v| v.0) - .unwrap_or(JsonValue::Null), - ); - } - Err(err_value) => { - map.insert( - "err".to_string(), - err_value - .map(|x| Json::from(*x)) - .map(|v| v.0) - .unwrap_or(JsonValue::Null), - ); - } + TypeAnnotatedValue::Result { + ok: _, + error: _, + value, + } => { + let mut map = serde_json::Map::new(); + match value { + Ok(ok_value) => { + map.insert( + "ok".to_string(), + ok_value + .clone() + .map(|x| get_json_from_typed_value(&*x)) + .unwrap_or(JsonValue::Null), + ); + } + Err(err_value) => { + map.insert( + "err".to_string(), + err_value + .clone() + .map(|x| get_json_from_typed_value(&*x)) + .unwrap_or(JsonValue::Null), + ); } - Json(JsonValue::Object(map)) } - - TypeAnnotatedValue::Handle { - id: _, - resource_mode: _, - uri, - resource_id, - } => Json(JsonValue::String(format!("{}/{}", uri.value, resource_id))), + JsonValue::Object(map) } + + TypeAnnotatedValue::Handle { + id: _, + resource_mode: _, + uri, + resource_id, + } => JsonValue::String(format!("{}/{}", uri.value, resource_id)), } } #[cfg(test)] mod tests { - use crate::json::{get_typed_value_from_json, Json}; + use crate::json::{get_json_from_typed_value, get_typed_value_from_json}; use crate::{TypeAnnotatedValue, Value}; use golem_wasm_ast::analysis::AnalysedType; use proptest::prelude::*; @@ -799,7 +789,8 @@ mod tests { val: Value, expected_type: &AnalysedType, ) -> Result> { - TypeAnnotatedValue::from_value(&val, expected_type).map(|result| Json::from(result).0) + TypeAnnotatedValue::from_value(&val, expected_type) + .map(|result| get_json_from_typed_value(&result)) } fn validate_function_parameter( From 2f244af125eec9f5486f7ad17083cec5dc984d88 Mon Sep 17 00:00:00 2001 From: Afsal Thaj Date: Sat, 30 Mar 2024 21:59:06 +1100 Subject: [PATCH 6/6] Fix clippy --- wasm-rpc/src/json.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wasm-rpc/src/json.rs b/wasm-rpc/src/json.rs index 4b67c855..a9f70dab 100644 --- a/wasm-rpc/src/json.rs +++ b/wasm-rpc/src/json.rs @@ -731,8 +731,8 @@ pub fn get_json_from_typed_value(typed_value: &TypeAnnotatedValue) -> JsonValue map.insert( case_name.clone(), case_value - .clone() - .map(|x| get_json_from_typed_value(&*x)) + .as_ref() + .map(|x| get_json_from_typed_value(x)) .unwrap_or(JsonValue::Null), ); JsonValue::Object(map) @@ -749,8 +749,8 @@ pub fn get_json_from_typed_value(typed_value: &TypeAnnotatedValue) -> JsonValue map.insert( "ok".to_string(), ok_value - .clone() - .map(|x| get_json_from_typed_value(&*x)) + .as_ref() + .map(|x| get_json_from_typed_value(x)) .unwrap_or(JsonValue::Null), ); } @@ -758,8 +758,8 @@ pub fn get_json_from_typed_value(typed_value: &TypeAnnotatedValue) -> JsonValue map.insert( "err".to_string(), err_value - .clone() - .map(|x| get_json_from_typed_value(&*x)) + .as_ref() + .map(|x| get_json_from_typed_value(x)) .unwrap_or(JsonValue::Null), ); }