From b7187e210ae7e794c87ae2f76d0f212e5d716b15 Mon Sep 17 00:00:00 2001 From: troido Date: Mon, 28 Sep 2020 17:55:41 +0200 Subject: added type validation for parameters --- content/encyclopediae/default_encyclopedia.json | 2 +- src/assemblage.rs | 71 +++++++++++++++---------- src/encyclopedia.rs | 4 +- src/parameterexpression.rs | 33 +++++++----- 4 files changed, 64 insertions(+), 46 deletions(-) diff --git a/content/encyclopediae/default_encyclopedia.json b/content/encyclopediae/default_encyclopedia.json index 2d37aca..1430a8d 100644 --- a/content/encyclopediae/default_encyclopedia.json +++ b/content/encyclopediae/default_encyclopedia.json @@ -65,7 +65,7 @@ ] }, "singleton": { - "arguments": {"ent": "template", "clan": ""}, + "arguments": {"ent": null, "clan": ""}, "components": [ ["Spawner", { "template": {"$arg": "ent"}, diff --git a/src/assemblage.rs b/src/assemblage.rs index fb60463..773ebf0 100644 --- a/src/assemblage.rs +++ b/src/assemblage.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use serde::{de, Serialize, Deserialize, Deserializer}; use crate::{ - parameterexpression::ParameterExpression, + parameterexpression::{ParameterExpression, EvaluationError}, parameter::{Parameter}, componentwrapper::{ComponentWrapper, ComponentType}, components::{Serialise, Clan}, @@ -23,40 +23,42 @@ pub struct Assemblage { impl Assemblage { pub fn validate(&self) -> AnyResult<()> { + + let arguments = self.arguments.iter().filter_map(|(k, v)|Some((k.clone(), v.clone()?))).collect::>(); for (comptype, parameters) in &self.components { + let mut is_complete = true; + let mut compargs = HashMap::new(); for paramname in comptype.parameters() { - let _param = parameters.get(paramname).ok_or(aerr!("missing parameter {} for component {:?}", paramname, comptype))?; - // todo: validate parameter types + let param = parameters.get(paramname).ok_or(aerr!("missing parameter {} for component {:?}", paramname, comptype))?; + match param.evaluate(&arguments, &Template::empty("")) { + Err(EvaluationError::MissingArgument(_)) => {is_complete = false;} + Err(EvaluationError::Other(msg)) => {return Err(aerr!("invalid value for {}: {}", paramname, msg))} + Ok(p) => {compargs.insert(paramname, p);} + } + } + if is_complete { + ComponentWrapper::load_component(*comptype, compargs)?; } } Ok(()) } - fn prepare_arguments(&self, kwargs: &HashMap) -> AnyResult> { - let mut arguments: HashMap<&str, Parameter> = HashMap::new(); - for (name, def) in self.arguments.iter() { - let param: Parameter= { - if let Some(val) = kwargs.get(name) { - val.clone() - } else if let Some(val) = def { - val.clone() - } else { - return Err(aerr!("argument <{:?}> has no value", (name, def))) - } - }; - arguments.insert(name, param); - } - Ok(arguments) - } pub fn instantiate(&self, template: &Template) -> AnyResult>{ - let kwargs = &template.kwargs; + let mut args = self.arguments.clone(); + for (key, param) in template.kwargs.clone() { + // todo: warn about unknown keys + args.insert(key, Some(param)); + } + let arguments = args.into_iter().map(|(k, v)|Ok((k.clone(), v.ok_or(aerr!("missing argument value for {}", k))?))).collect::>>()?; let mut components: Vec = Vec::new(); - let arguments = self.prepare_arguments(kwargs)?; for (comptype, compparams) in &self.components { let mut compargs: HashMap<&str, Parameter> = HashMap::new(); for (name, param) in compparams { - compargs.insert(name.as_str(), param.evaluate(&arguments, template).ok_or(aerr!("argument not found"))?); + compargs.insert(name.as_str(), param.evaluate(&arguments, template).map_err(|e| match e { + EvaluationError::MissingArgument(arg) => aerr!("argument {} not found", arg), + EvaluationError::Other(msg) => aerr!("{}", msg) + })?); } components.push(ComponentWrapper::load_component(*comptype, compargs)?); } @@ -192,12 +194,11 @@ mod tests { }] ] })).unwrap_err(); -// assert_eq!(result, "not a valid componenttype"); } -// #[test] + #[test] fn invalid_parameter_type(){ Assemblage::deserialize(&json!({ "arguments": {"sprite": "grass1"}, @@ -208,14 +209,13 @@ mod tests { "name": "grass" }] ] - })).unwrap_err(); -// assert_eq!(result, "parameter type incorrect"); + })).unwrap().validate().unwrap_err(); } -// #[test] + #[test] fn wrong_argument_default(){ Assemblage::deserialize(&json!({ "arguments": {"sprite": 1}, @@ -226,8 +226,21 @@ mod tests { "name": "grass" }] ] - })).unwrap_err(); -// assert_eq!(result, "invalid argument default"); + })).unwrap().validate().unwrap_err(); + } + + #[test] + fn missing_argument_default(){ + Assemblage::deserialize(&json!({ + "arguments": {"sprite": null}, + "components": [ + ["Visible", { + "sprite": {"$arg": "sprite"}, + "height": 0.1, + "name": "grass" + }] + ] + })).unwrap().validate().unwrap(); } diff --git a/src/encyclopedia.rs b/src/encyclopedia.rs index 2bb2960..76a7d28 100644 --- a/src/encyclopedia.rs +++ b/src/encyclopedia.rs @@ -26,8 +26,8 @@ pub struct Encyclopedia { impl Encyclopedia { pub fn validate(&self) -> AnyResult<()> { - for assemblage in self.assemblages.values() { - assemblage.validate()?; + for (name, assemblage) in self.assemblages.iter() { + assemblage.validate().map_err(|e| aerr!("invalid assemblage {}: {}", name.0, e))?; } Ok(()) } diff --git a/src/parameterexpression.rs b/src/parameterexpression.rs index c96bd93..dfba562 100644 --- a/src/parameterexpression.rs +++ b/src/parameterexpression.rs @@ -25,38 +25,43 @@ pub enum ParameterExpression { TemplateName } +pub enum EvaluationError { + MissingArgument(String), + Other(String) +} + impl ParameterExpression { - pub fn evaluate(&self, arguments: &HashMap<&str, Parameter>, template: &Template) -> Option { + pub fn evaluate(&self, arguments: &HashMap, template: &Template) -> Result { self.evaluate_(arguments, template, 0) } - fn evaluate_(&self, arguments: &HashMap<&str, Parameter>, template: &Template, nesting: usize) -> Option { + fn evaluate_(&self, arguments: &HashMap, template: &Template, nesting: usize) -> Result { if nesting > MAX_NESTING { - return None; + return Err(EvaluationError::Other("Maximum nesting reached in parameter evaluation".to_string())); } match self { Self::Constant(val) => { - Some(val.clone()) + Ok(val.clone()) } Self::List(values) => { - Some(Parameter::List(values.iter().map(|v| v.evaluate_(arguments, template, nesting+1)).collect::>>()?)) + Ok(Parameter::List(values.iter().map(|v| v.evaluate_(arguments, template, nesting+1)).collect::, EvaluationError>>()?)) } Self::Template{name, kwargs, save, clan} => { - Some(Parameter::Template(Template{ + Ok(Parameter::Template(Template{ name: name.clone(), save: *save, kwargs: kwargs .iter() .map( |(k, v)| - Some((k.clone(), v.evaluate_(arguments, template, nesting+1)?))) - .collect::>>()?, + Ok((k.clone(), v.evaluate_(arguments, template, nesting+1)?))) + .collect::, EvaluationError>>()?, clan: clan.clone() })) } Self::Argument(argname) => { - Some(arguments.get(argname.as_str())?.clone()) + Ok(arguments.get(argname.as_str()).ok_or(EvaluationError::MissingArgument(argname.to_string()))?.clone()) } Self::Random(options) => { let r = rand::thread_rng().gen_range(0, options.len()); @@ -68,10 +73,10 @@ impl ParameterExpression { if let Parameter::String(s) = option.evaluate_(arguments, template, nesting+1)? { string.push_str(&s); } else { - return None; + return Err(EvaluationError::Other(format!("string concatenation value not a string: {:?}", option))); } } - Some(Parameter::String(string)) + Ok(Parameter::String(string)) } Self::If(condition, thenval, elseval) => { if let Parameter::Bool(b) = condition.evaluate_(arguments, template, nesting+1)? { @@ -81,11 +86,11 @@ impl ParameterExpression { elseval.evaluate_(arguments, template, nesting+1) } } else { - None + return Err(EvaluationError::Other(format!("if condition not a bool: {:?}", condition))) } } - Self::TemplateSelf => Some(Parameter::Template(template.clone())), - Self::TemplateName => Some(Parameter::String(template.name.0.clone())), + Self::TemplateSelf => Ok(Parameter::Template(template.clone())), + Self::TemplateName => Ok(Parameter::String(template.name.0.clone())), } } -- cgit