use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ContentKind {
Markdown,
Text,
}
impl ContentKind {
#[must_use]
pub const fn extension(self) -> &'static str {
match self {
ContentKind::Markdown => "md",
ContentKind::Text => "txt",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Content {
Markdown(String),
Text(String),
}
impl Content {
#[must_use]
pub fn markdown(s: impl Into<String>) -> Self {
Content::Markdown(s.into())
}
#[must_use]
pub fn text(s: impl Into<String>) -> Self {
Content::Text(s.into())
}
#[must_use]
pub const fn kind(&self) -> ContentKind {
match self {
Content::Markdown(_) => ContentKind::Markdown,
Content::Text(_) => ContentKind::Text,
}
}
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Content::Markdown(s) | Content::Text(s) => s.as_str(),
}
}
#[must_use]
pub fn byte_len(&self) -> usize {
self.as_str().len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ContentHash(pub [u8; 32]);
impl ContentHash {
#[must_use]
pub fn of_bytes(bytes: &[u8]) -> Self {
ContentHash(*blake3::hash(bytes).as_bytes())
}
#[must_use]
pub fn of_content(c: &Content) -> Self {
Self::of_bytes(c.as_str().as_bytes())
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub fn to_hex(&self) -> String {
let mut out = String::with_capacity(64);
for b in self.0 {
out.push_str(&format!("{b:02x}"));
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_hash() {
let a = ContentHash::of_bytes(b"hello world");
let b = ContentHash::of_bytes(b"hello world");
assert_eq!(a, b);
}
#[test]
fn different_inputs_hash_differently() {
let a = ContentHash::of_bytes(b"a");
let b = ContentHash::of_bytes(b"b");
assert_ne!(a, b);
}
#[test]
fn hex_is_64_chars() {
let h = ContentHash::of_bytes(b"x");
assert_eq!(h.to_hex().len(), 64);
}
#[test]
fn extension_matches_kind() {
assert_eq!(Content::markdown("a").kind().extension(), "md");
assert_eq!(Content::text("a").kind().extension(), "txt");
}
}