diff --git a/docs/09.EXT-REFERENCE-ARG.md b/docs/09.EXT-REFERENCE-ARG.md index 76c5414d3..8884420b9 100644 --- a/docs/09.EXT-REFERENCE-ARG.md +++ b/docs/09.EXT-REFERENCE-ARG.md @@ -31,6 +31,30 @@ typedef struct - [jerryx_arg_function](#jerryx_arg_function) - [jerryx_arg_native_pointer](#jerryx_arg_native_pointer) - [jerryx_arg_ignore](#jerryx_arg_ignore) +- [jerryx_arg_object_properties](#jerryx_arg_object_properties) + +## jerryx_arg_object_props_t + +**Summary** + +The structure is used in `jerryx_arg_object_properties`. It provides the properties' names, +its corresponding JS-to-C mapping and other related information. + +**Prototype** + +```c +typedef struct +{ + const jerry_char_t **name_p; /**< property name list of the JS object */ + jerry_length_t name_cnt; /**< count of the name list */ + const jerryx_arg_t *c_arg_p; /**< points to the array of transformation steps */ + jerry_length_t c_arg_cnt; /**< the count of the `c_arg_p` array */ +} jerryx_arg_object_props_t; +``` + +**See also** + +- [jerryx_arg_object_properties](#jerryx_arg_object_properties) ## jerryx_arg_transform_func_t @@ -128,7 +152,7 @@ jerryx_arg_transform_this_and_args (const jerry_value_t this_val, **Example** ```c -// JS signature: function (requiredBool, requiredString, optionalNumber) +/* JS signature: function (requiredBool, requiredString, optionalNumber) */ static jerry_value_t my_external_handler (const jerry_value_t function_obj, const jerry_value_t this_val, const jerry_value_t args_p[], @@ -138,10 +162,10 @@ static jerry_value_t my_external_handler (const jerry_value_t function_obj, char required_str[16]; double optional_num = 1234.567; // default value - // "mapping" defines the steps to transform input arguments to C variables: + /* "mapping" defines the steps to transform input arguments to C variables. */ const jerryx_arg_t mapping[] = { - // `this` is the first value. No checking needed on `this` for this function. + /* `this` is the first value. No checking needed on `this` for this function. */ jerryx_arg_ignore (), jerryx_arg_boolean (&required_bool, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), @@ -149,22 +173,24 @@ static jerry_value_t my_external_handler (const jerry_value_t function_obj, jerryx_arg_number (&optional_num, JERRYX_ARG_NO_COERCE, JERRYX_ARG_OPTIONAL), }; - // Validate and transform: + /* Validate and transform. */ const jerry_value_t rv = jerryx_arg_transform_this_and_args (this_val, args_p, args_count, mapping, - ARRAY_LENGTH (mapping)); + 4); if (jerry_value_has_error_flag (rv)) { - // Handle error + /* Handle error. */ return rv; } - // Validated and tranformed successfully! - // required_bool, required_str and optional_num can now be used. - // ... + /* + * Validated and transformed successfully! + * required_bool, required_str and optional_num can now be used. + */ + ... } ``` @@ -177,6 +203,7 @@ static jerry_value_t my_external_handler (const jerry_value_t function_obj, - [jerryx_arg_function](#jerryx_arg_function) - [jerryx_arg_native_pointer](#jerryx_arg_native_pointer) - [jerryx_arg_custom](#jerryx_arg_custom) +- [jerryx_arg_object_properties](#jerryx_arg_object_properties) ## jerryx_arg_transform_args @@ -206,6 +233,41 @@ jerryx_arg_transform_args (const jerry_value_t *js_arg_p, - [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) +## jerryx_arg_transform_object_properties + +**Summary** + +Validate the properties of a JS object and assign them to the native arguments. + +*Note*: This function transforms properties of a single JS object into native C values. +To transform multiple objects in one pass (for example when converting multiple arguments +to an external handler), please use `jerryx_arg_object_properties` together with +`jerryx_arg_transform_this_and_args` or `jerryx_arg_transform_args`. + +**Prototype** + +```c +jerry_value_t +jerryx_arg_transform_object_properties (const jerry_value_t obj_val, + const jerry_char_t **name_p, + const jerry_length_t name_cnt, + const jerryx_arg_t *c_arg_p, + jerry_length_t c_arg_cnt); + +``` + + - `obj_val` - the JS object. + - `name_p` - points to the array of property names. + - `name_cnt` - the count of the `name_p` array. + - `c_arg_p` - points to the array of validation/transformation steps + - `c_arg_cnt` - the count of the `c_arg_p` array. + - return value - a `jerry_value_t` representing `undefined` if all validators passed or an `Error` if a validator failed. + +**See also** + +- [jerryx_arg_object_properties](#jerryx_arg_object_properties) + + # Helpers for commonly used validations ## jerryx_arg_number @@ -314,7 +376,7 @@ jerryx_arg_function (jerry_value_t *dest, **Summary** Create a validation/transformation step (`jerryx_arg_t`) that expects to -consume one `Object` JS argument that is 'backed' with a native pointer with +consume one `object` JS argument that is 'backed' with a native pointer with a given type info. In case the native pointer info matches, the transform will succeed and the object's native pointer will be assigned to `*dest`. @@ -335,6 +397,94 @@ jerryx_arg_native_pointer (void **dest, - [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) +## jerryx_arg_object_properties + +**Summary** + +Create a validation/transformation step (`jerryx_arg_t`) that expects to +consume one `object` JS argument and call `jerryx_arg_transform_object_properties` inside +to transform its properties to native arguments. +User should prepare the `jerryx_arg_object_props_t` instance, and pass it to this function. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_object_properties (const jerryx_arg_object_props_t *object_props_p, + jerryx_arg_optional_t opt_flag); +``` + - return value - the created `jerryx_arg_t` instance. + - `object_props_p` - provides information for properties transform. + - `opt_flag` - whether the argument is optional. + +**Example** + +```c +/** + * The binding function expects args_p[0] is an object, which has 3 properties: + * "enable": boolean + * "data": number + * "extra_data": number, optional + */ +static jerry_value_t my_external_handler (const jerry_value_t function_obj, + const jerry_value_t this_val, + const jerry_value_t args_p[], + const jerry_length_t args_count) +{ + bool required_bool; + double required_num; + double optional_num = 1234.567; // default value + + /* "prop_name_p" defines the name list of the expected properties' names. */ + const char *prop_name_p[] = { "enable", "data", "extra_data" }; + + /* "prop_mapping" defines the steps to transform properties to C variables. */ + const jerryx_arg_t prop_mapping[] = + jerryx_arg_boolean (&required_bool, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&required_num, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&optional_num, JERRYX_ARG_COERCE, JERRYX_ARG_OPTIONAL) + }; + + /* Prepare the jerryx_arg_object_props_t instance. */ + const jerryx_arg_object_props_t prop_info = + { + .name_p = (const jerry_char_t **) prop_name_p, + .name_cnt = 3, + .c_arg_p = prop_mapping, + .c_arg_cnt = 3 + }; + + /* It is the mapping used in the jerryx_arg_transform_args. */ + const jerryx_arg_t mapping[] = + { + jerryx_arg_object_properties (&prop_info, JERRYX_ARG_REQUIRED) + }; + + /* Validate and transform. */ + const jerry_value_t rv = jerryx_arg_transform_args (args_p, + args_count, + mapping, + 1); + + if (jerry_value_has_error_flag (rv)) + { + /* Handle error. */ + return rv; + } + + /* + * Validated and transformed successfully! + * required_bool, required_num and optional_num can now be used. + */ + ... +} + +``` + + **See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) +- [jerryx_arg_transform_object_properties](#jerryx_arg_transform_object_properties) # Functions to create custom validations diff --git a/jerry-ext/arg/arg-transform-functions.c b/jerry-ext/arg/arg-transform-functions.c index 2f9ec442d..b51c4b5ca 100644 --- a/jerry-ext/arg/arg-transform-functions.c +++ b/jerry-ext/arg/arg-transform-functions.c @@ -63,7 +63,7 @@ jerryx_arg_transform_number_strict (jerryx_arg_js_iterator_t *js_arg_iter_p, /** } /* jerryx_arg_transform_number_strict */ /** - * Tranform a JS argument to a double. Type coercion is allowed. + * Transform a JS argument to a double. Type coercion is allowed. * * @return jerry undefined: the transformer passes, * jerry error: the transformer fails. @@ -251,7 +251,7 @@ jerryx_arg_transform_native_pointer (jerryx_arg_js_iterator_t *js_arg_iter_p, /* if (!jerry_value_is_object (js_arg)) { return jerry_create_error (JERRY_ERROR_TYPE, - (jerry_char_t *) "It is not a object."); + (jerry_char_t *) "It is not an object."); } const jerry_object_native_info_t *expected_info_p; @@ -269,6 +269,27 @@ jerryx_arg_transform_native_pointer (jerryx_arg_js_iterator_t *js_arg_iter_p, /* return jerry_create_undefined (); } /* jerryx_arg_transform_native_pointer */ +/** + * Check whether the JS object's properties have expected types, and transform them into native args. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_object_props (jerryx_arg_js_iterator_t *js_arg_iter_p, /**< available JS args */ + const jerryx_arg_t *c_arg_p) /**< the native arg */ +{ + jerry_value_t js_arg = jerryx_arg_js_iterator_pop (js_arg_iter_p); + + const jerryx_arg_object_props_t *object_props = (const jerryx_arg_object_props_t *) c_arg_p->extra_info; + + return jerryx_arg_transform_object_properties (js_arg, + object_props->name_p, + object_props->name_cnt, + object_props->c_arg_p, + object_props->c_arg_cnt); +} /* jerryx_arg_transform_object_props */ + /** * Define transformer for optional argument. */ @@ -288,6 +309,7 @@ JERRYX_ARG_TRANSFORM_OPTIONAL (string) JERRYX_ARG_TRANSFORM_OPTIONAL (string_strict) JERRYX_ARG_TRANSFORM_OPTIONAL (function) JERRYX_ARG_TRANSFORM_OPTIONAL (native_pointer) +JERRYX_ARG_TRANSFORM_OPTIONAL (object_props) /** * Ignore the JS argument. diff --git a/jerry-ext/arg/arg.c b/jerry-ext/arg/arg.c index 33620988a..3acac7d5f 100644 --- a/jerry-ext/arg/arg.c +++ b/jerry-ext/arg/arg.c @@ -81,8 +81,56 @@ jerryx_arg_transform_this_and_args (const jerry_value_t this_val, /**< the this_ { jerry_release_value (ret); - return jerry_create_error (JERRY_ERROR_TYPE, (jerry_char_t *) "'this' validation failed"); + return jerry_create_error (JERRY_ERROR_TYPE, (jerry_char_t *) "'this' validation failed."); } return jerryx_arg_transform_args (js_arg_p, js_arg_cnt, c_arg_p + 1, c_arg_cnt - 1); } /* jerryx_arg_transform_this_and_args */ + +/** + * Validate the `obj_val`'s properties, + * and assign them to the native arguments. + * + * @return jerry undefined: all validators passed, + * jerry error: a validator failed. + */ +jerry_value_t +jerryx_arg_transform_object_properties (const jerry_value_t obj_val,/**< the JS object */ + const jerry_char_t **name_p, /**< property name list of the JS object */ + const jerry_length_t name_cnt, /**< count of the name list */ + const jerryx_arg_t *c_arg_p, /**< points to the array of transformation steps */ + jerry_length_t c_arg_cnt) /**< the count of the `c_arg_p` array */ +{ + if (!jerry_value_is_object (obj_val)) + { + return jerry_create_error (JERRY_ERROR_TYPE, (jerry_char_t *) "Not an object."); + } + + jerry_value_t prop[name_cnt]; + + for (jerry_length_t i = 0; i < name_cnt; i++, name_p++) + { + const jerry_value_t name_str = jerry_create_string (*name_p); + prop[i] = jerry_get_property (obj_val, name_str); + jerry_release_value (name_str); + + if (jerry_value_has_error_flag (prop[i])) + { + for (jerry_length_t j = 0; j < i; j++) + { + jerry_release_value (prop[j]); + } + + return prop[i]; + } + } + + const jerry_value_t ret = jerryx_arg_transform_args (prop, name_cnt, c_arg_p, c_arg_cnt); + + for (jerry_length_t i = 0; i < name_cnt; i++) + { + jerry_release_value (prop[i]); + } + + return ret; +} /* jerryx_arg_transform_object_properties */ diff --git a/jerry-ext/include/jerryscript-ext/arg.h b/jerry-ext/include/jerryscript-ext/arg.h index 99a30d28c..b890e910d 100644 --- a/jerry-ext/include/jerryscript-ext/arg.h +++ b/jerry-ext/include/jerryscript-ext/arg.h @@ -16,7 +16,6 @@ #ifndef JERRYX_ARG_H #define JERRYX_ARG_H -#include #include #include #include @@ -43,6 +42,17 @@ typedef struct jerryx_arg_js_iterator_t jerryx_arg_js_iterator_t; typedef jerry_value_t (*jerryx_arg_transform_func_t) (jerryx_arg_js_iterator_t *js_arg_iter_p, /**< available JS args */ const jerryx_arg_t *c_arg_p); /**< native arg */ +/** + * The structure used in jerryx_arg_object_properties + */ +typedef struct +{ + const jerry_char_t **name_p; /**< property name list of the JS object */ + jerry_length_t name_cnt; /**< count of the name list */ + const jerryx_arg_t *c_arg_p; /**< points to the array of transformation steps */ + jerry_length_t c_arg_cnt; /**< the count of the `c_arg_p` array */ +} jerryx_arg_object_props_t; + /** * The structure defining a single validation & transformation step. */ @@ -64,6 +74,12 @@ jerry_value_t jerryx_arg_transform_args (const jerry_value_t *js_arg_p, const jerryx_arg_t *c_arg_p, jerry_length_t c_arg_cnt); +jerry_value_t jerryx_arg_transform_object_properties (const jerry_value_t obj_val, + const jerry_char_t **name_p, + const jerry_length_t name_cnt, + const jerryx_arg_t *c_arg_p, + jerry_length_t c_arg_cnt); + /** * Indicates whether an argument is allowed to be coerced into the expected JS type. */ @@ -105,6 +121,8 @@ static inline jerryx_arg_t jerryx_arg_ignore (void); static inline jerryx_arg_t jerryx_arg_custom (void *dest, uintptr_t extra_info, jerryx_arg_transform_func_t func); +static inline jerryx_arg_t +jerryx_arg_object_properties (const jerryx_arg_object_props_t *object_props_p, jerryx_arg_optional_t opt_flag); jerry_value_t jerryx_arg_transform_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, diff --git a/jerry-ext/include/jerryscript-ext/arg.impl.h b/jerry-ext/include/jerryscript-ext/arg.impl.h index dbd2117ee..0a1e5482e 100644 --- a/jerry-ext/include/jerryscript-ext/arg.impl.h +++ b/jerry-ext/include/jerryscript-ext/arg.impl.h @@ -51,6 +51,10 @@ jerry_value_t jerryx_arg_transform_native_pointer_optional (jerryx_arg_js_iterat const jerryx_arg_t *c_arg_p); jerry_value_t jerryx_arg_transform_ignore (jerryx_arg_js_iterator_t *js_arg_iter_p, const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_object_props (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_object_props_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); /** * Create a validation/transformation step (`jerryx_arg_t`) that expects to @@ -213,7 +217,7 @@ jerryx_arg_function (jerry_value_t *dest, /**< pointer to the jerry_value_t wher /** * Create a validation/transformation step (`jerryx_arg_t`) that expects to - * consume one `Object` JS argument that is 'backed' with a native pointer with + * consume one `object` JS argument that is 'backed' with a native pointer with * a given type info. In case the native pointer info matches, the transform * will succeed and the object's native pointer will be assigned to *dest. * @@ -275,4 +279,32 @@ jerryx_arg_custom (void *dest, /**< pointer to the native argument where the res }; } /* jerryx_arg_custom */ +/** + * Create a jerryx_arg_t instance for object properties. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_object_properties (const jerryx_arg_object_props_t *object_props, /**< pointer to object property mapping */ + jerryx_arg_optional_t opt_flag) /**< whether the argument is optional */ +{ + jerryx_arg_transform_func_t func; + + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_object_props_optional; + } + else + { + func = jerryx_arg_transform_object_props; + } + + return (jerryx_arg_t) + { + .func = func, + .dest = NULL, + .extra_info = (uintptr_t) object_props + }; +} /* jerryx_arg_object_properties */ + #endif /* !JERRYX_ARG_IMPL_H */ diff --git a/tests/unit-ext/test-ext-arg.c b/tests/unit-ext/test-ext-arg.c index 47b099934..06c31b730 100644 --- a/tests/unit-ext/test-ext-arg.c +++ b/tests/unit-ext/test-ext-arg.c @@ -22,6 +22,7 @@ #include "test-common.h" #include +#include const char *test_source = ( "var arg1 = true;" @@ -40,6 +41,15 @@ const char *test_source = ( "test_validator2.call(obj_a, 5);" "test_validator2.call(obj_b, 5);" "test_validator2.call(obj_a, 1);" + "var obj1 = {prop1:true, prop2:'1.5'};" + "test_validator_prop1(obj1);" + "test_validator_prop2(obj1);" + "test_validator_prop2();" + "var obj2 = {prop1:true};" + "Object.defineProperty(obj2, 'prop2', {" + " get: function() { throw new TypeError('prop2 error') }" + "});" + "test_validator_prop3(obj2);" ); static const jerry_object_native_info_t thing_a_info = @@ -68,6 +78,8 @@ static my_type_b_t my_thing_b; static int validator1_count = 0; static int validator2_count = 0; +static int validator_prop_count = 0; + /** * The handler should have following arguments: * this: Ignore. @@ -211,6 +223,129 @@ test_validator2_handler (const jerry_value_t func_obj_val __attribute__((unused) return jerry_create_undefined (); } /* test_validator2_handler */ +/** + * Calling jerryx_arg_transform_object_properties directly. + */ +static jerry_value_t +test_validator_prop1_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val __attribute__((unused)), /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ +{ + bool native1 = false; + double native2 = 0; + double native3 = 3; + + const char *name_p[] = {"prop1", "prop2", "prop3"}; + + jerryx_arg_t mapping[] = + { + jerryx_arg_boolean (&native1, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&native2, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&native3, JERRYX_ARG_COERCE, JERRYX_ARG_OPTIONAL) + }; + + jerry_value_t is_ok = jerryx_arg_transform_object_properties (args_p[0], + (const jerry_char_t **) name_p, + 3, + mapping, + 3); + + TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); + TEST_ASSERT (native1); + TEST_ASSERT (native2 == 1.5); + TEST_ASSERT (native3 == 3); + + validator_prop_count ++; + + return jerry_create_undefined (); +} /* test_validator_prop1_handler */ + +/** + * Calling jerryx_arg_transform_object_properties indirectly by + * using jerryx_arg_object_properties. + */ +static jerry_value_t +test_validator_prop2_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val __attribute__((unused)), /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ +{ + bool native1 = false; + double native2 = 0; + double native3 = 3; + + jerryx_arg_object_props_t prop_info; + + const char *name_p[] = { "prop1", "prop2", "prop3" }; + + jerryx_arg_t prop_mapping[] = + { + jerryx_arg_boolean (&native1, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&native2, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&native3, JERRYX_ARG_COERCE, JERRYX_ARG_OPTIONAL) + }; + + prop_info.name_p = (const jerry_char_t **) name_p; + prop_info.name_cnt = 3; + prop_info.c_arg_p = prop_mapping; + prop_info.c_arg_cnt = 3; + + jerryx_arg_t mapping[] = + { + jerryx_arg_object_properties (&prop_info, JERRYX_ARG_OPTIONAL), + }; + + jerry_value_t is_ok = jerryx_arg_transform_args (args_p, 1, mapping, 1); + + + TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); + + if (validator_prop_count == 1) + { + TEST_ASSERT (native1); + TEST_ASSERT (native2 == 1.5); + TEST_ASSERT (native3 == 3); + } + + validator_prop_count ++; + + return jerry_create_undefined (); +} /* test_validator_prop2_handler */ + +static jerry_value_t +test_validator_prop3_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val __attribute__((unused)), /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ +{ + bool native1 = false; + bool native2 = true; + + const char *name_p[] = { "prop1", "prop2" }; + + jerryx_arg_t mapping[] = + { + jerryx_arg_boolean (&native1, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_boolean (&native2, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + }; + + jerry_value_t is_ok = jerryx_arg_transform_object_properties (args_p[0], + (const jerry_char_t **) name_p, + 2, + mapping, + 2); + + TEST_ASSERT (jerry_value_has_error_flag (is_ok)); + TEST_ASSERT (!native1); + TEST_ASSERT (native2); + + validator_prop_count ++; + jerry_release_value (is_ok); + + return jerry_create_undefined (); +} /* test_validator_prop3_handler */ + static jerry_value_t create_object_a_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ const jerry_value_t this_val, /**< this value */ @@ -272,6 +407,9 @@ main (void) register_js_function ("test_validator2", test_validator2_handler); register_js_function ("MyObjectA", create_object_a_handler); register_js_function ("MyObjectB", create_object_b_handler); + register_js_function ("test_validator_prop1", test_validator_prop1_handler); + register_js_function ("test_validator_prop2", test_validator_prop2_handler); + register_js_function ("test_validator_prop3", test_validator_prop3_handler); jerry_value_t parsed_code_val = jerry_parse ((jerry_char_t *) test_source, strlen (test_source), false); TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val)); @@ -280,6 +418,7 @@ main (void) TEST_ASSERT (!jerry_value_has_error_flag (res)); TEST_ASSERT (validator1_count == 4); TEST_ASSERT (validator2_count == 3); + TEST_ASSERT (validator_prop_count == 4); jerry_release_value (res); jerry_release_value (parsed_code_val);