haematite-cli 0.21.8

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use crate::serve::storage::Failure;

const DIGITS: &[u8; 16] = b"0123456789abcdef";

pub fn invalid() -> Failure {
    Failure::new(
        "query_continuation_invalid",
        "continuation is not a canonical encoded value",
    )
}

pub fn encode(bytes: &[u8]) -> String {
    let mut encoded = String::new();
    for byte in bytes {
        encoded.push(char::from(DIGITS[usize::from(byte >> 4)]));
        encoded.push(char::from(DIGITS[usize::from(byte & 15)]));
    }
    encoded
}

fn digit(byte: u8) -> Result<u8, Failure> {
    match byte {
        b'0'..=b'9' => Ok(byte - b'0'),
        b'a'..=b'f' => Ok(byte - b'a' + 10),
        _ => Err(invalid()),
    }
}

pub fn decode(encoded: &str) -> Result<Vec<u8>, Failure> {
    let (pairs, rest) = encoded.as_bytes().as_chunks::<2>();
    if !rest.is_empty() {
        return Err(invalid());
    }
    pairs
        .iter()
        .map(|pair| Ok((digit(pair[0])? << 4) | digit(pair[1])?))
        .collect()
}