Skip to main content

spec_driven_docs/self_depend/
venue.rs

1//! The venue axis, and the matrix it forms with the manager axis.
2//!
3//! A venue joins the list only where this repository's own release path
4//! publishes to it: the crate release-plz publishes to the registry, the
5//! flake this repository serves at every tag, and the archives cargo-dist
6//! attaches to each forge release. Every manager and venue pair carries one
7//! verdict below, and a manual pair carries a reason from a closed set.
8
9use serde::Serialize;
10
11use crate::self_depend::manager::Manager;
12
13/// Where a release of this tool is published.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, clap::ValueEnum)]
15#[serde(rename_all = "kebab-case")]
16pub enum Venue {
17    /// The crate on crates.io, built from source by the consumer.
18    Crates,
19    /// The flake this repository serves at every tag.
20    Flake,
21    /// The prebuilt archives attached to a forge release.
22    GithubRelease,
23}
24
25impl Venue {
26    /// Every venue, in the order a manager is offered them.
27    pub const ALL: [Self; 3] = [Self::Crates, Self::Flake, Self::GithubRelease];
28
29    /// The kebab-case word the command line and the report use.
30    #[must_use]
31    pub const fn as_str(self) -> &'static str {
32        match self {
33            Self::Crates => "crates",
34            Self::Flake => "flake",
35            Self::GithubRelease => "github-release",
36        }
37    }
38}
39
40impl std::fmt::Display for Venue {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.write_str(self.as_str())
43    }
44}
45
46/// Why a pair is manual, from the closed set.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
48#[serde(rename_all = "kebab-case")]
49pub enum Reason {
50    /// No package-set attribute is known for that manager.
51    AttributeUnknown,
52    /// A source hash the tool cannot compute offline is required.
53    HashNeeded,
54    /// The manager has no backend for that venue.
55    NoBackend,
56    /// No published plugin installs the tool.
57    PluginUnknown,
58}
59
60impl Reason {
61    /// The sentence a report prints.
62    #[must_use]
63    pub const fn as_str(self) -> &'static str {
64        match self {
65            Self::AttributeUnknown => "no package-set attribute is known for this manager",
66            Self::HashNeeded => "a source hash this tool cannot compute offline is required",
67            Self::NoBackend => "the manager has no backend for this venue",
68            Self::PluginUnknown => "no published plugin installs this tool",
69        }
70    }
71}
72
73/// What one manager and venue pair can do.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
75#[serde(rename_all = "kebab-case", tag = "verdict")]
76pub enum Verdict {
77    /// The tool renders the exact fragment the pair needs.
78    Renders,
79    /// The operator wires the pair by hand, for the stated reason.
80    Manual {
81        /// Why the tool renders nothing.
82        reason: Reason,
83    },
84}
85
86/// The one verdict for a pair.
87#[must_use]
88pub const fn verdict(manager: Manager, venue: Venue) -> Verdict {
89    match (manager, venue) {
90        (Manager::Flake | Manager::Devbox, Venue::Flake)
91        | (Manager::Mise, Venue::Crates | Venue::GithubRelease) => Verdict::Renders,
92        (Manager::Flake | Manager::Devbox, Venue::Crates) => Verdict::Manual {
93            reason: Reason::AttributeUnknown,
94        },
95        (Manager::Flake | Manager::Devbox, Venue::GithubRelease) => Verdict::Manual {
96            reason: Reason::HashNeeded,
97        },
98        (Manager::Mise, Venue::Flake) => Verdict::Manual {
99            reason: Reason::NoBackend,
100        },
101        (Manager::Asdf, _) => Verdict::Manual {
102            reason: Reason::PluginUnknown,
103        },
104    }
105}
106
107/// The first venue a manager renders a fragment for, in venue order.
108#[must_use]
109pub fn default_venue(manager: Manager) -> Option<Venue> {
110    Venue::ALL
111        .into_iter()
112        .find(|venue| matches!(verdict(manager, *venue), Verdict::Renders))
113}
114
115/// One row of the matrix, as the report prints it.
116#[derive(Debug, Clone, Serialize)]
117pub struct Pair {
118    /// The manager.
119    pub manager: Manager,
120    /// The venue.
121    pub venue: Venue,
122    /// What the pair can do.
123    #[serde(flatten)]
124    pub verdict: Verdict,
125}
126
127/// Every pair with its verdict, in manager then venue order.
128#[must_use]
129pub fn matrix() -> Vec<Pair> {
130    let mut rows = Vec::new();
131    for manager in Manager::ALL {
132        for venue in Venue::ALL {
133            rows.push(Pair {
134                manager,
135                venue,
136                verdict: verdict(manager, venue),
137            });
138        }
139    }
140    rows
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    /// VERIFIES acquisition:every-pair-carries-a-verdict
148    #[test]
149    fn every_pair_carries_a_verdict_and_every_manual_pair_a_reason() {
150        let rows = matrix();
151        assert_eq!(rows.len(), Manager::ALL.len() * Venue::ALL.len());
152        for row in &rows {
153            match row.verdict {
154                Verdict::Renders => {}
155                Verdict::Manual { reason } => assert!(!reason.as_str().is_empty()),
156            }
157        }
158    }
159
160    #[test]
161    fn the_rendering_pairs_are_the_four_this_release_proves() {
162        let renders: Vec<(Manager, Venue)> = matrix()
163            .into_iter()
164            .filter(|row| row.verdict == Verdict::Renders)
165            .map(|row| (row.manager, row.venue))
166            .collect();
167        assert_eq!(
168            renders,
169            [
170                (Manager::Flake, Venue::Flake),
171                (Manager::Mise, Venue::Crates),
172                (Manager::Mise, Venue::GithubRelease),
173                (Manager::Devbox, Venue::Flake),
174            ]
175        );
176    }
177
178    #[test]
179    fn a_manager_defaults_to_its_first_rendering_venue() {
180        assert_eq!(default_venue(Manager::Flake), Some(Venue::Flake));
181        assert_eq!(default_venue(Manager::Mise), Some(Venue::Crates));
182        assert_eq!(default_venue(Manager::Devbox), Some(Venue::Flake));
183        assert_eq!(default_venue(Manager::Asdf), None);
184    }
185}