use crate::constants::{HASH_LENGTH, MAX_NAME_LENGTH};
use crate::enums::EntryKind;
use crate::errors::VctrlError;
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Hash([u8; HASH_LENGTH]);
impl Hash {
pub const fn from_bytes(bytes: &[u8]) -> Result<Self, VctrlError> {
if bytes.len() != HASH_LENGTH {
return Err(VctrlError::InvalidHashLength(bytes.len()));
}
let mut arr = [0u8; HASH_LENGTH];
let mut i = 0;
while i < HASH_LENGTH {
arr[i] = bytes[i];
i += 1;
}
Ok(Self(arr))
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; HASH_LENGTH] {
&self.0
}
}
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash(")?;
for &byte in self.0.iter().take(8) {
write!(f, "{byte:02x}")?;
}
write!(f, "…)")
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &byte in &self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
fn validate_name(name: &str) -> Result<(), VctrlError> {
if name.is_empty() {
return Err(VctrlError::InvalidName("name is empty".into()));
}
if name.len() > MAX_NAME_LENGTH {
return Err(VctrlError::InvalidName(format!(
"name exceeds maximum length {MAX_NAME_LENGTH}: '{name}'"
)));
}
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeEntry {
name: String,
kind: EntryKind,
hash: Hash,
}
impl TreeEntry {
pub fn new(name: String, kind: EntryKind, hash: Hash) -> Result<Self, VctrlError> {
validate_name(&name)?;
Ok(Self { name, kind, hash })
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn kind(&self) -> EntryKind {
self.kind
}
#[must_use]
pub const fn hash(&self) -> &Hash {
&self.hash
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Blob {
data: Vec<u8>,
}
impl Blob {
#[must_use]
#[allow(clippy::missing_const_for_fn)] pub fn new(data: Vec<u8>) -> Self {
Self { data }
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tree {
entries: Vec<TreeEntry>,
}
impl Tree {
pub fn new(entries: Vec<TreeEntry>) -> Result<Self, VctrlError> {
for i in 1..entries.len() {
if entries[i - 1].name() >= entries[i].name() {
return Err(VctrlError::InvalidName(format!(
"Tree entries are not sorted or contain duplicates: '{}' vs '{}'",
entries[i - 1].name(),
entries[i].name()
)));
}
}
Ok(Self { entries })
}
#[must_use]
pub fn entries(&self) -> &[TreeEntry] {
&self.entries
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UserID {
name: String,
email: String,
}
impl UserID {
pub fn new(name: String, email: String) -> Result<Self, VctrlError> {
validate_name(&name)?;
if email.is_empty() {
return Err(VctrlError::InvalidName("email is empty".into()));
}
Ok(Self { name, email })
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn email(&self) -> &str {
&self.email
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Commit {
tree: Hash,
parents: Vec<Hash>,
author: UserID,
committer: UserID,
message: String,
}
impl Commit {
#[must_use]
#[allow(clippy::missing_const_for_fn)] pub fn new(
tree: Hash,
parents: Vec<Hash>,
author: UserID,
committer: UserID,
message: String,
) -> Self {
Self {
tree,
parents,
author,
committer,
message,
}
}
#[must_use]
pub const fn tree(&self) -> &Hash {
&self.tree
}
#[must_use]
pub fn parents(&self) -> &[Hash] {
&self.parents
}
#[must_use]
pub const fn author(&self) -> &UserID {
&self.author
}
#[must_use]
pub const fn committer(&self) -> &UserID {
&self.committer
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tag {
name: String,
target: Hash,
tagger: Option<UserID>,
message: String,
}
impl Tag {
pub fn new(
name: String,
target: Hash,
tagger: Option<UserID>,
message: String,
) -> Result<Self, VctrlError> {
validate_name(&name)?;
Ok(Self {
name,
target,
tagger,
message,
})
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn target(&self) -> &Hash {
&self.target
}
#[must_use]
pub const fn tagger(&self) -> Option<&UserID> {
self.tagger.as_ref()
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}