haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Attribution keys preserve the existing version and request ordering.

use super::{TAG, identifier};
use crate::serve::protocol::Version;
use crate::serve::storage::Failure;

const VERSION_BYTES: usize = 16;

/// Fixed-width unsigned big-endian values preserve numeric version order.
#[must_use]
pub fn version_prefix(version: Version) -> Vec<u8> {
    let mut prefix = Vec::with_capacity(TAG.len() + VERSION_BYTES + 1);
    prefix.extend_from_slice(TAG);
    prefix.extend_from_slice(&version.generation.to_be_bytes());
    prefix.extend_from_slice(&version.sequence.to_be_bytes());
    // The delimiter gives every version prefix a finite exclusive successor.
    prefix.push(0);
    prefix
}

/// Encode a version and validated request id as an attribution key.
///
/// # Errors
/// Returns `invalid_identifier` for an empty, overlong or NUL-containing request id.
pub fn key(version: Version, request_id: &str) -> Result<Vec<u8>, Failure> {
    identifier(request_id)?;
    let mut key = version_prefix(version);
    key.extend_from_slice(request_id.as_bytes());
    Ok(key)
}

/// Decode the version and request id from an attribution key.
///
/// # Errors
/// Returns `change_record_key_invalid` for a bad tag, truncated version, missing delimiter or invalid request id.
pub fn decode_key(key: &[u8]) -> Result<(Version, &str), Failure> {
    let invalid = || {
        Failure::new(
            "change_record_key_invalid",
            "attribution key has invalid tag, version or request id",
        )
    };
    let suffix = key.strip_prefix(TAG).ok_or_else(invalid)?;
    let generation = suffix
        .get(..8)
        .ok_or_else(invalid)?
        .try_into()
        .map_err(|_| invalid())?;
    let sequence = suffix
        .get(8..16)
        .ok_or_else(invalid)?
        .try_into()
        .map_err(|_| invalid())?;
    if suffix.get(VERSION_BYTES) != Some(&0) {
        return Err(invalid());
    }
    let request = std::str::from_utf8(suffix.get(VERSION_BYTES + 1..).ok_or_else(invalid)?)
        .map_err(|_| invalid())?;
    identifier(request).map_err(|_| invalid())?;
    Ok((
        Version {
            generation: u64::from_be_bytes(generation),
            sequence: u64::from_be_bytes(sequence),
        },
        request,
    ))
}