1use std::{error::Error, fmt, num::NonZeroUsize};
4
5use nonempty::NonEmpty;
6use radicle_git_ext::Oid;
7use serde::{Deserialize, Serialize};
8
9use crate::{signatures, TypeName};
10
11pub trait Storage {
13 type StoreError: Error + Send + Sync + 'static;
14 type LoadError: Error + Send + Sync + 'static;
15
16 type ObjectId;
17 type Parent;
18 type Signatures;
19
20 #[allow(clippy::type_complexity)]
22 fn store<G>(
23 &self,
24 resource: Option<Self::Parent>,
25 related: Vec<Self::Parent>,
26 signer: &G,
27 template: Template<Self::ObjectId>,
28 ) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError>
29 where
30 G: crypto::Signer;
31
32 #[allow(clippy::type_complexity)]
34 fn load(
35 &self,
36 id: Self::ObjectId,
37 ) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>;
38
39 fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError>;
41}
42
43pub struct Template<Id> {
45 pub type_name: TypeName,
46 pub tips: Vec<Id>,
47 pub message: String,
48 pub embeds: Vec<Embed<Oid>>,
49 pub contents: NonEmpty<Vec<u8>>,
50}
51
52pub type Contents = NonEmpty<Vec<u8>>;
55
56pub type Timestamp = u64;
58
59pub type EntryId = Oid;
61
62#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct Entry<Resource, Id, Signature> {
64 pub id: Id,
66 pub revision: Id,
68 pub signature: Signature,
71 pub resource: Option<Resource>,
74 pub parents: Vec<Resource>,
76 pub related: Vec<Resource>,
78 pub manifest: Manifest,
81 pub contents: Contents,
83 pub timestamp: Timestamp,
85}
86
87impl<Resource, Id, S> fmt::Display for Entry<Resource, Id, S>
88where
89 Id: fmt::Display,
90{
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "Entry {{ id: {} }}", self.id)
93 }
94}
95
96impl<Resource, Id, Signatures> Entry<Resource, Id, Signatures> {
97 pub fn id(&self) -> &Id {
98 &self.id
99 }
100
101 pub fn type_name(&self) -> &TypeName {
102 &self.manifest.type_name
103 }
104
105 pub fn contents(&self) -> &Contents {
106 &self.contents
107 }
108
109 pub fn resource(&self) -> Option<&Resource> {
110 self.resource.as_ref()
111 }
112}
113
114impl<R, Id> Entry<R, Id, signatures::Signatures>
115where
116 Id: AsRef<[u8]>,
117{
118 pub fn valid_signatures(&self) -> bool {
119 self.signature
120 .iter()
121 .all(|(key, sig)| key.verify(self.revision.as_ref(), sig).is_ok())
122 }
123}
124
125impl<R, Id> Entry<R, Id, signatures::ExtendedSignature>
126where
127 Id: AsRef<[u8]>,
128{
129 pub fn valid_signatures(&self) -> bool {
130 self.signature.verify(self.revision.as_ref())
131 }
132
133 pub fn author(&self) -> &crypto::PublicKey {
134 &self.signature.key
135 }
136}
137
138#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct Manifest {
141 #[serde(alias = "typename")] pub type_name: TypeName,
144 #[serde(default)]
146 pub version: Version,
147}
148
149impl Manifest {
150 pub fn new(type_name: TypeName, version: Version) -> Self {
152 Self { type_name, version }
153 }
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
158pub struct Version(NonZeroUsize);
159
160impl Default for Version {
161 fn default() -> Self {
162 Version(NonZeroUsize::MIN)
163 }
164}
165
166impl From<Version> for usize {
167 fn from(value: Version) -> Self {
168 value.0.into()
169 }
170}
171
172impl From<NonZeroUsize> for Version {
173 fn from(value: NonZeroUsize) -> Self {
174 Self(value)
175 }
176}
177
178impl Version {
179 pub fn new(version: usize) -> Option<Self> {
180 NonZeroUsize::new(version).map(Self)
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct Embed<T = Vec<u8>> {
188 pub name: String,
190 pub content: T,
192}
193
194impl<T: From<Oid>> Embed<T> {
195 pub fn store(
197 name: impl ToString,
198 content: &[u8],
199 repo: &git2::Repository,
200 ) -> Result<Self, git2::Error> {
201 let oid = repo.blob(content)?;
202
203 Ok(Self {
204 name: name.to_string(),
205 content: T::from(oid.into()),
206 })
207 }
208}
209
210impl Embed<Vec<u8>> {
211 pub fn oid(&self) -> Oid {
213 git2::Oid::hash_object(git2::ObjectType::Blob, &self.content)
215 .expect("Embed::oid: invalid object")
216 .into()
217 }
218
219 pub fn hashed<T: From<Oid>>(&self) -> Embed<T> {
221 Embed {
222 name: self.name.clone(),
223 content: T::from(self.oid()),
224 }
225 }
226}
227
228impl Embed<Oid> {
229 pub fn oid(&self) -> Oid {
231 self.content
232 }
233}