Skip to main content

kmp_memory_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
8/// of checking this at startup is that a missing capability surfaces as a
9/// message telling the operator what to update — instead of as a failure
10/// inside 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    pub fn new(
20        contract_version: u32,
21        library_version: impl Into<String>,
22        capabilities: impl IntoIterator<Item = impl Into<String>>,
23    ) -> Self {
24        Self {
25            contract_version,
26            library_version: library_version.into(),
27            capabilities: capabilities.into_iter().map(Into::into).collect(),
28        }
29    }
30
31    pub fn contract_version(&self) -> u32 {
32        self.contract_version
33    }
34
35    pub fn library_version(&self) -> &str {
36        &self.library_version
37    }
38
39    pub fn supports(&self, capability: &str) -> bool {
40        self.capabilities.contains(capability)
41    }
42
43    pub fn capabilities(&self) -> impl Iterator<Item = &str> {
44        self.capabilities.iter().map(String::as_str)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn a_report_names_its_contract_its_release_and_what_it_can_do() {
54        let report = ApiCapabilities::new(1, "0.1.0", ["wake", "ask"]);
55        assert_eq!(report.contract_version(), 1);
56        assert_eq!(report.library_version(), "0.1.0");
57        assert!(report.supports("wake"));
58        assert!(
59            !report.supports("forget"),
60            "a capability nobody declared must read as absent, not assumed"
61        );
62    }
63
64    #[test]
65    fn a_report_survives_the_wire() {
66        let report = ApiCapabilities::new(1, "0.1.0", ["wake"]);
67        let bytes = serde_json::to_vec(&report).expect("serializes");
68        assert_eq!(
69            serde_json::from_slice::<ApiCapabilities>(&bytes).expect("deserializes"),
70            report
71        );
72    }
73}