crev_data/proof/
content.rs1use crate::{proof, proof::Proof, Error, ParseError, Result};
2use chrono::{self, prelude::*};
3use crev_common::{
4 self,
5 serde::{as_base64, as_rfc3339_fixed, from_base64, from_rfc3339_fixed},
6};
7use derive_builder::Builder;
8use serde::{self, Deserialize, Serialize};
9use std::{fmt, io};
10
11pub type Date = chrono::DateTime<FixedOffset>;
12
13pub trait CommonOps {
15 fn common(&self) -> &Common;
17
18 fn kind(&self) -> &str {
20 self.common()
21 .kind
22 .as_ref()
23 .expect("Common types are expected to always have the `kind` field backfilled")
24 }
25
26 fn from(&self) -> &crate::PublicId {
28 &self.common().from
29 }
30
31 fn date(&self) -> &chrono::DateTime<chrono::offset::FixedOffset> {
33 &self.common().date
34 }
35
36 fn date_utc(&self) -> chrono::DateTime<Utc> {
38 self.date().with_timezone(&Utc)
39 }
40
41 fn author_id(&self) -> &crate::Id {
43 &self.common().from.id
44 }
45
46 fn author_public_id(&self) -> &crate::PublicId {
48 &self.common().from
49 }
50
51 fn ensure_kind_is(&self, kind: &str) -> ValidationResult<()> {
53 let expected = self.kind();
54 if expected != kind {
55 return Err(ValidationError::InvalidKind(Box::new((
56 expected.to_string(),
57 kind.to_string(),
58 ))));
59 }
60 Ok(())
61 }
62}
63
64#[derive(Clone, Debug, Serialize, Deserialize)]
66pub struct OriginalReference {
67 #[serde(serialize_with = "as_base64", deserialize_with = "from_base64")]
69 pub proof: Vec<u8>,
70 #[serde(skip_serializing_if = "String::is_empty", default = "Default::default")]
72 pub comment: String,
73}
74
75#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
77pub struct Common {
78 pub kind: Option<String>,
80 pub version: i64,
83 #[builder(default = "crev_common::now()")]
84 #[serde(
85 serialize_with = "as_rfc3339_fixed",
86 deserialize_with = "from_rfc3339_fixed"
87 )]
88 pub date: chrono::DateTime<FixedOffset>,
90 pub from: crate::PublicId,
92 #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
94 pub original: Option<OriginalReference>,
95}
96
97impl CommonOps for Common {
98 fn common(&self) -> &Common {
99 self
100 }
101}
102
103pub trait WithReview {
104 fn review(&self) -> &super::Review;
105}
106
107#[derive(Debug, thiserror::Error)]
108pub enum ValidationError {
109 #[error("Invalid kind: {}, expected: {}", _0.0, _0.1)]
110 InvalidKind(Box<(String, String)>),
111
112 #[error("Alternative source can't be empty")]
114 AlternativeSourceCanNotBeEmpty,
115
116 #[error("Alternative name can't be empty")]
118 AlternativeNameCanNotBeEmpty,
119
120 #[error("Issues with an empty `id` field are not allowed")]
122 IssuesWithAnEmptyIDFieldAreNotAllowed,
123
124 #[error("Advisories with no `id`s are not allowed")]
126 AdvisoriesWithNoIDSAreNotAllowed,
127
128 #[error("Advisories with an empty `id` field are not allowed")]
130 AdvisoriesWithAnEmptyIDFieldAreNotAllowed,
131}
132
133pub type ValidationResult<T> = std::result::Result<T, ValidationError>;
134
135pub trait Content: CommonOps {
143 fn validate_data(&self) -> ValidationResult<()> {
144 Ok(())
146 }
147
148 fn serialize_to(&self, fmt: &mut dyn std::fmt::Write) -> fmt::Result;
149}
150
151pub trait ContentDeserialize: Content + Sized {
152 fn deserialize_from<IO>(io: IO) -> std::result::Result<Self, Error>
153 where
154 IO: io::Read;
155}
156
157impl<T> ContentDeserialize for T
158where
159 T: serde::de::DeserializeOwned + Content + Sized,
160{
161 fn deserialize_from<IO>(io: IO) -> std::result::Result<Self, Error>
162 where
163 IO: io::Read,
164 {
165 let s: Self = serde_yaml::from_reader(io).map_err(ParseError::Proof)?;
166
167 s.validate_data()?;
168
169 Ok(s)
170 }
171}
172
173pub struct Draft {
179 pub(crate) title: String,
180 pub(crate) body: String,
181}
182
183impl Draft {
184 #[must_use]
185 pub fn title(&self) -> &str {
186 &self.title
187 }
188
189 #[must_use]
190 pub fn body(&self) -> &str {
191 &self.body
192 }
193}
194
195pub trait ContentWithDraft: Content {
199 fn to_draft(&self) -> Draft;
200
201 fn apply_draft(&self, body: &str) -> Result<Self>
202 where
203 Self: Sized;
204}
205
206pub trait ContentExt: Content {
207 fn serialize(&self) -> Result<String> {
208 let mut body = String::new();
209 self.serialize_to(&mut body)
210 .map_err(|e| crate::Error::YAMLFormat(e.to_string().into()))?;
211 Ok(body)
212 }
213
214 fn sign_by(&self, id: &crate::id::UnlockedId) -> Result<Proof> {
215 let body = self.serialize()?;
216 let signature = id.sign(body.as_bytes());
217 Ok(Proof {
218 digest: crev_common::blake2b256sum(body.as_bytes()),
219 body,
220 signature: crev_common::base64_encode(&signature),
221 common_content: self.common().clone(),
222 })
223 }
224
225 fn ensure_serializes_to_valid_proof(&self) -> Result<()> {
227 let body = self.serialize()?;
228 let signature = "somefakesignature";
229 let proof = proof::Proof {
230 digest: crev_common::blake2b256sum(body.as_bytes()),
231 body,
232 signature: crev_common::base64_encode(&signature),
233 common_content: self.common().clone(),
234 };
235 let parsed = proof::Proof::parse_from(std::io::Cursor::new(proof.to_string().as_bytes()))?;
236
237 if parsed.len() != 1 {
238 return Err(Error::SerializedTooManyProofs(parsed.len()));
239 }
240
241 Ok(())
242 }
243}
244
245impl<T> ContentExt for T where T: Content {}