ephemeris_release/proposal/
metadata.rs1const PREFIX: &str = "<!-- ephemeris:proposal-head=";
4const SUFFIX: &str = " -->";
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
8pub(crate) enum MarkerError {
9 #[error("the pull request body has no marker")]
11 Missing,
12 #[error("the pull request body has several markers")]
14 Several,
15 #[error("the marker value is not a 40 digit commit hash")]
17 Invalid,
18}
19
20#[derive(Clone, Debug, Eq, PartialEq)]
22pub(crate) struct ProposalMetadata {
23 pub(crate) proposal_head: String,
25}
26
27impl ProposalMetadata {
28 pub(crate) fn parse(body: &str) -> Result<Self, MarkerError> {
30 let mut markers = body
31 .lines()
32 .filter(|line| line.starts_with(PREFIX))
33 .filter_map(|line| line.strip_suffix(SUFFIX));
34 let marker = markers.next().ok_or(MarkerError::Missing)?;
35 if markers.next().is_some() {
36 return Err(MarkerError::Several);
37 }
38 let proposal_head = marker
39 .strip_prefix(PREFIX)
40 .filter(|head| valid_commit(head))
41 .ok_or(MarkerError::Invalid)?;
42 Ok(Self {
43 proposal_head: proposal_head.to_owned(),
44 })
45 }
46
47 #[must_use]
49 pub(crate) fn marker(&self) -> String {
50 format!("{PREFIX}{}{SUFFIX}", self.proposal_head)
51 }
52}
53
54fn valid_commit(value: &str) -> bool {
55 value.len() == 40 && value.bytes().all(|byte| byte.is_ascii_hexdigit())
56}
57
58#[cfg(test)]
59mod tests {
60 use super::{MarkerError, ProposalMetadata};
61
62 #[test]
63 fn proposal_metadata_requires_one_exact_commit_marker() {
64 let metadata = ProposalMetadata {
65 proposal_head: "0123456789abcdef0123456789abcdef01234567".into(),
66 };
67 let body = format!("Release proposal\n\n{}\n", metadata.marker());
68 assert_eq!(ProposalMetadata::parse(&body), Ok(metadata.clone()));
69 assert_eq!(
70 ProposalMetadata::parse("Release proposal\n"),
71 Err(MarkerError::Missing)
72 );
73 assert_eq!(
74 ProposalMetadata::parse(&format!("{}\n{}", metadata.marker(), metadata.marker())),
75 Err(MarkerError::Several)
76 );
77 assert_eq!(
78 ProposalMetadata::parse("<!-- ephemeris:proposal-head=short -->"),
79 Err(MarkerError::Invalid)
80 );
81 }
82
83 #[test]
84 fn the_two_field_marker_is_invalid() {
85 let body = "<!-- ephemeris:proposal-head=0123456789abcdef0123456789abcdef01234567 \
86 authority-head=0123456789abcdef0123456789abcdef01234567 -->";
87 assert_eq!(ProposalMetadata::parse(body), Err(MarkerError::Invalid));
88 }
89}