diff --git a/kclvm/runtime/src/value/api.rs b/kclvm/runtime/src/value/api.rs index e917f8f81..c28d19c3f 100644 --- a/kclvm/runtime/src/value/api.rs +++ b/kclvm/runtime/src/value/api.rs @@ -5,6 +5,8 @@ use std::{mem::transmute_copy, os::raw::c_char}; use crate::*; +use self::walker::walk_value_mut; + #[allow(non_camel_case_types)] pub type kclvm_context_t = Context; @@ -2476,8 +2478,13 @@ pub unsafe extern "C" fn kclvm_convert_collection_value( let tpe = c2str(tpe); let value = type_pack_and_check(ctx, value, vec![tpe]); let is_in_schema = ptr_as_ref(is_in_schema); - if value.is_schema() && !is_in_schema.is_truthy() { - value.schema_check_attr_optional(ctx, true); + // Schema required attribute validating. + if !is_in_schema.is_truthy() { + walk_value_mut(&value, &mut |value: &ValueRef| { + if value.is_schema() { + value.schema_check_attr_optional(ctx, true); + } + }) } value.into_raw(ctx) } diff --git a/kclvm/runtime/src/value/mod.rs b/kclvm/runtime/src/value/mod.rs index 64425c8be..a474d855b 100644 --- a/kclvm/runtime/src/value/mod.rs +++ b/kclvm/runtime/src/value/mod.rs @@ -69,3 +69,5 @@ pub use val_union::*; pub mod val_yaml; pub use val_yaml::*; + +pub mod walker; diff --git a/kclvm/runtime/src/value/val_schema.rs b/kclvm/runtime/src/value/val_schema.rs index 8555100b3..0d8a11426 100644 --- a/kclvm/runtime/src/value/val_schema.rs +++ b/kclvm/runtime/src/value/val_schema.rs @@ -4,6 +4,8 @@ use indexmap::IndexSet; use crate::*; +use self::walker::walk_value_mut; + pub const SETTINGS_OUTPUT_KEY: &str = "output_type"; pub const SETTINGS_SCHEMA_TYPE_KEY: &str = "__schema_type__"; pub const SETTINGS_OUTPUT_STANDALONE: &str = "STANDALONE"; @@ -165,21 +167,11 @@ impl ValueRef { if recursive { for value in attr_map.values() { // For composite type structures, we recursively check the schema within them. - if value.is_schema() { - value.schema_check_attr_optional(ctx, recursive); - } else if value.is_list() { - for v in &value.as_list_ref().values { - if v.is_schema() { - v.schema_check_attr_optional(ctx, recursive) - } - } - } else if value.is_dict() { - for v in value.as_dict_ref().values.values() { - if v.is_schema() { - v.schema_check_attr_optional(ctx, recursive) - } + walk_value_mut(&value, &mut |value: &ValueRef| { + if value.is_schema() { + value.schema_check_attr_optional(ctx, true); } - } + }) } } } diff --git a/kclvm/runtime/src/value/walker.rs b/kclvm/runtime/src/value/walker.rs new file mode 100644 index 000000000..dd61a84ac --- /dev/null +++ b/kclvm/runtime/src/value/walker.rs @@ -0,0 +1,47 @@ +use crate::{Value, ValueRef}; + +/// Walk the value recursively and deal the type using the `walk_fn` +pub fn walk_value(val: &ValueRef, walk_fn: &impl Fn(&ValueRef) -> ()) { + walk_fn(val); + match &*val.rc.borrow() { + Value::list_value(list_value) => { + for v in &list_value.values { + walk_value(v, walk_fn); + } + } + Value::dict_value(dict_value) => { + for (_, v) in &dict_value.values { + walk_value(v, walk_fn); + } + } + Value::schema_value(schema_value) => { + for (_, v) in &schema_value.config.values { + walk_value(v, walk_fn); + } + } + _ => {} + } +} + +/// Walk the value recursively and mutably and deal the type using the `walk_fn` +pub fn walk_value_mut(val: &ValueRef, walk_fn: &mut impl FnMut(&ValueRef) -> ()) { + walk_fn(val); + match &*val.rc.borrow() { + Value::list_value(list_value) => { + for v in &list_value.values { + walk_value_mut(v, walk_fn); + } + } + Value::dict_value(dict_value) => { + for (_, v) in &dict_value.values { + walk_value_mut(v, walk_fn); + } + } + Value::schema_value(schema_value) => { + for (_, v) in &schema_value.config.values { + walk_value_mut(v, walk_fn); + } + } + _ => {} + } +} diff --git a/test/grammar/schema/optional_attr/fail_15/main.k b/test/grammar/schema/optional_attr/fail_15/main.k new file mode 100644 index 000000000..7d3adbc34 --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_15/main.k @@ -0,0 +1,9 @@ +schema Data: + name: str + type: str + +data = { + name = "data" +} + +datas: [Data] = [data] diff --git a/test/grammar/schema/optional_attr/fail_15/stderr.golden.py b/test/grammar/schema/optional_attr/fail_15/stderr.golden.py new file mode 100644 index 000000000..e5a110212 --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_15/stderr.golden.py @@ -0,0 +1,18 @@ +import sys +import os + +import kclvm.kcl.error as kcl_error + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception(err_type=kcl_error.ErrType.EvaluationError_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=os.path.join(cwd, "main.k"), + line_no=9, + ), + ], + arg_msg="attribute 'type' of Data is required and can't be None or Undefined") + , file=sys.stdout +) diff --git a/test/grammar/schema/optional_attr/fail_16/main.k b/test/grammar/schema/optional_attr/fail_16/main.k new file mode 100644 index 000000000..eb8bc3dd6 --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_16/main.k @@ -0,0 +1,9 @@ +schema Data: + name: str + type: str + +data = { + name = "data" +} + +datas: [[Data]] = [[data]] diff --git a/test/grammar/schema/optional_attr/fail_16/stderr.golden.py b/test/grammar/schema/optional_attr/fail_16/stderr.golden.py new file mode 100644 index 000000000..e5a110212 --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_16/stderr.golden.py @@ -0,0 +1,18 @@ +import sys +import os + +import kclvm.kcl.error as kcl_error + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception(err_type=kcl_error.ErrType.EvaluationError_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=os.path.join(cwd, "main.k"), + line_no=9, + ), + ], + arg_msg="attribute 'type' of Data is required and can't be None or Undefined") + , file=sys.stdout +) diff --git a/test/grammar/schema/optional_attr/fail_17/main.k b/test/grammar/schema/optional_attr/fail_17/main.k new file mode 100644 index 000000000..0ed33fb6c --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_17/main.k @@ -0,0 +1,11 @@ +schema Data: + name: str + type: str + +data = { + name = "data" +} + +datas: {str:Data} = { + data = data +} diff --git a/test/grammar/schema/optional_attr/fail_17/stderr.golden.py b/test/grammar/schema/optional_attr/fail_17/stderr.golden.py new file mode 100644 index 000000000..65fa60d4b --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_17/stderr.golden.py @@ -0,0 +1,18 @@ +import sys +import os + +import kclvm.kcl.error as kcl_error + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception(err_type=kcl_error.ErrType.EvaluationError_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=os.path.join(cwd, "main.k"), + line_no=10, + ), + ], + arg_msg="attribute 'type' of Data is required and can't be None or Undefined") + , file=sys.stdout +) diff --git a/test/grammar/schema/optional_attr/fail_18/main.k b/test/grammar/schema/optional_attr/fail_18/main.k new file mode 100644 index 000000000..e8700b21d --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_18/main.k @@ -0,0 +1,11 @@ +schema Data: + name: str + type: str + +data = { + name = "data" +} + +datas: {str:{str:Data}} = { + data.data = data +} diff --git a/test/grammar/schema/optional_attr/fail_18/stderr.golden.py b/test/grammar/schema/optional_attr/fail_18/stderr.golden.py new file mode 100644 index 000000000..65fa60d4b --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_18/stderr.golden.py @@ -0,0 +1,18 @@ +import sys +import os + +import kclvm.kcl.error as kcl_error + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception(err_type=kcl_error.ErrType.EvaluationError_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=os.path.join(cwd, "main.k"), + line_no=10, + ), + ], + arg_msg="attribute 'type' of Data is required and can't be None or Undefined") + , file=sys.stdout +) diff --git a/test/grammar/schema/optional_attr/fail_19/main.k b/test/grammar/schema/optional_attr/fail_19/main.k new file mode 100644 index 000000000..dc378c832 --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_19/main.k @@ -0,0 +1,11 @@ +schema Data: + name: str + type: str + +data = { + name = "data" +} + +datas: {str:[Data]} = { + data = [data] +} diff --git a/test/grammar/schema/optional_attr/fail_19/stderr.golden.py b/test/grammar/schema/optional_attr/fail_19/stderr.golden.py new file mode 100644 index 000000000..65fa60d4b --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_19/stderr.golden.py @@ -0,0 +1,18 @@ +import sys +import os + +import kclvm.kcl.error as kcl_error + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception(err_type=kcl_error.ErrType.EvaluationError_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=os.path.join(cwd, "main.k"), + line_no=10, + ), + ], + arg_msg="attribute 'type' of Data is required and can't be None or Undefined") + , file=sys.stdout +) diff --git a/test/grammar/schema/optional_attr/fail_20/main.k b/test/grammar/schema/optional_attr/fail_20/main.k new file mode 100644 index 000000000..de01e7434 --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_20/main.k @@ -0,0 +1,11 @@ +schema Data: + name: str + type: str + +data = { + name = "data" +} + +datas: [{str:[Data]}] = [{ + data = [data] +}] diff --git a/test/grammar/schema/optional_attr/fail_20/stderr.golden.py b/test/grammar/schema/optional_attr/fail_20/stderr.golden.py new file mode 100644 index 000000000..65fa60d4b --- /dev/null +++ b/test/grammar/schema/optional_attr/fail_20/stderr.golden.py @@ -0,0 +1,18 @@ +import sys +import os + +import kclvm.kcl.error as kcl_error + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception(err_type=kcl_error.ErrType.EvaluationError_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=os.path.join(cwd, "main.k"), + line_no=10, + ), + ], + arg_msg="attribute 'type' of Data is required and can't be None or Undefined") + , file=sys.stdout +)