use crate::{Transform, TransformError, TransformerCategory};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UrlDecode;
impl Transform for UrlDecode {
fn name(&self) -> &'static str {
"URL Decode"
}
fn id(&self) -> &'static str {
"urldecode"
}
fn description(&self) -> &'static str {
"Decode URL-encoded text"
}
fn category(&self) -> TransformerCategory {
TransformerCategory::Decoder
}
fn transform(&self, input: &str) -> Result<String, TransformError> {
url_decode(input).map_err(|_e| TransformError::UrlDecodeError)
}
fn default_test_input(&self) -> &'static str {
"Hello%2C+World%21"
}
}
fn url_decode(input: &str) -> Result<String, &'static str> {
let mut decoded_bytes = Vec::with_capacity(input.len());
let mut bytes = input.bytes();
while let Some(byte) = bytes.next() {
match byte {
b'+' => decoded_bytes.push(b' '),
b'%' => {
let hi = bytes
.next()
.ok_or("Invalid URL encoding: unexpected end of input")?;
let lo = bytes
.next()
.ok_or("Invalid URL encoding: unexpected end of input")?;
let hex_to_digit = |b| match b {
b'0'..=b'9' => Ok(b - b'0'),
b'A'..=b'F' => Ok(b - b'A' + 10),
b'a'..=b'f' => Ok(b - b'a' + 10),
_ => Err("Invalid URL encoding: invalid hex digit"),
};
let high_nibble = hex_to_digit(hi)?;
let low_nibble = hex_to_digit(lo)?;
let decoded_byte = (high_nibble << 4) | low_nibble;
decoded_bytes.push(decoded_byte);
}
_ => decoded_bytes.push(byte),
}
}
String::from_utf8(decoded_bytes).map_err(|_| "Invalid UTF-8 sequence in decoded URL")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_url_decode() {
let transformer = UrlDecode;
assert_eq!(
transformer
.transform(transformer.default_test_input())
.unwrap(),
"Hello, World!"
);
assert_eq!(transformer.transform("a+b").unwrap(), "a b");
assert_eq!(transformer.transform("100%25").unwrap(), "100%");
}
}