release_kit/registry.rs
1//! The pinned-tool registry, parsed from the embedded `versions.toml`.
2//!
3//! One registry serves three readers: `rk versions` prints it raw, a
4//! landing copies the relevant pins into the record, and `rk status`
5//! compares a record's pins against it offline. Parsing happens at
6//! runtime over the embedded bytes, so what the readers see is
7//! necessarily what the binary carries.
8
9use serde::Deserialize;
10
11use crate::embedded;
12
13/// One pinned tool, with the fields the binary's readers use; the
14/// registry's prose fields stay in the raw print.
15#[derive(Debug, Clone, Deserialize)]
16pub struct Pin {
17 /// The tool's name, the key a record's `pins` map uses.
18 pub name: String,
19 /// The pinned version.
20 pub version: String,
21 /// The bindings that use the tool.
22 #[serde(default)]
23 pub used_by: Vec<String>,
24 /// The URL a freshness check queries, where one exists.
25 #[serde(default)]
26 pub check: Option<String>,
27}
28
29/// The registry's parsed shape; only the fields named here are read.
30#[derive(Debug, Deserialize)]
31struct Registry {
32 /// Every `[[tool]]` entry.
33 tool: Vec<Pin>,
34}
35
36/// Every pin the embedded registry declares, in authored order.
37///
38/// The embedded registry is authored in this repository and held valid by
39/// a test, so a parse failure is a build defect; this resolves it to an
40/// empty list rather than panicking, and the test is what catches it.
41#[must_use]
42pub fn pins() -> Vec<Pin> {
43 parse(embedded::VERSIONS)
44}
45
46/// The pins a technology's snippets use, keyed for a landing record.
47#[must_use]
48pub fn pins_for(tech: &str) -> Vec<Pin> {
49 pins()
50 .into_iter()
51 .filter(|pin| pin.used_by.iter().any(|user| user == tech))
52 .collect()
53}
54
55/// The pinned version of one tool, where the registry names it.
56#[must_use]
57pub fn version_of(name: &str) -> Option<String> {
58 pins()
59 .into_iter()
60 .find(|pin| pin.name == name)
61 .map(|pin| pin.version)
62}
63
64fn parse(text: &str) -> Vec<Pin> {
65 toml::from_str::<Registry>(text)
66 .map(|registry| registry.tool)
67 .unwrap_or_default()
68}
69
70#[cfg(test)]
71mod tests {
72 use super::{pins, pins_for};
73
74 /// The embedded registry parses, and every entry carries the fields
75 /// the readers depend on; a `versions.toml` edit that breaks parsing
76 /// fails here instead of silently emptying every reader.
77 #[test]
78 fn the_embedded_registry_parses_with_every_field() {
79 let pins = pins();
80 assert!(!pins.is_empty(), "the registry parsed to nothing");
81 for pin in &pins {
82 assert!(!pin.version.is_empty(), "{}: no version", pin.name);
83 assert!(!pin.used_by.is_empty(), "{}: no used_by", pin.name);
84 assert!(pin.check.is_some(), "{}: no check URL", pin.name);
85 }
86 }
87
88 #[test]
89 fn pins_filter_by_technology() {
90 let rust: Vec<String> = pins_for("rust").into_iter().map(|pin| pin.name).collect();
91 assert!(rust.contains(&"release-plz".to_owned()));
92 assert!(rust.contains(&"cargo-dist".to_owned()));
93 assert!(!rust.contains(&"git-cliff".to_owned()));
94 }
95}