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: signature::Signer<Self::Signatures>;
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 fn manifest_of(&self, id: &Oid) -> Result<Manifest, Self::LoadError>;
44}
45
46pub struct Template<Id> {
48 pub type_name: TypeName,
49 pub tips: Vec<Id>,
50 pub message: String,
51 pub embeds: Vec<Embed<Oid>>,
52 pub contents: NonEmpty<Vec<u8>>,
53}
54
55pub type Contents = NonEmpty<Vec<u8>>;
58
59pub type Timestamp = u64;
61
62pub type EntryId = Oid;
64
65#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct Entry<Resource, Id, Signature> {
67 pub id: Id,
69 pub revision: Id,
71 pub signature: Signature,
74 pub resource: Option<Resource>,
77 pub parents: Vec<Resource>,
79 pub related: Vec<Resource>,
81 pub manifest: Manifest,
84 pub contents: Contents,
86 pub timestamp: Timestamp,
88}
89
90impl<Resource, Id, S> fmt::Display for Entry<Resource, Id, S>
91where
92 Id: fmt::Display,
93{
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 write!(f, "Entry {{ id: {} }}", self.id)
96 }
97}
98
99impl<Resource, Id, Signatures> Entry<Resource, Id, Signatures> {
100 pub fn id(&self) -> &Id {
101 &self.id
102 }
103
104 pub fn type_name(&self) -> &TypeName {
105 &self.manifest.type_name
106 }
107
108 pub fn contents(&self) -> &Contents {
109 &self.contents
110 }
111
112 pub fn resource(&self) -> Option<&Resource> {
113 self.resource.as_ref()
114 }
115}
116
117impl<R, Id> Entry<R, Id, signatures::Signatures>
118where
119 Id: AsRef<[u8]>,
120{
121 pub fn valid_signatures(&self) -> bool {
122 self.signature
123 .iter()
124 .all(|(key, sig)| key.verify(self.revision.as_ref(), sig).is_ok())
125 }
126}
127
128impl<R, Id> Entry<R, Id, signatures::ExtendedSignature>
129where
130 Id: AsRef<[u8]>,
131{
132 pub fn valid_signatures(&self) -> bool {
133 self.signature.verify(self.revision.as_ref())
134 }
135
136 pub fn author(&self) -> &crypto::PublicKey {
137 &self.signature.key
138 }
139}
140
141#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct Manifest {
144 #[serde(alias = "typename")] pub type_name: TypeName,
147 #[serde(default)]
149 pub version: Version,
150}
151
152impl Manifest {
153 pub fn new(type_name: TypeName, version: Version) -> Self {
155 Self { type_name, version }
156 }
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
161pub struct Version(NonZeroUsize);
162
163impl Default for Version {
164 fn default() -> Self {
165 Version(NonZeroUsize::MIN)
166 }
167}
168
169impl From<Version> for usize {
170 fn from(value: Version) -> Self {
171 value.0.into()
172 }
173}
174
175impl From<NonZeroUsize> for Version {
176 fn from(value: NonZeroUsize) -> Self {
177 Self(value)
178 }
179}
180
181impl Version {
182 pub fn new(version: usize) -> Option<Self> {
183 NonZeroUsize::new(version).map(Self)
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct Embed<T = Vec<u8>> {
191 pub name: String,
193 pub content: T,
195}
196
197impl<T: From<Oid>> Embed<T> {
198 pub fn store(
200 name: impl ToString,
201 content: &[u8],
202 repo: &git2::Repository,
203 ) -> Result<Self, git2::Error> {
204 let oid = repo.blob(content)?;
205
206 Ok(Self {
207 name: name.to_string(),
208 content: T::from(oid.into()),
209 })
210 }
211}
212
213impl Embed<Vec<u8>> {
214 pub fn oid(&self) -> Oid {
216 git2::Oid::hash_object(git2::ObjectType::Blob, &self.content)
218 .expect("Embed::oid: invalid object")
219 .into()
220 }
221
222 pub fn hashed<T: From<Oid>>(&self) -> Embed<T> {
224 Embed {
225 name: self.name.clone(),
226 content: T::from(self.oid()),
227 }
228 }
229}
230
231impl Embed<Oid> {
232 pub fn oid(&self) -> Oid {
234 self.content
235 }
236}