From ac3ac9d384fb563d0fccff14e5223469b650487c Mon Sep 17 00:00:00 2001 From: Dmitriy Musatkin <63878209+DmitriyMusatkin@users.noreply.github.com> Date: Fri, 2 Aug 2024 09:11:16 -0700 Subject: [PATCH] Add support for string array request parameter to endpoint resolution (#42) --- include/aws/sdkutils/endpoints_rule_engine.h | 20 ++++- .../sdkutils/private/endpoints_types_impl.h | 61 +++++++------ source/endpoints_rule_engine.c | 87 +++++++++++++------ source/endpoints_ruleset.c | 54 +++++++++--- source/endpoints_standard_lib.c | 3 +- source/endpoints_types_impl.c | 39 ++++++++- tests/CMakeLists.txt | 1 + tests/endpoints_rule_engine_tests.c | 39 ++++++--- tests/resources/test-cases/partition-fn.json | 2 +- tests/resources/test-cases/string_array.json | 45 ++++++++++ tests/resources/valid-rules/string_array.json | 38 ++++++++ 11 files changed, 301 insertions(+), 88 deletions(-) create mode 100644 tests/resources/test-cases/string_array.json create mode 100644 tests/resources/valid-rules/string_array.json diff --git a/include/aws/sdkutils/endpoints_rule_engine.h b/include/aws/sdkutils/endpoints_rule_engine.h index 9a3997f..bfd60b8 100644 --- a/include/aws/sdkutils/endpoints_rule_engine.h +++ b/include/aws/sdkutils/endpoints_rule_engine.h @@ -19,7 +19,11 @@ struct aws_endpoints_resolved_endpoint; struct aws_endpoints_request_context; struct aws_hash_table; -enum aws_endpoints_parameter_type { AWS_ENDPOINTS_PARAMETER_STRING, AWS_ENDPOINTS_PARAMETER_BOOLEAN }; +enum aws_endpoints_parameter_type { + AWS_ENDPOINTS_PARAMETER_STRING, + AWS_ENDPOINTS_PARAMETER_BOOLEAN, + AWS_ENDPOINTS_PARAMETER_STRING_ARRAY +}; enum aws_endpoints_resolved_endpoint_type { AWS_ENDPOINTS_RESOLVED_ENDPOINT, AWS_ENDPOINTS_RESOLVED_ERROR }; AWS_EXTERN_C_BEGIN @@ -221,7 +225,6 @@ AWS_SDKUTILS_API int aws_endpoints_request_context_add_string( /* * Add boolean value to request context. - * Note: this function will make a copy of the memory backing the cursors. * The function will override any previous value stored in the context with the * same name. */ @@ -231,6 +234,19 @@ AWS_SDKUTILS_API int aws_endpoints_request_context_add_boolean( struct aws_byte_cursor name, bool value); +/* + * Add string array value to request context. + * Note: this function will make a copy of the memory backing the cursors. + * The function will override any previous value stored in the context with the + * same name. + */ +AWS_SDKUTILS_API int aws_endpoints_request_context_add_string_array( + struct aws_allocator *allocator, + struct aws_endpoints_request_context *context, + struct aws_byte_cursor name, + struct aws_byte_cursor *values, + size_t len); + /* * Resolve an endpoint given request context. * Resolved endpoint is returned through out_resolved_endpoint. diff --git a/include/aws/sdkutils/private/endpoints_types_impl.h b/include/aws/sdkutils/private/endpoints_types_impl.h index 2c0f19e..1b402bf 100644 --- a/include/aws/sdkutils/private/endpoints_types_impl.h +++ b/include/aws/sdkutils/private/endpoints_types_impl.h @@ -71,6 +71,35 @@ enum aws_endpoints_fn_type { AWS_ENDPOINTS_FN_LAST, }; +enum aws_endpoints_value_type { + /* Special value to represent that any value type is expected from resolving an expresion. + Note a valid value for a value type. */ + AWS_ENDPOINTS_VALUE_ANY, + + AWS_ENDPOINTS_VALUE_NONE, + AWS_ENDPOINTS_VALUE_STRING, + AWS_ENDPOINTS_VALUE_BOOLEAN, + AWS_ENDPOINTS_VALUE_OBJECT, /* Generic type returned by some functions. json string under the covers. */ + AWS_ENDPOINTS_VALUE_NUMBER, + AWS_ENDPOINTS_VALUE_ARRAY, + + AWS_ENDPOINTS_VALUE_SIZE +}; + +/* concrete type value */ +struct aws_endpoints_value { + enum aws_endpoints_value_type type; + union { + struct aws_owning_cursor owning_cursor_string; + bool boolean; + struct aws_owning_cursor owning_cursor_object; + double number; + struct aws_array_list array; + } v; + /* Value is a reference to another value, no need to clean it up. */ + bool is_ref; +}; + struct aws_endpoints_parameter { struct aws_allocator *allocator; @@ -80,10 +109,7 @@ struct aws_endpoints_parameter { struct aws_byte_cursor built_in; bool has_default_value; - union { - struct aws_byte_cursor string; - bool boolean; - } default_value; + struct aws_endpoints_value default_value; bool is_required; struct aws_byte_cursor documentation; @@ -210,21 +236,6 @@ struct aws_partitions_config { ****************************** */ -enum aws_endpoints_value_type { - /* Special value to represent that any value type is expected from resolving an expresion. - Note a valid value for a value type. */ - AWS_ENDPOINTS_VALUE_ANY, - - AWS_ENDPOINTS_VALUE_NONE, - AWS_ENDPOINTS_VALUE_STRING, - AWS_ENDPOINTS_VALUE_BOOLEAN, - AWS_ENDPOINTS_VALUE_OBJECT, /* Generic type returned by some functions. json string under the covers. */ - AWS_ENDPOINTS_VALUE_NUMBER, - AWS_ENDPOINTS_VALUE_ARRAY, - - AWS_ENDPOINTS_VALUE_SIZE -}; - struct aws_endpoints_request_context { struct aws_allocator *allocator; struct aws_ref_count ref_count; @@ -232,18 +243,6 @@ struct aws_endpoints_request_context { struct aws_hash_table values; }; -/* concrete type value */ -struct aws_endpoints_value { - enum aws_endpoints_value_type type; - union { - struct aws_owning_cursor owning_cursor_string; - bool boolean; - struct aws_owning_cursor owning_cursor_object; - double number; - struct aws_array_list array; - } v; -}; - /* wrapper around aws_endpoints_value to store it more easily in hash table*/ struct aws_endpoints_scope_value { struct aws_allocator *allocator; diff --git a/source/endpoints_rule_engine.c b/source/endpoints_rule_engine.c index 556450b..bdb40a3 100644 --- a/source/endpoints_rule_engine.c +++ b/source/endpoints_rule_engine.c @@ -186,13 +186,11 @@ static int s_init_top_level_scope( switch (value->type) { case AWS_ENDPOINTS_PARAMETER_STRING: - val->value.type = AWS_ENDPOINTS_VALUE_STRING; - val->value.v.owning_cursor_string = - aws_endpoints_non_owning_cursor_create(value->default_value.string); - break; case AWS_ENDPOINTS_PARAMETER_BOOLEAN: - val->value.type = AWS_ENDPOINTS_VALUE_BOOLEAN; - val->value.v.boolean = value->default_value.boolean; + case AWS_ENDPOINTS_PARAMETER_STRING_ARRAY: + val->value = value->default_value; + val->value.is_ref = true; + break; break; default: AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Unexpected parameter type."); @@ -310,8 +308,21 @@ static int s_resolve_expr( } case AWS_ENDPOINTS_EXPR_ARRAY: { out_value->type = AWS_ENDPOINTS_VALUE_ARRAY; - /* TODO: deep copy */ - out_value->v.array = expr->e.array; + { + size_t len = aws_array_list_length(&expr->e.array); + aws_array_list_init_dynamic(&out_value->v.array, allocator, len, sizeof(struct aws_endpoints_value)); + for (size_t i = 0; i < len; ++i) { + struct aws_endpoints_expr expr_elem; + aws_array_list_get_at(&expr->e.array, &expr_elem, i); + struct aws_endpoints_value val; + if (s_resolve_expr(allocator, &expr_elem, scope, &val)) { + AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Failed to resolve array element."); + aws_endpoints_value_clean_up(out_value); + goto on_error; + } + aws_array_list_set_at(&out_value->v.array, &val, i); + } + } break; } case AWS_ENDPOINTS_EXPR_REFERENCE: { @@ -325,14 +336,9 @@ static int s_resolve_expr( out_value->type = AWS_ENDPOINTS_VALUE_NONE; } else { struct aws_endpoints_scope_value *aws_endpoints_scope_value = element->value; + *out_value = aws_endpoints_scope_value->value; - if (aws_endpoints_scope_value->value.type == AWS_ENDPOINTS_VALUE_STRING) { - /* Value will not own underlying mem and instead its owned - by the scope, so set it to NULL. */ - out_value->v.owning_cursor_string.string = NULL; - } else if (aws_endpoints_scope_value->value.type == AWS_ENDPOINTS_VALUE_OBJECT) { - out_value->v.owning_cursor_object.string = NULL; - } + out_value->is_ref = true; } break; } @@ -450,6 +456,8 @@ int aws_endpoints_path_through_array( struct aws_endpoints_value *value, struct aws_byte_cursor path_cur, struct aws_endpoints_value *out_value) { + (void)allocator; + (void)scope; AWS_PRECONDITION(value->type == AWS_ENDPOINTS_VALUE_ARRAY); @@ -461,25 +469,20 @@ int aws_endpoints_path_through_array( goto on_error; } - if (index < aws_array_list_length(&value->v.array)) { + if (index >= aws_array_list_length(&value->v.array)) { out_value->type = AWS_ENDPOINTS_VALUE_NONE; return AWS_OP_SUCCESS; } - struct aws_endpoints_expr *expr = NULL; - if (aws_array_list_get_at_ptr(&value->v.array, (void **)&expr, (size_t)index)) { + struct aws_endpoints_value *val = NULL; + if (aws_array_list_get_at_ptr(&value->v.array, (void **)&val, (size_t)index)) { AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Failed to index into resolved value"); goto on_error; } - struct aws_endpoints_value val; - if (s_resolve_expr(allocator, expr, scope, &val)) { - AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Failed to resolve val."); - aws_endpoints_value_clean_up(&val); - goto on_error; - } + *out_value = *val; + out_value->is_ref = true; - *out_value = val; return AWS_OP_SUCCESS; on_error: @@ -597,7 +600,10 @@ static int s_resolve_templated_value_with_pathing( goto on_error; } } else { - AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Invalid value type for pathing through."); + AWS_LOGF_ERROR( + AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, + "Invalid value type for pathing through. type %d", + scope_value->value.type); goto on_error; } @@ -737,6 +743,34 @@ int aws_endpoints_request_context_add_boolean( return AWS_OP_SUCCESS; } +int aws_endpoints_request_context_add_string_array( + struct aws_allocator *allocator, + struct aws_endpoints_request_context *context, + struct aws_byte_cursor name, + struct aws_byte_cursor *values, + size_t len) { + + struct aws_endpoints_scope_value *val = aws_endpoints_scope_value_new(allocator, name); + val->value.type = AWS_ENDPOINTS_VALUE_ARRAY; + aws_array_list_init_dynamic(&val->value.v.array, allocator, len, sizeof(struct aws_endpoints_value)); + + for (size_t i = 0; i < len; ++i) { + struct aws_endpoints_value elem = { + .is_ref = false, + .type = AWS_ENDPOINTS_VALUE_STRING, + .v.owning_cursor_object = aws_endpoints_owning_cursor_from_cursor(allocator, values[i])}; + + aws_array_list_set_at(&val->value.v.array, &elem, i); + } + + if (aws_hash_table_put(&context->values, &val->name.cur, val, NULL)) { + aws_endpoints_scope_value_destroy(val); + return aws_raise_error(AWS_ERROR_SDKUTILS_ENDPOINTS_RESOLVE_INIT_FAILED); + }; + + return AWS_OP_SUCCESS; +} + /* ****************************** * Rule engine. @@ -1126,7 +1160,6 @@ int aws_endpoints_rule_engine_resolve( result = aws_raise_error(AWS_ERROR_SDKUTILS_ENDPOINTS_RULESET_EXHAUSTED); on_done: - AWS_LOGF_DEBUG(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Resolved endpoint with status %d", result); s_scope_clean_up(&scope); return result; } diff --git a/source/endpoints_ruleset.c b/source/endpoints_ruleset.c index 99f31a5..8947854 100644 --- a/source/endpoints_ruleset.c +++ b/source/endpoints_ruleset.c @@ -14,6 +14,7 @@ /* parameter types */ static struct aws_byte_cursor s_string_type_cur = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("string"); static struct aws_byte_cursor s_boolean_type_cur = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("boolean"); +static struct aws_byte_cursor s_string_array_type_cur = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("stringArray"); /* rule types */ static struct aws_byte_cursor s_endpoint_type_cur = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("endpoint"); @@ -52,7 +53,7 @@ int aws_endpoints_parameter_get_default_string( AWS_PRECONDITION(out_cursor); if (parameter->type == AWS_ENDPOINTS_PARAMETER_STRING) { - *out_cursor = parameter->default_value.string; + *out_cursor = parameter->default_value.v.owning_cursor_string.cur; return AWS_OP_SUCCESS; }; @@ -67,7 +68,7 @@ int aws_endpoints_parameter_get_default_boolean( AWS_PRECONDITION(out_bool); if (parameter->type == AWS_ENDPOINTS_PARAMETER_BOOLEAN) { - *out_bool = ¶meter->default_value.boolean; + *out_bool = ¶meter->default_value.v.boolean; return AWS_OP_SUCCESS; }; @@ -405,6 +406,8 @@ static int s_on_parameter_key( type = AWS_ENDPOINTS_PARAMETER_STRING; } else if (aws_byte_cursor_eq_ignore_case(&type_cur, &s_boolean_type_cur)) { type = AWS_ENDPOINTS_PARAMETER_BOOLEAN; + } else if (aws_byte_cursor_eq_ignore_case(&type_cur, &s_string_array_type_cur)) { + type = AWS_ENDPOINTS_PARAMETER_STRING_ARRAY; } else { AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_PARSING, "Unexpected type for parameter."); goto on_error; @@ -446,13 +449,40 @@ static int s_on_parameter_key( struct aws_json_value *default_node = aws_json_value_get_from_object(value, aws_byte_cursor_from_c_str("default")); parameter->has_default_value = default_node != NULL; if (default_node != NULL) { - if (type == AWS_ENDPOINTS_PARAMETER_STRING && - aws_json_value_get_string(default_node, ¶meter->default_value.string)) { - AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_PARSING, "Unexpected type for default parameter value."); - goto on_error; - } else if ( - type == AWS_ENDPOINTS_PARAMETER_BOOLEAN && - aws_json_value_get_boolean(default_node, ¶meter->default_value.boolean)) { + if (type == AWS_ENDPOINTS_PARAMETER_STRING && aws_json_value_is_string(default_node)) { + struct aws_byte_cursor cur; + aws_json_value_get_string(default_node, &cur); + parameter->default_value.type = AWS_ENDPOINTS_VALUE_STRING; + parameter->default_value.v.owning_cursor_string = aws_endpoints_non_owning_cursor_create(cur); + } else if (type == AWS_ENDPOINTS_PARAMETER_BOOLEAN && aws_json_value_is_boolean(default_node)) { + parameter->default_value.type = AWS_ENDPOINTS_VALUE_BOOLEAN; + aws_json_value_get_boolean(default_node, ¶meter->default_value.v.boolean); + } else if (type == AWS_ENDPOINTS_PARAMETER_STRING_ARRAY && aws_json_value_is_array(default_node)) { + parameter->default_value.type = AWS_ENDPOINTS_VALUE_ARRAY; + size_t len = aws_json_get_array_size(default_node); + aws_array_list_init_dynamic( + ¶meter->default_value.v.array, wrapper->allocator, len, sizeof(struct aws_endpoints_value)); + for (size_t i = 0; i < len; ++i) { + struct aws_json_value *element = aws_json_get_array_element(default_node, i); + if (!aws_json_value_is_string(element)) { + AWS_LOGF_ERROR( + AWS_LS_SDKUTILS_ENDPOINTS_PARSING, + "Unexpected type for default parameter value. String array parameter must have string " + "elements"); + goto on_error; + } + + struct aws_byte_cursor cur; + aws_json_value_get_string(element, &cur); + + struct aws_endpoints_value val = { + .is_ref = false, + .type = AWS_ENDPOINTS_VALUE_STRING, + .v.owning_cursor_string = aws_endpoints_non_owning_cursor_create(cur)}; + + aws_array_list_set_at(¶meter->default_value.v.array, &val, i); + } + } else { AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_PARSING, "Unexpected type for default parameter value."); goto on_error; } @@ -643,7 +673,7 @@ static int s_parse_endpoints_rule_data_endpoint( if (headers_node != NULL) { if (s_init_members_from_json(allocator, headers_node, &data_rule->headers, s_on_headers_key)) { - AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_PARSING, "Failed to extract parameters."); + AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_PARSING, "Failed to extract headers."); goto on_error; } } @@ -914,12 +944,12 @@ static void s_endpoints_ruleset_destroy(void *data) { struct aws_endpoints_ruleset *ruleset = data; - aws_json_value_destroy(ruleset->json_root); - aws_hash_table_clean_up(&ruleset->parameters); aws_array_list_deep_clean_up(&ruleset->rules, s_on_rule_array_element_clean_up); + aws_json_value_destroy(ruleset->json_root); + aws_mem_release(ruleset->allocator, ruleset); } diff --git a/source/endpoints_standard_lib.c b/source/endpoints_standard_lib.c index b880f01..978bec8 100644 --- a/source/endpoints_standard_lib.c +++ b/source/endpoints_standard_lib.c @@ -94,7 +94,8 @@ static int s_resolve_fn_get_attr( goto on_done; } } else { - AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Invalid value type for pathing through."); + AWS_LOGF_ERROR( + AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Invalid value type for pathing through. type: %d", argv_value.type); result = aws_raise_error(AWS_ERROR_SDKUTILS_ENDPOINTS_RESOLVE_FAILED); goto on_done; } diff --git a/source/endpoints_types_impl.c b/source/endpoints_types_impl.c index f06c43d..6e0a387 100644 --- a/source/endpoints_types_impl.c +++ b/source/endpoints_types_impl.c @@ -11,6 +11,8 @@ #include #include +void s_endpoints_value_clean_up_cb(void *value); + uint64_t aws_endpoints_fn_name_hash[AWS_ENDPOINTS_FN_LAST]; void aws_endpoints_rule_engine_init(void) { @@ -85,6 +87,10 @@ void aws_endpoints_parameter_destroy(struct aws_endpoints_parameter *parameter) return; } + if (parameter->has_default_value && parameter->type == AWS_ENDPOINTS_PARAMETER_STRING_ARRAY) { + aws_array_list_deep_clean_up(¶meter->default_value.v.array, s_endpoints_value_clean_up_cb); + } + aws_mem_release(parameter->allocator, parameter); } @@ -189,6 +195,7 @@ void aws_endpoints_scope_value_destroy(struct aws_endpoints_scope_value *scope_v return; } aws_string_destroy(scope_value->name.string); + aws_endpoints_value_clean_up(&scope_value->value); aws_mem_release(scope_value->allocator, scope_value); } @@ -198,6 +205,10 @@ void aws_endpoints_value_clean_up_cb(void *value); void aws_endpoints_value_clean_up(struct aws_endpoints_value *aws_endpoints_value) { AWS_PRECONDITION(aws_endpoints_value); + if (aws_endpoints_value->is_ref) { + goto on_done; + } + if (aws_endpoints_value->type == AWS_ENDPOINTS_VALUE_STRING) { aws_string_destroy(aws_endpoints_value->v.owning_cursor_string.string); } @@ -207,13 +218,14 @@ void aws_endpoints_value_clean_up(struct aws_endpoints_value *aws_endpoints_valu } if (aws_endpoints_value->type == AWS_ENDPOINTS_VALUE_ARRAY) { - aws_array_list_deep_clean_up(&aws_endpoints_value->v.array, aws_endpoints_value_clean_up_cb); + aws_array_list_deep_clean_up(&aws_endpoints_value->v.array, s_endpoints_value_clean_up_cb); } +on_done: AWS_ZERO_STRUCT(*aws_endpoints_value); } -void aws_endpoints_value_clean_up_cb(void *value) { +void s_endpoints_value_clean_up_cb(void *value) { struct aws_endpoints_value *aws_endpoints_value = value; aws_endpoints_value_clean_up(aws_endpoints_value); } @@ -224,15 +236,36 @@ int aws_endpoints_deep_copy_parameter_value( struct aws_endpoints_value *to) { to->type = from->type; + to->is_ref = false; if (to->type == AWS_ENDPOINTS_VALUE_STRING) { - to->v.owning_cursor_string = aws_endpoints_owning_cursor_create(allocator, from->v.owning_cursor_string.string); + to->v.owning_cursor_string = + aws_endpoints_owning_cursor_from_cursor(allocator, from->v.owning_cursor_string.cur); } else if (to->type == AWS_ENDPOINTS_VALUE_BOOLEAN) { to->v.boolean = from->v.boolean; + } else if (to->type == AWS_ENDPOINTS_VALUE_ARRAY) { + size_t len = aws_array_list_length(&from->v.array); + aws_array_list_init_dynamic(&to->v.array, allocator, len, sizeof(struct aws_endpoints_value)); + for (size_t i = 0; i < len; ++i) { + struct aws_endpoints_value val; + aws_array_list_get_at(&from->v.array, &val, i); + + struct aws_endpoints_value to_val; + if (aws_endpoints_deep_copy_parameter_value(allocator, &val, &to_val)) { + AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Unexpected array element type."); + goto on_error; + } + + aws_array_list_set_at(&to->v.array, &to_val, i); + } } else { AWS_LOGF_ERROR(AWS_LS_SDKUTILS_ENDPOINTS_RESOLVE, "Unexpected value type."); return aws_raise_error(AWS_ERROR_INVALID_STATE); } return AWS_OP_SUCCESS; + +on_error: + aws_endpoints_value_clean_up(to); + return AWS_OP_ERR; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 27a3d21..b7aba08 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -87,6 +87,7 @@ add_test_case(test_endpoints_uri_encode) add_test_case(test_endpoints_valid_hostlabel) add_test_case(test_endpoints_condition_mem_clean_up) add_test_case(test_endpoints_custom) +add_test_case(test_endpoints_string_array) add_test_case(endpoints_uri_normalize_path) add_test_case(endpoints_byte_buf_init_from_resolved_templated_string) diff --git a/tests/endpoints_rule_engine_tests.c b/tests/endpoints_rule_engine_tests.c index e62f657..8457641 100644 --- a/tests/endpoints_rule_engine_tests.c +++ b/tests/endpoints_rule_engine_tests.c @@ -114,6 +114,11 @@ struct iteration_wrapper { struct aws_endpoints_request_context *context; }; +enum { + /* something is really wrong if there is more than 4 elems during the test */ + MAX_STRING_ARRAY_ELEMENTS = 4 +}; + static int s_on_parameter_key( const struct aws_byte_cursor *key, const struct aws_json_value *value, @@ -125,23 +130,27 @@ static int s_on_parameter_key( if (aws_json_value_is_string(value)) { struct aws_byte_cursor cur; - if (aws_json_value_get_string(value, &cur) || - aws_endpoints_request_context_add_string(wrapper->allocator, wrapper->context, *key, cur)) { - goto on_error; - } + ASSERT_SUCCESS(aws_json_value_get_string(value, &cur)); + ASSERT_SUCCESS(aws_endpoints_request_context_add_string(wrapper->allocator, wrapper->context, *key, cur)); return AWS_OP_SUCCESS; } else if (aws_json_value_is_boolean(value)) { bool b; - if (aws_json_value_get_boolean(value, &b) || - aws_endpoints_request_context_add_boolean(wrapper->allocator, wrapper->context, *key, b)) { - goto on_error; + ASSERT_SUCCESS(aws_json_value_get_boolean(value, &b)); + ASSERT_SUCCESS(aws_endpoints_request_context_add_boolean(wrapper->allocator, wrapper->context, *key, b)); + return AWS_OP_SUCCESS; + } else if (aws_json_value_is_array(value)) { + struct aws_byte_cursor strings[MAX_STRING_ARRAY_ELEMENTS]; + size_t len = aws_json_get_array_size(value); + ASSERT_TRUE(len <= MAX_STRING_ARRAY_ELEMENTS); + for (size_t i = 0; i < len; ++i) { + struct aws_json_value *str = aws_json_get_array_element(value, i); + ASSERT_SUCCESS(aws_json_value_get_string(str, &strings[i])); } + ASSERT_SUCCESS( + aws_endpoints_request_context_add_string_array(wrapper->allocator, wrapper->context, *key, strings, len)); return AWS_OP_SUCCESS; - } else { - goto on_error; } -on_error: return AWS_OP_ERR; } @@ -274,7 +283,6 @@ static int eval_expected(struct aws_allocator *allocator, struct aws_byte_cursor aws_json_value_get_from_object(endpoint, aws_byte_cursor_from_c_str("url")); struct aws_byte_cursor expected_url; aws_json_value_get_string(expected_url_node, &expected_url); - AWS_LOGF_DEBUG(0, PRInSTR " " PRInSTR, AWS_BYTE_CURSOR_PRI(url), AWS_BYTE_CURSOR_PRI(expected_url)); ASSERT_TRUE(aws_byte_cursor_eq(&url, &expected_url)); struct aws_byte_cursor properties; @@ -485,3 +493,12 @@ static int s_test_endpoints_custom(struct aws_allocator *allocator, void *ctx) { return AWS_OP_SUCCESS; } + +AWS_TEST_CASE(test_endpoints_string_array, s_test_endpoints_string_array) +static int s_test_endpoints_string_array(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(eval_expected(allocator, aws_byte_cursor_from_c_str("string_array.json"))); + + return AWS_OP_SUCCESS; +} diff --git a/tests/resources/test-cases/partition-fn.json b/tests/resources/test-cases/partition-fn.json index 967a4e7..f296672 100644 --- a/tests/resources/test-cases/partition-fn.json +++ b/tests/resources/test-cases/partition-fn.json @@ -122,4 +122,4 @@ } } ] - } \ No newline at end of file + } diff --git a/tests/resources/test-cases/string_array.json b/tests/resources/test-cases/string_array.json new file mode 100644 index 0000000..c9305cb --- /dev/null +++ b/tests/resources/test-cases/string_array.json @@ -0,0 +1,45 @@ +{ + "version": "1.0", + "testCases": [ + { + "documentation": "Default array values used", + "params": {}, + "expect": { + "endpoint": { + "url": "https://example.com/defaultValue1" + } + } + }, + { + "documentation": "Empty array", + "params": { + "stringArrayParam": [] + }, + "expect": { + "error": "no array values set" + } + }, + { + "documentation": "Static value", + "params": { + "stringArrayParam": ["staticValue1"] + }, + "expect": { + "endpoint": { + "url": "https://example.com/staticValue1" + } + } + }, + { + "documentation": "bound value from input", + "params": { + "stringArrayParam": ["key1", "key2", "key3", "key4"] + }, + "expect": { + "endpoint": { + "url": "https://example.com/key1" + } + } + } + ] +} diff --git a/tests/resources/valid-rules/string_array.json b/tests/resources/valid-rules/string_array.json new file mode 100644 index 0000000..84bf99c --- /dev/null +++ b/tests/resources/valid-rules/string_array.json @@ -0,0 +1,38 @@ +{ + "version": "1.0", + "parameters": { + "stringArrayParam": { + "type": "stringArray", + "required": true, + "default": ["defaultValue1", "defaultValue2"], + "documentation": "docs" + } + }, + "rules": [ + { + "documentation": "Template first array value into URI if set", + "conditions": [ + { + "fn": "getAttr", + "argv": [ + { + "ref": "stringArrayParam" + }, + "[0]" + ], + "assign": "arrayValue" + } + ], + "endpoint": { + "url": "https://example.com/{arrayValue}" + }, + "type": "endpoint" + }, + { + "conditions": [], + "documentation": "error fallthrough", + "error": "no array values set", + "type": "error" + } + ] +}