1use serde::Deserialize;
10
11use crate::embedded;
12
13#[derive(Debug, Clone, Deserialize)]
16pub struct Pin {
17 pub name: String,
19 pub version: String,
21 #[serde(default)]
25 pub action: Option<String>,
26 #[serde(default)]
29 pub commit: Option<String>,
30 #[serde(default)]
34 pub ref_class: Option<String>,
35 #[serde(default)]
39 pub used_by: Vec<String>,
40 #[serde(default)]
42 pub check: Option<String>,
43}
44
45#[derive(Debug, Deserialize)]
47struct Registry {
48 tool: Vec<Pin>,
50}
51
52#[must_use]
58pub fn pins() -> Vec<Pin> {
59 parse(embedded::VERSIONS)
60}
61
62#[must_use]
66pub fn pins_for(selected: &[crate::profile::catalog::Selection]) -> Vec<Pin> {
67 let keys: Vec<String> = selected
68 .iter()
69 .filter(|selection| selection.lands())
70 .flat_map(crate::profile::catalog::pin_keys)
71 .collect();
72 pins()
73 .into_iter()
74 .filter(|pin| pin.used_by.iter().any(|user| keys.contains(user)))
75 .collect()
76}
77
78#[must_use]
80pub fn version_of(name: &str) -> Option<String> {
81 pins()
82 .into_iter()
83 .find(|pin| pin.name == name)
84 .map(|pin| pin.version)
85}
86
87fn parse(text: &str) -> Vec<Pin> {
88 toml::from_str::<Registry>(text)
89 .map(|registry| registry.tool)
90 .unwrap_or_default()
91}
92
93#[cfg(test)]
94mod tests {
95 use super::{pins, pins_for};
96
97 #[test]
101 fn the_embedded_registry_parses_with_every_field() {
102 let pins = pins();
103 assert!(!pins.is_empty(), "the registry parsed to nothing");
104 for pin in &pins {
105 assert!(!pin.version.is_empty(), "{}: no version", pin.name);
106 assert!(!pin.used_by.is_empty(), "{}: no used_by", pin.name);
107 assert!(
108 pin.check.is_some() || (pin.action.is_some() && pin.commit.is_some()),
109 "{}: no check URL and no ref to resolve",
110 pin.name
111 );
112 }
113 }
114
115 #[test]
118 fn every_used_by_entry_names_a_catalogued_capability() {
119 let drivers = crate::profile::catalog::known_drivers();
120 for pin in pins() {
121 for user in &pin.used_by {
122 let (id, driver) = user
123 .split_once('/')
124 .map_or((user.as_str(), None), |(id, driver)| (id, Some(driver)));
125 assert!(
126 crate::profile::catalog::ALL.contains(&id),
127 "{}: used_by names {user}, which is no capability",
128 pin.name
129 );
130 if let Some(driver) = driver {
131 assert!(
132 drivers.iter().any(|known| known == driver),
133 "{}: used_by names the driver {driver}, which no binding ships",
134 pin.name
135 );
136 }
137 }
138 }
139 }
140
141 #[test]
142 fn pins_filter_by_selected_capability() {
143 let params =
144 crate::landing::Params::for_test("acme/widget", Some(crate::landing::Style::Trunk));
145 let selected = crate::profile::catalog::select(
146 ¶ms,
147 &crate::profile::catalog::Availability::embedded(),
148 );
149 let rust: Vec<String> = pins_for(&selected)
150 .into_iter()
151 .map(|pin| pin.name)
152 .collect();
153 assert!(rust.contains(&"release-plz".to_owned()));
154 assert!(rust.contains(&"cargo-dist".to_owned()));
155 assert!(rust.contains(&"conventional-pre-commit".to_owned()));
156 assert!(!rust.contains(&"git-cliff".to_owned()));
157 assert!(!rust.contains(&"scorecard-action".to_owned()));
158 let guards = crate::landing::Params::for_test_release_less(
160 &[],
161 None,
162 crate::profile::ReleaseMode::None,
163 );
164 let selected = crate::profile::catalog::select(
165 &guards,
166 &crate::profile::catalog::Availability::embedded(),
167 );
168 let names: Vec<String> = pins_for(&selected)
169 .into_iter()
170 .map(|pin| pin.name)
171 .collect();
172 assert_eq!(names, ["conventional-pre-commit", "pre-commit-hooks"]);
173 }
174}