Skip to main content

made_api/
api_capabilities.rs

1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5/// What an implementation says it is and what it can do.
6///
7/// Reported by the implementation, never inferred by the consumer. The point of
8/// checking this at startup is that a missing capability surfaces as a message
9/// telling the operator what to update — instead of as a failure inside
10/// whatever the consumer was doing when it first needed the capability.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ApiCapabilities {
13    contract_version: u32,
14    library_version: String,
15    capabilities: BTreeSet<String>,
16}
17
18impl ApiCapabilities {
19    #[must_use]
20    pub fn new(
21        contract_version: u32,
22        library_version: impl Into<String>,
23        capabilities: impl IntoIterator<Item = impl Into<String>>,
24    ) -> Self {
25        Self {
26            contract_version,
27            library_version: library_version.into(),
28            capabilities: capabilities.into_iter().map(Into::into).collect(),
29        }
30    }
31
32    #[must_use]
33    pub fn contract_version(&self) -> u32 {
34        self.contract_version
35    }
36
37    #[must_use]
38    pub fn library_version(&self) -> &str {
39        &self.library_version
40    }
41
42    #[must_use]
43    pub fn supports(&self, capability: &str) -> bool {
44        self.capabilities.contains(capability)
45    }
46
47    pub fn capabilities(&self) -> impl Iterator<Item = &str> {
48        self.capabilities.iter().map(String::as_str)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn a_report_names_its_contract_its_release_and_what_it_can_do() {
58        let report = ApiCapabilities::new(1, "0.1.0", ["list_ceremonies", "get_ceremony"]);
59        assert_eq!(report.contract_version(), 1);
60        assert_eq!(report.library_version(), "0.1.0");
61        assert!(report.supports("list_ceremonies"));
62        assert!(
63            !report.supports("promote_pattern"),
64            "a capability nobody declared must read as absent, not assumed"
65        );
66    }
67
68    #[test]
69    fn a_report_survives_the_wire() {
70        let report = ApiCapabilities::new(1, "0.1.0", ["get_ceremony"]);
71        let bytes = serde_json::to_vec(&report).expect("serializes");
72        assert_eq!(
73            serde_json::from_slice::<ApiCapabilities>(&bytes).expect("deserializes"),
74            report
75        );
76    }
77}