ghidra-cli/src/domain/identifier.rs
2026-07-28 19:49:43 +00:00

80 lines
2.2 KiB
Rust

use std::{fmt, str::FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// A lowercase SHA-256 digest.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct Digest(String);
impl Digest {
/// Returns the canonical hexadecimal spelling.
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for Digest {
type Err = IdentifierError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.len() != 64 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(IdentifierError::Digest);
}
Ok(Self(value.to_ascii_lowercase()))
}
}
impl fmt::Display for Digest {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
/// A random 128-bit invocation identifier encoded as 32 lowercase hex digits.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct InvocationId(String);
impl InvocationId {
/// Parses a canonical invocation identifier.
pub fn parse(value: &str) -> Result<Self, IdentifierError> {
if value.len() != 32
|| !value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
return Err(IdentifierError::Invocation);
}
Ok(Self(value.to_owned()))
}
}
/// Identifier validation failure.
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
pub enum IdentifierError {
/// SHA-256 spelling is invalid.
#[error("digest must contain exactly 64 hexadecimal characters")]
Digest,
/// Invocation identifier spelling is invalid.
#[error("invocation ID must contain exactly 32 lowercase hexadecimal characters")]
Invocation,
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use std::str::FromStr as _;
use super::Digest;
#[test]
fn digest_accepts_hex_and_canonicalizes_case() {
let digest = Digest::from_str(&"A".repeat(64)).expect("valid digest");
assert_eq!(digest.as_str(), "a".repeat(64));
assert!(Digest::from_str(&"a".repeat(63)).is_err());
}
}