#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BranchKeyError {
pub message: String,
}
impl std::fmt::Display for BranchKeyError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str(&self.message);
}
}
impl std::error::Error for BranchKeyError {}
const ESCAPE: u8 = b'%';
fn is_verbatim(byte: u8) -> bool {
return byte.is_ascii_lowercase()
|| byte.is_ascii_digit()
|| byte == b'-'
|| byte == b'_'
|| byte == b'.';
}
pub fn encode_branch_key(ref_name: &str) -> String {
let mut key = String::with_capacity(ref_name.len());
for &byte in ref_name.as_bytes() {
if is_verbatim(byte) {
key.push(char::from(byte));
} else {
key.push_str(&format!("%{byte:02X}"));
}
}
return key;
}
fn decode_escape(key: &[u8], index: usize) -> Result<u8, BranchKeyError> {
let digits = key.get(index + 1..index + 3).unwrap_or(&[]);
if digits.len() != 2
|| !digits.iter().all(|digit| {
return is_upper_hex(*digit);
})
{
return Err(BranchKeyError {
message: format!(
"Push coordination key has a malformed escape at {index}: {}",
String::from_utf8_lossy(key)
),
});
}
let high = hex_value(digits[0]);
let low = hex_value(digits[1]);
let byte = high * 16 + low;
if is_verbatim(byte) {
return Err(BranchKeyError {
message: format!(
"Push coordination key escapes a verbatim character at {index}: {}",
String::from_utf8_lossy(key)
),
});
}
return Ok(byte);
}
fn is_upper_hex(digit: u8) -> bool {
return digit.is_ascii_digit() || (b'A'..=b'F').contains(&digit);
}
fn hex_value(digit: u8) -> u8 {
if digit.is_ascii_digit() {
return digit - b'0';
}
return digit - b'A' + 10;
}
pub fn decode_branch_key(key: &str) -> Result<String, BranchKeyError> {
let bytes = key.as_bytes();
let mut decoded: Vec<u8> = Vec::with_capacity(bytes.len());
let mut index = 0;
while index < bytes.len() {
let byte = bytes[index];
if byte == ESCAPE {
decoded.push(decode_escape(bytes, index)?);
index += 3;
} else if is_verbatim(byte) {
decoded.push(byte);
index += 1;
} else {
return Err(BranchKeyError {
message: format!(
"Push coordination key holds an unescaped character at {index}: {key}"
),
});
}
}
return match String::from_utf8(decoded) {
Ok(text) => Ok(text),
Err(error) => Err(BranchKeyError {
message: format!("Push coordination key is not UTF-8 ({error}): {key}"),
}),
};
}
#[cfg(test)]
#[path = "auto_push_branch_key_tests.rs"]
mod tests;