1use serde::de::DeserializeOwned;
21use tatara_lisp::TataraDomain;
22
23use crate::SpecError;
24
25pub trait Spec: TataraDomain + DeserializeOwned + Sized {
33 const CANONICAL_LISP: &'static str;
36
37 fn load_canonical_all() -> Result<Vec<Self>, SpecError> {
47 crate::loader::load_all::<Self>(Self::CANONICAL_LISP)
48 }
49
50 fn load_named(name: &str) -> Result<Self, SpecError>
59 where
60 Self: HasName,
61 {
62 Self::load_canonical_all()?
63 .into_iter()
64 .find(|s| s.name() == name)
65 .ok_or_else(|| {
66 SpecError::Load(format!(
67 "no ({}) with :name {name:?}",
68 Self::KEYWORD,
69 ))
70 })
71 }
72}
73
74pub trait HasName {
78 fn name(&self) -> &str;
79}
80
81impl Spec for crate::derivation::DerivationAlgorithm {
85 const CANONICAL_LISP: &'static str = crate::derivation::CPPNIX_INPUT_ADDRESSED_LISP;
86}
87impl HasName for crate::derivation::DerivationAlgorithm {
88 fn name(&self) -> &str { &self.name }
89}
90
91impl Spec for crate::flake::FlakeShape {
92 const CANONICAL_LISP: &'static str = crate::flake::CPPNIX_FLAKE_SHAPE_LISP;
93}
94impl HasName for crate::flake::FlakeShape {
95 fn name(&self) -> &str { &self.name }
96}
97
98impl Spec for crate::probe::Probe {
99 const CANONICAL_LISP: &'static str = crate::probe::CANONICAL_PROBES_LISP;
100}
101impl HasName for crate::probe::Probe {
102 fn name(&self) -> &str { &self.name }
103}
104
105impl Spec for crate::rebuild::RebuildProbe {
106 const CANONICAL_LISP: &'static str = crate::rebuild::CANONICAL_REBUILD_PROBES_LISP;
107}
108impl HasName for crate::rebuild::RebuildProbe {
109 fn name(&self) -> &str { &self.name }
110}
111
112impl Spec for crate::module_system::ModuleEvalAlgorithm {
113 const CANONICAL_LISP: &'static str = crate::module_system::CANONICAL_MODULE_SYSTEM_LISP;
114}
115impl HasName for crate::module_system::ModuleEvalAlgorithm {
116 fn name(&self) -> &str { &self.name }
117}
118
119impl Spec for crate::module_system::OptionTypeSpec {
120 const CANONICAL_LISP: &'static str = crate::module_system::CANONICAL_MODULE_SYSTEM_LISP;
121}
122impl HasName for crate::module_system::OptionTypeSpec {
123 fn name(&self) -> &str { &self.name }
124}
125
126impl Spec for crate::module_system::PriorityRank {
127 const CANONICAL_LISP: &'static str = crate::module_system::CANONICAL_MODULE_SYSTEM_LISP;
128}
129impl HasName for crate::module_system::PriorityRank {
130 fn name(&self) -> &str { &self.name }
131}
132
133impl Spec for crate::activation_script::ActivationScriptAlgorithm {
134 const CANONICAL_LISP: &'static str = crate::activation_script::CANONICAL_ACTIVATION_LISP;
135}
136impl HasName for crate::activation_script::ActivationScriptAlgorithm {
137 fn name(&self) -> &str { &self.name }
138}
139
140impl Spec for crate::fetcher::FetcherSpec {
141 const CANONICAL_LISP: &'static str = crate::fetcher::CANONICAL_FETCHERS_LISP;
142}
143impl HasName for crate::fetcher::FetcherSpec {
144 fn name(&self) -> &str { &self.name }
145}
146
147impl Spec for crate::substituter::SubstituterSpec {
148 const CANONICAL_LISP: &'static str = crate::substituter::CANONICAL_SUBSTITUTERS_LISP;
149}
150impl HasName for crate::substituter::SubstituterSpec {
151 fn name(&self) -> &str { &self.name }
152}
153
154impl Spec for crate::sandbox::SandboxSpec {
155 const CANONICAL_LISP: &'static str = crate::sandbox::CANONICAL_SANDBOX_LISP;
156}
157impl HasName for crate::sandbox::SandboxSpec {
158 fn name(&self) -> &str { &self.name }
159}
160
161impl Spec for crate::store_layout::StoreLayout {
162 const CANONICAL_LISP: &'static str = crate::store_layout::CANONICAL_STORE_LAYOUT_LISP;
163}
164impl HasName for crate::store_layout::StoreLayout {
165 fn name(&self) -> &str { &self.name }
166}
167
168impl Spec for crate::gc::GcAlgorithm {
169 const CANONICAL_LISP: &'static str = crate::gc::CANONICAL_GC_LISP;
170}
171impl HasName for crate::gc::GcAlgorithm {
172 fn name(&self) -> &str { &self.name }
173}
174
175impl Spec for crate::hash::HashAlgorithm {
176 const CANONICAL_LISP: &'static str = crate::hash::CANONICAL_HASH_LISP;
177}
178impl HasName for crate::hash::HashAlgorithm {
179 fn name(&self) -> &str { &self.name }
180}
181
182impl Spec for crate::hash::HashEncoding {
183 const CANONICAL_LISP: &'static str = crate::hash::CANONICAL_HASH_LISP;
184}
185impl HasName for crate::hash::HashEncoding {
186 fn name(&self) -> &str { &self.name }
187}
188
189impl Spec for crate::nar::NarFormat {
190 const CANONICAL_LISP: &'static str = crate::nar::CANONICAL_NAR_LISP;
191}
192impl HasName for crate::nar::NarFormat {
193 fn name(&self) -> &str { &self.name }
194}
195
196impl Spec for crate::narinfo::NarinfoFormat {
197 const CANONICAL_LISP: &'static str = crate::narinfo::CANONICAL_NARINFO_LISP;
198}
199impl HasName for crate::narinfo::NarinfoFormat {
200 fn name(&self) -> &str { &self.name }
201}
202
203impl Spec for crate::eval_cache::EvalCacheFormat {
204 const CANONICAL_LISP: &'static str = crate::eval_cache::CANONICAL_EVAL_CACHE_LISP;
205}
206impl HasName for crate::eval_cache::EvalCacheFormat {
207 fn name(&self) -> &str { &self.name }
208}
209
210impl Spec for crate::profile::ProfileFormat {
211 const CANONICAL_LISP: &'static str = crate::profile::CANONICAL_PROFILE_LISP;
212}
213impl HasName for crate::profile::ProfileFormat {
214 fn name(&self) -> &str { &self.name }
215}
216
217impl Spec for crate::realisation::RealisationFormat {
218 const CANONICAL_LISP: &'static str = crate::realisation::CANONICAL_REALISATION_LISP;
219}
220impl HasName for crate::realisation::RealisationFormat {
221 fn name(&self) -> &str { &self.name }
222}
223
224impl Spec for crate::lock_file::LockFileFormat {
225 const CANONICAL_LISP: &'static str = crate::lock_file::CANONICAL_LOCK_FILE_LISP;
226}
227impl HasName for crate::lock_file::LockFileFormat {
228 fn name(&self) -> &str { &self.name }
229}
230
231impl Spec for crate::registry::RegistryFormat {
232 const CANONICAL_LISP: &'static str = crate::registry::CANONICAL_REGISTRY_LISP;
233}
234impl HasName for crate::registry::RegistryFormat {
235 fn name(&self) -> &str { &self.name }
236}
237
238impl Spec for crate::trust_model::TrustModel {
239 const CANONICAL_LISP: &'static str = crate::trust_model::CANONICAL_TRUST_LISP;
240}
241impl HasName for crate::trust_model::TrustModel {
242 fn name(&self) -> &str { &self.name }
243}
244
245impl Spec for crate::worker_protocol::WorkerProtocol {
246 const CANONICAL_LISP: &'static str = crate::worker_protocol::CANONICAL_WORKER_PROTOCOL_LISP;
247}
248impl HasName for crate::worker_protocol::WorkerProtocol {
249 fn name(&self) -> &str { &self.name }
250}
251
252impl Spec for crate::worker_protocol::WorkerOpcode {
253 const CANONICAL_LISP: &'static str = crate::worker_protocol::CANONICAL_WORKER_PROTOCOL_LISP;
254}
255impl HasName for crate::worker_protocol::WorkerOpcode {
256 fn name(&self) -> &str { &self.name }
257}
258
259impl Spec for crate::catalog::SubstrateDomain {
260 const CANONICAL_LISP: &'static str = crate::catalog::CANONICAL_CATALOG_LISP;
261}
262impl HasName for crate::catalog::SubstrateDomain {
263 fn name(&self) -> &str { &self.name }
264}
265
266impl Spec for crate::cli_coverage::SuiCommand {
267 const CANONICAL_LISP: &'static str = crate::cli_coverage::CANONICAL_CLI_COVERAGE_LISP;
268}
269impl HasName for crate::cli_coverage::SuiCommand {
270 fn name(&self) -> &str { &self.name }
271}
272
273impl Spec for crate::store_ops::StoreSlice {
274 const CANONICAL_LISP: &'static str = crate::store_ops::CANONICAL_STORE_OPS_LISP;
275}
276impl HasName for crate::store_ops::StoreSlice {
277 fn name(&self) -> &str { &self.name }
278}
279
280impl Spec for crate::store_inventory::StoreInventoryProfile {
281 const CANONICAL_LISP: &'static str = crate::store_inventory::CANONICAL_STORE_INVENTORY_LISP;
282}
283impl HasName for crate::store_inventory::StoreInventoryProfile {
284 fn name(&self) -> &str { &self.name }
285}
286
287impl Spec for crate::store_transform::StoreTransform {
288 const CANONICAL_LISP: &'static str = crate::store_transform::CANONICAL_STORE_TRANSFORMS_LISP;
289}
290impl HasName for crate::store_transform::StoreTransform {
291 fn name(&self) -> &str { &self.name }
292}
293
294impl Spec for crate::store_recipe::StoreRecipe {
295 const CANONICAL_LISP: &'static str = crate::store_recipe::CANONICAL_STORE_RECIPES_LISP;
296}
297impl HasName for crate::store_recipe::StoreRecipe {
298 fn name(&self) -> &str { &self.name }
299}
300
301impl Spec for crate::store_query::StoreQuery {
302 const CANONICAL_LISP: &'static str = crate::store_query::CANONICAL_STORE_QUERIES_LISP;
303}
304impl HasName for crate::store_query::StoreQuery {
305 fn name(&self) -> &str { &self.name }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::probe::Probe;
312
313 #[test]
314 fn spec_load_canonical_all_works_through_trait() {
315 let probes = Probe::load_canonical_all().expect("load via trait");
316 assert!(!probes.is_empty());
317 }
318
319 #[test]
320 fn spec_load_named_finds_known_probe() {
321 let p = Probe::load_named("getflake-outPath").expect("load via trait");
322 assert_eq!(p.name(), "getflake-outPath");
323 }
324
325 #[test]
326 fn spec_load_named_errors_on_missing() {
327 let err = Probe::load_named("no-such-probe").expect_err("must error");
328 match err {
329 SpecError::Load(msg) => {
330 assert!(msg.contains("no-such-probe"));
331 assert!(msg.contains("defprobe"),
332 "error must mention the keyword");
333 }
334 _ => panic!("expected SpecError::Load"),
335 }
336 }
337
338 #[test]
339 fn spec_keyword_matches_attribute() {
340 assert_eq!(Probe::KEYWORD, "defprobe");
342 }
343
344 fn count_canonical<S: Spec>() -> usize {
347 S::load_canonical_all().map(|v| v.len()).unwrap_or(0)
348 }
349
350 #[test]
351 fn generic_count_over_spec_trait() {
352 let derivation_count = count_canonical::<crate::derivation::DerivationAlgorithm>();
353 let fetcher_count = count_canonical::<crate::fetcher::FetcherSpec>();
354 let catalog_count = count_canonical::<crate::catalog::SubstrateDomain>();
355 assert!(derivation_count >= 1);
358 assert!(fetcher_count >= 5);
359 assert!(catalog_count >= 15);
360 }
361}