feat: scaffold v0.1 foundation
This commit is contained in:
parent
c4fb0c6a01
commit
170ca32f58
34 changed files with 6175 additions and 5 deletions
80
src/domain/identifier.rs
Normal file
80
src/domain/identifier.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue