confium_coordinator/
marketplace.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct MarketplaceEntry {
8 pub manifest: crate::plugin_manifest::PluginManifest,
9 pub registry: RegistryInfo,
10 pub install: InstallInfo,
11 pub verification: VerificationInfo,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct RegistryInfo {
17 pub registry_name: String,
18 pub published_at: chrono::DateTime<chrono::Utc>,
19 pub download_count: u64,
20 pub featured: bool,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct InstallInfo {
26 pub command: String,
27 pub binary_url: Option<String>,
28 pub checksum_sha256: Option<String>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct VerificationInfo {
34 pub signed_by: String,
35 pub signature_algorithm: String,
36 pub signature_hex: String,
37 pub verified: bool,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, Default)]
42pub struct SearchQuery {
43 pub query: Option<String>,
44 pub interface: Option<String>,
45 pub algorithm: Option<String>,
46 pub min_version: Option<String>,
47}
48
49pub fn matches(entry: &MarketplaceEntry, query: &SearchQuery) -> bool {
51 if let Some(ref q) = query.query {
52 let q_lower = q.to_lowercase();
53 if !entry.manifest.name.to_lowercase().contains(&q_lower)
54 && !entry.manifest.description.to_lowercase().contains(&q_lower)
55 {
56 return false;
57 }
58 }
59 if let Some(ref iface) = query.interface {
60 if !entry.manifest.interfaces.iter().any(|i| i == iface) {
61 return false;
62 }
63 }
64 if let Some(ref alg) = query.algorithm {
65 if !entry.manifest.algorithms.iter().any(|a| a == alg) {
66 return false;
67 }
68 }
69 true
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::plugin_manifest::PluginManifest;
76
77 fn make_entry(name: &str) -> MarketplaceEntry {
78 MarketplaceEntry {
79 manifest: PluginManifest {
80 name: name.into(),
81 version: "1.0.0".into(),
82 description: "A test plugin".into(),
83 author: "Test".into(),
84 license: "MIT".into(),
85 interfaces: vec!["hash".into()],
86 dependencies: vec![],
87 algorithms: vec!["SHA-256".into()],
88 homepage: None,
89 },
90 registry: RegistryInfo {
91 registry_name: "official".into(),
92 published_at: chrono::Utc::now(),
93 download_count: 100,
94 featured: false,
95 },
96 install: InstallInfo {
97 command: "confium install test".into(),
98 binary_url: None,
99 checksum_sha256: None,
100 },
101 verification: VerificationInfo {
102 signed_by: "confium-bot".into(),
103 signature_algorithm: "Ed25519".into(),
104 signature_hex: "abc123".into(),
105 verified: true,
106 },
107 }
108 }
109
110 #[test]
111 fn entry_serializes() {
112 let entry = make_entry("test");
113 let json = serde_json::to_string(&entry).unwrap();
114 assert!(json.contains("test"));
115 assert!(json.contains("registry"));
116 }
117
118 #[test]
119 fn search_by_name() {
120 let entry = make_entry("my-hash-plugin");
121 let query = SearchQuery {
122 query: Some("hash".into()),
123 ..Default::default()
124 };
125 assert!(matches(&entry, &query));
126 }
127
128 #[test]
129 fn search_no_match() {
130 let entry = make_entry("crypto-plugin");
131 let query = SearchQuery {
132 query: Some("networking".into()),
133 ..Default::default()
134 };
135 assert!(!matches(&entry, &query));
136 }
137
138 #[test]
139 fn search_by_interface() {
140 let entry = make_entry("x");
141 let query = SearchQuery {
142 interface: Some("hash".into()),
143 ..Default::default()
144 };
145 assert!(matches(&entry, &query));
146 }
147
148 #[test]
149 fn search_by_algorithm() {
150 let entry = make_entry("x");
151 let query = SearchQuery {
152 algorithm: Some("SHA-256".into()),
153 ..Default::default()
154 };
155 assert!(matches(&entry, &query));
156 }
157
158 #[test]
159 fn search_wrong_interface() {
160 let entry = make_entry("x");
161 let query = SearchQuery {
162 interface: Some("aead".into()),
163 ..Default::default()
164 };
165 assert!(!matches(&entry, &query));
166 }
167
168 #[test]
169 fn empty_query_matches_all() {
170 let entry = make_entry("anything");
171 assert!(matches(&entry, &SearchQuery::default()));
172 }
173
174 #[test]
175 fn combined_filters() {
176 let entry = make_entry("hash-plugin");
177 let query = SearchQuery {
178 query: Some("hash".into()),
179 interface: Some("hash".into()),
180 ..Default::default()
181 };
182 assert!(matches(&entry, &query));
183 }
184}