nmbrs_workload/catalog.rs
1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Bundled-workload catalog — SRD-85.
5//!
6//! Workloads embedded into the artifact, discoverable by
7//! catalog name (`cql/keyvalue`, `examples/lfsr`). The catalog
8//! is assembled once at process startup ([`install`]) from the
9//! manifest the `nmbrs` binary's build script generates: the
10//! curated `workloads/` set (its `cql/` subtree only when a CQL
11//! engine feature is enabled, so the catalog is truthful about
12//! what *this* binary can run) plus the `examples/` and
13//! `drivers/` tiers.
14//!
15//! Visibility tiers separate the two audiences: everything in
16//! the catalog is runnable by name, but only the
17//! [`Tier::Curated`] entries are *listed* by default —
18//! `nmbrs describe workloads` shows the products, not fifty test
19//! fixtures; `--all` (or the `examples` subtopic) reveals the
20//! rest.
21//!
22//! Resolution policy lives with the resolver
23//! (`nmbrs-runtime::runner::resolve_workload`): nearest-first —
24//! the logical filesystem location is favored over the catalog
25//! by default, and a name that resolves both ways picks the
26//! local file with a LOGGED WARNING naming both candidates.
27//! Shadowing is allowed but never silent; `--strict` promotes
28//! the warning to an error.
29
30use std::sync::OnceLock;
31
32/// Visibility tier of a bundled workload.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Tier {
35 /// Curated, real-world workloads — listed by default.
36 /// Required to carry a `description:` (lint-enforced).
37 Curated,
38 /// Teaching examples and coverage workloads — bundled and
39 /// runnable (artifact smoke-testing as-is, anywhere), but
40 /// unlisted unless explicitly requested.
41 Example,
42}
43
44impl Tier {
45 pub fn as_str(self) -> &'static str {
46 match self {
47 Tier::Curated => "curated",
48 Tier::Example => "example",
49 }
50 }
51}
52
53/// One embedded workload.
54#[derive(Debug)]
55pub struct BundledWorkload {
56 /// Catalog name: `<namespace>/<stem>` (no extension), e.g.
57 /// `cql/keyvalue`, `examples/signals/lfsr`. Top-level
58 /// curated entries have no namespace.
59 pub name: &'static str,
60 /// Visibility tier.
61 pub tier: Tier,
62 /// The yaml source, embedded at build time.
63 pub source: &'static str,
64}
65
66static CATALOG: OnceLock<Vec<&'static BundledWorkload>> = OnceLock::new();
67
68/// Install the catalog from the contributing manifests. Called
69/// once at process startup (before any workload resolution);
70/// subsequent calls are ignored — the first installation wins,
71/// which keeps test harnesses that re-enter startup idempotent.
72///
73/// Panics on a duplicate catalog name across sets: two crates
74/// claiming one name is a build/packaging bug, not an operator
75/// condition.
76pub fn install(sets: &[&'static [BundledWorkload]]) {
77 let _ = CATALOG.set({
78 let mut all: Vec<&'static BundledWorkload> = sets.iter().flat_map(|s| s.iter()).collect();
79 all.sort_by_key(|w| w.name);
80 for pair in all.windows(2) {
81 assert_ne!(
82 pair[0].name, pair[1].name,
83 "bundled workload name collision across catalog sets: `{}`",
84 pair[0].name,
85 );
86 }
87 all
88 });
89}
90
91/// Exact-name lookup. No globbing, no fuzzy matching —
92/// `nmbrs describe workloads` is the discovery surface, not the
93/// resolver.
94pub fn lookup(name: &str) -> Option<&'static BundledWorkload> {
95 CATALOG
96 .get()?
97 .binary_search_by_key(&name, |w| w.name)
98 .ok()
99 .map(|idx| CATALOG.get().unwrap()[idx])
100}
101
102/// All catalog entries in name order. Empty when no catalog was
103/// installed (library consumers without the nmbrs binary's
104/// startup hook).
105pub fn iter() -> impl Iterator<Item = &'static BundledWorkload> {
106 CATALOG
107 .get()
108 .map(|v| v.as_slice())
109 .unwrap_or(&[])
110 .iter()
111 .copied()
112}
113
114/// Entries of one tier, in name order.
115pub fn iter_tier(tier: Tier) -> impl Iterator<Item = &'static BundledWorkload> {
116 iter().filter(move |w| w.tier == tier)
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 // The install-once global makes classic unit isolation
124 // awkward; these tests exercise the pure parts and a single
125 // install path. (E2e coverage drives the real assembled
126 // catalog through the nmbrs binary.)
127 static SET_A: &[BundledWorkload] = &[
128 BundledWorkload {
129 name: "alpha",
130 tier: Tier::Curated,
131 source: "description: a\n",
132 },
133 BundledWorkload {
134 name: "examples/beta",
135 tier: Tier::Example,
136 source: "# b\n",
137 },
138 ];
139
140 #[test]
141 fn install_lookup_and_tier_filter() {
142 install(&[SET_A]);
143 // Idempotent re-install is a no-op.
144 install(&[]);
145 assert!(lookup("alpha").is_some());
146 assert!(lookup("examples/beta").is_some());
147 assert!(lookup("nope").is_none());
148 let curated: Vec<_> = iter_tier(Tier::Curated).map(|w| w.name).collect();
149 assert_eq!(curated, vec!["alpha"]);
150 let all: Vec<_> = iter().map(|w| w.name).collect();
151 assert_eq!(all, vec!["alpha", "examples/beta"]);
152 }
153}