From 83effe352f810ac79edad189771a466b05b73156 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 24 Nov 2025 16:58:51 -0500 Subject: [PATCH] add URLSearchParams::to_raw_string() method --- include/ada/url_search_params-inl.h | 17 +++++++++++ include/ada/url_search_params.h | 7 +++++ include/ada_c.h | 1 + src/ada_c.cpp | 12 ++++++++ tests/ada_c.cpp | 43 +++++++++++++++++++++++++++ tests/url_search_params.cpp | 46 +++++++++++++++++++++++++++++ 6 files changed, 126 insertions(+) diff --git a/include/ada/url_search_params-inl.h b/include/ada/url_search_params-inl.h index 5787a0598..67f043a46 100644 --- a/include/ada/url_search_params-inl.h +++ b/include/ada/url_search_params-inl.h @@ -134,6 +134,23 @@ inline std::string url_search_params::to_string() const { return out; } +inline std::string url_search_params::to_raw_string() const { + auto character_set = ada::character_sets::QUERY_PERCENT_ENCODE; + std::string out{}; + for (size_t i = 0; i < params.size(); i++) { + auto key = ada::unicode::percent_encode(params[i].first, character_set); + auto value = ada::unicode::percent_encode(params[i].second, character_set); + + if (i != 0) { + out += "&"; + } + out.append(key); + out += "="; + out.append(value); + } + return out; +} + inline void url_search_params::set(const std::string_view key, const std::string_view value) { const auto find = [&key](const auto ¶m) { return param.first == key; }; diff --git a/include/ada/url_search_params.h b/include/ada/url_search_params.h index fb8ead258..57318c694 100644 --- a/include/ada/url_search_params.h +++ b/include/ada/url_search_params.h @@ -100,6 +100,13 @@ struct url_search_params { */ inline std::string to_string() const; + /** + * Returns a serialized query string without normalizing the key-value pairs. + * Unlike to_string(), this method does not apply additional transformations + * to the percent-encoded output. + */ + inline std::string to_raw_string() const; + /** * Returns a simple JS-style iterator over all of the keys in this * url_search_params. The keys in the iterator are not unique. The valid diff --git a/include/ada_c.h b/include/ada_c.h index 5e124984c..6ac31387e 100644 --- a/include/ada_c.h +++ b/include/ada_c.h @@ -130,6 +130,7 @@ void ada_free_search_params(ada_url_search_params result); size_t ada_search_params_size(ada_url_search_params result); void ada_search_params_sort(ada_url_search_params result); ada_owned_string ada_search_params_to_string(ada_url_search_params result); +ada_owned_string ada_search_params_to_raw_string(ada_url_search_params result); void ada_search_params_append(ada_url_search_params result, const char* key, size_t key_length, const char* value, diff --git a/src/ada_c.cpp b/src/ada_c.cpp index 48a344604..476ca9c02 100644 --- a/src/ada_c.cpp +++ b/src/ada_c.cpp @@ -487,6 +487,18 @@ ada_owned_string ada_search_params_to_string(ada_url_search_params result) { return owned; } +ada_owned_string ada_search_params_to_raw_string(ada_url_search_params result) { + ada::result& r = + *(ada::result*)result; + if (!r) return ada_owned_string{nullptr, 0}; + std::string out = r->to_raw_string(); + ada_owned_string owned{}; + owned.length = out.size(); + owned.data = new char[owned.length]; + memcpy((void*)owned.data, out.data(), owned.length); + return owned; +} + size_t ada_search_params_size(ada_url_search_params result) { ada::result& r = *(ada::result*)result; diff --git a/tests/ada_c.cpp b/tests/ada_c.cpp index 692e704de..e32dffc5a 100644 --- a/tests/ada_c.cpp +++ b/tests/ada_c.cpp @@ -357,6 +357,49 @@ TEST(ada_c, ada_url_search_params) { SUCCEED(); } +TEST(ada_c, ada_search_params_to_raw_string) { + std::string input("a=b c&d=e+f"); + auto out = ada_parse_search_params(input.c_str(), input.length()); + + // Note: + in input is decoded to space during parsing + // to_string normalizes spaces to + + ada_owned_string str = ada_search_params_to_string(out); + ASSERT_EQ(convert_string(str), "a=b+c&d=e+f"); + ada_free_owned_string(str); + + // to_raw_string preserves %20 encoding for spaces + ada_owned_string raw_str = ada_search_params_to_raw_string(out); + ASSERT_EQ(convert_string(raw_str), "a=b%20c&d=e%20f"); + ada_free_owned_string(raw_str); + + ada_free_search_params(out); + + SUCCEED(); +} + +TEST(ada_c, ada_search_params_to_raw_string_remove_preserves_encoding) { + // Test the exact scenario from the issue + std::string input("a=%20&b=remove&c=2"); + auto params = ada_parse_search_params(input.c_str(), input.length()); + + // Remove parameter "b" + ada_search_params_remove(params, "b", 1); + + // to_string normalizes space to + + ada_owned_string str = ada_search_params_to_string(params); + ASSERT_EQ(convert_string(str), "a=+&c=2"); + ada_free_owned_string(str); + + // to_raw_string preserves %20 encoding for spaces + ada_owned_string raw_str = ada_search_params_to_raw_string(params); + ASSERT_EQ(convert_string(raw_str), "a=%20&c=2"); + ada_free_owned_string(raw_str); + + ada_free_search_params(params); + + SUCCEED(); +} + TEST(ada_c, ada_get_version) { std::string_view raw = ada_get_version(); ada_version_components parsed = ada_get_version_components(); diff --git a/tests/url_search_params.cpp b/tests/url_search_params.cpp index da3017e7d..3cec626c0 100644 --- a/tests/url_search_params.cpp +++ b/tests/url_search_params.cpp @@ -448,3 +448,49 @@ TEST(url_search_params, sort_unicode_code_units_edge_case) { ASSERT_EQ(keys.next(), "\xf0\x9f\x8c\x88\xef\xac\x83"); SUCCEED(); } + +TEST(url_search_params, to_raw_string_no_normalization) { + auto params = ada::url_search_params(); + params.append("a", "b c"); + // to_string normalizes space to + + ASSERT_EQ(params.to_string(), "a=b+c"); + // to_raw_string preserves %20 encoding + ASSERT_EQ(params.to_raw_string(), "a=b%20c"); + SUCCEED(); +} + +TEST(url_search_params, to_raw_string_with_special_chars) { + auto params = ada::url_search_params(); + params.append("key1", "value with spaces"); + params.append("key2", "another value"); + // to_string normalizes spaces to + + ASSERT_EQ(params.to_string(), "key1=value+with+spaces&key2=another+value"); + // to_raw_string preserves %20 encoding + ASSERT_EQ(params.to_raw_string(), + "key1=value%20with%20spaces&key2=another%20value"); + SUCCEED(); +} + +TEST(url_search_params, to_raw_string_with_accents) { + auto params = ada::url_search_params(); + params.append("key1", "\u00E9t\u00E9"); + params.append("key2", "C\u00E9line Dion++"); + // Both should encode accents the same way + // to_string normalizes spaces to +, to_raw_string uses %20 + // Note: + signs are not encoded by QUERY_PERCENT_ENCODE + ASSERT_EQ(params.to_string(), + "key1=%C3%A9t%C3%A9&key2=C%C3%A9line+Dion%2B%2B"); + ASSERT_EQ(params.to_raw_string(), + "key1=%C3%A9t%C3%A9&key2=C%C3%A9line%20Dion++"); + SUCCEED(); +} + +TEST(url_search_params, to_raw_string_empty_values) { + auto params = ada::url_search_params(); + params.append("a", ""); + params.append("", "b"); + params.append("", ""); + ASSERT_EQ(params.to_raw_string(), "a=&=b&="); + ASSERT_EQ(params.to_string(), "a=&=b&="); + SUCCEED(); +}