use std::cmp::Ordering;
use crate::{ChangeId, ObjectId, Prefix, change_id::ReverseHexDisplay, oid};
#[derive(Debug, thiserror::Error)]
#[expect(missing_docs)]
pub enum Error {
#[error(
"The minimum hex length of a short object id is {}, got {hex_len}",
Prefix::MIN_HEX_LEN
)]
TooShort { hex_len: usize },
#[error("An object of kind {object_kind} cannot be larger than {} in hex, but {hex_len} was requested", object_kind.len_in_hex())]
TooLong { object_kind: crate::Kind, hex_len: usize },
}
pub mod from_hex {
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
#[expect(missing_docs)]
pub enum Error {
#[error(
"The minimum hex length of a short object id is {}, got {hex_len}",
super::Prefix::MIN_HEX_LEN
)]
TooShort { hex_len: usize },
#[error("An id cannot be larger than {} chars in hex, but {hex_len} was requested", crate::Kind::longest().len_in_hex())]
TooLong { hex_len: usize },
#[error("Invalid hex character")]
Invalid,
}
}
impl Prefix {
pub const MIN_HEX_LEN: usize = 4;
pub fn new(id: &oid, hex_len: usize) -> Result<Self, Error> {
if hex_len > id.kind().len_in_hex() {
Err(Error::TooLong {
object_kind: id.kind(),
hex_len,
})
} else if hex_len < Self::MIN_HEX_LEN {
Err(Error::TooShort { hex_len })
} else {
let mut prefix = ObjectId::null(id.kind());
let b = prefix.as_mut_slice();
let copy_len = hex_len.div_ceil(2);
b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]);
if hex_len % 2 == 1 {
b[hex_len / 2] &= 0xf0;
}
Ok(Prefix { bytes: prefix, hex_len })
}
}
#[inline]
#[must_use]
pub fn hex_to_buf<'a>(&self, buf: &'a mut [u8]) -> &'a mut str {
let complete_bytes = self.hex_len / 2;
let complete_hex_len = complete_bytes * 2;
if complete_bytes != 0 {
faster_hex::hex_encode(&self.bytes.as_bytes()[..complete_bytes], &mut buf[..complete_hex_len])
.expect("buffer size was checked before encoding");
}
if self.hex_len % 2 == 1 {
const HEX: &[u8; 16] = b"0123456789abcdef";
buf[complete_hex_len] = HEX[usize::from(self.bytes.as_bytes()[complete_bytes] >> 4)];
}
std::str::from_utf8_mut(&mut buf[..self.hex_len]).expect("hexadecimal object IDs are valid UTF-8")
}
#[inline]
pub fn write_hex_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
let mut buf = crate::Kind::hex_buf();
out.write_all(self.hex_to_buf(&mut buf).as_bytes())
}
pub fn as_oid(&self) -> &oid {
&self.bytes
}
pub fn hex_len(&self) -> usize {
self.hex_len
}
pub fn cmp_oid(&self, candidate: &oid) -> Ordering {
let common_len = self.hex_len / 2;
self.bytes.as_bytes()[..common_len]
.cmp(&candidate.as_bytes()[..common_len])
.then(if self.hex_len % 2 == 1 {
let half_byte_idx = self.hex_len / 2;
self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0))
} else {
Ordering::Equal
})
}
pub fn from_hex(value: &str) -> Result<Self, from_hex::Error> {
let hex_len = value.len();
if hex_len < Self::MIN_HEX_LEN {
return Err(from_hex::Error::TooShort { hex_len });
}
Self::from_hex_nonempty(value)
}
pub fn from_hex_nonempty(value: &str) -> Result<Self, from_hex::Error> {
let hex_len = value.len();
if hex_len > crate::Kind::longest().len_in_hex() {
return Err(from_hex::Error::TooLong { hex_len });
} else if hex_len == 0 {
return Err(from_hex::Error::TooShort { hex_len });
}
let kind = crate::Kind::from_hex_len(hex_len).expect("hex-len is already checked");
let mut bytes = ObjectId::null(kind);
let dst = &mut bytes.as_mut_slice()[..hex_len.div_ceil(2)];
let decode_result = if hex_len % 2 == 0 {
faster_hex::hex_decode(value.as_bytes(), dst)
} else {
let mut hex = crate::Kind::hex_buf();
hex[..hex_len].copy_from_slice(value.as_bytes());
hex[hex_len] = b'0';
faster_hex::hex_decode(&hex[..=hex_len], dst)
};
decode_result.map_err(|e| match e {
faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => from_hex::Error::Invalid,
faster_hex::Error::InvalidLength(_) => panic!("This is already checked"),
})?;
Ok(Prefix { bytes, hex_len })
}
pub fn from_reverse_hex(value: &str) -> Result<Self, from_hex::Error> {
let hex_len = value.len();
if hex_len < Self::MIN_HEX_LEN {
return Err(from_hex::Error::TooShort { hex_len });
}
Self::from_reverse_hex_nonempty(value)
}
pub fn from_reverse_hex_nonempty(value: &str) -> Result<Self, from_hex::Error> {
let hex_len = value.len();
if hex_len > crate::Kind::longest().len_in_hex() {
return Err(from_hex::Error::TooLong { hex_len });
} else if hex_len == 0 {
return Err(from_hex::Error::TooShort { hex_len });
}
let mut hex = crate::Kind::hex_buf();
crate::change_id::reverse_hex_to_hex(value.as_bytes(), &mut hex[..hex_len])
.map_err(|()| from_hex::Error::Invalid)?;
let hex = std::str::from_utf8(&hex[..hex_len]).expect("translated reverse hex is always ASCII");
Self::from_hex_nonempty(hex)
}
pub fn to_reverse_hex(&self) -> ReverseHexDisplay<'_> {
ReverseHexDisplay::new(&self.bytes, self.hex_len)
}
}
impl TryFrom<&str> for Prefix {
type Error = from_hex::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Prefix::from_hex(value)
}
}
impl std::fmt::Display for Prefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.bytes.to_hex_with_len(self.hex_len).fmt(f)
}
}
impl From<ObjectId> for Prefix {
fn from(oid: ObjectId) -> Self {
Prefix {
bytes: oid,
hex_len: oid.kind().len_in_hex(),
}
}
}
impl From<ChangeId> for Prefix {
fn from(change_id: ChangeId) -> Self {
ObjectId::from(change_id).into()
}
}