diff --git a/CHANGELOG.md b/CHANGELOG.md index c42f729d..2fd39743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v0.66.0 - 2025-10-21 - The `tap` function from the `function` module has been deprecated. +- `uri.query_to_string` now correctly handles `+` in query params. ## v0.65.0 - 2025-09-29 diff --git a/src/gleam/uri.gleam b/src/gleam/uri.gleam index 9413b990..6b8f5f08 100644 --- a/src/gleam/uri.gleam +++ b/src/gleam/uri.gleam @@ -556,7 +556,13 @@ pub fn query_to_string(query: List(#(String, String))) -> String { } fn query_pair(pair: #(String, String)) -> StringTree { - string_tree.from_strings([percent_encode(pair.0), "=", percent_encode(pair.1)]) + [percent_encode_query(pair.0), "=", percent_encode_query(pair.1)] + |> string_tree.from_strings +} + +fn percent_encode_query(part: String) -> String { + percent_encode(part) + |> string.replace(each: "+", with: "%2B") } /// Encodes a string into a percent encoded representation. diff --git a/test/gleam/uri_test.gleam b/test/gleam/uri_test.gleam index 5dfb02d4..602cd3ff 100644 --- a/test/gleam/uri_test.gleam +++ b/test/gleam/uri_test.gleam @@ -413,6 +413,12 @@ pub fn query_to_string_test() { assert query_string == "weebl%20bob=1&city=%C3%B6rebro" } +pub fn query_to_string_special_characters_test() { + let query_string = + uri.query_to_string([#("weebl bob", "1+1-1*1.1~1!1'1(1);%")]) + assert query_string == "weebl%20bob=1%2B1-1*1.1~1!1'1(1)%3B%25" +} + pub fn empty_query_to_string_test() { let query_string = uri.query_to_string([]) assert query_string == "" @@ -557,6 +563,13 @@ pub fn parse_segments_test() { assert uri.path_segments("/weebl/../bob") == ["bob"] } +pub fn query_to_string_parse_query_opposite_unreserved_marks_test() { + let queries = [#("weebl bob", "1+1-1*1.1~1!1'1(1);%"), #("city", "örebro")] + let query_string = uri.query_to_string(queries) + let parsed = uri.parse_query(query_string) + assert parsed == Ok(queries) +} + pub fn origin1_test() { let assert Ok(parsed) = uri.parse("http://example.test/path?weebl#bob") assert uri.origin(parsed) == Ok("http://example.test")