URL Encode / Decode — Rust source
Percent-encode or decode URLs and query parameters. Choose component (encodeURIComponent) or full-URI (encodeURI) mode. 100% client-side.
This is the Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.
// URL encode / decode — component-level (encodeURIComponent) and full URI (encodeURI).
//
// Language: Rust
// CosmoDev polyglot showcase port of the `url-encode` tool.
// Ported from src/tools/UrlEncodeTool.tsx — display source, part of CosmoDev's
// polyglot tool pages.
//
// Explicit percent-encoding matching JavaScript's encodeURIComponent/encodeURI.
// Rust strings are UTF-8, so we walk the raw bytes: any byte outside the safe
// set becomes %XX (uppercase hex). Component scope leaves A-Za-z0-9-_.!~*'()
// unescaped; full URI scope additionally leaves the reserved set ;,/?:@&=+$#.
// Decode reverses this, returning Err on malformed % sequences or invalid
// UTF-8; full URI leaves encoded reserved bytes intact. No external crates —
// the percent crate is not used, to keep the showcase dependency-free.
fn is_component_safe(c: u8) -> bool {
c.is_ascii_alphanumeric()
|| matches!(c, b'-' | b'_' | b'.' | b'!' | b'~' | b'*' | b'\'' | b'(' | b')')
}
fn is_uri_extra(c: u8) -> bool {
matches!(c, b';' | b',' | b'/' | b'?' | b':' | b'@' | b'&' | b'=' | b'+' | b'$' | b'#')
}
const RESERVED: &[u8] = b";/?:@&=+$#";
const HEX: &[u8; 16] = b"0123456789ABCDEF";
/// Percent-encode `input`. `full_uri` selects encodeURI vs encodeURIComponent.
pub fn encode(input: &str, full_uri: bool) -> String {
let mut out = String::with_capacity(input.len());
for &c in input.as_bytes() {
if is_component_safe(c) || (full_uri && is_uri_extra(c)) {
out.push(c as char);
} else {
out.push('%');
out.push(HEX[(c >> 4) as usize] as char);
out.push(HEX[(c & 0x0f) as usize] as char);
}
}
out
}
fn unhex(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
// Reads the two hex digits after '%' at index `i`. Err on malformed input.
fn unhex_pair(bytes: &[u8], i: usize) -> Result<u8, &'static str> {
if i + 2 >= bytes.len() {
return Err("malformed URI sequence");
}
let hi = unhex(bytes[i + 1]).ok_or("malformed URI sequence")?;
let lo = unhex(bytes[i + 2]).ok_or("malformed URI sequence")?;
Ok(hi << 4 | lo)
}
/// Percent-decode `input`. `full_uri` selects decodeURI vs decodeURIComponent.
pub fn decode(input: &str, full_uri: bool) -> Result<String, &'static str> {
let bytes = input.as_bytes();
let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] != b'%' {
out.push(bytes[i]);
i += 1;
continue;
}
let b = unhex_pair(bytes, i)?;
if b < 0x80 {
if full_uri && RESERVED.contains(&b) {
out.extend_from_slice(&bytes[i..i + 3]); // preserve encoded reserved byte
} else {
out.push(b);
}
i += 3;
continue;
}
let seq_len = match b {
_ if b & 0xe0 == 0xc0 => 2,
_ if b & 0xf0 == 0xe0 => 3,
_ if b & 0xf8 == 0xf0 => 4,
_ => return Err("malformed URI sequence"),
};
out.push(b);
for n in 1..seq_len {
let pi = i + 3 * n;
if pi >= bytes.len() || bytes[pi] != b'%' {
return Err("malformed URI sequence");
}
let cb = unhex_pair(bytes, pi)?;
if cb & 0xc0 != 0x80 {
return Err("malformed URI sequence");
}
out.push(cb);
}
i += 3 * seq_len;
}
String::from_utf8(out).map_err(|_| "malformed URI sequence")
}
fn main() {
println!("{}", encode("hello world & café", false));
println!("{:?}", decode("hello%20world%20%26%20caf%C3%A9", false));
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →