Skip to main content

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 workflow reference — `owner/action@ref` — where the tool is a
22    /// GitHub Action. The ref here is the discovery ref a freshness check
23    /// reads; the commit below is what the workflows execute.
24    #[serde(default)]
25    pub action: Option<String>,
26    /// The immutable execution commit the workflows pin, where the tool
27    /// is an action.
28    #[serde(default)]
29    pub commit: Option<String>,
30    /// How the discovery ref moves: a moving major or minor tag, an
31    /// exact tag, or a maintained branch. Movement is an update signal,
32    /// never evidence of an attack.
33    #[serde(default)]
34    pub ref_class: Option<String>,
35    /// The bindings that use the tool.
36    #[serde(default)]
37    pub used_by: Vec<String>,
38    /// The URL a freshness check queries, where one exists.
39    #[serde(default)]
40    pub check: Option<String>,
41}
42
43/// The registry's parsed shape; only the fields named here are read.
44#[derive(Debug, Deserialize)]
45struct Registry {
46    /// Every `[[tool]]` entry.
47    tool: Vec<Pin>,
48}
49
50/// Every pin the embedded registry declares, in authored order.
51///
52/// The embedded registry is authored in this repository and held valid by
53/// a test, so a parse failure is a build defect; this resolves it to an
54/// empty list rather than panicking, and the test is what catches it.
55#[must_use]
56pub fn pins() -> Vec<Pin> {
57    parse(embedded::VERSIONS)
58}
59
60/// The pins a technology's snippets use, keyed for a landing record.
61#[must_use]
62pub fn pins_for(tech: &str) -> Vec<Pin> {
63    pins()
64        .into_iter()
65        .filter(|pin| pin.used_by.iter().any(|user| user == tech))
66        .collect()
67}
68
69/// The pinned version of one tool, where the registry names it.
70#[must_use]
71pub fn version_of(name: &str) -> Option<String> {
72    pins()
73        .into_iter()
74        .find(|pin| pin.name == name)
75        .map(|pin| pin.version)
76}
77
78fn parse(text: &str) -> Vec<Pin> {
79    toml::from_str::<Registry>(text)
80        .map(|registry| registry.tool)
81        .unwrap_or_default()
82}
83
84#[cfg(test)]
85mod tests {
86    use super::{pins, pins_for};
87
88    /// The embedded registry parses, and every entry carries the fields
89    /// the readers depend on; a `versions.toml` edit that breaks parsing
90    /// fails here instead of silently emptying every reader.
91    #[test]
92    fn the_embedded_registry_parses_with_every_field() {
93        let pins = pins();
94        assert!(!pins.is_empty(), "the registry parsed to nothing");
95        for pin in &pins {
96            assert!(!pin.version.is_empty(), "{}: no version", pin.name);
97            assert!(!pin.used_by.is_empty(), "{}: no used_by", pin.name);
98            assert!(
99                pin.check.is_some() || (pin.action.is_some() && pin.commit.is_some()),
100                "{}: no check URL and no ref to resolve",
101                pin.name
102            );
103        }
104    }
105
106    #[test]
107    fn pins_filter_by_technology() {
108        let rust: Vec<String> = pins_for("rust").into_iter().map(|pin| pin.name).collect();
109        assert!(rust.contains(&"release-plz".to_owned()));
110        assert!(rust.contains(&"cargo-dist".to_owned()));
111        assert!(!rust.contains(&"git-cliff".to_owned()));
112    }
113}