crev_data/proof/review/
code.rs

1use crate::{
2    proof, proof::content::ValidationResult, serde_content_serialize, serde_draft_serialize,
3    ParseError, Result,
4};
5use crev_common::{
6    self,
7    serde::{as_base64, from_base64},
8};
9use derive_builder::Builder;
10use proof::{CommonOps, Content};
11use serde::{Deserialize, Serialize};
12use std::{self, default::Default, fmt, path::PathBuf};
13
14const CURRENT_CODE_REVIEW_PROOF_SERIALIZATION_VERSION: i64 = -1;
15
16fn cur_version() -> i64 {
17    CURRENT_CODE_REVIEW_PROOF_SERIALIZATION_VERSION
18}
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
21pub struct File {
22    pub path: PathBuf,
23    #[serde(serialize_with = "as_base64", deserialize_with = "from_base64")]
24    pub digest: Vec<u8>,
25    #[serde(rename = "digest-type")]
26    #[serde(
27        skip_serializing_if = "proof::equals_default_digest_type",
28        default = "proof::default_digest_type"
29    )]
30    pub digest_type: String,
31}
32
33/// Body of a Code Review Proof
34#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
35// TODO: validate setters(no newlines, etc)
36// TODO: https://github.com/colin-kiegel/rust-derive-builder/issues/136
37pub struct Code {
38    #[serde(flatten)]
39    pub common: proof::Common,
40    #[serde(rename = "package")]
41    pub package: proof::PackageInfo,
42    #[serde(flatten)]
43    #[builder(default = "Default::default()")]
44    pub review: super::Review,
45    #[serde(skip_serializing_if = "String::is_empty", default = "Default::default")]
46    #[builder(default = "Default::default()")]
47    pub comment: String,
48    #[serde(
49        skip_serializing_if = "std::vec::Vec::is_empty",
50        default = "std::vec::Vec::new"
51    )]
52    #[builder(default = "Default::default()")]
53    pub files: Vec<File>,
54}
55
56impl Code {
57    pub const KIND: &'static str = "code review";
58}
59
60impl CodeBuilder {
61    pub fn from<VALUE: Into<crate::PublicId>>(&mut self, value: VALUE) -> &mut Self {
62        if let Some(ref mut common) = self.common {
63            common.from = value.into();
64        } else {
65            self.common = Some(proof::Common {
66                kind: Some(Code::KIND.into()),
67                version: cur_version(),
68                date: crev_common::now(),
69                from: value.into(),
70                original: None,
71            });
72        }
73        self
74    }
75}
76
77impl proof::CommonOps for Code {
78    fn common(&self) -> &proof::Common {
79        &self.common
80    }
81
82    fn kind(&self) -> &str {
83        // Backfill the `kind` if it is empty (legacy format)
84        self.common.kind.as_deref().unwrap_or(Self::KIND)
85    }
86}
87
88impl fmt::Display for Code {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        self.serialize_to(f).map_err(|_| fmt::Error)
91    }
92}
93
94/// Like `Code` but serializes for interactive editing
95#[derive(Clone, Debug, Serialize, Deserialize)]
96pub struct Draft {
97    review: super::Review,
98    #[serde(default = "Default::default")]
99    comment: String,
100}
101
102impl Draft {
103    pub fn parse(s: &str) -> Result<Self> {
104        Ok(serde_yaml::from_str(s).map_err(ParseError::Draft)?)
105    }
106}
107
108impl From<Code> for Draft {
109    fn from(code: Code) -> Self {
110        Draft {
111            review: code.review,
112            comment: code.comment,
113        }
114    }
115}
116
117impl proof::WithReview for Code {
118    fn review(&self) -> &super::Review {
119        &self.review
120    }
121}
122
123impl proof::content::Content for Code {
124    fn validate_data(&self) -> ValidationResult<()> {
125        self.ensure_kind_is(Code::KIND)?;
126        Ok(())
127    }
128
129    fn serialize_to(&self, fmt: &mut dyn std::fmt::Write) -> fmt::Result {
130        serde_content_serialize!(self, fmt);
131        Ok(())
132    }
133}
134
135impl proof::ContentWithDraft for Code {
136    fn to_draft(&self) -> proof::Draft {
137        proof::Draft {
138            title: format!(
139                "Code Review of {} files of {} {}",
140                self.files.len(),
141                self.package.id.id.name,
142                self.package.id.version
143            ),
144            body: Draft::from(self.clone()).to_string(),
145        }
146    }
147
148    fn apply_draft(&self, s: &str) -> Result<Self> {
149        let draft = Draft::parse(s)?;
150
151        let mut copy = self.clone();
152        copy.review = draft.review;
153        copy.comment = draft.comment;
154
155        copy.validate_data()?;
156        Ok(copy)
157    }
158}
159
160impl fmt::Display for Draft {
161    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
162        serde_draft_serialize!(self, fmt);
163        Ok(())
164    }
165}