///|
pub(all) enum ParamValue {
Str(String)
Int(Int64)
Bool(Bool)
Strs(Array[String])
Ints(Array[Int64])
} derive(Eq, Debug)
///|
/// Ordered parameters permit repeated keys and deterministic tests.
pub(all) struct Params {
entries : Array[(String, ParamValue)]
} derive(Eq, Debug)
///|
pub fn Params::new() -> Params {
{ entries: [] }
}
///|
pub fn Params::of(entries : Array[(String, ParamValue)]) -> Params {
{ entries, }
}
///|
pub fn Params::put(self : Self, key : String, value : ParamValue) -> Unit {
self.entries.push((key, value))
}
///|
pub fn Params::put_opt(self : Self, key : String, value : ParamValue?) -> Unit {
if value is Some(v) {
self.put(key, v)
}
}
///|
pub fn Params::put_string(self : Self, key : String, value : String?) -> Unit {
self.put_opt(key, value.map(v => Str(v)))
}
///|
pub fn Params::put_int(self : Self, key : String, value : Int64?) -> Unit {
self.put_opt(key, value.map(v => Int(v)))
}
///|
pub fn Params::put_bool(self : Self, key : String, value : Bool?) -> Unit {
self.put_opt(key, value.map(v => Bool(v)))
}
///|
pub fn Params::put_strings(
self : Self,
key : String,
value : Array[String]?,
) -> Unit {
self.put_opt(key, value.map(v => Strs(v)))
}
///|
pub fn Params::encode(self : Self) -> String {
let out = StringBuilder::new()
let mut first = true
fn append(key : String, value : String) -> Unit {
if !first {
out.write_char('&')
}
first = false
out.write_string(percent_encode(key))
out.write_char('=')
out.write_string(percent_encode(value))
}
for entry in self.entries {
let (key, value) = entry
match value {
Str(value) => append(key, value)
Int(value) => append(key, value.to_string())
Bool(value) => append(key, if value { "true" } else { "false" })
Strs(values) =>
for value in values {
append(key, value)
}
Ints(values) =>
for value in values {
append(key, value.to_string())
}
}
}
out.to_string()
}
///|
/// RFC 3986 percent-encoding over UTF-8 bytes.
pub fn percent_encode(text : String) -> String {
let bytes = @utf8.encode(text)
let out = StringBuilder::new(size_hint=bytes.length())
for byte in bytes {
let value = byte.to_int()
if is_unreserved(value) {
out.write_char(value.unsafe_to_char())
} else {
out.write_char('%')
out.write_char(hex_digit(value >> 4))
out.write_char(hex_digit(value & 0x0f))
}
}
out.to_string()
}
///|
fn is_unreserved(byte : Int) -> Bool {
(byte >= 'a'.to_int() && byte <= 'z'.to_int()) ||
(byte >= 'A'.to_int() && byte <= 'Z'.to_int()) ||
(byte >= '0'.to_int() && byte <= '9'.to_int()) ||
byte == '-'.to_int() ||
byte == '.'.to_int() ||
byte == '_'.to_int() ||
byte == '~'.to_int()
}
///|
fn hex_digit(value : Int) -> Char {
if value < 10 {
('0'.to_int() + value).unsafe_to_char()
} else {
('A'.to_int() + value - 10).unsafe_to_char()
}
}