Skip to content

Commit 33fd0fb

Browse files
authored
Add percent_encode and percent_decode (#121)
1 parent 8e42152 commit 33fd0fb

File tree

3 files changed

+104
-1
lines changed

3 files changed

+104
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
- The `function` module gains `curry2` to `curry6`.
66
- The `list` module gains the `each`, and `partition` functions.
77
- The `int` and `float` modules gain the `negate` function.
8+
- The `int` module gains the `to_float` function.
89
- The `result` module gains the `all` function.
910
- The `dynamic` module gains the `option` function.
10-
- The `int` module gains the `to_float` function.
11+
- The `uri` module gains the `percent_encode` and `percent_decode` functions.
1112

1213
## v0.11.0 - 2020-08-22
1314

src/gleam/uri.gleam

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import gleam/string
1414
import gleam/dynamic.{Dynamic}
1515
import gleam/map.{Map}
1616
import gleam/function
17+
import gleam/pair
1718

1819
/// Type representing holding the parsed components of an URI.
1920
/// All components of a URI are optional, except the path.
@@ -119,6 +120,33 @@ pub fn query_to_string(query: List(tuple(String, String))) -> String {
119120
|> result.unwrap("")
120121
}
121122

123+
/// Encode a string into a percent encoded representation.
124+
/// Note that this encodes space as +.
125+
///
126+
/// ## Example
127+
///
128+
/// percent_encode("100% great")
129+
/// > "100%25+great"
130+
///
131+
pub fn percent_encode(value: String) -> String {
132+
query_to_string([tuple("k", value)])
133+
|> string.replace(each: "k=", with: "")
134+
}
135+
136+
/// Decode a percent encoded string.
137+
///
138+
/// ## Example
139+
///
140+
/// percent_decode("100%25+great")
141+
/// > Ok("100% great")
142+
///
143+
pub fn percent_decode(value: String) -> Result(String, Nil) {
144+
string.concat(["k=", value])
145+
|> parse_query
146+
|> result.then(list.head)
147+
|> result.map(pair.second)
148+
}
149+
122150
fn do_remove_dot_segments(
123151
input: List(String),
124152
accumulator: List(String),

test/gleam/uri_test.gleam

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import gleam/uri
22
import gleam/should
3+
import gleam/string
4+
import gleam/list
35
import gleam/option.{None, Some}
46

57
pub fn full_parse_test() {
@@ -92,6 +94,78 @@ pub fn empty_query_to_string_test() {
9294
should.equal(query_string, "")
9395
}
9496

97+
fn percent_codec_fixtures() {
98+
[
99+
tuple(" ", "+"),
100+
tuple(",", "%2C"),
101+
tuple(";", "%3B"),
102+
tuple(":", "%3A"),
103+
tuple("!", "%21"),
104+
tuple("?", "%3F"),
105+
tuple("'", "%27"),
106+
tuple("(", "%28"),
107+
tuple(")", "%29"),
108+
tuple("[", "%5B"),
109+
tuple("@", "%40"),
110+
tuple("/", "%2F"),
111+
tuple("\\", "%5C"),
112+
tuple("&", "%26"),
113+
tuple("#", "%23"),
114+
tuple("=", "%3D"),
115+
tuple("~", "%7E"),
116+
tuple("ñ", "%C3%B1"),
117+
// Allowed chars
118+
tuple("-", "-"),
119+
tuple("_", "_"),
120+
tuple(".", "."),
121+
tuple("*", "*"),
122+
tuple("100% great", "100%25+great"),
123+
]
124+
}
125+
126+
pub fn percent_encode_test() {
127+
percent_codec_fixtures()
128+
|> list.map(fn(t) {
129+
let tuple(a, b) = t
130+
uri.percent_encode(a)
131+
|> should.equal(b)
132+
})
133+
}
134+
135+
pub fn percent_encode_consistency_test() {
136+
let k = "foo bar[]"
137+
let v = "ñaña (,:*~)"
138+
139+
assert query_string = uri.query_to_string([tuple(k, v)])
140+
141+
let encoded_key = uri.percent_encode(k)
142+
let encoded_value = uri.percent_encode(v)
143+
let manual_query_string = string.concat([encoded_key, "=", encoded_value])
144+
145+
should.equal(query_string, manual_query_string)
146+
}
147+
148+
pub fn percent_decode_test() {
149+
percent_codec_fixtures()
150+
|> list.map(fn(t) {
151+
let tuple(a, b) = t
152+
uri.percent_decode(b)
153+
|> should.equal(Ok(a))
154+
})
155+
}
156+
157+
pub fn percent_decode_consistency_test() {
158+
let k = "foo+bar[]"
159+
let v = "%C3%B6rebro"
160+
let query = string.concat([k, "=", v])
161+
assert Ok(parsed) = uri.parse_query(query)
162+
163+
assert Ok(decoded_key) = uri.percent_decode(k)
164+
assert Ok(decoded_value) = uri.percent_decode(v)
165+
166+
should.equal(parsed, [tuple(decoded_key, decoded_value)])
167+
}
168+
95169
pub fn parse_segments_test() {
96170
should.equal(uri.path_segments("/"), [])
97171
should.equal(uri.path_segments("/foo/bar"), ["foo", "bar"])

0 commit comments

Comments
 (0)