use std::{error::Error, fmt, num::NonZeroUsize};
use nonempty::NonEmpty;
use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize};
use crate::{signatures, TypeName};
pub trait Storage {
type StoreError: Error + Send + Sync + 'static;
type LoadError: Error + Send + Sync + 'static;
type ObjectId;
type Parent;
type Signatures;
#[allow(clippy::type_complexity)]
fn store<G>(
&self,
resource: Option<Self::Parent>,
related: Vec<Self::Parent>,
signer: &G,
template: Template<Self::ObjectId>,
) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError>
where
G: crypto::Signer;
#[allow(clippy::type_complexity)]
fn load(
&self,
id: Self::ObjectId,
) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>;
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError>;
}
pub struct Template<Id> {
pub type_name: TypeName,
pub tips: Vec<Id>,
pub message: String,
pub embeds: Vec<Embed>,
pub contents: NonEmpty<Vec<u8>>,
}
pub type Contents = NonEmpty<Vec<u8>>;
pub type Timestamp = u64;
pub type EntryId = Oid;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Entry<Resource, Id, Signature> {
pub id: Id,
pub revision: Id,
pub signature: Signature,
pub resource: Option<Resource>,
pub parents: Vec<Resource>,
pub related: Vec<Resource>,
pub manifest: Manifest,
pub contents: Contents,
pub timestamp: Timestamp,
}
impl<Resource, Id, S> fmt::Display for Entry<Resource, Id, S>
where
Id: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Entry {{ id: {} }}", self.id)
}
}
impl<Resource, Id, Signatures> Entry<Resource, Id, Signatures> {
pub fn id(&self) -> &Id {
&self.id
}
pub fn type_name(&self) -> &TypeName {
&self.manifest.type_name
}
pub fn contents(&self) -> &Contents {
&self.contents
}
pub fn resource(&self) -> Option<&Resource> {
self.resource.as_ref()
}
}
impl<R, Id> Entry<R, Id, signatures::Signatures>
where
Id: AsRef<[u8]>,
{
pub fn valid_signatures(&self) -> bool {
self.signature
.iter()
.all(|(key, sig)| key.verify(self.revision.as_ref(), sig).is_ok())
}
}
impl<R, Id> Entry<R, Id, signatures::ExtendedSignature>
where
Id: AsRef<[u8]>,
{
pub fn valid_signatures(&self) -> bool {
self.signature.verify(self.revision.as_ref())
}
pub fn author(&self) -> &crypto::PublicKey {
&self.signature.key
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Manifest {
#[serde(alias = "typename")] pub type_name: TypeName,
#[serde(default)]
pub version: Version,
}
impl Manifest {
pub fn new(type_name: TypeName, version: Version) -> Self {
Self { type_name, version }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Version(NonZeroUsize);
impl Default for Version {
fn default() -> Self {
Version(NonZeroUsize::MIN)
}
}
impl From<Version> for usize {
fn from(value: Version) -> Self {
value.0.into()
}
}
impl From<NonZeroUsize> for Version {
fn from(value: NonZeroUsize) -> Self {
Self(value)
}
}
impl Version {
pub fn new(version: usize) -> Option<Self> {
NonZeroUsize::new(version).map(Self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Embed<T = Vec<u8>> {
pub name: String,
pub content: T,
}
impl Embed<Vec<u8>> {
pub fn oid(&self) -> Oid {
git2::Oid::hash_object(git2::ObjectType::Blob, &self.content)
.expect("Embed::oid: invalid object")
.into()
}
pub fn hashed<T: From<Oid>>(&self) -> Embed<T> {
Embed {
name: self.name.clone(),
content: T::from(self.oid()),
}
}
}
impl Embed<Oid> {
pub fn oid(&self) -> Oid {
self.content
}
}