1#![allow(dead_code)]
3
4use std::collections::{HashMap, HashSet};
5
6use super::marketplace_manager::get_marketplace;
7use super::schemas::MarketplaceSource;
8use super::types::KnownMarketplace;
9
10pub fn format_failure_details(failures: &[PluginFailure], include_reasons: bool) -> String {
12 let max_show = 2;
13 let details: Vec<String> = failures
14 .iter()
15 .take(max_show)
16 .map(|f| {
17 let reason = f
18 .reason
19 .as_deref()
20 .or(f.error.as_deref())
21 .unwrap_or("unknown error");
22 if include_reasons {
23 format!("{} ({})", f.name, reason)
24 } else {
25 f.name.clone()
26 }
27 })
28 .collect();
29
30 let remaining = failures.len().saturating_sub(max_show);
31 let more_text = if remaining > 0 {
32 format!(" and {} more", remaining)
33 } else {
34 String::new()
35 };
36
37 let sep = if include_reasons { "; " } else { ", " };
38 format!("{}{}", details.join(sep), more_text)
39}
40
41#[derive(Debug, Clone)]
42pub struct PluginFailure {
43 pub name: String,
44 pub reason: Option<String>,
45 pub error: Option<String>,
46}
47
48pub fn get_marketplace_source_display(source: &MarketplaceSource) -> String {
50 match source {
51 MarketplaceSource::Github { repo, .. } => repo.clone(),
52 MarketplaceSource::Url { url } => url.clone(),
53 MarketplaceSource::Git { url, .. } => url.clone(),
54 MarketplaceSource::Directory { path } => path.clone(),
55 MarketplaceSource::File { path } => path.clone(),
56 MarketplaceSource::Settings { name, .. } => format!("settings:{}", name),
57 MarketplaceSource::GitSubdir { url, .. } => url.clone(),
58 }
59}
60
61pub fn create_plugin_id(plugin_name: &str, marketplace_name: &str) -> String {
63 format!("{}@{}", plugin_name, marketplace_name)
64}
65
66pub async fn load_marketplaces_with_graceful_degradation(
68 config: &HashMap<String, KnownMarketplace>,
69) -> MarketplaceLoadResult {
70 let mut marketplaces = Vec::new();
71 let mut failures = Vec::new();
72
73 for (name, marketplace_config) in config {
74 if !is_plugin_source_allowed_by_policy(&marketplace_config.source) {
75 continue;
76 }
77
78 let data = match get_marketplace(name).await {
79 Ok(d) => Some(d),
80 Err(err) => {
81 failures.push(MarketplaceFailure {
82 name: name.clone(),
83 error: err.to_string(),
84 });
85 log::error!("Failed to load marketplace {}: {}", name, err);
86 None
87 }
88 };
89
90 marketplaces.push(MarketplaceLoadEntry {
91 name: name.clone(),
92 config: marketplace_config.clone(),
93 data,
94 });
95 }
96
97 MarketplaceLoadResult {
98 marketplaces,
99 failures,
100 }
101}
102
103#[derive(Debug)]
104pub struct MarketplaceLoadResult {
105 pub marketplaces: Vec<MarketplaceLoadEntry>,
106 pub failures: Vec<MarketplaceFailure>,
107}
108
109#[derive(Debug)]
110pub struct MarketplaceLoadEntry {
111 pub name: String,
112 pub config: KnownMarketplace,
113 pub data: Option<super::types::PluginMarketplace>,
114}
115
116#[derive(Debug)]
117pub struct MarketplaceFailure {
118 pub name: String,
119 pub error: String,
120}
121
122pub fn format_marketplace_loading_errors(
124 failures: &[MarketplaceFailure],
125 success_count: usize,
126) -> Option<MarketplaceLoadingError> {
127 if failures.is_empty() {
128 return None;
129 }
130
131 if success_count > 0 {
132 let message = if failures.len() == 1 {
133 format!(
134 "Warning: Failed to load marketplace '{}': {}",
135 failures[0].name, failures[0].error
136 )
137 } else {
138 let names: Vec<&str> = failures.iter().map(|f| f.name.as_str()).collect();
139 format!(
140 "Warning: Failed to load {} marketplaces: {}",
141 failures.len(),
142 names.join(", ")
143 )
144 };
145 Some(MarketplaceLoadingError {
146 error_type: "warning".to_string(),
147 message,
148 })
149 } else {
150 let errors: Vec<String> = failures
151 .iter()
152 .map(|f| format!("{}: {}", f.name, f.error))
153 .collect();
154 Some(MarketplaceLoadingError {
155 error_type: "error".to_string(),
156 message: format!(
157 "Failed to load all marketplaces. Errors: {}",
158 errors.join("; ")
159 ),
160 })
161 }
162}
163
164#[derive(Debug)]
165pub struct MarketplaceLoadingError {
166 pub error_type: String,
167 pub message: String,
168}
169
170pub fn get_strict_known_marketplaces() -> Option<Vec<MarketplaceSource>> {
172 None
173}
174
175pub fn get_blocked_marketplaces() -> Option<Vec<MarketplaceSource>> {
177 None
178}
179
180pub fn get_plugin_trust_message() -> Option<String> {
182 None
183}
184
185pub fn is_source_allowed_by_policy(_source: &MarketplaceSource) -> bool {
187 true
188}
189
190pub fn is_plugin_source_allowed_by_policy(_source: &super::types::PluginSource) -> bool {
192 true
193}
194
195pub fn is_source_in_blocklist(source: &MarketplaceSource) -> bool {
197 match get_blocked_marketplaces() {
198 None => false,
199 Some(blocklist) => blocklist
200 .iter()
201 .any(|blocked| sources_equal(source, blocked)),
202 }
203}
204
205fn sources_equal(a: &MarketplaceSource, b: &MarketplaceSource) -> bool {
207 match (a, b) {
208 (MarketplaceSource::Url { url: a_url }, MarketplaceSource::Url { url: b_url }) => {
209 a_url == b_url
210 }
211 (
212 MarketplaceSource::Github {
213 repo: a_repo,
214 ref_: a_ref,
215 path: a_path,
216 },
217 MarketplaceSource::Github {
218 repo: b_repo,
219 ref_: b_ref,
220 path: b_path,
221 },
222 ) => a_repo == b_repo && a_ref == b_ref && a_path == b_path,
223 (
224 MarketplaceSource::Directory { path: a_path },
225 MarketplaceSource::Directory { path: b_path },
226 ) => a_path == b_path,
227 (MarketplaceSource::File { path: a_path }, MarketplaceSource::File { path: b_path }) => {
228 a_path == b_path
229 }
230 _ => false,
231 }
232}
233
234pub fn extract_host_from_source(source: &MarketplaceSource) -> Option<String> {
236 match source {
237 MarketplaceSource::Github { .. } => Some("github.com".to_string()),
238 MarketplaceSource::Git { url, .. } | MarketplaceSource::Url { url } => url::Url::parse(url)
239 .ok()
240 .and_then(|u| u.host_str().map(|h| h.to_string())),
241 _ => None,
242 }
243}
244
245pub fn format_source_for_display(source: &MarketplaceSource) -> String {
247 match source {
248 MarketplaceSource::Github { repo, ref_: r, .. } => {
249 format!(
250 "github:{}{}",
251 repo,
252 r.as_ref().map(|r| format!("@{}", r)).unwrap_or_default()
253 )
254 }
255 MarketplaceSource::Url { url } => url.clone(),
256 MarketplaceSource::Git { url, ref_: r, .. } => {
257 format!(
258 "git:{}{}",
259 url,
260 r.as_ref().map(|r| format!("@{}", r)).unwrap_or_default()
261 )
262 }
263 MarketplaceSource::Directory { path } => format!("dir:{}", path),
264 MarketplaceSource::File { path } => format!("file:{}", path),
265 MarketplaceSource::Settings { name, plugins } => {
266 format!(
267 "settings:{} ({} plugin{})",
268 name,
269 plugins.len(),
270 if plugins.len() == 1 { "" } else { "s" }
271 )
272 }
273 MarketplaceSource::GitSubdir { url, path, .. } => {
274 format!("git-subdir:{}:{}", url, path)
275 }
276 }
277}