release_kit/commands/
payload.rs1use serde::Serialize;
11
12use crate::cli::payload::PayloadArgs;
13use crate::digest::Digest;
14use crate::embedded;
15use crate::error::RkError;
16use crate::output::Output;
17
18const PAYLOAD_SCHEMA: u32 = 1;
22
23#[derive(Debug, Serialize)]
25pub struct Artifact {
26 pub path: String,
28 pub sha256: Digest,
30}
31
32#[derive(Debug, Serialize)]
34pub struct Report {
35 pub release_kit_version: &'static str,
37 pub payload_schema: u32,
39 pub payload_sha256: Digest,
42 pub artifacts: Vec<Artifact>,
44}
45
46#[must_use]
48pub fn report() -> Report {
49 let artifacts: Vec<Artifact> = embedded::artifacts()
50 .into_iter()
51 .map(|(path, bytes)| Artifact {
52 path,
53 sha256: Digest::of(bytes),
54 })
55 .collect();
56 Report {
57 release_kit_version: env!("CARGO_PKG_VERSION"),
58 payload_schema: PAYLOAD_SCHEMA,
59 payload_sha256: aggregate(&artifacts),
60 artifacts,
61 }
62}
63
64fn aggregate(artifacts: &[Artifact]) -> Digest {
68 let mut lines = String::new();
69 for artifact in artifacts {
70 lines.push_str(&artifact.path);
71 lines.push('\n');
72 lines.push_str(&artifact.sha256.to_string());
73 lines.push('\n');
74 }
75 Digest::of(lines.as_bytes())
76}
77
78pub fn run(args: &PayloadArgs) -> Result<(), RkError> {
85 let out = Output::new(args.json);
86 let report = report();
87 out.result_line(format!("release-kit {}", report.release_kit_version));
88 out.result_line(format!("payload sha256 {}", report.payload_sha256));
89 for root in embedded::PAYLOAD_ROOTS {
90 let count = report
91 .artifacts
92 .iter()
93 .filter(|a| a.path == root || a.path.starts_with(&format!("{root}/")))
94 .count();
95 let noun = if count == 1 { "file" } else { "files" };
96 out.result_line(format!("{root}: {count} {noun}"));
97 }
98 out.emit(&report)
99}
100
101#[cfg(test)]
102mod tests {
103 #![allow(clippy::expect_used)]
104
105 use super::{Artifact, Report, aggregate, report};
106 use crate::digest::Digest;
107
108 #[test]
111 fn the_payload_report_schema_snapshot_holds() {
112 let fixture = Report {
113 release_kit_version: "0.0.0",
114 payload_schema: 1,
115 payload_sha256: Digest::of(b""),
116 artifacts: vec![Artifact {
117 path: "versions.toml".into(),
118 sha256: Digest::of(b""),
119 }],
120 };
121 assert_eq!(
122 serde_json::to_string(&fixture).expect("a report serializes"),
123 r#"{"release_kit_version":"0.0.0","payload_schema":1,"payload_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","artifacts":[{"path":"versions.toml","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}]}"#
124 );
125 }
126
127 #[test]
128 fn the_report_names_the_cargo_version_and_every_artifact() {
129 let report = report();
130 assert_eq!(report.release_kit_version, env!("CARGO_PKG_VERSION"));
131 assert!(!report.artifacts.is_empty());
132 assert_eq!(report.payload_sha256, aggregate(&report.artifacts));
133 }
134
135 #[test]
137 fn the_aggregate_covers_paths_and_order() {
138 let mut artifacts = report().artifacts;
139 let original = aggregate(&artifacts);
140 artifacts.reverse();
141 assert_ne!(original, aggregate(&artifacts));
142 }
143}