diff --git a/rad/src/operators/map.rs b/rad/src/operators/map.rs index 6047d1416..da4bc692b 100644 --- a/rad/src/operators/map.rs +++ b/rad/src/operators/map.rs @@ -1,13 +1,84 @@ -use std::convert::TryInto; +use std::{collections::BTreeMap, convert::TryInto}; use serde_cbor::value::{Value, from_value}; +use witnet_data_structures::radon_report::ReportContext; use crate::{ - error::RadError, - operators::string, - types::{RadonType, RadonTypes, array::RadonArray, map::RadonMap, string::RadonString}, + error::RadError, operators::string, script::{execute_radon_script, partial_results_extract, unpack_subscript, RadonScriptExecutionSettings}, types::{array::RadonArray, map::RadonMap, string::RadonString, RadonType, RadonTypes} }; +pub fn alter( + input: &RadonMap, + args: &[Value], + context: &mut ReportContext +) -> Result { + let wrong_args = || RadError::WrongArguments { + input_type: RadonMap::radon_type_name(), + operator: "Alter".to_string(), + args: args.to_vec(), + }; + + let first_arg = args.first().ok_or_else(wrong_args)?; + let mut input_keys = vec![]; + match first_arg { + Value::Array(keys) => { + for key in keys.iter() { + let key_string = from_value::(key.to_owned()).map_err(|_| wrong_args())?; + input_keys.push(key_string); + } + } + Value::Text(key) => { + input_keys.push(key.clone()); + } + _ => return Err(wrong_args()) + }; + + let subscript = args.get(1).ok_or_else(wrong_args)?; + match subscript { + Value::Array(_arg) => { + let subscript_err = |e| RadError::Subscript { + input_type: "RadonMap".to_string(), + operator: "Alter".to_string(), + inner: Box::new(e), + }; + let subscript = unpack_subscript(subscript).map_err(subscript_err)?; + + let not_found = |key_str: &str| RadError::MapKeyNotFound { key: String::from(key_str) }; + + let input_map = input.value(); + let mut output_map = input.value().clone(); + let mut reports = vec![]; + + let settings = RadonScriptExecutionSettings::tailored_to_stage(&context.stage); + for key in input_keys { + let value = input_map + .get(key.as_str()) + .ok_or_else(|| not_found(key.as_str()))?; + let report = execute_radon_script( + value.clone(), + subscript.as_slice(), + context, + settings + )?; + // If there is an error while altering value, short-circuit and bubble up the error as it comes + // from the radon script execution + if let RadonTypes::RadonError(error) = &report.result { + return Err(error.clone().into_inner()); + } else { + output_map.insert(key, report.result.clone()); + } + reports.push(report); + } + + // Extract the partial results from the reports and put them in the execution context if needed + partial_results_extract(&subscript, &reports, context); + + Ok(RadonMap::from(output_map)) + } + _ => Err(wrong_args()) + } +} + fn inner_get(input: &RadonMap, args: &[Value]) -> Result { let wrong_args = || RadError::WrongArguments { input_type: RadonMap::radon_type_name(), @@ -76,11 +147,61 @@ pub fn keys(input: &RadonMap) -> RadonArray { RadonArray::from(v) } +pub fn pick(input: &RadonMap, args: &[Value]) -> Result { + let not_found = |key_str: &str| RadError::MapKeyNotFound { + key: String::from(key_str) + }; + + let wrong_args = || RadError::WrongArguments { + input_type: RadonMap::radon_type_name(), + operator: "Pick".to_string(), + args: args.to_vec(), + }; + + let mut input_keys = vec![]; + if args.len() > 1 { + return Err(wrong_args()); + } else { + let first_arg = args.first().ok_or_else(wrong_args)?; + match first_arg { + Value::Array(keys) => { + for key in keys.iter() { + let key_string = from_value::(key.to_owned()).map_err(|_| wrong_args())?; + input_keys.push(key_string); + } + } + Value::Text(key) => { + input_keys.push(key.clone()); + } + _ => return Err(wrong_args()) + }; + } + + let mut output_map = BTreeMap::::default(); + for key in input_keys { + if let Some(value) = input.value().get(&key) { + output_map.insert(key, value.clone()); + } else { + return Err(not_found(key.as_str())) + } + } + Ok(RadonMap::from(output_map)) +} + pub fn values(input: &RadonMap) -> RadonArray { let v: Vec = input.value().values().cloned().collect(); RadonArray::from(v) } +pub fn to_string(input: &RadonMap) -> Result { + let json_string = serde_json::to_string(&input.value()) + .map_err(|_| RadError::Decode { + from: "RadonMap", + to: "RadonString" + })?; + Ok(RadonString::from(json_string)) +} + /// This module was introduced for encapsulating the interim legacy logic before WIP-0024 is /// introduced, for the sake of maintainability. /// diff --git a/rad/src/operators/mod.rs b/rad/src/operators/mod.rs index f57c5b9b7..ffc647f8e 100644 --- a/rad/src/operators/mod.rs +++ b/rad/src/operators/mod.rs @@ -83,7 +83,7 @@ pub enum RadonOpCodes { FloatTruncate = 0x5D, /////////////////////////////////////////////////////////////////////// // Map operator codes (start at 0x60) - // MapEntries = 0x60, + MapToString = 0x60, MapGetArray = 0x61, MapGetBoolean = 0x62, MapGetBytes = 0x63, @@ -93,6 +93,8 @@ pub enum RadonOpCodes { MapGetString = 0x67, MapKeys = 0x68, MapValues = 0x69, + MapAlter = 0x6B, + MapPick = 0x6E, /////////////////////////////////////////////////////////////////////// // String operator codes (start at 0x70) StringAsBoolean = 0x70, diff --git a/rad/src/script.rs b/rad/src/script.rs index fc308ee70..41d58de32 100644 --- a/rad/src/script.rs +++ b/rad/src/script.rs @@ -1,4 +1,8 @@ -use std::convert::TryFrom; +use std::{ + clone::Clone, + convert::TryFrom, + iter, +}; use serde_cbor::{ self as cbor, @@ -280,6 +284,27 @@ pub fn create_radon_script_from_filters_and_reducer( Ok(radoncall_vec) } +pub fn partial_results_extract( + subscript: &[RadonCall], + reports: &[RadonReport], + context: &mut ReportContext, +) { + if let Stage::Retrieval(metadata) = &mut context.stage { + metadata.subscript_partial_results.push(subscript.iter().chain(iter::once(&(RadonOpCodes::Fail, None))).enumerate().map(|(index, _)| + reports + .iter() + .map(|report| + report.partial_results + .as_ref() + .expect("Execution reports from applying subscripts are expected to contain partial results") + .get(index) + .expect("Execution reports from applying same subscript on multiple values should contain the same number of partial results") + .clone() + ).collect::>() + ).collect::>>()); + } +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; diff --git a/rad/src/types/map.rs b/rad/src/types/map.rs index ac764fab3..dbfcdb0b4 100644 --- a/rad/src/types/map.rs +++ b/rad/src/types/map.rs @@ -132,6 +132,7 @@ impl Operable for RadonMap { match call { (RadonOpCodes::Identity, None) => identity(RadonTypes::from(self.clone())), + (RadonOpCodes::MapToString, None) => map_operators::to_string(self).map(RadonTypes::from), (RadonOpCodes::MapGetArray, Some(args)) => { map_operators::get::(self, args.as_slice()).map(RadonTypes::from) } @@ -161,6 +162,9 @@ impl Operable for RadonMap { } (RadonOpCodes::MapKeys, None) => Ok(RadonTypes::from(map_operators::keys(self))), (RadonOpCodes::MapValues, None) => Ok(RadonTypes::from(map_operators::values(self))), + (RadonOpCodes::MapAlter, Some(args)) => map_operators::alter(self, args, context).map(RadonTypes::from), + (RadonOpCodes::MapPick, Some(args)) => map_operators::pick(self, args).map(RadonTypes::from), + (op_code, args) => Err(RadError::UnsupportedOperator { input_type: RADON_MAP_TYPE_NAME.to_string(), operator: op_code.to_string(),