1use std::collections::BTreeMap;
20
21use serde::{Deserialize, Serialize};
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct IndexEntry {
26 pub name: String,
27 pub latest: String,
28 #[serde(default)]
29 pub description: String,
30 #[serde(default, rename = "publishers")]
31 pub publishers: Vec<String>,
32 #[serde(rename = "versions-url")]
33 pub versions_url: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, Default)]
38pub struct RegistryIndex {
39 #[serde(default, rename = "plugin")]
40 pub plugins: Vec<IndexEntry>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct PluginIndex {
46 pub name: String,
47 pub latest: String,
48 #[serde(default)]
49 pub description: String,
50 #[serde(default, rename = "version")]
51 pub versions: Vec<VersionEntry>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
56pub struct VersionEntry {
57 pub version: String,
58 #[serde(rename = "manifest-url")]
59 pub manifest_url: String,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Manifest {
65 pub plugin: ManifestPlugin,
66 #[serde(default)]
67 pub confium: ConfiumMeta,
68 #[serde(default)]
69 pub dependencies: BTreeMap<String, String>,
70 #[serde(default)]
71 pub interfaces: BTreeMap<String, u32>,
72 #[serde(default)]
73 pub algorithms: AlgorithmMap,
74 pub artifact: Artifact,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ManifestPlugin {
80 pub name: String,
81 pub version: String,
82 #[serde(default)]
83 pub publisher: String,
84 #[serde(default)]
85 pub license: String,
86 #[serde(default)]
87 pub homepage: String,
88 #[serde(default)]
89 pub source: String,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct ConfiumMeta {
95 #[serde(rename = "contract-version", default)]
96 pub contract_version: u32,
97 #[serde(rename = "min-runtime", default)]
98 pub min_runtime: String,
99}
100
101pub type AlgorithmMap = BTreeMap<String, Vec<String>>;
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Artifact {
108 #[serde(default)]
109 pub url: String,
110 #[serde(default)]
111 pub size: u64,
112 #[serde(default)]
113 pub sha256: String,
114 #[serde(default)]
115 pub mirrors: Vec<String>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
120pub struct TrustRoot {
121 pub name: String,
122 #[serde(rename = "key-id")]
123 pub key_id: String,
124 pub fingerprint: String,
125 #[serde(rename = "key-url")]
126 pub key_url: String,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131pub struct TrustRootsFile {
132 #[serde(rename = "min-signatures", default = "default_min_signatures")]
133 pub min_signatures: u32,
134 #[serde(default, rename = "publisher")]
135 pub publishers: Vec<TrustRoot>,
136}
137
138fn default_min_signatures() -> u32 {
139 1
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 const MASTER_FIXTURE: &str = include_str!("../../../sites/registry/index.toml");
150 const PLUGIN_FIXTURE: &str = include_str!("../../../sites/registry/plugins/botan/index.toml");
151
152 const SAMPLE_MANIFEST: &str = r#"
153[plugin]
154name = "botan"
155version = "3.2.0"
156publisher = "ribose"
157license = "BSD-2-Clause"
158
159[confium]
160contract-version = 0
161min-runtime = "0.3.0"
162
163[interfaces]
164hash = 0
165aead = 0
166
167[algorithms]
168hash = ["SHA-256", "SHA-512"]
169aead = ["AES-256-GCM"]
170
171[artifact]
172url = "https://example.com/libcfm-botan.dylib"
173size = 1234
174sha256 = "abcd"
175mirrors = ["https://mirror.example.com/x"]
176"#;
177
178 #[test]
179 fn manifest_round_trips() {
180 let manifest: Manifest = toml::from_str(SAMPLE_MANIFEST).expect("parse");
181 assert_eq!(manifest.plugin.name, "botan");
182 assert_eq!(manifest.plugin.version, "3.2.0");
183 assert_eq!(manifest.confium.contract_version, 0);
184 assert_eq!(manifest.interfaces.get("hash"), Some(&0));
185 assert_eq!(
186 manifest.algorithms.get("hash"),
187 Some(&vec!["SHA-256".to_string(), "SHA-512".to_string()])
188 );
189 assert_eq!(manifest.artifact.size, 1234);
190 assert_eq!(manifest.artifact.mirrors.len(), 1);
191 }
192
193 #[test]
194 fn registry_index_parses() {
195 let src = r#"
196[[plugin]]
197name = "botan"
198latest = "3.2.0"
199description = "Botan"
200publishers = ["ribose"]
201versions-url = "/plugins/botan/index.toml"
202"#;
203 let idx: RegistryIndex = toml::from_str(src).expect("parse");
204 assert_eq!(idx.plugins.len(), 1);
205 assert_eq!(idx.plugins[0].name, "botan");
206 assert_eq!(idx.plugins[0].publishers, vec!["ribose"]);
207 }
208
209 #[test]
210 fn trust_roots_parse_with_defaults() {
211 let src = r#"
212[[publisher]]
213name = "ribose"
214key-id = "0xABCD"
215fingerprint = "AAAA"
216key-url = "/publishers/ribose.asc"
217"#;
218 let roots: TrustRootsFile = toml::from_str(src).expect("parse");
219 assert_eq!(roots.min_signatures, 1);
220 assert_eq!(roots.publishers[0].name, "ribose");
221 assert_eq!(roots.publishers[0].key_id, "0xABCD");
222 }
223
224 #[test]
225 fn real_master_fixture_parses() {
226 let cat: RegistryIndex = toml::from_str(MASTER_FIXTURE).expect("master fixture parses");
227 let botan = cat
228 .plugins
229 .iter()
230 .find(|p| p.name == "botan")
231 .expect("botan entry present in fixture");
232 assert_eq!(botan.latest, "3.2.0");
233 assert_eq!(botan.publishers, vec!["ribose", "ni4"]);
234 assert_eq!(botan.versions_url, "/plugins/botan/index.toml");
235 }
236
237 #[test]
238 fn real_plugin_fixture_parses() {
239 let idx: PluginIndex = toml::from_str(PLUGIN_FIXTURE).expect("plugin fixture parses");
240 assert_eq!(idx.name, "botan");
241 assert_eq!(idx.latest, "3.2.0");
242 assert_eq!(idx.versions.len(), 1);
243 assert_eq!(
244 idx.versions[0].manifest_url,
245 "/plugins/botan/3.2.0/manifest.toml"
246 );
247 }
248
249 #[test]
250 fn real_plugin_fixture_resolves_latest() {
251 let idx: PluginIndex = toml::from_str(PLUGIN_FIXTURE).expect("plugin fixture parses");
252 let target = idx
253 .versions
254 .iter()
255 .find(|v| v.version == idx.latest)
256 .map(|v| v.manifest_url.as_str())
257 .expect("latest resolves");
258 assert_eq!(target, "/plugins/botan/3.2.0/manifest.toml");
259 }
260}