diff --git a/golem-api-grpc/proto/golem/rib/expr.proto b/golem-api-grpc/proto/golem/rib/expr.proto index f9bc9eadde..a821089344 100644 --- a/golem-api-grpc/proto/golem/rib/expr.proto +++ b/golem-api-grpc/proto/golem/rib/expr.proto @@ -203,6 +203,8 @@ message ArmPattern { ConstructorArmPattern constructor = 3; LiteralArmPattern literal = 4; TupleConstructorArmPattern tuple_constructor = 5; + ListConstructorArmPattern list_constructor = 6; + RecordConstructorArmPattern record_constructor = 7; } } @@ -224,6 +226,20 @@ message TupleConstructorArmPattern { repeated ArmPattern patterns = 1; } +message ListConstructorArmPattern { + repeated ArmPattern patterns = 1; +} + +message RecordConstructorArmPattern { + repeated RecordFieldArmPattern fields = 1; +} + +message RecordFieldArmPattern { + string name = 1; + ArmPattern pattern = 2; +} + + message LiteralArmPattern { Expr expr = 1; } diff --git a/golem-rib/src/compiler/byte_code.rs b/golem-rib/src/compiler/byte_code.rs index 86af7a7c13..cebf097e8a 100644 --- a/golem-rib/src/compiler/byte_code.rs +++ b/golem-rib/src/compiler/byte_code.rs @@ -496,8 +496,8 @@ mod internal { #[cfg(test)] mod compiler_tests { use super::*; - use crate::{ArmPattern, InferredType, MatchArm, Number, VariableId}; - use golem_wasm_ast::analysis::{AnalysedType, NameTypePair, TypeRecord, TypeStr}; + use crate::{compiler, ArmPattern, InferredType, MatchArm, Number, VariableId}; + use golem_wasm_ast::analysis::{AnalysedType, NameTypePair, TypeRecord, TypeStr, TypeU32}; use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; #[test] @@ -983,4 +983,399 @@ mod compiler_tests { assert_eq!(instructions, expected_instructions); } + + #[cfg(test)] + mod global_input_tests { + use crate::compiler::byte_code::compiler_tests::internal; + use crate::{compiler, Expr}; + use golem_wasm_ast::analysis::{ + AnalysedType, NameOptionTypePair, NameTypePair, TypeEnum, TypeList, TypeOption, + TypeRecord, TypeResult, TypeStr, TypeTuple, TypeU32, TypeU64, TypeVariant, + }; + + #[tokio::test] + async fn test_variant_type_info() { + let request_value_type = AnalysedType::Variant(TypeVariant { + cases: vec![ + NameOptionTypePair { + name: "register-user".to_string(), + typ: Some(AnalysedType::U64(TypeU64)), + }, + NameOptionTypePair { + name: "process-user".to_string(), + typ: Some(AnalysedType::Str(TypeStr)), + }, + NameOptionTypePair { + name: "validate".to_string(), + typ: None, + }, + ], + }); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + // x = request, implies we are expecting a global variable + // called request as the input to Rib. + // my-worker-function is a function that takes a Variant as input, + // implies the type of request is a Variant. + // This means the rib interpreter env has to have a request variable in it, + // with a value that should be of the type Variant + let expr = r#" + my-worker-function(request); + match request { + process-user(user) => user, + _ => "default" + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + #[tokio::test] + async fn test_result_type_info() { + let request_value_type = AnalysedType::Result(TypeResult { + ok: Some(Box::new(AnalysedType::U64(TypeU64))), + err: Some(Box::new(AnalysedType::Str(TypeStr))), + }); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + // x = request, implies we are expecting a global variable + // called request as the input to Rib. + // my-worker-function is a function that takes a Result as input, + // implies the type of request is a Result. + // This means the rib interpreter env has to have a request variable in it, + // with a value that should be of the type Result + let expr = r#" + my-worker-function(request); + match request { + ok(x) => "${x}", + err(msg) => msg + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + #[tokio::test] + async fn test_option_type_info() { + let request_value_type = AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::Str(TypeStr)), + }); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + // x = request, implies we are expecting a global variable + // called request as the input to Rib. + // my-worker-function is a function that takes a Option as input, + // implies the type of request is a Result. + // This means the rib interpreter env has to have a request variable in it, + // with a value that should be of the type Option + let expr = r#" + my-worker-function(request); + match request { + some(x) => x, + none => "error" + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + #[tokio::test] + async fn test_enum_type_info() { + let request_value_type = AnalysedType::Enum(TypeEnum { + cases: vec!["prod".to_string(), "dev".to_string(), "test".to_string()], + }); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + // x = request, implies we are expecting a global variable + // called request as the input to Rib. + // my-worker-function is a function that takes a Option as input, + // implies the type of request is a Result. + // This means the rib interpreter env has to have a request variable in it, + // with a value that should be of the type Option + let expr = r#" + my-worker-function(request); + match request { + prod => "p", + dev => "d", + test => "t" + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + #[tokio::test] + async fn test_record_global_input() { + let request_value_type = AnalysedType::Record(TypeRecord { + fields: vec![NameTypePair { + name: "path".to_string(), + typ: AnalysedType::Record(TypeRecord { + fields: vec![NameTypePair { + name: "user".to_string(), + typ: AnalysedType::Str(TypeStr), + }], + }), + }], + }); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + // x = request, implies we are expecting a global variable + // called request as the input to Rib. + // my-worker-function is a function that takes a Record of path -> user -> str as input + // implies the type of request is a Record. + // This means the rib interpreter env has to have a request variable in it, + // with a value that should be of the type Record + let expr = r#" + let x = request; + my-worker-function(x); + + let name = x.path.user; + + match x { + { path : { user : some_name } } => some_name, + _ => name + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + #[tokio::test] + async fn test_tuple_global_input() { + let request_value_type = AnalysedType::Tuple(TypeTuple { + items: vec![ + AnalysedType::Str(TypeStr), + AnalysedType::U32(TypeU32), + AnalysedType::Record(TypeRecord { + fields: vec![NameTypePair { + name: "user".to_string(), + typ: AnalysedType::Str(TypeStr), + }], + }), + ], + }); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + // x = request, implies we are expecting a global variable + // called request as the input to Rib. + // my-worker-function is a function that takes a Tuple, + // implies the type of request is a Tuple. + let expr = r#" + let x = request; + my-worker-function(x); + match x { + (_, _, record) => record.user, + _ => "fallback" + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + #[tokio::test] + async fn test_list_global_input() { + let request_value_type = AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::Str(TypeStr)), + }); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + // x = request, implies we are expecting a global variable + // called request as the input to Rib. + // my-worker-function is a function that takes a List, + // implies the type of request should be a List + let expr = r#" + let x = request; + my-worker-function(x); + match x { + [a, b, c] => a, + _ => "fallback" + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + } + + #[tokio::test] + async fn test_str_global_input() { + let request_value_type = AnalysedType::Str(TypeStr); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + let expr = r#" + let x = request; + my-worker-function(x); + match x { + "foo" => "success", + _ => "fallback" + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + #[tokio::test] + async fn test_number_global_input() { + let request_value_type = AnalysedType::U32(TypeU32); + + let output_analysed_type = AnalysedType::Str(TypeStr); + + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![request_value_type.clone()], + output_analysed_type, + ); + + let expr = r#" + let x = request; + my-worker-function(x); + match x { + 1 => "success", + 0 => "failure" + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let expected_type_info = + internal::rib_input_type_info(vec![("request", request_value_type)]); + + assert_eq!(compiled.global_input_type_info, expected_type_info); + } + + mod internal { + use crate::RibInputTypeInfo; + use golem_wasm_ast::analysis::{ + AnalysedExport, AnalysedFunction, AnalysedFunctionParameter, AnalysedFunctionResult, + AnalysedType, + }; + use std::collections::HashMap; + + pub(crate) fn get_component_metadata( + function_name: &str, + input_types: Vec, + output: AnalysedType, + ) -> Vec { + let analysed_function_parameters = input_types + .into_iter() + .enumerate() + .map(|(index, typ)| AnalysedFunctionParameter { + name: format!("param{}", index), + typ, + }) + .collect(); + + vec![AnalysedExport::Function(AnalysedFunction { + name: function_name.to_string(), + parameters: analysed_function_parameters, + results: vec![AnalysedFunctionResult { + name: None, + typ: output, + }], + })] + } + + pub(crate) fn rib_input_type_info(types: Vec<(&str, AnalysedType)>) -> RibInputTypeInfo { + let mut type_info = HashMap::new(); + for (name, typ) in types { + type_info.insert(name.to_string(), typ); + } + RibInputTypeInfo { types: type_info } + } + } } diff --git a/golem-rib/src/compiler/desugar.rs b/golem-rib/src/compiler/desugar.rs index 737e0d19c8..e2c2a0ac59 100644 --- a/golem-rib/src/compiler/desugar.rs +++ b/golem-rib/src/compiler/desugar.rs @@ -97,18 +97,33 @@ mod internal { inferred_type_of_pred, ), - ArmPattern::Constructor(constructor_name, expressions) => hande_constructor( + ArmPattern::Constructor(constructor_name, arm_patterns) => hande_constructor( pred_expr, constructor_name, - expressions, + arm_patterns, resolution, inferred_type_of_pred, ), - ArmPattern::TupleConstructor(expressions) => hande_constructor( + ArmPattern::TupleConstructor(arm_patterns) => hande_constructor( pred_expr, "tuple", - expressions, + arm_patterns, + resolution, + inferred_type_of_pred, + ), + + ArmPattern::ListConstructor(arm_patterns) => hande_constructor( + pred_expr, + "list", + arm_patterns, + resolution, + inferred_type_of_pred, + ), + + ArmPattern::RecordConstructor(field_arm_pattern_collection) => handle_record( + pred_expr, + field_arm_pattern_collection, resolution, inferred_type_of_pred, ), @@ -238,6 +253,63 @@ mod internal { } } + fn handle_record( + pred_expr: &Expr, + bind_patterns: &[(String, ArmPattern)], + resolution: &Expr, + pred_expr_inferred_type: InferredType, + ) -> Option { + match pred_expr_inferred_type { + InferredType::Record(field_and_types) => { + // Resolution body is a list of expressions which grows (may be with some let bindings) + // as we recursively iterate over the bind patterns + // where bind patterns are {name: x, age: _, address : _ } in the case of `match record { {name: x, age: _, address : _ } ) =>` + // These will exist prior to the original resolution of a successful tuple match. + let mut resolution_body = vec![]; + + // The conditions keep growing as we recursively iterate over the bind patterns + // and there are multiple conditions (if condition) for each element in the tuple + let mut conditions = vec![]; + + // We assume pred-expr can be queried by field using Expr::select_field and we pick each element in the bind pattern + // to get the corresponding expr in pred-expr and keep recursively iterating until the record is completed. + // However there is no resolution body for each of this iteration, so we use an empty expression + // and finally push the original resolution body once we fully build the conditions. + for (field, arm_pattern) in bind_patterns.iter() { + let new_pred = Expr::select_field(pred_expr.clone(), field); + let new_pred_type = field_and_types + .iter() + .find(|(f, _)| f == field) + .map(|(_, t)| t.clone()) + .unwrap_or(InferredType::Unknown); + + let branch = get_conditions( + &MatchArm::new(arm_pattern.clone(), Expr::empty_expr()), + &new_pred, + None, + new_pred_type.clone(), + ); + + if let Some(x) = branch { + conditions.push(x.condition); + resolution_body.push(x.body) + } + } + + resolution_body.push(resolution.clone()); + + let and_cond = Expr::and_combine(conditions); + + and_cond.map(|c| IfThenBranch { + condition: c, + body: Expr::multiple(resolution_body), + }) + } + + _ => None, + } + } + fn hande_constructor( pred_expr: &Expr, constructor_name: &str, @@ -352,6 +424,48 @@ mod internal { }) } + InferredType::List(inferred_type) => { + // Resolution body is a list of expressions which grows (may be with some let bindings) + // as we recursively iterate over the bind patterns + // where bind patterns are x, _, y in the case of `match list_ { [x, _, y]) =>` + // These will exist prior to the original resolution of a successful list match. + let mut resolution_body = vec![]; + + // The conditions keep growing as we recursively iterate over the bind patterns + // and there are multiple conditions (if condition) for each element in the list + let mut conditions = vec![]; + + // We assume pred-expr is indexed (i.e, list is indexed), and we pick each element in the bind pattern + // and get the corresponding expr in pred-expr and keep recursively iterating until the list is completed. + // However there is no resolution body for each of this iteration, so we use an empty expression + // and finally push the original resolution body once we fully build the conditions. + for (index, arm_pattern) in bind_patterns.iter().enumerate() { + let new_pred = Expr::select_index(pred_expr.clone(), index); + let new_pred_type = inferred_type.clone(); + + let branch = get_conditions( + &MatchArm::new(arm_pattern.clone(), Expr::empty_expr()), + &new_pred, + None, + *new_pred_type, + ); + + if let Some(x) = branch { + conditions.push(x.condition); + resolution_body.push(x.body) + } + } + + resolution_body.push(resolution.clone()); + + let and_cond = Expr::and_combine(conditions); + + and_cond.map(|c| IfThenBranch { + condition: c, + body: Expr::multiple(resolution_body), + }) + } + InferredType::Unknown => Some(IfThenBranch { condition: Expr::boolean(false), body: resolution.clone(), diff --git a/golem-rib/src/compiler/mod.rs b/golem-rib/src/compiler/mod.rs index e748cdd8e1..bb394a46a0 100644 --- a/golem-rib/src/compiler/mod.rs +++ b/golem-rib/src/compiler/mod.rs @@ -29,6 +29,17 @@ mod type_with_unit; pub fn compile( expr: &Expr, export_metadata: &Vec, +) -> Result { + compile_with_limited_globals(expr, export_metadata, None) +} + +// Rib allows global input variables, however, we can choose to fail compilation +// if they don't fall under a pre-defined set of global variables. +// There is no restriction imposed to the type of this variable. +pub fn compile_with_limited_globals( + expr: &Expr, + export_metadata: &Vec, + allowed_global_variables: Option>, ) -> Result { let type_registry = FunctionTypeRegistry::from_export_metadata(export_metadata); let mut expr_cloned = expr.clone(); @@ -36,21 +47,39 @@ pub fn compile( .infer_types(&type_registry) .map_err(|e| e.join("\n"))?; - let rib_input = + let global_input_type_info = RibInputTypeInfo::from_expr(&mut expr_cloned).map_err(|e| format!("Error: {}", e))?; - let rib_byte_code = RibByteCode::from_expr(expr_cloned)?; + if let Some(allowed_global_variables) = &allowed_global_variables { + let mut un_allowed_variables = vec![]; + + for (name, _) in global_input_type_info.types.iter() { + if !allowed_global_variables.contains(name) { + un_allowed_variables.push(name.clone()); + } + } + + if !un_allowed_variables.is_empty() { + return Err(format!( + "Global variables not allowed: {}. Allowed: {}", + un_allowed_variables.join(", "), + allowed_global_variables.join(", ") + )); + } + } + + let byte_code = RibByteCode::from_expr(expr_cloned)?; Ok(CompilerOutput { - byte_code: rib_byte_code, - rib_input, + byte_code, + global_input_type_info, }) } #[derive(Debug, Clone)] pub struct CompilerOutput { pub byte_code: RibByteCode, - pub rib_input: RibInputTypeInfo, + pub global_input_type_info: RibInputTypeInfo, } impl TryFrom for CompilerOutput { @@ -64,7 +93,7 @@ impl TryFrom for CompilerOutput { Ok(CompilerOutput { byte_code, - rib_input, + global_input_type_info: rib_input, }) } } @@ -76,7 +105,7 @@ impl From for ProtoCompilerOutput { value.byte_code, )), rib_input: Some(golem_api_grpc::proto::golem::rib::RibInputType::from( - value.rib_input, + value.global_input_type_info, )), } } diff --git a/golem-rib/src/expr.rs b/golem-rib/src/expr.rs index 84969d6954..f33ed518e1 100644 --- a/golem-rib/src/expr.rs +++ b/golem-rib/src/expr.rs @@ -23,6 +23,7 @@ use crate::{ use bincode::{Decode, Encode}; use combine::stream::position; use combine::EasyParser; +use golem_api_grpc::proto::golem::rib::RecordFieldArmPattern; use golem_wasm_ast::analysis::AnalysedType; use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; use serde::{Deserialize, Serialize, Serializer}; @@ -679,8 +680,10 @@ impl MatchArm { pub enum ArmPattern { WildCard, As(String, Box), - Constructor(String, Vec), - TupleConstructor(Vec), // Just because tuple doesn't have a name to fall into Constructor + Constructor(String, Vec), // Can handle enums, variants, option, result etc + TupleConstructor(Vec), + RecordConstructor(Vec<(String, ArmPattern)>), + ListConstructor(Vec), Literal(Box), } @@ -703,6 +706,20 @@ impl ArmPattern { } result } + ArmPattern::RecordConstructor(patterns) => { + let mut result = vec![]; + for (_, pattern) in patterns { + result.extend(pattern.get_expr_literals_mut()); + } + result + } + ArmPattern::ListConstructor(patterns) => { + let mut result = vec![]; + for pattern in patterns { + result.extend(pattern.get_expr_literals_mut()); + } + result + } ArmPattern::WildCard => vec![], } } @@ -725,6 +742,20 @@ impl ArmPattern { } result } + ArmPattern::RecordConstructor(patterns) => { + let mut result = vec![]; + for (_, pattern) in patterns { + result.extend(pattern.get_expr_literals()); + } + result + } + ArmPattern::ListConstructor(patterns) => { + let mut result = vec![]; + for pattern in patterns { + result.extend(pattern.get_expr_literals()); + } + result + } ArmPattern::WildCard => vec![], } } @@ -1282,6 +1313,29 @@ impl TryFrom for ArmPattern { let inner = expr.ok_or("Missing expr")?; Ok(ArmPattern::Literal(Box::new(inner.try_into()?))) } + golem_api_grpc::proto::golem::rib::arm_pattern::Pattern::RecordConstructor( + golem_api_grpc::proto::golem::rib::RecordConstructorArmPattern { fields }, + ) => { + let fields = fields + .into_iter() + .map(|field| { + let name = field.name; + let proto_pattern = field.pattern.ok_or("Missing pattern")?; + let arm_pattern = ArmPattern::try_from(proto_pattern)?; + Ok((name, arm_pattern)) + }) + .collect::, String>>()?; + Ok(ArmPattern::RecordConstructor(fields)) + } + golem_api_grpc::proto::golem::rib::arm_pattern::Pattern::ListConstructor( + golem_api_grpc::proto::golem::rib::ListConstructorArmPattern { patterns }, + ) => { + let patterns = patterns + .into_iter() + .map(ArmPattern::try_from) + .collect::, _>>()?; + Ok(ArmPattern::ListConstructor(patterns)) + } } } } @@ -1343,6 +1397,39 @@ impl From for golem_api_grpc::proto::golem::rib::ArmPattern { ), } } + + ArmPattern::RecordConstructor(fields) => { + golem_api_grpc::proto::golem::rib::ArmPattern { + pattern: Some( + golem_api_grpc::proto::golem::rib::arm_pattern::Pattern::RecordConstructor( + golem_api_grpc::proto::golem::rib::RecordConstructorArmPattern { + fields: fields + .into_iter() + .map(|(name, pattern)| RecordFieldArmPattern { + name, + pattern: Some(pattern.into()), + }) + .collect(), + }, + ), + ), + } + } + + ArmPattern::ListConstructor(patterns) => { + golem_api_grpc::proto::golem::rib::ArmPattern { + pattern: Some( + golem_api_grpc::proto::golem::rib::arm_pattern::Pattern::ListConstructor( + golem_api_grpc::proto::golem::rib::ListConstructorArmPattern { + patterns: patterns + .into_iter() + .map(golem_api_grpc::proto::golem::rib::ArmPattern::from) + .collect(), + }, + ), + ), + } + } } } } diff --git a/golem-rib/src/inferred_type.rs b/golem-rib/src/inferred_type.rs index 17188b3b74..6577def69d 100644 --- a/golem-rib/src/inferred_type.rs +++ b/golem-rib/src/inferred_type.rs @@ -668,10 +668,17 @@ impl InferredType { Box::new(a_type.unify_with_required(b_type)?), )), (InferredType::Flags(a_flags), InferredType::Flags(b_flags)) => { - if a_flags != b_flags { - return Err(vec!["Flags do not match".to_string()]); + if a_flags.len() >= b_flags.len() { + if b_flags.iter().all(|b| a_flags.contains(b)) { + Ok(InferredType::Flags(a_flags.clone())) + } else { + Err(vec!["Flags do not match".to_string()]) + } + } else if a_flags.iter().all(|a| b_flags.contains(a)) { + Ok(InferredType::Flags(b_flags.clone())) + } else { + Err(vec!["Flags do not match".to_string()]) } - Ok(InferredType::Flags(a_flags.clone())) } (InferredType::Enum(a_variants), InferredType::Enum(b_variants)) => { if a_variants != b_variants { diff --git a/golem-rib/src/interpreter/env.rs b/golem-rib/src/interpreter/env.rs index 8340887253..f31d80a365 100644 --- a/golem-rib/src/interpreter/env.rs +++ b/golem-rib/src/interpreter/env.rs @@ -83,6 +83,15 @@ impl InterpreterEnv { } } + pub fn from( + input: HashMap, + call_worker_function_async: RibFunctionInvoke, + ) -> Self { + let mut env = Self::from_input(input); + env.call_worker_function_async = call_worker_function_async; + env + } + pub fn insert(&mut self, key: EnvironmentKey, value: RibInterpreterResult) { self.env.insert(key, value); } diff --git a/golem-rib/src/interpreter/mod.rs b/golem-rib/src/interpreter/mod.rs index b40373953c..d86dd8f7f6 100644 --- a/golem-rib/src/interpreter/mod.rs +++ b/golem-rib/src/interpreter/mod.rs @@ -20,12 +20,12 @@ pub use rib_interpreter::*; use crate::RibByteCode; use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; use std::collections::HashMap; - mod env; mod literal; mod result; mod rib_interpreter; mod stack; +mod tests; pub async fn interpret( rib: &RibByteCode, diff --git a/golem-rib/src/interpreter/result.rs b/golem-rib/src/interpreter/result.rs index 76c4a8b591..c100ae5677 100644 --- a/golem-rib/src/interpreter/result.rs +++ b/golem-rib/src/interpreter/result.rs @@ -105,7 +105,7 @@ mod internal { use crate::interpreter::literal::{GetLiteralValue, LiteralValue}; use golem_wasm_ast::analysis::AnalysedType; use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; - use golem_wasm_rpc::protobuf::{TypedEnum, TypedVariant}; + use golem_wasm_rpc::protobuf::{TypedEnum, TypedFlags, TypedVariant}; pub(crate) fn compare_typed_value( left: &TypeAnnotatedValue, @@ -125,11 +125,23 @@ mod internal { (left, right) { compare_enums(left, right) + } else if let (TypeAnnotatedValue::Flags(left), TypeAnnotatedValue::Flags(right)) = + (left, right) + { + compare_flags(left, right) } else { Err(unsupported_type_error(left, right)) } } + fn compare_flags(left: &TypedFlags, right: &TypedFlags) -> Result { + if left.values == right.values { + Ok(TypeAnnotatedValue::Bool(true)) + } else { + Ok(TypeAnnotatedValue::Bool(false)) + } + } + fn compare_variants( left: &TypedVariant, right: &TypedVariant, diff --git a/golem-rib/src/interpreter/rib_interpreter.rs b/golem-rib/src/interpreter/rib_interpreter.rs index 994aaa3075..8ee11279bd 100644 --- a/golem-rib/src/interpreter/rib_interpreter.rs +++ b/golem-rib/src/interpreter/rib_interpreter.rs @@ -980,10 +980,8 @@ mod internal { #[cfg(test)] mod interpreter_tests { use super::*; - use crate::{compiler, Expr, FunctionTypeRegistry, InstructionId, VariableId}; - use golem_wasm_ast::analysis::{ - AnalysedType, NameTypePair, TypeList, TypeRecord, TypeS32, TypeStr, - }; + use crate::{InstructionId, VariableId}; + use golem_wasm_ast::analysis::{AnalysedType, NameTypePair, TypeList, TypeRecord, TypeS32}; use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; use golem_wasm_rpc::protobuf::{NameValuePair, TypedList, TypedRecord}; @@ -1268,11 +1266,19 @@ mod interpreter_tests { assert_eq!(result.get_val().unwrap(), TypeAnnotatedValue::S32(2)); } - #[tokio::test] - async fn test_interpreter_for_pattern_match_on_option_nested() { - let mut interpreter = Interpreter::default(); + mod pattern_match_tests { + use crate::interpreter::rib_interpreter::interpreter_tests::internal; + use crate::{compiler, Expr, FunctionTypeRegistry, Interpreter}; + use golem_wasm_ast::analysis::{ + AnalysedType, NameTypePair, TypeRecord, TypeStr, TypeTuple, TypeU16, TypeU64, + }; + use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; + + #[tokio::test] + async fn test_interpreter_for_pattern_match_on_option_nested() { + let mut interpreter = Interpreter::default(); - let expr = r#" + let expr = r#" let x = some(some(1u64)); match x { @@ -1282,19 +1288,19 @@ mod interpreter_tests { } "#; - let mut expr = Expr::from_text(expr).unwrap(); - expr.infer_types(&FunctionTypeRegistry::empty()).unwrap(); - let compiled = compiler::compile(&expr, &vec![]).unwrap(); - let result = interpreter.run(compiled.byte_code).await.unwrap(); + let mut expr = Expr::from_text(expr).unwrap(); + expr.infer_types(&FunctionTypeRegistry::empty()).unwrap(); + let compiled = compiler::compile(&expr, &vec![]).unwrap(); + let result = interpreter.run(compiled.byte_code).await.unwrap(); - assert_eq!(result.get_val().unwrap(), TypeAnnotatedValue::U64(1)); - } + assert_eq!(result.get_val().unwrap(), TypeAnnotatedValue::U64(1)); + } - #[tokio::test] - async fn test_interpreter_for_pattern_match_on_tuple() { - let mut interpreter = Interpreter::default(); + #[tokio::test] + async fn test_interpreter_for_pattern_match_on_tuple() { + let mut interpreter = Interpreter::default(); - let expr = r#" + let expr = r#" let x: tuple = (1, "foo", "bar"); match x { @@ -1302,22 +1308,22 @@ mod interpreter_tests { } "#; - let mut expr = Expr::from_text(expr).unwrap(); - expr.infer_types(&FunctionTypeRegistry::empty()).unwrap(); - let compiled = compiler::compile(&expr, &vec![]).unwrap(); - let result = interpreter.run(compiled.byte_code).await.unwrap(); + let mut expr = Expr::from_text(expr).unwrap(); + expr.infer_types(&FunctionTypeRegistry::empty()).unwrap(); + let compiled = compiler::compile(&expr, &vec![]).unwrap(); + let result = interpreter.run(compiled.byte_code).await.unwrap(); - assert_eq!( - result.get_val().unwrap(), - TypeAnnotatedValue::Str("1 foo bar".to_string()) - ); - } + assert_eq!( + result.get_val().unwrap(), + TypeAnnotatedValue::Str("1 foo bar".to_string()) + ); + } - #[tokio::test] - async fn test_interpreter_for_pattern_match_on_tuple_with_option_some() { - let mut interpreter = Interpreter::default(); + #[tokio::test] + async fn test_interpreter_for_pattern_match_on_tuple_with_option_some() { + let mut interpreter = Interpreter::default(); - let expr = r#" + let expr = r#" let x: tuple, str> = (1, some("foo"), "bar"); match x { @@ -1326,23 +1332,23 @@ mod interpreter_tests { } "#; - let mut expr = Expr::from_text(expr).unwrap(); - expr.infer_types(&FunctionTypeRegistry::empty()).unwrap(); + let mut expr = Expr::from_text(expr).unwrap(); + expr.infer_types(&FunctionTypeRegistry::empty()).unwrap(); - let compiled = compiler::compile(&expr, &vec![]).unwrap(); - let result = interpreter.run(compiled.byte_code).await.unwrap(); + let compiled = compiler::compile(&expr, &vec![]).unwrap(); + let result = interpreter.run(compiled.byte_code).await.unwrap(); - assert_eq!( - result.get_val().unwrap(), - TypeAnnotatedValue::Str("1 foo bar".to_string()) - ); - } + assert_eq!( + result.get_val().unwrap(), + TypeAnnotatedValue::Str("1 foo bar".to_string()) + ); + } - #[tokio::test] - async fn test_interpreter_for_pattern_match_on_tuple_with_option_none() { - let mut interpreter = Interpreter::default(); + #[tokio::test] + async fn test_interpreter_for_pattern_match_on_tuple_with_option_none() { + let mut interpreter = Interpreter::default(); - let expr = r#" + let expr = r#" let x: tuple, str> = (1, none, "bar"); match x { @@ -1351,26 +1357,29 @@ mod interpreter_tests { } "#; - let expr = Expr::from_text(expr).unwrap(); - let compiled = compiler::compile(&expr, &vec![]).unwrap(); - let result = interpreter.run(compiled.byte_code).await.unwrap(); + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &vec![]).unwrap(); + let result = interpreter.run(compiled.byte_code).await.unwrap(); - assert_eq!( - result.get_val().unwrap(), - TypeAnnotatedValue::Str("1 bar".to_string()) - ); - } + assert_eq!( + result.get_val().unwrap(), + TypeAnnotatedValue::Str("1 bar".to_string()) + ); + } - #[tokio::test] - async fn test_interpreter_for_pattern_match_on_tuple_with_all_types() { - let mut interpreter = Interpreter::default(); + #[tokio::test] + async fn test_interpreter_for_pattern_match_on_tuple_with_all_types() { + let mut interpreter = Interpreter::default(); - let tuple = internal::get_analysed_type_tuple(); + let tuple = internal::get_analysed_type_tuple(); - let analysed_exports = - internal::get_component_metadata("foo", vec![tuple], AnalysedType::Str(TypeStr)); + let analysed_exports = internal::get_component_metadata( + "foo", + vec![tuple], + Some(AnalysedType::Str(TypeStr)), + ); - let expr = r#" + let expr = r#" let record = { request : { path : { user : "jak" } }, y : "bar" }; let input = (1, ok(100), "bar", record, process-user("jon"), register-user(1u64), validate, prod, dev, test); @@ -1382,29 +1391,29 @@ mod interpreter_tests { "#; - let expr = Expr::from_text(expr).unwrap(); - let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); - let result = interpreter.run(compiled.byte_code).await.unwrap(); + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let result = interpreter.run(compiled.byte_code).await.unwrap(); - assert_eq!( - result.get_val().unwrap(), - TypeAnnotatedValue::Str("foo 100 1 bar jak validate prod dev test".to_string()) - ); - } + assert_eq!( + result.get_val().unwrap(), + TypeAnnotatedValue::Str("foo 100 1 bar jak validate prod dev test".to_string()) + ); + } - #[tokio::test] - async fn test_interpreter_for_pattern_match_on_tuple_with_wild_pattern() { - let mut interpreter = Interpreter::default(); + #[tokio::test] + async fn test_interpreter_for_pattern_match_on_tuple_with_wild_pattern() { + let mut interpreter = Interpreter::default(); - let tuple = internal::get_analysed_type_tuple(); + let tuple = internal::get_analysed_type_tuple(); - let analysed_exports = internal::get_component_metadata( - "my-worker-function", - vec![tuple], - AnalysedType::Str(TypeStr), - ); + let analysed_exports = internal::get_component_metadata( + "my-worker-function", + vec![tuple], + Some(AnalysedType::Str(TypeStr)), + ); - let expr = r#" + let expr = r#" let record = { request : { path : { user : "jak" } }, y : "baz" }; let input = (1, ok(1), "bar", record, process-user("jon"), register-user(1u64), validate, prod, dev, test); @@ -1415,22 +1424,15 @@ mod interpreter_tests { } "#; - let expr = Expr::from_text(expr).unwrap(); - let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); - let result = interpreter.run(compiled.byte_code).await.unwrap(); - - assert_eq!( - result.get_val().unwrap(), - TypeAnnotatedValue::Str("dev 1 bar jak baz".to_string()) - ); - } + let expr = Expr::from_text(expr).unwrap(); + let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); + let result = interpreter.run(compiled.byte_code).await.unwrap(); - mod pattern_match_tests { - use crate::interpreter::rib_interpreter::interpreter_tests::internal; - use crate::{compiler, Expr}; - use golem_wasm_ast::analysis::{ - AnalysedType, NameTypePair, TypeRecord, TypeStr, TypeTuple, TypeU16, TypeU64, - }; + assert_eq!( + result.get_val().unwrap(), + TypeAnnotatedValue::Str("dev 1 bar jak baz".to_string()) + ); + } #[tokio::test] async fn test_record_output_in_pattern_match() { @@ -1438,14 +1440,15 @@ mod interpreter_tests { let output_analysed_type = internal::get_analysed_type_result(); let result_value = - internal::type_annotated_value_result(&output_analysed_type, r#"ok(1)"#); + internal::get_type_annotated_value(&output_analysed_type, r#"ok(1)"#); - let mut interpreter = internal::test_executor(&output_analysed_type, &result_value); + let mut interpreter = + internal::static_test_interpreter(&output_analysed_type, &result_value); let analysed_exports = internal::get_component_metadata( "my-worker-function", vec![input_analysed_type], - output_analysed_type, + Some(output_analysed_type), ); let expr = r#" @@ -1462,7 +1465,7 @@ mod interpreter_tests { let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); let result = interpreter.run(compiled.byte_code).await.unwrap(); - let expected = internal::type_annotated_value_result( + let expected = internal::get_type_annotated_value( &AnalysedType::Record(TypeRecord { fields: vec![ NameTypePair { @@ -1487,14 +1490,15 @@ mod interpreter_tests { let output_analysed_type = internal::get_analysed_type_result(); let result_value = - internal::type_annotated_value_result(&output_analysed_type, r#"err("failed")"#); + internal::get_type_annotated_value(&output_analysed_type, r#"err("failed")"#); - let mut interpreter = internal::test_executor(&output_analysed_type, &result_value); + let mut interpreter = + internal::static_test_interpreter(&output_analysed_type, &result_value); let analysed_exports = internal::get_component_metadata( "my-worker-function", vec![input_analysed_type], - output_analysed_type, + Some(output_analysed_type), ); let expr = r#" @@ -1512,7 +1516,7 @@ mod interpreter_tests { let compiled = compiler::compile(&expr, &analysed_exports).unwrap(); let result = interpreter.run(compiled.byte_code).await.unwrap(); - let expected = internal::type_annotated_value_result( + let expected = internal::get_type_annotated_value( &AnalysedType::Tuple(TypeTuple { items: vec![AnalysedType::Str(TypeStr), AnalysedType::Str(TypeStr)], }), @@ -1582,7 +1586,7 @@ mod interpreter_tests { ], }); - let result_value = internal::type_annotated_value_result( + let result_value = internal::get_type_annotated_value( &result_type, r#" success({order-id: "foo"}) @@ -1593,7 +1597,7 @@ mod interpreter_tests { internal::get_shopping_cart_metadata_with_cart_resource_with_parameters(); let compiled = compiler::compile(&expr, &component_metadata).unwrap(); - let mut rib_executor = internal::test_executor(&result_type, &result_value); + let mut rib_executor = internal::static_test_interpreter(&result_type, &result_value); let result = rib_executor.run(compiled.byte_code).await.unwrap(); assert_eq!(result.get_val().unwrap(), result_value); @@ -1632,7 +1636,7 @@ mod interpreter_tests { })), }); - let result_value = internal::type_annotated_value_result( + let result_value = internal::get_type_annotated_value( &result_type, r#" [{product-id: "foo", name: "bar", price: 100.0, quantity: 1}, {product-id: "bar", name: "baz", price: 200.0, quantity: 2}] @@ -1643,7 +1647,7 @@ mod interpreter_tests { internal::get_shopping_cart_metadata_with_cart_resource_with_parameters(); let compiled = compiler::compile(&expr, &component_metadata).unwrap(); - let mut rib_executor = internal::test_executor(&result_type, &result_value); + let mut rib_executor = internal::static_test_interpreter(&result_type, &result_value); let result = rib_executor.run(compiled.byte_code).await.unwrap(); assert_eq!( @@ -1763,7 +1767,7 @@ mod interpreter_tests { })), }); - let result_value = internal::type_annotated_value_result( + let result_value = internal::get_type_annotated_value( &result_type, r#" [{product-id: "foo", name: "bar", price: 100.0, quantity: 1}, {product-id: "bar", name: "baz", price: 200.0, quantity: 2}] @@ -1773,7 +1777,7 @@ mod interpreter_tests { let component_metadata = internal::get_shopping_cart_metadata_with_cart_raw_resource(); let compiled = compiler::compile(&expr, &component_metadata).unwrap(); - let mut rib_executor = internal::test_executor(&result_type, &result_value); + let mut rib_executor = internal::static_test_interpreter(&result_type, &result_value); let result = rib_executor.run(compiled.byte_code).await.unwrap(); assert_eq!( @@ -1833,7 +1837,7 @@ mod interpreter_tests { ], }); - let result_value = internal::type_annotated_value_result( + let result_value = internal::get_type_annotated_value( &result_type, r#" success({order-id: "foo"}) @@ -1843,31 +1847,31 @@ mod interpreter_tests { let component_metadata = internal::get_shopping_cart_metadata_with_cart_raw_resource(); let compiled = compiler::compile(&expr, &component_metadata).unwrap(); - let mut rib_executor = internal::test_executor(&result_type, &result_value); + let mut rib_executor = internal::static_test_interpreter(&result_type, &result_value); let result = rib_executor.run(compiled.byte_code).await.unwrap(); assert_eq!(result.get_val().unwrap(), result_value); } - } - #[tokio::test] - async fn test_interpreter_with_resource_drop() { - let expr = r#" + #[tokio::test] + async fn test_interpreter_with_resource_drop() { + let expr = r#" golem:it/api.{cart.drop}(); "success" "#; - let expr = Expr::from_text(expr).unwrap(); - let component_metadata = internal::get_shopping_cart_metadata_with_cart_raw_resource(); + let expr = Expr::from_text(expr).unwrap(); + let component_metadata = internal::get_shopping_cart_metadata_with_cart_raw_resource(); - let compiled = compiler::compile(&expr, &component_metadata).unwrap(); + let compiled = compiler::compile(&expr, &component_metadata).unwrap(); - let mut rib_interpreter = Interpreter::default(); - let result = rib_interpreter.run(compiled.byte_code).await.unwrap(); + let mut rib_interpreter = Interpreter::default(); + let result = rib_interpreter.run(compiled.byte_code).await.unwrap(); - assert_eq!( - result.get_val().unwrap(), - TypeAnnotatedValue::Str("success".to_string()) - ); + assert_eq!( + result.get_val().unwrap(), + TypeAnnotatedValue::Str("success".to_string()) + ); + } } mod internal { @@ -1967,7 +1971,7 @@ mod interpreter_tests { pub(crate) fn get_component_metadata( function_name: &str, input_types: Vec, - output: AnalysedType, + output: Option, ) -> Vec { let analysed_function_parameters = input_types .into_iter() @@ -1978,13 +1982,20 @@ mod interpreter_tests { }) .collect(); + let results = if let Some(output) = output { + vec![AnalysedFunctionResult { + name: None, + typ: output, + }] + } else { + // Representing Unit + vec![] + }; + vec![AnalysedExport::Function(AnalysedFunction { name: function_name.to_string(), parameters: analysed_function_parameters, - results: vec![AnalysedFunctionResult { - name: None, - typ: output, - }], + results, })] } @@ -2193,14 +2204,14 @@ mod interpreter_tests { vec![instance] } - pub(crate) fn type_annotated_value_result( + pub(crate) fn get_type_annotated_value( analysed_type: &AnalysedType, wasm_wave_str: &str, ) -> TypeAnnotatedValue { golem_wasm_rpc::type_annotated_value_from_str(analysed_type, wasm_wave_str).unwrap() } - pub(crate) fn test_executor( + pub(crate) fn static_test_interpreter( result_type: &AnalysedType, result_value: &TypeAnnotatedValue, ) -> Interpreter { diff --git a/golem-rib/src/interpreter/tests/mod.rs b/golem-rib/src/interpreter/tests/mod.rs new file mode 100644 index 0000000000..16374df421 --- /dev/null +++ b/golem-rib/src/interpreter/tests/mod.rs @@ -0,0 +1,1718 @@ +#[cfg(test)] +mod comprehensive_test { + use crate::{compiler, Expr}; + use golem_wasm_ast::analysis::{AnalysedType, NameTypePair, TypeRecord, TypeStr, TypeU64}; + use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; + + #[tokio::test] + async fn test_interpreter_complex_rib() { + let expr = r#" + + let str1: str = request.body.name; + let str2: str = request.headers.name; + let str3: str = request.path.name; + + let unused = function-unit-response(str1); + + let str_output = function-no-arg(); + + let unused = function-no-arg-unit(); + + let str_response = function-str-response(str_output); + + let number_response = function-number-response(str1); + + let option_str_response = function-option-str-response(str2); + + let option_number_response = function-option-number-response(str1); + + let option_option_response = function-option-option-response(str1); + + let option_variant_response = function-option-variant-response(str1); + + let option_enum_response = function-option-enum-response(str1); + + let option_tuple_response = function-option-tuple-response(str1); + + let option_record_response = function-option-record-response(str1); + + let option_list_response = function-option-list-response(str1); + + let list_number_response = function-list-number-response(str1); + + let list_str_response = function-list-str-response(str1); + + let list_option_response = function-list-option-response(str1); + + let list_list_response = function-list-list-response(str1); + + let list_variant_response = function-list-variant-response(str1); + + let list_enum_response = function-list-enum-response(str1); + + let list_tuple_response = function-list-tuple-response(str1); + + let list_record_response = function-list-record-response(str1); + + let result_of_str_response = function-result-str-response(str1); + + let result_of_number_response = function-result-number-response(str1); + + let result_of_variant_response = function-result-variant-response(str1); + + let result_of_enum_response = function-result-enum-response(str1); + + let result_of_tuple_response = function-result-tuple-response(str1); + + let result_of_flag_response = function-result-flag-response(str1); + + let result_of_record_response = function-result-record-response(str1); + + let result_of_list_response = function-result-list-response(str1); + + let tuple_response = function-tuple-response(str1); + + let enum_response = function-enum-response(str1); + + let flag_response = function-flag-response(str1); + + let variant_response = function-variant-response(str1); + + let str_response_processed = str_response == "foo"; + + let number_response_processed = if number_response == 42u64 then "foo" else "bar"; + + let option_str_response_processed = match option_str_response { + some(text) => text, + none => "not found" + }; + + let option_number_response_processed = match option_number_response { + some(number) => number, + none => 0 + }; + + let option_option_response_processed = match option_option_response { + some(some(x)) => x, + none => "not found" + }; + + let option_variant_response_processed = match option_variant_response { + some(case-str(_)) => "found", + _ => "not found" + }; + + let option_enum_response_processed = match option_enum_response { + some(enum-a) => "a", + some(enum-b) => "b", + _ => "not found" + }; + + let option_tuple_response_processed = match option_tuple_response { + some((text, _, _, _, _, _, _, _, _, _, _, _)) => text, + _ => "not found" + }; + + let option_record_response_processed = match option_record_response { + some({data-body: {list-of-str : _}}) => "found list", + _ => "not found" + }; + + let option_list_response_processed = match option_list_response { + some([_]) => "found list", + _ => "not found" + }; + + let list_number_response_processed = match list_number_response { + [number] => if number > 10u64 then "greater" else "lesser", + _ => "not found" + }; + + let list_str_response_processed = match list_str_response { + [text] => text, + _ => "not found" + }; + + + let list_option_response_processed = match list_option_response { + [some(text)] => text, + _ => "not found" + }; + + + let list_list_response_processed = match list_list_response { + [[text]] => text, + _ => "not found" + }; + + + let list_variant_response_processed = match list_variant_response { + [case-str(text)] => text, + _ => "not found" + }; + + let list_enum_response_processed = match list_enum_response { + [enum-a] => "a", + [enum-b] => "b", + _ => "not found" + }; + + let list_tuple_response_processed = match list_tuple_response { + [(text, _, _, _, _, _, _, _, _, _, _, _)] => text, + _ => "not found" + }; + + let list_record_response_processed = match list_record_response { + [{data-body: {list-of-str : [text]}}] => text, + _ => "not found" + }; + + let result_of_str_response_processed = match result_of_str_response { + ok(text) => text, + err(msg) => "not found" + }; + + let result_of_number_response_processed = match result_of_number_response { + ok(number) => number, + err(msg) => 0 + }; + + let result_of_variant_response_processed = match result_of_variant_response { + ok(case-str(_)) => "found", + err(msg) => "not found" + }; + + let result_of_enum_response_processed = match result_of_enum_response { + ok(enum-a) => "a", + ok(enum-b) => "b", + ok(enum-c) => "c", + err(msg) => "not found" + }; + + let result_of_tuple_response_processed = match result_of_tuple_response { + ok((text, _, _, _, _, _, _, _, _, _, _, _)) => text, + err(msg) => "not found" + }; + + let result_of_flag_response_processed = match result_of_flag_response { + ok({featurex, featurey, featurez}) => "found all flags", + ok({featurex}) => "found x", + ok({featurey}) => "found x", + ok({featurex, featurey}) => "found x and y", + _ => "not found" + }; + + let result_of_record_response_processed = match result_of_record_response { + ok({data-body: {list-of-str : _}}) => "found list", + err(msg) => "not found" + }; + + let tuple_response_processed = match tuple_response { + (_, _, _, _, _, _, _, _, _, _, case-hello(a), _) => "${a}" + }; + + let enum_response_processed = match enum_response { + enum-a => "a", + enum-b => "b", + enum-c => "c", + _ => "not found" + }; + + let variant_response_processed = match variant_response { + case-str(text) => text, + _ => "not found" + }; + + { + a : option_str_response_processed, + b: option_number_response_processed, + c: option_option_response_processed, + d: option_variant_response_processed, + e: option_enum_response_processed, + f: option_tuple_response_processed, + g: option_record_response_processed, + h: option_list_response_processed, + i: list_number_response_processed, + j: list_str_response_processed, + k: list_option_response_processed, + l: list_list_response_processed, + m: list_variant_response_processed, + n: list_enum_response_processed, + o: list_tuple_response_processed, + p: list_record_response_processed, + q: result_of_str_response_processed, + r: result_of_number_response_processed, + s: result_of_variant_response_processed, + t: result_of_enum_response_processed, + u: result_of_tuple_response_processed, + v: result_of_flag_response_processed, + w: result_of_record_response_processed, + x: tuple_response_processed, + y: enum_response_processed, + z: variant_response_processed + } + "#; + + let expr = Expr::from_text(expr).unwrap(); + + let compiled_expr = compiler::compile(&expr, &component_metadata::component_metadata()) + .unwrap() + .byte_code; + + let mut rib_executor = mock_interpreter::interpreter(); + let result = rib_executor.run(compiled_expr).await.unwrap(); + + assert_eq!(result.get_val().unwrap(), expected_type_annotated_value()); + } + + fn expected_type_annotated_value() -> TypeAnnotatedValue { + let wasm_wave_str = "{a: \"foo\", b: 42, c: \"foo\", d: \"found\", e: \"a\", f: \"foo\", g: \"found list\", h: \"found list\", i: \"greater\", j: \"foo\", k: \"foo\", l: \"foo\", m: \"foo\", n: \"a\", o: \"foo\", p: \"foo\", q: \"foo\", r: 42, s: \"found\", t: \"a\", u: \"foo\", v: \"found x\", w: \"found list\", x: \"42\", y: \"a\", z: \"foo\"}"; + + test_utils::get_type_annotated_value(&expected_analysed_type(), wasm_wave_str) + } + + fn expected_analysed_type() -> AnalysedType { + AnalysedType::Record(TypeRecord { + fields: vec![ + NameTypePair { + name: "a".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "b".to_string(), + typ: AnalysedType::U64(TypeU64), + }, + NameTypePair { + name: "c".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "d".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "e".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "f".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "g".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "h".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "i".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "j".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "k".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "l".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "m".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "n".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "o".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "p".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "q".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "r".to_string(), + typ: AnalysedType::U64(TypeU64), + }, + NameTypePair { + name: "s".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "t".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "u".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "v".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "w".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "x".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "y".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "z".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + ], + }) + } + + mod component_metadata { + use crate::interpreter::tests::comprehensive_test::function_metadata; + use golem_wasm_ast::analysis::AnalysedExport; + + pub(crate) fn component_metadata() -> Vec { + let mut exports = vec![]; + exports.extend(function_metadata::function_unit_response()); + exports.extend(function_metadata::function_no_arg()); + exports.extend(function_metadata::function_no_arg_unit()); + exports.extend(function_metadata::function_str_response()); + exports.extend(function_metadata::function_number_response()); + exports.extend(function_metadata::function_option_of_str_response()); + exports.extend(function_metadata::function_option_of_number_response()); + exports.extend(function_metadata::function_option_of_option_response()); + exports.extend(function_metadata::function_option_of_variant_response()); + exports.extend(function_metadata::function_option_of_enum_response()); + exports.extend(function_metadata::function_option_of_tuple_response()); + exports.extend(function_metadata::function_option_of_record_response()); + exports.extend(function_metadata::function_option_of_list_response()); + exports.extend(function_metadata::function_list_of_number_response()); + exports.extend(function_metadata::function_list_of_str_response()); + exports.extend(function_metadata::function_list_of_option_response()); + exports.extend(function_metadata::function_list_of_list_response()); + exports.extend(function_metadata::function_list_of_variant_response()); + exports.extend(function_metadata::function_list_of_enum_response()); + exports.extend(function_metadata::function_list_of_tuple_response()); + exports.extend(function_metadata::function_list_of_record_response()); + exports.extend(function_metadata::function_result_of_str_response()); + exports.extend(function_metadata::function_result_of_number_response()); + exports.extend(function_metadata::function_result_of_option_response()); + exports.extend(function_metadata::function_result_of_variant_response()); + exports.extend(function_metadata::function_result_of_enum_response()); + exports.extend(function_metadata::function_result_of_tuple_response()); + exports.extend(function_metadata::function_result_of_flag_response()); + exports.extend(function_metadata::function_result_of_record_response()); + exports.extend(function_metadata::function_result_of_list_response()); + exports.extend(function_metadata::function_tuple_response()); + exports.extend(function_metadata::function_enum_response()); + exports.extend(function_metadata::function_flag_response()); + exports.extend(function_metadata::function_variant_response()); + exports.extend(function_metadata::function_record_response()); + exports.extend(function_metadata::function_all_inputs()); + + exports + } + } + + mod function_metadata { + use crate::interpreter::tests::comprehensive_test::{data_types, test_utils}; + use golem_wasm_ast::analysis::AnalysedExport; + + pub(crate) fn function_unit_response() -> Vec { + test_utils::get_function_component_metadata( + "function-unit-response", + vec![data_types::str_type()], + None, + ) + } + + pub(crate) fn function_no_arg() -> Vec { + test_utils::get_function_component_metadata( + "function-no-arg", + vec![], + Some(data_types::str_type()), + ) + } + + pub(crate) fn function_no_arg_unit() -> Vec { + test_utils::get_function_component_metadata("function-no-arg-unit", vec![], None) + } + + pub(crate) fn function_str_response() -> Vec { + test_utils::get_function_component_metadata( + "function-str-response", + vec![data_types::str_type()], + Some(data_types::str_type()), + ) + } + + pub(crate) fn function_number_response() -> Vec { + test_utils::get_function_component_metadata( + "function-number-response", + vec![data_types::str_type()], + Some(data_types::number_type()), + ) + } + + pub(crate) fn function_option_of_str_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-str-response", + vec![data_types::str_type()], + Some(data_types::option_of_str_type()), + ) + } + + pub(crate) fn function_option_of_number_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-number-response", + vec![data_types::str_type()], + Some(data_types::option_of_number_type()), + ) + } + + pub(crate) fn function_option_of_option_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-option-response", + vec![data_types::str_type()], + Some(data_types::option_of_option_type()), + ) + } + + pub(crate) fn function_option_of_variant_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-variant-response", + vec![data_types::str_type()], + Some(data_types::option_of_variant_type()), + ) + } + + pub(crate) fn function_option_of_enum_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-enum-response", + vec![data_types::str_type()], + Some(data_types::option_of_enum_type()), + ) + } + + pub(crate) fn function_option_of_tuple_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-tuple-response", + vec![data_types::str_type()], + Some(data_types::option_of_tuple()), + ) + } + + pub(crate) fn function_option_of_record_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-record-response", + vec![data_types::str_type()], + Some(data_types::option_of_record_type()), + ) + } + + pub(crate) fn function_option_of_list_response() -> Vec { + test_utils::get_function_component_metadata( + "function-option-list-response", + vec![data_types::str_type()], + Some(data_types::option_of_list()), + ) + } + + pub(crate) fn function_list_of_number_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-number-response", + vec![data_types::str_type()], + Some(data_types::list_of_number_type_type()), + ) + } + + pub(crate) fn function_list_of_str_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-str-response", + vec![data_types::str_type()], + Some(data_types::list_of_str_type()), + ) + } + + pub(crate) fn function_list_of_option_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-option-response", + vec![data_types::str_type()], + Some(data_types::list_of_option_type()), + ) + } + + pub(crate) fn function_list_of_list_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-list-response", + vec![data_types::str_type()], + Some(data_types::list_of_list_type()), + ) + } + + pub(crate) fn function_list_of_variant_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-variant-response", + vec![data_types::str_type()], + Some(data_types::list_of_variant_type()), + ) + } + + pub(crate) fn function_list_of_enum_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-enum-response", + vec![data_types::str_type()], + Some(data_types::list_of_enum_type()), + ) + } + + pub(crate) fn function_list_of_tuple_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-tuple-response", + vec![data_types::str_type()], + Some(data_types::list_of_tuple()), + ) + } + + pub(crate) fn function_list_of_record_response() -> Vec { + test_utils::get_function_component_metadata( + "function-list-record-response", + vec![data_types::str_type()], + Some(data_types::list_of_record_type()), + ) + } + + pub(crate) fn function_result_of_str_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-str-response", + vec![data_types::str_type()], + Some(data_types::result_of_str_type()), + ) + } + + pub(crate) fn function_result_of_number_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-number-response", + vec![data_types::str_type()], + Some(data_types::result_of_number_type()), + ) + } + + pub(crate) fn function_result_of_option_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-option-response", + vec![data_types::str_type()], + Some(data_types::result_of_option_type()), + ) + } + + pub(crate) fn function_result_of_variant_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-variant-response", + vec![data_types::str_type()], + Some(data_types::result_of_variant_type()), + ) + } + + pub(crate) fn function_result_of_enum_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-enum-response", + vec![data_types::str_type()], + Some(data_types::result_of_enum_type()), + ) + } + + pub(crate) fn function_result_of_tuple_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-tuple-response", + vec![data_types::str_type()], + Some(data_types::result_of_tuple_type()), + ) + } + + pub(crate) fn function_result_of_flag_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-flag-response", + vec![data_types::str_type()], + Some(data_types::result_of_flag_type()), + ) + } + + pub(crate) fn function_result_of_record_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-record-response", + vec![data_types::str_type()], + Some(data_types::result_of_record_type()), + ) + } + + pub(crate) fn function_result_of_list_response() -> Vec { + test_utils::get_function_component_metadata( + "function-result-list-response", + vec![data_types::str_type()], + Some(data_types::result_of_list_type()), + ) + } + + pub(crate) fn function_tuple_response() -> Vec { + test_utils::get_function_component_metadata( + "function-tuple-response", + vec![data_types::str_type()], + Some(data_types::tuple_type()), + ) + } + + pub(crate) fn function_enum_response() -> Vec { + test_utils::get_function_component_metadata( + "function-enum-response", + vec![data_types::str_type()], + Some(data_types::enum_type()), + ) + } + + pub(crate) fn function_flag_response() -> Vec { + test_utils::get_function_component_metadata( + "function-flag-response", + vec![data_types::str_type()], + Some(data_types::flag_type()), + ) + } + + pub(crate) fn function_variant_response() -> Vec { + test_utils::get_function_component_metadata( + "function-variant-response", + vec![data_types::str_type()], + Some(data_types::variant_type()), + ) + } + + pub(crate) fn function_record_response() -> Vec { + test_utils::get_function_component_metadata( + "function-record-response", + vec![data_types::str_type()], + Some(data_types::record_type()), + ) + } + + pub(crate) fn function_all_inputs() -> Vec { + test_utils::get_function_component_metadata( + "function-all-inputs", + vec![ + data_types::str_type(), + data_types::number_type(), + data_types::option_of_str_type(), + data_types::option_of_number_type(), + data_types::option_of_option_type(), + data_types::option_of_variant_type(), + data_types::option_of_enum_type(), + data_types::option_of_tuple(), + data_types::option_of_record_type(), + data_types::option_of_list(), + data_types::list_of_number_type_type(), + data_types::list_of_str_type(), + data_types::list_of_option_type(), + data_types::list_of_list_type(), + data_types::list_of_variant_type(), + data_types::list_of_enum_type(), + data_types::list_of_tuple(), + data_types::list_of_record_type(), + data_types::result_of_str_type(), + data_types::result_of_number_type(), + data_types::result_of_option_type(), + data_types::result_of_variant_type(), + data_types::result_of_enum_type(), + data_types::result_of_tuple_type(), + data_types::result_of_flag_type(), + data_types::result_of_record_type(), + data_types::result_of_list_type(), + data_types::tuple_type(), + data_types::enum_type(), + data_types::flag_type(), + data_types::variant_type(), + data_types::record_type(), + ], + Some(data_types::str_type()), + ) + } + } + + mod data_types { + use crate::interpreter::tests::comprehensive_test::test_utils; + use golem_wasm_ast::analysis::*; + + // Result + pub(crate) fn result_of_str_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(AnalysedType::Str(TypeStr))), + err: Some(Box::new(AnalysedType::Str(TypeStr))), + }) + } + + pub(crate) fn result_of_number_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(AnalysedType::U64(TypeU64))), + err: Some(Box::new(AnalysedType::U64(TypeU64))), + }) + } + + pub(crate) fn result_of_option_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(option_of_str_type())), + err: Some(Box::new(option_of_str_type())), + }) + } + + pub(crate) fn result_of_variant_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(variant_type())), + err: Some(Box::new(variant_type())), + }) + } + + pub(crate) fn result_of_enum_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(enum_type())), + err: Some(Box::new(enum_type())), + }) + } + + pub(crate) fn result_of_tuple_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(tuple_type())), + err: Some(Box::new(tuple_type())), + }) + } + + pub(crate) fn result_of_flag_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(flag_type())), + err: Some(Box::new(flag_type())), + }) + } + + pub(crate) fn result_of_record_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(record_type())), + err: Some(Box::new(record_type())), + }) + } + + pub(crate) fn result_of_list_type() -> AnalysedType { + AnalysedType::Result(TypeResult { + ok: Some(Box::new(list_of_str_type())), + err: Some(Box::new(list_of_str_type())), + }) + } + + // List + pub(crate) fn list_of_number_type_type() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::U64(TypeU64)), + }) + } + + pub(crate) fn list_of_str_type() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::Str(TypeStr)), + }) + } + + pub(crate) fn list_of_option_type() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::Str(TypeStr)), + })), + }) + } + + pub(crate) fn list_of_list_type() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::Str(TypeStr)), + })), + }) + } + + pub(crate) fn list_of_variant_type() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(variant_type()), + }) + } + + pub(crate) fn list_of_enum_type() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(enum_type()), + }) + } + + pub(crate) fn list_of_tuple() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(tuple_type()), + }) + } + + pub(crate) fn list_of_record_type() -> AnalysedType { + AnalysedType::List(TypeList { + inner: Box::new(record_type()), + }) + } + + pub(crate) fn option_of_number_type() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::U64(TypeU64)), + }) + } + + // Option + pub(crate) fn option_of_str_type() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::Str(TypeStr)), + }) + } + + pub(crate) fn option_of_option_type() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::Str(TypeStr)), + })), + }) + } + + pub(crate) fn option_of_variant_type() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(variant_type()), + }) + } + + pub(crate) fn option_of_enum_type() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(enum_type()), + }) + } + + pub(crate) fn option_of_tuple() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(tuple_type()), + }) + } + + pub(crate) fn option_of_record_type() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(record_type()), + }) + } + + pub(crate) fn option_of_list() -> AnalysedType { + AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::Str(TypeStr)), + })), + }) + } + + // Record + pub(crate) fn record_type() -> AnalysedType { + test_utils::analysed_type_record(vec![ + ( + "string-headers", + test_utils::analysed_type_record(vec![( + "authorization-string", + AnalysedType::Str(TypeStr), + )]), + ), + ( + "data-body", + test_utils::analysed_type_record(vec![ + ("str", AnalysedType::Str(TypeStr)), + ("list-of-str", list_of_str_type()), + ("list-of-option", list_of_option_type()), + ("list-of-list", list_of_list_type()), + ("list-of-variant", list_of_variant_type()), + ("list-of-enum", list_of_enum_type()), + ("list-of-tuple", list_of_tuple()), + ( + "list-of-record", + AnalysedType::List(TypeList { + inner: Box::new(test_utils::analysed_type_record(vec![ + ("field-string-one", AnalysedType::Str(TypeStr)), + ("field-string-two", AnalysedType::Str(TypeStr)), + ])), + }), + ), + ("option-of-str", option_of_str_type()), + ("option-of-option", option_of_option_type()), + ("option-of-variant", option_of_variant_type()), + ("option-of-enum", option_of_enum_type()), + ("option-of-tuple", option_of_tuple()), + ( + "option-of-record", + AnalysedType::Option(TypeOption { + inner: Box::new(test_utils::analysed_type_record(vec![ + ("field-string-one", AnalysedType::Str(TypeStr)), + ("field-string-two", AnalysedType::Str(TypeStr)), + ])), + }), + ), + ("option-of-list", option_of_list()), + ( + "nested-record", + test_utils::analysed_type_record(vec![ + ("field-string-one", AnalysedType::Str(TypeStr)), + ("field-string-two", AnalysedType::Str(TypeStr)), + ]), + ), + ("variant-data-a", variant_type()), + ("variant-data-b", variant_type()), + ("variant-data-c", variant_type()), + ("variant-data-d", variant_type()), + ("variant-data-e", variant_type()), + ("variant-data-f", variant_type()), + ("enum-data-a", enum_type()), + ("enum-data-b", enum_type()), + ("enum-data-c", enum_type()), + ("flags-data-a", flag_type()), + ("flags-data-b", flag_type()), + ("flags-data-c", flag_type()), + ("result-data-a", result_of_str_type()), + ("result-data-b", result_of_number_type()), + ("result-data-c", result_of_enum_type()), + ("result-data-d", result_of_variant_type()), + ("result-data-e", result_of_tuple_type()), + ("result-data-f", result_of_option_type()), + ("result-data-g", result_of_str_type()), + ("result-data-h", result_of_number_type()), + ("result-data-i", result_of_enum_type()), + ("result-data-j", result_of_variant_type()), + ("result-data-k", result_of_tuple_type()), + ("result-data-l", result_of_option_type()), + ("result-data-m", result_of_flag_type()), + ("result-data-n", result_of_flag_type()), + ("tuple-data", tuple_type()), + ("character-data", AnalysedType::Chr(TypeChr)), + ("f64-data", AnalysedType::F64(TypeF64)), + ("f32-data", AnalysedType::F32(TypeF32)), + ("u64-data", AnalysedType::U64(TypeU64)), + ("s64-data", AnalysedType::S64(TypeS64)), + ("u32-data", AnalysedType::U32(TypeU32)), + ("s32-data", AnalysedType::S32(TypeS32)), + ("u16-data", AnalysedType::U16(TypeU16)), + ("s16-data", AnalysedType::S16(TypeS16)), + ("u8-data", AnalysedType::U8(TypeU8)), + ("s8-data", AnalysedType::S8(TypeS8)), + ("boolean-data", AnalysedType::Bool(TypeBool)), + ]), + ), + ]) + } + + // Tuple + pub(crate) fn tuple_type() -> AnalysedType { + AnalysedType::Tuple(TypeTuple { + items: vec![ + AnalysedType::Str(TypeStr), + AnalysedType::U64(TypeU64), + AnalysedType::S32(TypeS32), + AnalysedType::F32(TypeF32), + AnalysedType::F64(TypeF64), + AnalysedType::Bool(TypeBool), + AnalysedType::Chr(TypeChr), + AnalysedType::Option(TypeOption { + inner: Box::new(AnalysedType::S16(TypeS16)), + }), + AnalysedType::Result(TypeResult { + ok: Some(Box::new(AnalysedType::U8(TypeU8))), + err: Some(Box::new(AnalysedType::S8(TypeS8))), + }), + AnalysedType::List(TypeList { + inner: Box::new(AnalysedType::Bool(TypeBool)), + }), + AnalysedType::Variant(TypeVariant { + cases: vec![ + NameOptionTypePair { + name: "case-hello".to_string(), + typ: Some(AnalysedType::F64(TypeF64)), + }, + NameOptionTypePair { + name: "case-none".to_string(), + typ: None, + }, + ], + }), + AnalysedType::Record(TypeRecord { + // Option + fields: vec![ + NameTypePair { + name: "field-one".to_string(), + typ: AnalysedType::Bool(TypeBool), + }, + NameTypePair { + name: "field-two".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + ], + }), + ], + }) + } + + // Enum + pub(crate) fn enum_type() -> AnalysedType { + AnalysedType::Enum(TypeEnum { + cases: vec![ + "enum-a".to_string(), + "enum-b".to_string(), + "enum-c".to_string(), + ], + }) + } + + // Str + pub(crate) fn str_type() -> AnalysedType { + AnalysedType::Str(TypeStr) + } + + // Number + pub(crate) fn number_type() -> AnalysedType { + AnalysedType::U64(TypeU64) + } + + // Flag + pub(crate) fn flag_type() -> AnalysedType { + AnalysedType::Flags(TypeFlags { + names: vec![ + "featurex".to_string(), + "featurey".to_string(), + "featurez".to_string(), + ], + }) + } + + // Variant + pub(crate) fn variant_type() -> AnalysedType { + AnalysedType::Variant(TypeVariant { + cases: vec![ + NameOptionTypePair { + name: "case-none".to_string(), + typ: None, + }, + NameOptionTypePair { + name: "case-str".to_string(), + typ: Some(AnalysedType::Str(TypeStr)), // Variant case for String + }, + NameOptionTypePair { + name: "case-u64".to_string(), + typ: Some(AnalysedType::U64(TypeU64)), // Variant case for u64 + }, + NameOptionTypePair { + name: "case-s32".to_string(), + typ: Some(AnalysedType::S32(TypeS32)), // Variant case for i32 + }, + NameOptionTypePair { + name: "case-f32".to_string(), + typ: Some(AnalysedType::F32(TypeF32)), // Variant case for f32 + }, + NameOptionTypePair { + name: "case-f64".to_string(), + typ: Some(AnalysedType::F64(TypeF64)), // Variant case for f64 + }, + NameOptionTypePair { + name: "case-bool".to_string(), + typ: Some(AnalysedType::Bool(TypeBool)), // Variant case for bool + }, + NameOptionTypePair { + name: "case-chr".to_string(), + typ: Some(AnalysedType::Chr(TypeChr)), // Variant case for char + }, + NameOptionTypePair { + name: "case-list".to_string(), + typ: Some(AnalysedType::List(TypeList { + // Variant case for List + inner: Box::new(AnalysedType::S16(TypeS16)), + })), + }, + NameOptionTypePair { + name: "case-option".to_string(), + typ: Some(AnalysedType::Option(TypeOption { + // Variant case for Option + inner: Box::new(AnalysedType::U16(TypeU16)), + })), + }, + NameOptionTypePair { + name: "case-result".to_string(), + typ: Some(AnalysedType::Result(TypeResult { + // Variant case for Result + ok: Some(Box::new(AnalysedType::U8(TypeU8))), + err: Some(Box::new(AnalysedType::S8(TypeS8))), + })), + }, + NameOptionTypePair { + name: "case-record".to_string(), + typ: Some(AnalysedType::Record(TypeRecord { + // Variant case for Record + fields: vec![ + NameTypePair { + name: "field1".to_string(), + typ: AnalysedType::Str(TypeStr), + }, + NameTypePair { + name: "field2".to_string(), + typ: AnalysedType::Bool(TypeBool), + }, + ], + })), + }, + NameOptionTypePair { + name: "case-tuple".to_string(), + typ: Some(AnalysedType::Tuple(TypeTuple { + // Variant case for Tuple + items: vec![AnalysedType::F32(TypeF32), AnalysedType::U32(TypeU32)], + })), + }, + ], + }) + } + } + + mod mock_data { + use crate::interpreter::tests::comprehensive_test::{data_types, test_utils}; + use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; + + pub(crate) fn result_of_str() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::result_of_str_type(), "ok(\"foo\")") + } + + pub(crate) fn result_of_number() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::result_of_number_type(), "ok(42)") + } + + pub(crate) fn result_of_option() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::result_of_option_type(), + "ok(some(\"foo\"))", + ) + } + + pub(crate) fn result_of_variant() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::result_of_variant_type(), + "ok(case-str(\"foo\"))", + ) + } + + pub(crate) fn result_of_enum() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::result_of_enum_type(), "ok(enum-a)") + } + + pub(crate) fn result_of_tuple() -> TypeAnnotatedValue { + let tuple_str = test_utils::convert_type_annotated_value_to_str(&tuple()); + let wave_str = format!("ok({})", tuple_str); + test_utils::get_type_annotated_value( + &data_types::result_of_tuple_type(), + wave_str.as_str(), + ) + } + + pub(crate) fn result_of_flag() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::result_of_flag_type(), + "ok({featurex})", + ) + } + + pub(crate) fn result_of_record() -> TypeAnnotatedValue { + let record_str = test_utils::convert_type_annotated_value_to_str(&record()); + let wave_str = format!("ok({})", &record_str); + test_utils::get_type_annotated_value( + &data_types::result_of_record_type(), + wave_str.as_str(), + ) + } + + pub(crate) fn result_of_list() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::result_of_list_type(), + "ok([\"foo\"])", + ) + } + + pub(crate) fn list_of_number() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::list_of_number_type_type(), "[42]") + } + + pub(crate) fn list_of_str() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::list_of_str_type(), "[\"foo\"]") + } + + pub(crate) fn list_of_option() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::list_of_option_type(), + "[some(\"foo\")]", + ) + } + + pub(crate) fn list_of_list() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::list_of_list_type(), "[[\"foo\"]]") + } + + pub(crate) fn list_of_variant() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::list_of_variant_type(), + "[case-str(\"foo\")]", + ) + } + + pub(crate) fn list_of_enum() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::list_of_enum_type(), "[enum-a]") + } + + pub(crate) fn list_of_tuple() -> TypeAnnotatedValue { + let tuple_str = test_utils::convert_type_annotated_value_to_str(&tuple()); + let wave_str = format!("[{}, {}]", &tuple_str, &tuple_str); + test_utils::get_type_annotated_value(&data_types::list_of_tuple(), wave_str.as_str()) + } + + pub(crate) fn list_of_record() -> TypeAnnotatedValue { + let record_str = test_utils::convert_type_annotated_value_to_str(&record()); + let wave_str = format!("[{}]", &record_str); + test_utils::get_type_annotated_value( + &data_types::list_of_record_type(), + wave_str.as_str(), + ) + } + + pub(crate) fn option_of_number() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::option_of_number_type(), "some(42)") + } + + pub(crate) fn option_of_str() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::option_of_str_type(), "some(\"foo\")") + } + + pub(crate) fn option_of_option() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::option_of_option_type(), + "some(some(\"foo\"))", + ) + } + + pub(crate) fn option_of_variant() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::option_of_variant_type(), + "some(case-str(\"foo\"))", + ) + } + + pub(crate) fn option_of_enum() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::option_of_enum_type(), "some(enum-a)") + } + + pub(crate) fn option_of_tuple() -> TypeAnnotatedValue { + let tuple_str = test_utils::convert_type_annotated_value_to_str(&tuple()); + let wave_str = format!("some({})", tuple_str); + test_utils::get_type_annotated_value(&data_types::option_of_tuple(), wave_str.as_str()) + } + + pub(crate) fn option_of_record() -> TypeAnnotatedValue { + let record_str = test_utils::convert_type_annotated_value_to_str(&record()); + let wave_str = format!("some({})", &record_str); + test_utils::get_type_annotated_value( + &data_types::option_of_record_type(), + wave_str.as_str(), + ) + } + + pub(crate) fn option_of_list() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::option_of_list(), "some([\"foo\"])") + } + + pub(crate) fn tuple() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::tuple_type(), + r#" + ("foo", 42, 42, 42, 42, true, 'a', some(42), ok(42), [true], case-hello(42.0), {field-one: true, field-two: "foo"})"#, + ) + } + + pub(crate) fn enum_data() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::enum_type(), "enum-a") + } + + pub(crate) fn str_data() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::str_type(), "\"foo\"") + } + + pub(crate) fn number_data() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::number_type(), "42") + } + + pub(crate) fn flag() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::flag_type(), "{featurex}") + } + + pub(crate) fn variant() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value(&data_types::variant_type(), "case-str(\"foo\")") + } + + pub(crate) fn record() -> TypeAnnotatedValue { + test_utils::get_type_annotated_value( + &data_types::record_type(), + r#" + { + string-headers: {authorization-string: "foo"}, + data-body: { + str: "foo", + list-of-str: ["foo"], + list-of-option: ["foo"], + list-of-list: [["foo"]], + list-of-variant: [case-str("foo")], + list-of-enum: [enum-a], + list-of-tuple: [("foo", 42, 42, 42, 42, true, 'a', some(42), ok(42), [true], case-hello(42.0), {field-one: true, field-two: "foo"})], + list-of-record: [{field-string-one: "foo", field-string-two: "foo"}], + nested-record: {field-string-one: "foo", field-string-two: "foo"}, + option-of-str: some("foo"), + option-of-option: some(some("foo")), + option-of-variant: some(case-str("foo")), + option-of-enum: some(enum-a), + option-of-tuple: some(("foo", 42, 42, 42, 42, true, 'a', some(42), ok(42), [true], case-hello(42.0), {field-one: true, field-two: "foo"})), + option-of-record: some({field-string-one: "foo", field-string-two: "foo"}), + option-of-list: some(["foo"]), + variant-data-a: case-str("foo") + variant-data-b: case-str("foo"), + variant-data-c: case-str("foo"), + variant-data-d: case-str("foo"), + variant-data-e: case-str("foo"), + variant-data-f: case-str("foo"), + variant-data-g: case-str("foo"), + enum-data-a: enum-a, + enum-data-b: enum-b, + enum-data-c: enum-c, + flags-data-a: { featurex }, + flags-data-b: { featurex, featurey }, + flags-data-c: { featurex, featurey, featurez }, + result-data-a: ok("foo"), + result-data-b: ok(42), + result-data-c: ok(enum-a), + result-data-d: ok(case-str("foo")), + result-data-e: ok(("foo", 42, 42, 42, 42, true, 'a', some(42), ok(42), [true], case-hello(42.0), {field-one: true, field-two: "foo"})), + result-data-f: ok(some("foo")), + result-data-g: err("foo"), + result-data-h: err(42), + result-data-i: err(enum-a), + result-data-j: err(case-str("foo")), + result-data-k: err(("foo", 42, 42, 42, 42, true, 'a', some(42), ok(42), [true], case-hello(42.0), {field-one: true, field-two: "foo"})), + result-data-l: err(some("foo")), + result-data-m: ok({ featurex, featurey, featurez }), + result-data-n: err({ featurex, featurey, featurez }) + tuple-data: ("foo", 42, 42, 42, 42, true, 'a', some(42), ok(42), [true], case-hello(42.0), {field-one: true, field-two: "foo"}), + character-data : 'x', + f64-data : 3.14, + f32-data : 3.14, + u64-data : 42, + s64-data : 42, + u32-data : 42, + s32-data : 42, + u16-data : 42, + s16-data : 42, + u8-data : 42, + s8-data : 42, + boolean-data : true + } + }"#, + ) + } + } + + mod mock_interpreter { + use crate::interpreter::env::InterpreterEnv; + use crate::interpreter::stack::InterpreterStack; + use crate::interpreter::tests::comprehensive_test::{mock_data, test_utils}; + use crate::{Interpreter, RibFunctionInvoke}; + use golem_wasm_ast::analysis::{AnalysedType, TypeStr}; + use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; + use golem_wasm_rpc::protobuf::TypedTuple; + #[cfg(test)] + use std::collections::HashMap; + use std::sync::Arc; + + pub(crate) fn interpreter() -> Interpreter { + let functions_and_results: Vec<(&str, Option)> = vec![ + ("function-unit-response", None), + ("function-no-arg", Some(mock_data::str_data())), + ("function-no-arg-unit", None), + ("function-str-response", Some(mock_data::str_data())), + ("function-number-response", Some(mock_data::number_data())), + ( + "function-option-str-response", + Some(mock_data::option_of_str()), + ), + ( + "function-option-number-response", + Some(mock_data::option_of_number()), + ), + ( + "function-option-option-response", + Some(mock_data::option_of_option()), + ), + ( + "function-option-variant-response", + Some(mock_data::option_of_variant()), + ), + ( + "function-option-enum-response", + Some(mock_data::option_of_enum()), + ), + ( + "function-option-tuple-response", + Some(mock_data::option_of_tuple()), + ), + ( + "function-option-record-response", + Some(mock_data::option_of_record()), + ), + ( + "function-option-list-response", + Some(mock_data::option_of_list()), + ), + ( + "function-list-number-response", + Some(mock_data::list_of_number()), + ), + ("function-list-str-response", Some(mock_data::list_of_str())), + ( + "function-list-option-response", + Some(mock_data::list_of_option()), + ), + ( + "function-list-list-response", + Some(mock_data::list_of_list()), + ), + ( + "function-list-variant-response", + Some(mock_data::list_of_variant()), + ), + ( + "function-list-enum-response", + Some(mock_data::list_of_enum()), + ), + ( + "function-list-tuple-response", + Some(mock_data::list_of_tuple()), + ), + ( + "function-list-record-response", + Some(mock_data::list_of_record()), + ), + ( + "function-result-str-response", + Some(mock_data::result_of_str()), + ), + ( + "function-result-number-response", + Some(mock_data::result_of_number()), + ), + ( + "function-result-option-response", + Some(mock_data::result_of_option()), + ), + ( + "function-result-variant-response", + Some(mock_data::result_of_variant()), + ), + ( + "function-result-enum-response", + Some(mock_data::result_of_enum()), + ), + ( + "function-result-tuple-response", + Some(mock_data::result_of_tuple()), + ), + ( + "function-result-flag-response", + Some(mock_data::result_of_flag()), + ), + ( + "function-result-record-response", + Some(mock_data::result_of_record()), + ), + ( + "function-result-list-response", + Some(mock_data::result_of_list()), + ), + ("function-tuple-response", Some(mock_data::tuple())), + ("function-enum-response", Some(mock_data::enum_data())), + ("function-flag-response", Some(mock_data::flag())), + ("function-variant-response", Some(mock_data::variant())), + ("function-record-response", Some(mock_data::record())), + ("function-all-inputs", Some(mock_data::str_data())), + ]; + + let functions_and_result: HashMap> = + functions_and_results + .into_iter() + .map(|(name, result)| (FunctionName(name.to_string()), result)) + .collect(); + + let record_input_type = test_utils::analysed_type_record(vec![ + ( + "headers", + test_utils::analysed_type_record(vec![("name", AnalysedType::Str(TypeStr))]), + ), + ( + "body", + test_utils::analysed_type_record(vec![("name", AnalysedType::Str(TypeStr))]), + ), + ( + "path", + test_utils::analysed_type_record(vec![("name", AnalysedType::Str(TypeStr))]), + ), + ]); + + let record_input_value = test_utils::get_type_annotated_value( + &record_input_type, + r#" { headers : { name : "foo" }, body : { name : "bar" }, path : { name : "baz" } }"#, + ); + + let mut interpreter_env_input: HashMap = HashMap::new(); + interpreter_env_input.insert("request".to_string(), record_input_value); + + dynamic_test_interpreter(functions_and_result, interpreter_env_input) + } + + #[derive(Clone, Hash, PartialEq, Eq)] + struct FunctionName(pub(crate) String); + + fn dynamic_test_interpreter( + functions_and_result: HashMap>, + interpreter_env_input: HashMap, + ) -> Interpreter { + Interpreter { + stack: InterpreterStack::default(), + env: InterpreterEnv::from( + interpreter_env_input, + dynamic_worker_invoke(functions_and_result), + ), + } + } + + fn dynamic_worker_invoke( + functions_and_result: HashMap>, + ) -> RibFunctionInvoke { + let value = functions_and_result.clone(); + + Arc::new(move |a, _| { + Box::pin({ + let value = value.get(&FunctionName(a)).cloned().flatten(); + let analysed_type = value.clone().map(|x| AnalysedType::try_from(&x).unwrap()); + + async move { + let analysed_type = analysed_type.clone(); + let value = value.clone(); + + if let Some(value) = value { + Ok(TypeAnnotatedValue::Tuple(TypedTuple { + typ: vec![golem_wasm_ast::analysis::protobuf::Type::from( + &analysed_type.unwrap(), + )], + value: vec![golem_wasm_rpc::protobuf::TypeAnnotatedValue { + type_annotated_value: Some(value), + }], + })) + } else { + // Representing Unit + Ok(TypeAnnotatedValue::Tuple(TypedTuple { + typ: vec![], + value: vec![], + })) + } + } + }) + }) + } + } + + mod test_utils { + #[cfg(test)] + use golem_wasm_ast::analysis::*; + use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; + + pub(crate) fn analysed_type_record(fields: Vec<(&str, AnalysedType)>) -> AnalysedType { + AnalysedType::Record(TypeRecord { + fields: fields + .into_iter() + .map(|(name, typ)| NameTypePair { + name: name.to_string(), + typ, + }) + .collect(), + }) + } + + pub(crate) fn get_type_annotated_value( + analysed_type: &AnalysedType, + wasm_wave_str: &str, + ) -> TypeAnnotatedValue { + let result = + golem_wasm_rpc::type_annotated_value_from_str(analysed_type, wasm_wave_str); + + match result { + Ok(value) => value, + Err(err) => panic!( + "Wasm wave syntax error {:?} {} {}", + analysed_type, wasm_wave_str, err + ), + } + } + + pub(crate) fn convert_type_annotated_value_to_str( + type_annotated_value: &TypeAnnotatedValue, + ) -> String { + golem_wasm_rpc::type_annotated_value_to_string(type_annotated_value).unwrap() + } + + pub(crate) fn get_function_component_metadata( + function_name: &str, + input_types: Vec, + output: Option, + ) -> Vec { + let analysed_function_parameters = input_types + .into_iter() + .enumerate() + .map(|(index, typ)| AnalysedFunctionParameter { + name: format!("param{}", index), + typ, + }) + .collect(); + + let results = if let Some(output) = output { + vec![AnalysedFunctionResult { + name: None, + typ: output, + }] + } else { + // Representing Unit + vec![] + }; + + vec![AnalysedExport::Function(AnalysedFunction { + name: function_name.to_string(), + parameters: analysed_function_parameters, + results, + })] + } + } +} diff --git a/golem-rib/src/parser/pattern_match.rs b/golem-rib/src/parser/pattern_match.rs index 12ef99adf1..7225d36857 100644 --- a/golem-rib/src/parser/pattern_match.rs +++ b/golem-rib/src/parser/pattern_match.rs @@ -118,11 +118,11 @@ mod arm_pattern { } mod internal { - use combine::attempt; use combine::many1; - use combine::parser::char::{char, digit, letter}; + use combine::parser::char::{digit, letter}; use combine::parser::char::{spaces, string}; use combine::sep_by; + use combine::{attempt, sep_by1}; use combine::{choice, ParseError}; use combine::{parser::char::char as char_, Parser}; @@ -145,6 +145,8 @@ mod internal { attempt(result().map(|expr| ArmPattern::Literal(Box::new(expr)))), attempt(custom_arm_pattern_constructor()), attempt(tuple_arm_pattern_constructor()), + attempt(list_arm_pattern_constructor()), + attempt(record_arm_pattern_constructor()), )) } @@ -201,6 +203,79 @@ mod internal { .map(|(_, patterns, _)| ArmPattern::TupleConstructor(patterns)) } + fn list_arm_pattern_constructor() -> impl Parser + where + Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, + { + ( + string("[").skip(spaces()), + sep_by(arm_pattern().skip(spaces()), char_(',').skip(spaces())), + string("]").skip(spaces()), + ) + .map(|(_, patterns, _)| ArmPattern::ListConstructor(patterns)) + } + + struct KeyArmPattern { + key: String, + pattern: ArmPattern, + } + + fn record_arm_pattern_constructor() -> impl Parser + where + Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, + { + ( + string("{").skip(spaces()), + sep_by1(key_arm_pattern().skip(spaces()), char_(',').skip(spaces())), + string("}").skip(spaces()), + ) + .map(|(_, patterns, _)| { + let patterns: Vec = patterns; + ArmPattern::RecordConstructor( + patterns + .into_iter() + .map(|pattern| (pattern.key, pattern.pattern)) + .collect(), + ) + }) + } + + fn key_arm_pattern() -> impl Parser + where + Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, + { + ( + record_key().skip(spaces()), + char_(':').skip(spaces()), + arm_pattern(), + ) + .map(|(var, _, arm_pattern)| KeyArmPattern { + key: var, + pattern: arm_pattern, + }) + } + + fn record_key() -> impl Parser + where + Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, + { + many1(letter().or(char_('_').or(char_('-')))) + .map(|s: Vec| s.into_iter().collect()) + .message("Invalid identifier") + } + fn constructor_type_name() -> impl Parser where Input: combine::Stream, @@ -208,7 +283,7 @@ mod internal { >::StreamError, >, { - many1(letter().or(digit()).or(char_('_')).or(char('-'))) + many1(letter().or(digit()).or(char_('_')).or(char_('-'))) .map(|s: Vec| s.into_iter().collect()) .message("Unable to parse custom constructor name") } diff --git a/golem-rib/src/text/writer.rs b/golem-rib/src/text/writer.rs index 29a4692cc3..ebe7015854 100644 --- a/golem-rib/src/text/writer.rs +++ b/golem-rib/src/text/writer.rs @@ -382,6 +382,35 @@ mod internal { writer.write_str(")") } + + ArmPattern::ListConstructor(patterns) => { + writer.write_str("[")?; + + for (idx, pattern) in patterns.iter().enumerate() { + if idx != 0 { + writer.write_str(",")?; + } + write_constructor(pattern, writer)?; + } + + writer.write_str("]") + } + + ArmPattern::RecordConstructor(fields) => { + writer.write_str("{")?; + + for (idx, (key, value)) in fields.iter().enumerate() { + if idx != 0 { + writer.write_str(",")?; + } + writer.write_str(key)?; + writer.write_str(":")?; + write_constructor(value, writer)?; + } + + writer.write_str("}") + } + ArmPattern::Literal(expr) => match *expr.clone() { Expr::Identifier(s, _) => writer.write_str(s.name()), any_expr => writer.write_expr(&any_expr), diff --git a/golem-rib/src/type_inference/identifier_inference.rs b/golem-rib/src/type_inference/identifier_inference.rs index 8bb035fb5e..c0160295c1 100644 --- a/golem-rib/src/type_inference/identifier_inference.rs +++ b/golem-rib/src/type_inference/identifier_inference.rs @@ -148,6 +148,16 @@ mod internal { collect_all_identifiers(pattern, state) } } + ArmPattern::ListConstructor(patterns) => { + for pattern in patterns { + collect_all_identifiers(pattern, state) + } + } + ArmPattern::RecordConstructor(fields) => { + for (_, pattern) in fields { + collect_all_identifiers(pattern, state) + } + } ArmPattern::Literal(expr) => accumulate_types_of_identifiers(&mut *expr, state), } } diff --git a/golem-rib/src/type_inference/pattern_match_binding.rs b/golem-rib/src/type_inference/pattern_match_binding.rs index 03b34021b3..e4eea777fa 100644 --- a/golem-rib/src/type_inference/pattern_match_binding.rs +++ b/golem-rib/src/type_inference/pattern_match_binding.rs @@ -107,6 +107,18 @@ mod internal { go(arm_pattern, global_arm_index, match_identifiers); } } + + ArmPattern::ListConstructor(arm_patterns) => { + for arm_pattern in arm_patterns { + go(arm_pattern, global_arm_index, match_identifiers); + } + } + + ArmPattern::RecordConstructor(fields) => { + for (_, arm_pattern) in fields { + go(arm_pattern, global_arm_index, match_identifiers); + } + } } } diff --git a/golem-rib/src/type_inference/type_pull_up.rs b/golem-rib/src/type_inference/type_pull_up.rs index 2fc0ebb79c..0f5663fba3 100644 --- a/golem-rib/src/type_inference/type_pull_up.rs +++ b/golem-rib/src/type_inference/type_pull_up.rs @@ -235,6 +235,19 @@ mod internal { pull_up_types_of_arm_pattern(arm_pattern)?; } } + + ArmPattern::ListConstructor(arm_patterns) => { + for arm_pattern in arm_patterns { + pull_up_types_of_arm_pattern(arm_pattern)?; + } + } + + ArmPattern::RecordConstructor(fields) => { + for (_, arm_pattern) in fields { + pull_up_types_of_arm_pattern(arm_pattern)?; + } + } + ArmPattern::Literal(expr) => { expr.pull_types_up()?; } diff --git a/golem-rib/src/type_inference/type_push_down.rs b/golem-rib/src/type_inference/type_push_down.rs index c5157e97e2..5a83bebb03 100644 --- a/golem-rib/src/type_inference/type_push_down.rs +++ b/golem-rib/src/type_inference/type_push_down.rs @@ -288,6 +288,28 @@ mod internal { } } } + + ArmPattern::ListConstructor(patterns) => { + if let InferredType::List(inner_type) = predicate_type { + for pattern in &mut *patterns { + update_arm_pattern_type(pattern, inner_type)?; + } + } + } + + ArmPattern::RecordConstructor(fields) => { + if let InferredType::Record(record_fields) = predicate_type { + for (field, pattern) in fields { + let inner_type = record_fields + .iter() + .find(|(name, _)| name == field) + .map(|(_, typ)| typ) + .ok_or(format!("Field {} not found in record type", field))?; + update_arm_pattern_type(pattern, inner_type)?; + } + } + } + ArmPattern::WildCard => {} } diff --git a/golem-rib/src/type_inference/type_unification.rs b/golem-rib/src/type_inference/type_unification.rs index 99034f4c8e..b0912564c0 100644 --- a/golem-rib/src/type_inference/type_unification.rs +++ b/golem-rib/src/type_inference/type_unification.rs @@ -386,6 +386,19 @@ mod internal { push_arm_pattern_expr(pattern, queue); } } + + ArmPattern::ListConstructor(patterns) => { + for pattern in patterns { + push_arm_pattern_expr(pattern, queue); + } + } + + ArmPattern::RecordConstructor(fields) => { + for (_, pattern) in fields { + push_arm_pattern_expr(pattern, queue); + } + } + ArmPattern::WildCard => {} } } diff --git a/golem-worker-service-base/src/worker_binding/compiled_golem_worker_binding.rs b/golem-worker-service-base/src/worker_binding/compiled_golem_worker_binding.rs index ea45a8e8d1..48f9d5eb5f 100644 --- a/golem-worker-service-base/src/worker_binding/compiled_golem_worker_binding.rs +++ b/golem-worker-service-base/src/worker_binding/compiled_golem_worker_binding.rs @@ -1,4 +1,4 @@ -use crate::worker_binding::{GolemWorkerBinding, ResponseMapping}; +use crate::worker_binding::{compile_rib, GolemWorkerBinding, ResponseMapping}; use bincode::{Decode, Encode}; use golem_service_base::model::VersionedComponentId; use golem_wasm_ast::analysis::AnalysedExport; @@ -54,12 +54,12 @@ impl WorkerNameCompiled { worker_name: &Expr, exports: &Vec, ) -> Result { - let worker_name_compiled = rib::compile(worker_name, exports)?; + let worker_name_compiled = compile_rib(worker_name, exports)?; Ok(WorkerNameCompiled { worker_name: worker_name.clone(), compiled_worker_name: worker_name_compiled.byte_code, - rib_input: worker_name_compiled.rib_input, + rib_input: worker_name_compiled.global_input_type_info, }) } } @@ -76,12 +76,12 @@ impl IdempotencyKeyCompiled { idempotency_key: &Expr, exports: &Vec, ) -> Result { - let idempotency_key_compiled = rib::compile(idempotency_key, exports)?; + let idempotency_key_compiled = compile_rib(idempotency_key, exports)?; Ok(IdempotencyKeyCompiled { idempotency_key: idempotency_key.clone(), compiled_idempotency_key: idempotency_key_compiled.byte_code, - rib_input: idempotency_key_compiled.rib_input, + rib_input: idempotency_key_compiled.global_input_type_info, }) } } @@ -98,12 +98,12 @@ impl ResponseMappingCompiled { response_mapping: &ResponseMapping, exports: &Vec, ) -> Result { - let response_compiled = rib::compile(&response_mapping.0, exports)?; + let response_compiled = compile_rib(&response_mapping.0, exports)?; Ok(ResponseMappingCompiled { response_rib_expr: response_mapping.0.clone(), compiled_response: response_compiled.byte_code, - rib_input: response_compiled.rib_input, + rib_input: response_compiled.global_input_type_info, }) } } diff --git a/golem-worker-service-base/src/worker_binding/mod.rs b/golem-worker-service-base/src/worker_binding/mod.rs index d9c88a9af3..b283f7c1d0 100644 --- a/golem-worker-service-base/src/worker_binding/mod.rs +++ b/golem-worker-service-base/src/worker_binding/mod.rs @@ -1,6 +1,8 @@ pub(crate) use compiled_golem_worker_binding::*; +use golem_wasm_ast::analysis::AnalysedExport; pub(crate) use golem_worker_binding::*; pub(crate) use request_details::*; +use rib::{CompilerOutput, Expr}; pub(crate) use rib_input_value_resolver::*; pub(crate) use worker_binding_resolver::*; @@ -9,3 +11,14 @@ mod golem_worker_binding; mod request_details; mod rib_input_value_resolver; mod worker_binding_resolver; + +pub fn compile_rib( + worker_name: &Expr, + export_metadata: &Vec, +) -> Result { + rib::compile_with_limited_globals( + worker_name, + export_metadata, + Some(vec!["request".to_string()]), + ) +} diff --git a/golem-worker-service-base/src/worker_service_rib_interpreter/mod.rs b/golem-worker-service-base/src/worker_service_rib_interpreter/mod.rs index e19d8d9b66..f50c5ef3cf 100644 --- a/golem-worker-service-base/src/worker_service_rib_interpreter/mod.rs +++ b/golem-worker-service-base/src/worker_service_rib_interpreter/mod.rs @@ -135,2528 +135,3 @@ impl WorkerServiceRibInterpreter for DefaultEvaluator { .map_err(EvaluationError) } } - -#[cfg(test)] -mod tests { - use async_trait::async_trait; - - use std::collections::HashMap; - use std::str::FromStr; - use std::sync::Arc; - - use golem_service_base::type_inference::infer_analysed_type; - use golem_wasm_ast::analysis::{ - AnalysedExport, AnalysedType, NameTypePair, TypeList, TypeOption, TypeRecord, TypeStr, - TypeU32, TypeU64, - }; - - use golem_wasm_rpc::json::TypeAnnotatedValueJsonExtensions; - use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; - use golem_wasm_rpc::protobuf::{NameOptionTypePair, TypeVariant, TypedTuple, TypedVariant}; - use http::{HeaderMap, Uri}; - use poem_openapi::types::ToJSON; - use rib::{ - Expr, FunctionTypeRegistry, RibFunctionInvoke, RibInputTypeInfo, RibInterpreterResult, - }; - use serde_json::json; - - use crate::api_definition::http::AllPathPatterns; - use crate::worker_binding::{RequestDetails, RibInputValue, RibInputValueResolver}; - - use crate::worker_bridge_execution::to_response::ToResponse; - use crate::worker_service_rib_interpreter::{ - DefaultEvaluator, EvaluationError, WorkerServiceRibInterpreter, - }; - use test_utils::*; - - // An extension to over worker-service specific rib-interpreter - // to make testing easy. Especially the compilation of Expr is done as we go - // instead of a separate compilation and invoking use byte-code - #[async_trait] - trait WorkerServiceRibInterpreterTestExt { - async fn evaluate_pure_expr_with_request_details( - &self, - expr: &Expr, - input: &RequestDetails, - ) -> Result; - - async fn evaluate_with_worker_response( - &self, - expr: &Expr, - worker_bridge_response: Option, - metadata: Vec, - input: Option<(RequestDetails, AnalysedType)>, - ) -> Result; - - async fn evaluate_with_worker_response_as_rib_result( - &self, - expr: &Expr, - worker_response: Option, - metadata: Vec, - request_input: Option<(RequestDetails, AnalysedType)>, - ) -> Result; - - async fn evaluate_pure_expr( - &self, - expr: &Expr, - ) -> Result; - } - - #[async_trait] - impl WorkerServiceRibInterpreterTestExt for T { - async fn evaluate_pure_expr_with_request_details( - &self, - expr: &Expr, - input: &RequestDetails, - ) -> Result { - let rib_input_json = input.as_json(); // Simply convert to json and try and infer the analysed type - let analysed_type = infer_analysed_type(&rib_input_json); - let mut type_info = HashMap::new(); - type_info.insert("request".to_string(), analysed_type); - - let rib_input_value = input - .resolve_rib_input_value(&RibInputTypeInfo { types: type_info }) - .unwrap(); - - let mut expr = expr.clone(); - let _ = expr.infer_types(&FunctionTypeRegistry::empty()).unwrap(); - - let compiled_expr = rib::compile(&expr, &vec![]).unwrap(); - - let eval_result = self - .evaluate_pure(&compiled_expr.byte_code, &rib_input_value) - .await?; - - Ok(eval_result.get_val().ok_or(EvaluationError( - "The text is evaluated to unit and doesn't have a value".to_string(), - ))?) - } - - async fn evaluate_with_worker_response_as_rib_result( - &self, - expr: &Expr, - worker_response: Option, - metadata: Vec, - request_input: Option<(RequestDetails, AnalysedType)>, - ) -> Result { - let expr = expr.clone(); - let compiled = rib::compile(&expr, &metadata)?; - - let mut type_info = HashMap::new(); - let mut rib_input = HashMap::new(); - - if let Some(worker_response) = worker_response.clone() { - // Collect worker details and request details into rib-input value - let worker_response_analysed_type = - AnalysedType::try_from(&worker_response).unwrap(); - type_info.insert("worker".to_string(), worker_response_analysed_type); - rib_input.insert("worker".to_string(), worker_response.clone()); - } - - if let Some((request_details, analysed_type)) = request_input { - let mut type_info = HashMap::new(); - type_info.insert("request".to_string(), analysed_type); - let rib_input_type_info = RibInputTypeInfo { types: type_info }; - let request_rib_input_value = request_details - .resolve_rib_input_value(&rib_input_type_info) - .unwrap(); - rib_input.insert( - "request".to_string(), - request_rib_input_value - .value - .get("request") - .unwrap() - .clone(), - ); - } - - let invoke_result = match worker_response { - Some(ref result) => TypeAnnotatedValue::Tuple(TypedTuple { - value: vec![golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some(result.clone()), - }], - typ: vec![], - }), - None => TypeAnnotatedValue::Tuple(TypedTuple { - value: vec![], - typ: vec![], - }), - }; - - let worker_invoke_function: RibFunctionInvoke = Arc::new(move |_, _| { - Box::pin({ - let value = invoke_result.clone(); - async move { Ok(value) } - }) - }); - - let result = - rib::interpret(&compiled.byte_code, rib_input, worker_invoke_function).await?; - - Ok(result) - } - - // This will invoke worker - async fn evaluate_with_worker_response( - &self, - expr: &Expr, - worker_response: Option, - metadata: Vec, - request_input: Option<(RequestDetails, AnalysedType)>, - ) -> Result { - let eval_result = self - .evaluate_with_worker_response_as_rib_result( - expr, - worker_response, - metadata, - request_input, - ) - .await?; - - eval_result.get_val().ok_or(EvaluationError( - "The text is evaluated to unit and doesn't have a value".to_string(), - )) - } - - async fn evaluate_pure_expr( - &self, - expr: &Expr, - ) -> Result { - let compiled = rib::compile(expr, &vec![]).unwrap(); - - self.evaluate_pure(&compiled.byte_code, &RibInputValue::empty()) - .await - } - } - - #[tokio::test] - async fn test_evaluation_with_request_path() { - let noop_executor = DefaultEvaluator::noop(); - let uri = Uri::builder().path_and_query("/pId/items").build().unwrap(); - - let path_pattern = AllPathPatterns::from_str("/{id}/items").unwrap(); - - let request_details = request_details_from_request_path_variables(uri, path_pattern); - - // The spec that will become part of the component metadata - let request_path_type = - get_analysed_type_record(vec![("id".to_string(), AnalysedType::Str(TypeStr))]); - - let request_type = - get_analysed_type_record(vec![("path".to_string(), request_path_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = request; - let result = foo(x); - request.path.id - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("pId".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_id() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - - - "id": "bId", - "name": "bName", - "titles": [ - "bTitle1", "bTitle2" - ], - "address": { - "street": "bStreet", - "city": "bCity" - } - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ( - "titles".to_string(), - AnalysedType::List(TypeList { - inner: Box::new(AnalysedType::Str(TypeStr)), - }), - ), - ( - "address".to_string(), - get_analysed_type_record(vec![ - ("street".to_string(), AnalysedType::Str(TypeStr)), - ("city".to_string(), AnalysedType::Str(TypeStr)), - ]), - ), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - // TODO; result2 should be automatically inferred - let expr_str = r#"${ - let x = { body : { id: "bId", name: "bName", titles: request.body.titles, address: request.body.address } }; - let result = foo(x); - let result2: str = request.body.id; - match result { some(value) => "personal-id", none => result2 } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap(); - - assert_eq!(result, TypeAnnotatedValue::Str("bId".to_string())); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_select_index() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - - - "id": "bId", - "name": "bName", - "titles": [ - "bTitle1", "bTitle2" - ], - "address": { - "street": "bStreet", - "city": "bCity" - } - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ( - "titles".to_string(), - AnalysedType::List(TypeList { - inner: Box::new(AnalysedType::Str(TypeStr)), - }), - ), - ( - "address".to_string(), - get_analysed_type_record(vec![ - ("street".to_string(), AnalysedType::Str(TypeStr)), - ("city".to_string(), AnalysedType::Str(TypeStr)), - ]), - ), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = { body : { id: "bId", name: "bName", titles: request.body.titles, address: request.body.address } }; - let result = foo(x); - match result { some(value) => "personal-id", none => x.body.titles[1] } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap(); - - assert_eq!(result, TypeAnnotatedValue::Str("bTitle2".to_string())); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_select_from_object() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - - "address": { - "street": "bStreet", - "city": "bCity" - } - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![( - "address".to_string(), - get_analysed_type_record(vec![ - ("street".to_string(), AnalysedType::Str(TypeStr)), - ("city".to_string(), AnalysedType::Str(TypeStr)), - ]), - )]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr = rib::from_string(r#"${foo(request); request.body.address.street}"#).unwrap(); - - let expected_evaluated_result = TypeAnnotatedValue::Str("bStreet".to_string()); - let result = noop_executor - .evaluate_with_worker_response( - &expr, - Some(worker_response), - component_metadata, - Some((request_details, request_type)), - ) - .await; - assert_eq!(result, Ok(expected_evaluated_result)); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_if_condition() { - let noop_executor = DefaultEvaluator::noop(); - - let mut header_map = HeaderMap::new(); - header_map.insert("authorisation", "admin".parse().unwrap()); - - let resolved_variables = get_request_details( - r#" - { - "id": "bId" - }"#, - &header_map, - ); - - let expr = - rib::from_string(r#"${let input: str = request.headers.authorisation; let x: u64 = 200; let y: u64 = 401; if input == "admin" then x else y}"#) - .unwrap(); - let expected_evaluated_result = TypeAnnotatedValue::U64("200".parse().unwrap()); - let result = noop_executor - .evaluate_pure_expr_with_request_details(&expr, &resolved_variables) - .await; - assert_eq!(result, Ok(expected_evaluated_result)); - - let noop_executor = DefaultEvaluator::noop(); - - let mut header_map = HeaderMap::new(); - header_map.insert("authorisation", "admin".parse().unwrap()); - - let request_details = get_request_details( - r#" - { - "id": "bId", - "name": "bName" - }"#, - &header_map, - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ]); - - let request_type = get_analysed_type_record(vec![ - ("body".to_string(), request_body_type.clone()), - ( - "headers".to_string(), - get_analysed_type_record(vec![( - "authorisation".to_string(), - AnalysedType::Str(TypeStr), - )]), - ), - ]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = request; - let result = foo(x); - let success: u64 = 200; - let failure: u64 = 401; - let auth = request.headers.authorisation; - if auth == "admin" then success else failure - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap(); - - let expected_evaluated_result = TypeAnnotatedValue::U64("200".parse().unwrap()); - - assert_eq!(result, expected_evaluated_result); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_select_unknown_field() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - - - "id": "bId", - "name": "bName", - "titles": [ - "bTitle1", "bTitle2" - ], - "address": { - "street": "bStreet", - "city": "bCity" - } - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ( - "titles".to_string(), - AnalysedType::List(TypeList { - inner: Box::new(AnalysedType::Str(TypeStr)), - }), - ), - ( - "address".to_string(), - get_analysed_type_record(vec![ - ("street".to_string(), AnalysedType::Str(TypeStr)), - ("city".to_string(), AnalysedType::Str(TypeStr)), - ]), - ), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = { body : { id: "bId", name: "bName", titles: request.body.titles, address: request.body.address } }; - let result = foo(x); - match result { some(value) => "personal-id", none => x.body.address.street2 } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await; - - // A compile time failure - assert!(result.is_err()); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_select_invalid_index() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - - - "id": "bId", - "name": "bName", - "titles": [ - "bTitle1", "bTitle2" - ], - "address": { - "street": "bStreet", - "city": "bCity" - } - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ( - "titles".to_string(), - AnalysedType::List(TypeList { - inner: Box::new(AnalysedType::Str(TypeStr)), - }), - ), - ( - "address".to_string(), - get_analysed_type_record(vec![ - ("street".to_string(), AnalysedType::Str(TypeStr)), - ("city".to_string(), AnalysedType::Str(TypeStr)), - ]), - ), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = { body : { id: "bId", name: "bName", titles: request.body.titles, address: request.body.address } }; - let result = foo(x); - match result { some(value) => "personal-id", none => x.body.titles[4] } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap_err() - .0; - - assert!(result.contains("Index 4 not found in the list")); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_invalid_index_of_object() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - - - "id": "bId", - "name": "bName", - "titles": [ - "bTitle1", "bTitle2" - ], - "address": { - "street": "bStreet", - "city": "bCity" - } - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ( - "titles".to_string(), - AnalysedType::List(TypeList { - inner: Box::new(AnalysedType::Str(TypeStr)), - }), - ), - ( - "address".to_string(), - get_analysed_type_record(vec![ - ("street".to_string(), AnalysedType::Str(TypeStr)), - ("city".to_string(), AnalysedType::Str(TypeStr)), - ]), - ), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = { body : { id: "bId", name: "bName", titles: request.body.titles, address: request.body.address } }; - let result = foo(x); - match result { some(value) => "personal-id", none => x.body.address[4] } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap_err() - .0; - - let _expected = TypeAnnotatedValue::Str("bStreet".to_string()); - - assert!(result.contains("Types do not match. Inferred to be both List(Str) and Record([(\"street\", Str), (\"city\", Str)])")); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_invalid_type_comparison() { - let noop_executor = DefaultEvaluator::noop(); - - let mut header_map = HeaderMap::new(); - header_map.insert("authorisation", "admin".parse().unwrap()); - - let request_details = get_request_details( - r#" - { - "id": "bId", - "name": "bName" - }"#, - &header_map, - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ]); - - let request_type = get_analysed_type_record(vec![ - ("body".to_string(), request_body_type.clone()), - ( - "headers".to_string(), - get_analysed_type_record(vec![( - "authorisation".to_string(), - AnalysedType::Str(TypeStr), - )]), - ), - ]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = request; - let result = foo(x); - if request.headers.authorisation then 200 else 401 - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let error_message = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap_err() - .0; - - let _expected = TypeAnnotatedValue::Str("bName".to_string()); - - assert!(error_message.contains("Types do not match. Inferred to be both Str and Bool")); - } - - #[tokio::test] - async fn test_evaluation_with_request_info_inference() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - "id": "bId", - "name": "bName" - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let x = request; - let result = foo(x); - match result { some(value) => "personal-id", none => x.body.name } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("bName".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_for_no_arg_unit_function() { - let noop_executor = DefaultEvaluator::noop(); - - let component_metadata = get_analysed_export_for_no_arg_unit_function("foo"); - - let expr_str = r#"${ - foo(); - "foo executed" - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response(&expr1, None, component_metadata.clone(), None) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("foo executed".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_for_no_arg_function() { - let noop_executor = DefaultEvaluator::noop(); - - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = get_analysed_export_for_no_arg_function("foo", return_type); - - let expr_str = r#"${ - let result = foo(); - match result { some(value) => "ok", none => "err" } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("err".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_for_function_returning_unit() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - "id": "bId", - "name": "bName" - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - let component_metadata = - get_analysed_export_for_unit_function("foo", vec![request_type.clone()]); - - let expr_str = r#"${ - foo( { id: "bId", name: "bName" }); - let result = foo( { id: "bId", name: "bName" }); - result - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response_as_rib_result( - &expr1, - None, - component_metadata.clone(), - Some((request_details.clone(), request_type)), - ) - .await - .unwrap(); - - let response: poem::Response = value1.to_response(&request_details); - - assert!(response.into_body().is_empty()); - } - - #[tokio::test] - async fn test_evaluation_with_request_body_with_select_fields() { - let noop_executor = DefaultEvaluator::noop(); - - let request_details = get_request_details( - r#" - { - - - "id": "bId", - "name": "bName", - "titles": [ - "bTitle1", "bTitle2" - ], - "address": { - "street": "bStreet", - "city": "bCity" - } - - }"#, - &HeaderMap::new(), - ); - - // The spec that will become part of the component metadata - let request_body_type = get_analysed_type_record(vec![ - ("id".to_string(), AnalysedType::Str(TypeStr)), - ("name".to_string(), AnalysedType::Str(TypeStr)), - ( - "titles".to_string(), - AnalysedType::List(TypeList { - inner: Box::new(AnalysedType::Str(TypeStr)), - }), - ), - ( - "address".to_string(), - get_analysed_type_record(vec![ - ("street".to_string(), AnalysedType::Str(TypeStr)), - ("city".to_string(), AnalysedType::Str(TypeStr)), - ]), - ), - ]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Output from worker - doesn't matter - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![request_type.clone()], return_type); - - let expr_str = r#"${ - let input = { body: { id: "bId", name: "bName", titles: request.body.titles, address: request.body.address } }; - let result = foo(input); - match result { some(value) => "personal-id", none => request.body.address.street } - }"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - Some((request_details, request_type)), - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("bStreet".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_zero_worker_response() { - let noop_executor = DefaultEvaluator::noop(); - - let resolved_variables = get_request_details( - r#" - { - "path": { - "id": "pId" - } - }"#, - &HeaderMap::new(), - ); - - let expr = rib::from_string("${let s: str = worker.response.address.street; s}").unwrap(); - let result = noop_executor - .evaluate_pure_expr_with_request_details(&expr, &resolved_variables) - .await; - assert!(result.is_err()); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_some() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let record = create_record(vec![( - "id".to_string(), - TypeAnnotatedValue::Str("pId".to_string()), - )]) - .unwrap(); - let worker_response = create_option(record).unwrap(); - - // Output from worker - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { some(value) => "personal-id", none => "not found" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("personal-id".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_none() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let worker_response = create_none(Some(&AnalysedType::Str(TypeStr))); - - // Output from worker - let return_type = AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::try_from(&worker_response).unwrap()), - }); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { some(value) => "personal-id", none => "not found" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("not found".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_if_else() { - let noop_executor = DefaultEvaluator::noop(); - - let uri = Uri::builder() - .path_and_query("/shopping-cart/foo") - .build() - .unwrap(); - - let path_pattern = AllPathPatterns::from_str("/shopping-cart/{id}").unwrap(); - - let request_details = - request_details_from_request_path_variables(uri.clone(), path_pattern.clone()); - - let worker_response_inner = create_record(vec![( - "id".to_string(), - TypeAnnotatedValue::Str("baz".to_string()), - )]) - .unwrap(); - - let worker_response = - create_ok_result(worker_response_inner, Some(AnalysedType::Str(TypeStr))).unwrap(); - - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - // The spec that will become part of the component metadata - let request_path_type = - get_analysed_type_record(vec![("id".to_string(), AnalysedType::Str(TypeStr))]); - - let request_type = - get_analysed_type_record(vec![("path".to_string(), request_path_type.clone())]); - - let metadata = get_analysed_exports("foo", vec![request_type.clone()], return_type); - - // TODO; inlining request.path.id all over should work too - let expr1 = rib::from_string( - r#"${ - let x = request; - let foo_result = foo(x); - let txt = request.path.id; - if txt == "foo" then "bar" else match foo_result { ok(value) => txt, err(msg) => "empty" } - }"#, - ) - .unwrap(); - - let result1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - metadata.clone(), - Some((request_details.clone(), request_type.clone())), - ) - .await; - - // TODO; inlining request.path.id all over should work too - let expr2 = rib::from_string( - r#"${ - let x = request; - let foo_result = foo(x); - let txt = request.path.id; - if txt == "bar" then "foo" else match foo_result { ok(foo) => foo.id, err(msg) => "empty" } - }"#, - - ).unwrap(); - - let result2 = noop_executor - .evaluate_with_worker_response( - &expr2, - Some(worker_response), - metadata.clone(), - Some((request_details.clone(), request_type.clone())), - ) - .await; - - let error_worker_response = - create_error_result(TypeAnnotatedValue::Str("Error".to_string()), None).unwrap(); - - let _new_request_details = request_details_from_request_path_variables(uri, path_pattern); - - let _expr3 = rib::from_string( - r#"${if request.path.id == "bar" then "foo" else match worker.response { ok(foo) => foo.id, err(msg) => "empty" }}"#, - - ).unwrap(); - - let result3 = noop_executor - .evaluate_with_worker_response( - &expr2, - Some(error_worker_response), - metadata, - Some((request_details, request_type)), - ) - .await; - - assert_eq!( - (result1, result2, result3), - ( - Ok(TypeAnnotatedValue::Str("bar".to_string())), - Ok(TypeAnnotatedValue::Str("baz".to_string())), - Ok(TypeAnnotatedValue::Str("empty".to_string())) - ) - ); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_unused_variables() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let field_value = TypeAnnotatedValue::Str("pId".to_string()); - - let record_value = create_singleton_record("id", &field_value).unwrap(); - - let worker_response = - create_ok_result(record_value.clone(), Some(AnalysedType::Str(TypeStr))).unwrap(); - - // Output from worker - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { ok(value) => "personal-id", err(msg) => "not found" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("personal-id".to_string()); - - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_use_success_variable() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let field_value = TypeAnnotatedValue::Str("pId".to_string()); - - let record_value = create_singleton_record("id", &field_value).unwrap(); - - let worker_response = - create_ok_result(record_value.clone(), Some(AnalysedType::Str(TypeStr))).unwrap(); - - // Output from worker - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { ok(value) => value.id, err(msg) => "not found" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - assert_eq!(&value1, &TypeAnnotatedValue::Str("pId".to_string())); // The value is the same as the worker response unwrapping ok result - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_select_field() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let field_value = TypeAnnotatedValue::Str("pId".to_string()); - - let record_value = create_singleton_record("id", &field_value).unwrap(); - - let worker_response = create_ok_result(record_value, None).unwrap(); - - // Output from worker - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { ok(value) => value.id, err(_) => "not found" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("pId".to_string()); - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_select_from_array() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let sequence_value = create_list(vec![ - TypeAnnotatedValue::Str("id1".to_string()), - TypeAnnotatedValue::Str("id2".to_string()), - ]) - .unwrap(); - - let record_value = create_singleton_record("ids", &sequence_value).unwrap(); - - let worker_response = create_ok_result(record_value, None).unwrap(); - - // Output from worker - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { ok(value) => value.ids[0], err(_) => "not found" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("id1".to_string()); - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_some_construction() { - let noop_executor = DefaultEvaluator::noop(); - - let return_type = - get_result_type_fully_formed(AnalysedType::U32(TypeU32), AnalysedType::Str(TypeStr)); - - let worker_response = create_ok_result(TypeAnnotatedValue::U32(10), None).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = - r#"${let result = foo(1); match result { ok(x) => some(1u64), err(_) => none }}"#; - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = create_option(TypeAnnotatedValue::U64(1)).unwrap(); - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_none_construction() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = - rib::from_string(r#"${match ok(1u64) { ok(value) => none, err(_) => some(1u64) }}"#) - .unwrap(); - let result = noop_executor - .evaluate_pure_expr(&expr) - .await - .map(|v| v.get_val().unwrap()); - - let expected = create_none(Some(&AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::U64(TypeU64)), - }))); - - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_ok_construction() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = rib::from_string( - "${match ok(\"afsal\") { ok(value) => ok(1u64), err(_) => err(2u64) }}", - ) - .unwrap(); - let result = noop_executor - .evaluate_pure_expr(&expr) - .await - .map(|v| v.get_val().unwrap()); - let expected = - create_ok_result(TypeAnnotatedValue::U64(1), Some(AnalysedType::U64(TypeU64))).unwrap(); - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_err_construction() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = rib::from_string( - "${let xyz: u64 = 2; match err(\"afsal\") { ok(_) => ok(\"1\"), err(msg) => err(xyz) }}", - ) - .unwrap(); - - let result = noop_executor - .evaluate_pure_expr(&expr) - .await - .map(|v| v.get_val().unwrap()); - - let expected = - create_error_result(TypeAnnotatedValue::U64(2), Some(AnalysedType::Str(TypeStr))) - .unwrap(); - - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_wild_card() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = - rib::from_string("${let x: u64 = 10; let lef: u64 = 1; let rig: u64 = 2; match err(x) { ok(_) => ok(lef), err(_) => err(rig) }}").unwrap(); - - let result = noop_executor - .evaluate_pure_expr(&expr) - .await - .map(|v| v.get_val().unwrap()); - - let expected = - create_error_result(TypeAnnotatedValue::U64(2), Some(AnalysedType::U64(TypeU64))) - .unwrap(); - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_with_name_alias() { - let noop_executor = DefaultEvaluator::noop(); - - let uri = Uri::builder() - .path_and_query("/shopping-cart/foo") - .build() - .unwrap(); - - let path_pattern = AllPathPatterns::from_str("/shopping-cart/{id}").unwrap(); - - let request_details = - request_details_from_request_path_variables(uri.clone(), path_pattern.clone()); - - let request_type = get_analysed_type_record(vec![( - "path".to_string(), - get_analysed_type_record(vec![("id".to_string(), AnalysedType::Str(TypeStr))]), - )]); - - let worker_response = create_error_result( - create_ok_result( - create_record(vec![("id".to_string(), TypeAnnotatedValue::U64(1))]).unwrap(), - Some(AnalysedType::Str(TypeStr)), - ) - .unwrap(), - Some(AnalysedType::Str(TypeStr)), - ) - .unwrap(); - - let function_return_type = AnalysedType::try_from(&worker_response).unwrap(); - let metadata = - get_analysed_exports("foo", vec![request_type.clone()], function_return_type); - - // TODO; a @ err(value) => a should work - let expr = rib::from_string( - r#"${ - let x = request; - let y = foo(x); - match y { err(value) => err(value) } - }"#, - ) - .unwrap(); - - let result = noop_executor - .evaluate_with_worker_response( - &expr, - Some(worker_response), - metadata, - Some((request_details, request_type)), - ) - .await - .unwrap(); - - let output_json = result.to_json_value(); - - let expected_json = json!({ - "err": { - "ok": { - "id": 1 - } - }, - }); - assert_eq!(output_json, expected_json); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_variant_positive() { - let noop_executor = DefaultEvaluator::noop(); - - let worker_response = TypeAnnotatedValue::Variant(Box::new(TypedVariant { - case_name: "Foo".to_string(), - case_value: Some(Box::new(golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some( - create_singleton_record("id", &TypeAnnotatedValue::Str("pId".to_string())) - .unwrap(), - ), - })), - typ: Some(TypeVariant { - cases: vec![NameOptionTypePair { - name: "Foo".to_string(), - typ: Some( - (&AnalysedType::Record(TypeRecord { - fields: vec![NameTypePair { - name: "id".to_string(), - typ: AnalysedType::Str(TypeStr), - }], - })) - .into(), - ), - }], - }), - })); - - let variant_analysed_type = AnalysedType::try_from(&worker_response).unwrap(); - - let json = worker_response.to_json_value(); - - let request_input = get_request_details(&json.to_json_string(), &HeaderMap::new()); - - let request_body_type = variant_analysed_type.clone(); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - let component_metadata = get_analysed_exports( - "foo", - vec![request_type.clone()], - variant_analysed_type.clone(), - ); - - let expr = - rib::from_string(r#"${let x = foo(request); match x { Foo(value) => ok(value.id) }}"#) - .unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr, - Some(worker_response), - component_metadata, - Some((request_input, request_type)), - ) - .await; - - let expected = create_ok_result(TypeAnnotatedValue::Str("pId".to_string()), None).unwrap(); - - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_variant_nested_some_type() { - let noop_executor = DefaultEvaluator::noop(); - - let worker_response = TypeAnnotatedValue::Variant(Box::new(TypedVariant { - case_name: "Foo".to_string(), - case_value: Some(Box::new(golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some( - test_utils::create_option( - create_record(vec![( - "id".to_string(), - TypeAnnotatedValue::Str("pId".to_string()), - )]) - .unwrap(), - ) - .unwrap(), - ), - })), - typ: Some(TypeVariant { - cases: vec![ - NameOptionTypePair { - name: "Foo".to_string(), - typ: Some( - (&AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::Record(TypeRecord { - fields: vec![NameTypePair { - name: "id".to_string(), - typ: AnalysedType::Str(TypeStr), - }], - })), - })) - .into(), - ), - }, - NameOptionTypePair { - name: "Bar".to_string(), - typ: Some( - (&AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::Record(TypeRecord { - fields: vec![NameTypePair { - name: "id".to_string(), - typ: AnalysedType::Str(TypeStr), - }], - })), - })) - .into(), - ), - }, - ], - }), - })); - - let variant_analysed_type = AnalysedType::try_from(&worker_response).unwrap(); - - let json = worker_response.to_json_value(); - - let request_input = get_request_details(&json.to_json_string(), &HeaderMap::new()); - - let request_body_type = variant_analysed_type.clone(); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - let component_metadata = get_analysed_exports( - "foo", - vec![request_type.clone()], - variant_analysed_type.clone(), - ); - - let expr = rib::from_string( - r#"${let x = foo(request); match x { Foo(none) => "not found", Foo(some(value)) => value.id }}"#, - ) - .unwrap(); - let result = noop_executor - .evaluate_with_worker_response( - &expr, - Some(worker_response), - component_metadata, - Some((request_input, request_type)), - ) - .await; - - let expected = TypeAnnotatedValue::Str("pId".to_string()); - - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_pattern_match_variant_construction_and_pattern_match() { - let noop_executor = DefaultEvaluator::noop(); - - // Input request - let request_input = get_request_details( - r#" - { - "user-id": "afsal" - }"#, - &HeaderMap::new(), - ); - - let request_body_type = - get_analysed_type_record(vec![("user-id".to_string(), AnalysedType::Str(TypeStr))]); - - let request_type = - get_analysed_type_record(vec![("body".to_string(), request_body_type.clone())]); - - // Worker Response - let worker_response = TypeAnnotatedValue::Variant(Box::new(TypedVariant { - case_name: "register".to_string(), - case_value: Some(Box::new(golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some(TypeAnnotatedValue::Str("adam".to_string())), - })), - typ: Some(TypeVariant { - cases: vec![ - NameOptionTypePair { - name: "register".to_string(), - typ: Some((&AnalysedType::Str(TypeStr)).into()), - }, - NameOptionTypePair { - name: "follow".to_string(), - typ: Some((&AnalysedType::Str(TypeStr)).into()), - }, - NameOptionTypePair { - name: "noop".to_string(), - typ: None, - }, - ], - }), - })); - - let variant_type = AnalysedType::try_from(&worker_response).unwrap(); - - // A component metadata that has process function which takes a variant input and returns a variant output - let component_metadata = get_analysed_exports( - "process", - vec![ - variant_type.clone(), - variant_type.clone(), - variant_type.clone(), - AnalysedType::Str(TypeStr), - ], - variant_type.clone(), - ); - - let expr = rib::from_string( - r#"${ - let user: str = request.body.user-id; - let action1 = register(user); - let action2 = follow("michael"); - let action3 = noop; - let result = process(action1, action2, action3, user); - - let x = match result { - follow(user) => "follow ${user}", - register(user) => "register ${user}", - noop => "noop" - }; - - let y = match action2 { - follow(user) => "follow ${user}", - register(user) => "register ${user}", - noop => "noop" - }; - - let z = match noop { - follow(user) => "follow ${user}", - register(user) => "register ${user}", - noop => "noop" - }; - - { x: x, y: y, z: z } - - }"#, - ) - .unwrap(); - - // worker executor returning a variant - let result = noop_executor - .evaluate_with_worker_response( - &expr, - Some(worker_response), - component_metadata, - Some((request_input, request_type)), - ) - .await; - - let expected = create_record(vec![ - ( - "x".to_string(), - TypeAnnotatedValue::Str("register adam".to_string()), - ), - ( - "y".to_string(), - TypeAnnotatedValue::Str("follow michael".to_string()), - ), - ("z".to_string(), TypeAnnotatedValue::Str("noop".to_string())), - ]) - .unwrap(); - - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_wave_like_syntax_ok_record() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = rib::from_string("${let x: u64 = 1; {a : ok(x)}}").unwrap(); - - let result = noop_executor.evaluate_pure_expr(&expr).await; - - let inner_record_ok = create_ok_result(TypeAnnotatedValue::U64(1), None).unwrap(); - let record = create_record(vec![("a".to_string(), inner_record_ok)]).unwrap(); - - let expected = Ok(RibInterpreterResult::Val(record)); - assert_eq!(result, expected); - } - - #[tokio::test] - async fn test_evaluation_with_wave_like_syntax_err_record() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = rib::from_string("${let n: u64 = 1; {a : err(n)}}").unwrap(); - - let result = noop_executor.evaluate_pure_expr(&expr).await; - - let inner_result = create_error_result(TypeAnnotatedValue::U64(1), None).unwrap(); - - let expected = RibInterpreterResult::Val( - create_record(vec![("a".to_string(), inner_result)]).unwrap(), - ); - - assert_eq!(result, Ok(expected)); - } - - #[tokio::test] - async fn test_evaluation_with_wave_like_syntax_simple_list() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = rib::from_string("${let x: list = [1,2,3]; x}").unwrap(); - - let result = noop_executor.evaluate_pure_expr(&expr).await; - - let list = create_list(vec![ - TypeAnnotatedValue::U64(1), - TypeAnnotatedValue::U64(2), - TypeAnnotatedValue::U64(3), - ]) - .unwrap(); - - let expected = Ok(RibInterpreterResult::Val(list)); - - assert_eq!(result, expected); - } - - #[tokio::test] - async fn test_evaluation_with_wave_like_syntax_simple_tuple() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = - rib::from_string("${let x: tuple, u64, u64> = (some(1),2,3); x}").unwrap(); - - let result = noop_executor.evaluate_pure_expr(&expr).await; - - let optional_value = create_option(TypeAnnotatedValue::U64(1)).unwrap(); - - let expected = create_tuple(vec![ - optional_value, - TypeAnnotatedValue::U64(2), - TypeAnnotatedValue::U64(3), - ]) - .unwrap(); - - assert_eq!(result, Ok(RibInterpreterResult::Val(expected))); - } - - #[tokio::test] - async fn test_evaluation_wave_like_syntax_flag() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = rib::from_string("${{A, B, C}}").unwrap(); - - let result = noop_executor.evaluate_pure_expr(&expr).await; - - let flags = create_flags(vec!["A".to_string(), "B".to_string(), "C".to_string()]); - - let expected = Ok(RibInterpreterResult::Val(flags)); - - assert_eq!(result, expected); - } - - #[tokio::test] - async fn test_evaluation_with_wave_like_syntax_result_list() { - let noop_executor = DefaultEvaluator::noop(); - - let expr = rib::from_string("${let x: u64 = 1; let y: u64 = 2; [ok(x),ok(y)]}").unwrap(); - - let result = noop_executor.evaluate_pure_expr(&expr).await; - - let list = create_list(vec![ - create_ok_result(TypeAnnotatedValue::U64(1), None).unwrap(), - create_ok_result(TypeAnnotatedValue::U64(2), None).unwrap(), - ]) - .unwrap(); - - let expected = Ok(RibInterpreterResult::Val(list)); - - assert_eq!(result, expected); - } - - #[tokio::test] - async fn test_evaluation_with_multiple_lines() { - let noop_executor = DefaultEvaluator::noop(); - - let program = r" - let n1: u64 = 1; - let n2: u64 = 2; - let x = { a : n1 }; - let y = { b : n2 }; - let z = x.a > y.b; - z - "; - - let expr = rib::from_string(format!("${{{}}}", program)).unwrap(); - - let result = noop_executor.evaluate_pure_expr(&expr).await; - - let expected = Ok(RibInterpreterResult::Val(TypeAnnotatedValue::Bool(false))); - - assert_eq!(result, expected); - } - - mod test_utils { - use crate::api_definition::http::{AllPathPatterns, PathPattern, VarInfo}; - use crate::http::router::RouterPattern; - use crate::worker_binding::RequestDetails; - - use crate::worker_service_rib_interpreter::tests::WorkerServiceRibInterpreterTestExt; - use crate::worker_service_rib_interpreter::{DefaultEvaluator, EvaluationError}; - use golem_service_base::type_inference::infer_analysed_type; - use golem_wasm_ast::analysis::{ - AnalysedExport, AnalysedFunction, AnalysedFunctionParameter, AnalysedFunctionResult, - AnalysedType, NameTypePair, TypeOption, TypeRecord, TypeResult, TypeStr, TypeU32, - TypeU64, - }; - use golem_wasm_rpc::json::TypeAnnotatedValueJsonExtensions; - use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; - use golem_wasm_rpc::protobuf::{ - NameOptionTypePair, TypeVariant, TypedOption, TypedVariant, - }; - use http::{HeaderMap, Uri}; - use serde_json::{json, Value}; - use std::collections::HashMap; - - use golem_wasm_ast::analysis::TypeTuple; - use golem_wasm_rpc::protobuf::typed_result::ResultValue; - use golem_wasm_rpc::protobuf::NameValuePair; - use golem_wasm_rpc::protobuf::Type; - use golem_wasm_rpc::protobuf::TypeAnnotatedValue as RootTypeAnnotatedValue; - use golem_wasm_rpc::protobuf::{TypedFlags, TypedTuple}; - use golem_wasm_rpc::protobuf::{TypedList, TypedRecord, TypedResult}; - - pub(crate) fn create_tuple( - value: Vec, - ) -> Result { - let mut types = vec![]; - - for value in value.iter() { - let typ = Type::try_from(value) - .map_err(|_| EvaluationError("Failed to get type".to_string()))?; - types.push(typ); - } - - Ok(TypeAnnotatedValue::Tuple(TypedTuple { - value: value - .into_iter() - .map(|result| golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some(result.clone()), - }) - .collect(), - typ: types, - })) - } - - pub(crate) fn create_flags(value: Vec) -> TypeAnnotatedValue { - TypeAnnotatedValue::Flags(TypedFlags { - values: value.clone(), - typ: value.clone(), - }) - } - - pub(crate) fn create_ok_result( - value: TypeAnnotatedValue, - error_type: Option, - ) -> Result { - let typ = golem_wasm_rpc::protobuf::Type::try_from(&value) - .map_err(|_| EvaluationError("Failed to get type".to_string()))?; - - let typed_value = TypeAnnotatedValue::Result(Box::new(TypedResult { - result_value: Some(ResultValue::OkValue(Box::new( - golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some(value), - }, - ))), - ok: Some(typ), - error: error_type.map(|x| (&x).into()), - })); - - Ok(typed_value) - } - - pub(crate) fn create_error_result( - value: TypeAnnotatedValue, - optional_ok_type: Option, - ) -> Result { - let typ = golem_wasm_rpc::protobuf::Type::try_from(&value) - .map_err(|_| EvaluationError("Failed to get type".to_string()))?; - - let typed_value = TypeAnnotatedValue::Result(Box::new(TypedResult { - result_value: Some(ResultValue::ErrorValue(Box::new( - golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some(value), - }, - ))), - ok: optional_ok_type.map(|x| (&x).into()), - error: Some(typ), - })); - - Ok(typed_value) - } - - pub(crate) fn create_option( - value: TypeAnnotatedValue, - ) -> Result { - let typ = Type::try_from(&value) - .map_err(|_| EvaluationError("Failed to get analysed type".to_string()))?; - - Ok(TypeAnnotatedValue::Option(Box::new(TypedOption { - value: Some(Box::new(RootTypeAnnotatedValue { - type_annotated_value: Some(value), - })), - typ: Some(typ), - }))) - } - - pub(crate) fn create_list( - values: Vec, - ) -> Result { - match values.first() { - Some(value) => { - let typ = Type::try_from(value) - .map_err(|_| EvaluationError("Failed to get analysed type".to_string()))?; - - Ok(TypeAnnotatedValue::List(TypedList { - values: values - .into_iter() - .map(|result| RootTypeAnnotatedValue { - type_annotated_value: Some(result.clone()), - }) - .collect(), - typ: Some(typ), - })) - } - None => Ok(TypeAnnotatedValue::List(TypedList { - values: vec![], - typ: Some((&AnalysedType::Tuple(TypeTuple { items: vec![] })).into()), - })), - } - } - pub(crate) fn create_singleton_record( - binding_variable: &str, - value: &TypeAnnotatedValue, - ) -> Result { - create_record(vec![(binding_variable.to_string(), value.clone())]) - } - - pub(crate) fn create_record( - values: Vec<(String, TypeAnnotatedValue)>, - ) -> Result { - let mut name_type_pairs = vec![]; - let mut name_value_pairs = vec![]; - - for (key, value) in values.iter() { - let typ = value - .try_into() - .map_err(|_| EvaluationError("Failed to get type".to_string()))?; - name_type_pairs.push(NameTypePair { - name: key.to_string(), - typ, - }); - - name_value_pairs.push(NameValuePair { - name: key.to_string(), - value: Some(golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some(value.clone()), - }), - }); - } - - Ok(TypeAnnotatedValue::Record(TypedRecord { - typ: name_type_pairs - .iter() - .map(|x| golem_wasm_ast::analysis::protobuf::NameTypePair { - name: x.name.clone(), - typ: Some(golem_wasm_ast::analysis::protobuf::Type::from(&x.typ)), - }) - .collect(), - value: name_value_pairs, - })) - } - - pub(crate) fn create_none(typ: Option<&AnalysedType>) -> TypeAnnotatedValue { - TypeAnnotatedValue::Option(Box::new(TypedOption { - value: None, - typ: typ.map(|t| t.into()), - })) - } - - #[allow(dead_code)] - pub(crate) fn get_complex_variant_typed_value() -> TypeAnnotatedValue { - let record = - create_singleton_record("id", &TypeAnnotatedValue::Str("pId".to_string())).unwrap(); - - let result = create_ok_result(record, None).unwrap(); - - let optional = create_option(result).unwrap(); - - let variant_type = TypeVariant { - cases: vec![ - NameOptionTypePair { - name: "Foo".to_string(), - typ: Some( - (&AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::Result(TypeResult { - ok: Some(Box::new(AnalysedType::Record(TypeRecord { - fields: vec![NameTypePair { - name: "id".to_string(), - typ: AnalysedType::Str(TypeStr), - }], - }))), - err: None, - })), - })) - .into(), - ), - }, - NameOptionTypePair { - name: "Bar".to_string(), - typ: Some( - (&AnalysedType::Option(TypeOption { - inner: Box::new(AnalysedType::Result(TypeResult { - ok: Some(Box::new(AnalysedType::Record(TypeRecord { - fields: vec![NameTypePair { - name: "id".to_string(), - typ: AnalysedType::Str(TypeStr), - }], - }))), - err: None, - })), - })) - .into(), - ), - }, - ], - }; - - TypeAnnotatedValue::Variant(Box::new(TypedVariant { - case_name: "Foo".to_string(), - case_value: Some(Box::new(golem_wasm_rpc::protobuf::TypeAnnotatedValue { - type_annotated_value: Some(optional), - })), - typ: Some(variant_type), - })) - } - - pub(crate) fn get_result_type_fully_formed( - ok_type: AnalysedType, - err_type: AnalysedType, - ) -> AnalysedType { - AnalysedType::Result(TypeResult { - ok: Some(Box::new(ok_type)), - err: Some(Box::new(err_type)), - }) - } - - pub(crate) fn get_analysed_type_record( - record_type: Vec<(String, AnalysedType)>, - ) -> AnalysedType { - let record = TypeRecord { - fields: record_type - .into_iter() - .map(|(name, typ)| NameTypePair { name, typ }) - .collect(), - }; - AnalysedType::Record(record) - } - - pub(crate) fn get_analysed_exports( - function_name: &str, - input_types: Vec, - output: AnalysedType, - ) -> Vec { - let analysed_function_parameters = input_types - .into_iter() - .enumerate() - .map(|(index, typ)| AnalysedFunctionParameter { - name: format!("param{}", index), - typ, - }) - .collect(); - - vec![AnalysedExport::Function(AnalysedFunction { - name: function_name.to_string(), - parameters: analysed_function_parameters, - results: vec![AnalysedFunctionResult { - name: None, - typ: output, - }], - })] - } - - pub(crate) fn get_analysed_export_for_unit_function( - function_name: &str, - input_types: Vec, - ) -> Vec { - let analysed_function_parameters = input_types - .into_iter() - .enumerate() - .map(|(index, typ)| AnalysedFunctionParameter { - name: format!("param{}", index), - typ, - }) - .collect(); - - vec![AnalysedExport::Function(AnalysedFunction { - name: function_name.to_string(), - parameters: analysed_function_parameters, - results: vec![], - })] - } - - pub(crate) fn get_analysed_export_for_no_arg_function( - function_name: &str, - output: AnalysedType, - ) -> Vec { - vec![AnalysedExport::Function(AnalysedFunction { - name: function_name.to_string(), - parameters: vec![], - results: vec![AnalysedFunctionResult { - name: None, - typ: output, - }], - })] - } - - pub(crate) fn get_analysed_export_for_no_arg_unit_function( - function_name: &str, - ) -> Vec { - vec![AnalysedExport::Function(AnalysedFunction { - name: function_name.to_string(), - parameters: vec![], - results: vec![], - })] - } - - pub(crate) fn get_function_response_analysed_type_result() -> Vec { - vec![ - AnalysedExport::Function(AnalysedFunction { - name: "foo".to_string(), - parameters: vec![AnalysedFunctionParameter { - name: "my_parameter".to_string(), - typ: AnalysedType::U64(TypeU64), - }], - results: vec![AnalysedFunctionResult { - name: None, - typ: AnalysedType::Result(TypeResult { - ok: Some(Box::new(AnalysedType::Str(TypeStr))), - err: Some(Box::new(AnalysedType::Str(TypeStr))), - }), - }], - }), - AnalysedExport::Function(AnalysedFunction { - name: "baz".to_string(), - parameters: vec![AnalysedFunctionParameter { - name: "my_parameter".to_string(), - typ: AnalysedType::U32(TypeU32), - }], - results: vec![], - }), - ] - } - - pub(crate) fn get_simple_worker_response_err() -> TypeAnnotatedValue { - TypeAnnotatedValue::parse_with_type( - &json!({"err": "afsal" }), - &AnalysedType::Result(TypeResult { - ok: None, - err: Some(Box::new(AnalysedType::Str(TypeStr))), - }), - ) - .unwrap() - } - - #[allow(dead_code)] - pub(crate) fn get_err_worker_response() -> TypeAnnotatedValue { - TypeAnnotatedValue::parse_with_type( - &json!({"err": { "id" : "afsal"} }), - &AnalysedType::Result(TypeResult { - err: Some(Box::new(AnalysedType::Record(TypeRecord { - fields: vec![NameTypePair { - name: "id".to_string(), - typ: AnalysedType::Str(TypeStr), - }], - }))), - ok: None, - }), - ) - .unwrap() - } - - #[allow(dead_code)] - pub(crate) fn get_worker_response(input: &str) -> TypeAnnotatedValue { - let value: Value = serde_json::from_str(input).expect("Failed to parse json"); - - let expected_type = infer_analysed_type(&value); - - TypeAnnotatedValue::parse_with_type(&value, &expected_type).unwrap() - } - - pub(crate) fn get_request_details(input: &str, header_map: &HeaderMap) -> RequestDetails { - let request_body: Value = serde_json::from_str(input).expect("Failed to parse json"); - - RequestDetails::from( - &HashMap::new(), - &HashMap::new(), - &[], - &request_body, - header_map, - ) - .unwrap() - } - - pub(crate) fn request_details_from_request_path_variables( - uri: Uri, - path_pattern: AllPathPatterns, - ) -> RequestDetails { - let base_path: Vec<&str> = RouterPattern::split(uri.path()).collect(); - - let path_params = path_pattern - .path_patterns - .into_iter() - .enumerate() - .filter_map(|(index, pattern)| match pattern { - PathPattern::Literal(_) => None, - PathPattern::Var(var) => Some((var, base_path[index])), - }) - .collect::>(); - - RequestDetails::from( - &path_params, - &HashMap::new(), - &path_pattern.query_params, - &Value::Null, - &HeaderMap::new(), - ) - .unwrap() - } - - #[tokio::test] - async fn expr_to_string_round_trip_match_expr_err() { - let noop_executor = DefaultEvaluator::noop(); - - let worker_response = get_simple_worker_response_err(); - let component_metadata = get_function_response_analysed_type_result(); - - let expr_str = - r#"${let result = foo(1); match result { ok(x) => "foo", err(msg) => "error" }}"#; - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expr2_string = expr1.to_string(); - let expr2 = rib::from_string(expr2_string.as_str()).unwrap(); - let value2 = noop_executor - .evaluate_with_worker_response( - &expr2, - Some(worker_response), - component_metadata, - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("error".to_string()); - assert_eq!((&value1, &value2), (&expected, &expected)); - } - - #[tokio::test] - async fn expr_to_string_round_trip_match_expr_append() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let sequence_value = create_list(vec![ - TypeAnnotatedValue::Str("id1".to_string()), - TypeAnnotatedValue::Str("id2".to_string()), - ]) - .unwrap(); - - let record_value = create_singleton_record("ids", &sequence_value).unwrap(); - - let worker_response = - create_error_result(record_value, Some(AnalysedType::Str(TypeStr))).unwrap(); - - // Output from worker - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { ok(x) => "ok", err(x) => "append-${x.ids[0]}" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("append-id1".to_string()); - assert_eq!(&value1, &expected); - } - - #[tokio::test] - async fn expr_to_string_round_trip_match_expr_append_suffix() { - let noop_executor = DefaultEvaluator::noop(); - - // Output from worker - let sequence_value = create_list(vec![ - TypeAnnotatedValue::Str("id1".to_string()), - TypeAnnotatedValue::Str("id2".to_string()), - ]) - .unwrap(); - - let record_value = create_singleton_record("ids", &sequence_value).unwrap(); - - let worker_response = - create_ok_result(record_value, Some(AnalysedType::Str(TypeStr))).unwrap(); - - // Output from worker - let return_type = AnalysedType::try_from(&worker_response).unwrap(); - - let component_metadata = - get_analysed_exports("foo", vec![AnalysedType::U64(TypeU64)], return_type); - - let expr_str = r#"${let result = foo(1); match result { ok(x) => "prefix-${x.ids[0]}", err(msg) => "prefix-error-suffix" }}"#; - - let expr1 = rib::from_string(expr_str).unwrap(); - let value1 = noop_executor - .evaluate_with_worker_response( - &expr1, - Some(worker_response.clone()), - component_metadata.clone(), - None, - ) - .await - .unwrap(); - - let expected = TypeAnnotatedValue::Str("prefix-id1".to_string()); - assert_eq!(&value1, &expected); - } - } -}