Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/ada/url_search_params-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 &param) { return param.first == key; };
Expand Down
7 changes: 7 additions & 0 deletions include/ada/url_search_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions include/ada_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions src/ada_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ada::url_search_params>& r =
*(ada::result<ada::url_search_params>*)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<ada::url_search_params>& r =
*(ada::result<ada::url_search_params>*)result;
Expand Down
43 changes: 43 additions & 0 deletions tests/ada_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Copy link

@raoxiaoyan raoxiaoyan Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anonrig Thanks for your effort. This is wonderful for supporting this new feature from us(Kong). I was wondering if it is possible to only remove b and then keep the other parts the same as before.
Before: a=b c&b=remove&d=e+f
Removed b: a=b c&d=e+f (What we expected.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following. Can you recommend a test case?

Copy link

@bungle bungle Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anonrig,

I wrote bindings to LuaJIT on this, and it is almost what we are after, here is an example code:

local search = require("resty.ada.search").parse("a=%20&b=,&c=remove&e=+&f=a b")
local normalized = search:remove("c"):tostring()
local raw = search:to_raw_string()
print("NORMALIZED: ", normalized)
print("RAW: ", raw)

This outputs:

NORMALIZED: a=+&b=%2C&e=+&f=a+b
RAW: a=%20&b=,&e=%20&f=a%20b

So the RAW seems to still do space normalization aka + and (space) is turned to %20. In fact I was expecting it to turn them to + as you see in NORMALIZED version. But it is probably best if no normalization at all happens in raw mode, that is the output would look like this:

RAW: a=%20&b=,&e=+&f=a b

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we remove percent encode calls above, we can make it more raw. Does that work for you? (just to double validate)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anonrig that would work for us! Thank you. I was thinking about exactly the same (removing the percent encoding in raw).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bungle I've updated the implementation. Please take a look. Once we're OK with the result, I'll land it and make a new release.

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();
Expand Down
46 changes: 46 additions & 0 deletions tests/url_search_params.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Loading