1#![allow(dead_code)]
3
4use std::path::{Path, PathBuf};
5
6use super::schemas::{PluginManifest, PluginMarketplaceEntry};
7use super::types::PluginMarketplace;
8
9pub struct ValidationResult {
10 pub success: bool,
11 pub errors: Vec<ValidationError>,
12 pub warnings: Vec<ValidationWarning>,
13 pub file_path: String,
14 pub file_type: String,
15}
16
17#[derive(Debug)]
18pub struct ValidationError {
19 pub path: String,
20 pub message: String,
21}
22
23#[derive(Debug)]
24pub struct ValidationWarning {
25 pub path: String,
26 pub message: String,
27}
28
29pub async fn validate_plugin_manifest(file_path: &str) -> ValidationResult {
31 let absolute_path = match std::fs::canonicalize(file_path) {
32 Ok(p) => p,
33 Err(e) => {
34 return ValidationResult {
35 success: false,
36 errors: vec![ValidationError {
37 path: "file".to_string(),
38 message: format!("File not found: {}", e),
39 }],
40 warnings: Vec::new(),
41 file_path: file_path.to_string(),
42 file_type: "plugin".to_string(),
43 };
44 }
45 };
46
47 let content = match tokio::fs::read_to_string(&absolute_path).await {
48 Ok(c) => c,
49 Err(e) => {
50 return ValidationResult {
51 success: false,
52 errors: vec![ValidationError {
53 path: "file".to_string(),
54 message: format!("Failed to read file: {}", e),
55 }],
56 warnings: Vec::new(),
57 file_path: absolute_path.to_string_lossy().to_string(),
58 file_type: "plugin".to_string(),
59 };
60 }
61 };
62
63 let parsed: serde_json::Value = match serde_json::from_str(&content) {
64 Ok(v) => v,
65 Err(e) => {
66 return ValidationResult {
67 success: false,
68 errors: vec![ValidationError {
69 path: "json".to_string(),
70 message: format!("Invalid JSON syntax: {}", e),
71 }],
72 warnings: Vec::new(),
73 file_path: absolute_path.to_string_lossy().to_string(),
74 file_type: "plugin".to_string(),
75 };
76 }
77 };
78
79 let manifest: PluginManifest = match serde_json::from_value(parsed) {
80 Ok(m) => m,
81 Err(e) => {
82 return ValidationResult {
83 success: false,
84 errors: e
85 .to_string()
86 .lines()
87 .map(|line| ValidationError {
88 path: "manifest".to_string(),
89 message: line.to_string(),
90 })
91 .collect(),
92 warnings: Vec::new(),
93 file_path: absolute_path.to_string_lossy().to_string(),
94 file_type: "plugin".to_string(),
95 };
96 }
97 };
98
99 let mut warnings = Vec::new();
100
101 if !regex::Regex::new(r"^[a-z0-9]+(-[a-z0-9]+)*$")
103 .unwrap()
104 .is_match(&manifest.name)
105 {
106 warnings.push(ValidationWarning {
107 path: "name".to_string(),
108 message: format!("Plugin name \"{}\" is not kebab-case.", manifest.name),
109 });
110 }
111
112 if manifest.version.is_none() {
114 warnings.push(ValidationWarning {
115 path: "version".to_string(),
116 message: "No version specified.".to_string(),
117 });
118 }
119
120 if manifest.description.is_none() {
122 warnings.push(ValidationWarning {
123 path: "description".to_string(),
124 message: "No description provided.".to_string(),
125 });
126 }
127
128 ValidationResult {
129 success: true,
130 errors: Vec::new(),
131 warnings,
132 file_path: absolute_path.to_string_lossy().to_string(),
133 file_type: "plugin".to_string(),
134 }
135}
136
137pub async fn validate_marketplace_manifest(file_path: &str) -> ValidationResult {
139 let absolute_path = match std::fs::canonicalize(file_path) {
140 Ok(p) => p,
141 Err(e) => {
142 return ValidationResult {
143 success: false,
144 errors: vec![ValidationError {
145 path: "file".to_string(),
146 message: format!("File not found: {}", e),
147 }],
148 warnings: Vec::new(),
149 file_path: file_path.to_string(),
150 file_type: "marketplace".to_string(),
151 };
152 }
153 };
154
155 let content = match tokio::fs::read_to_string(&absolute_path).await {
156 Ok(c) => c,
157 Err(e) => {
158 return ValidationResult {
159 success: false,
160 errors: vec![ValidationError {
161 path: "file".to_string(),
162 message: format!("Failed to read file: {}", e),
163 }],
164 warnings: Vec::new(),
165 file_path: absolute_path.to_string_lossy().to_string(),
166 file_type: "marketplace".to_string(),
167 };
168 }
169 };
170
171 let marketplace: PluginMarketplace = match serde_json::from_str(&content) {
172 Ok(m) => m,
173 Err(e) => {
174 return ValidationResult {
175 success: false,
176 errors: vec![ValidationError {
177 path: "json".to_string(),
178 message: format!("Invalid JSON syntax: {}", e),
179 }],
180 warnings: Vec::new(),
181 file_path: absolute_path.to_string_lossy().to_string(),
182 file_type: "marketplace".to_string(),
183 };
184 }
185 };
186
187 let mut warnings = Vec::new();
188
189 if marketplace.plugins.is_empty() {
191 warnings.push(ValidationWarning {
192 path: "plugins".to_string(),
193 message: "Marketplace has no plugins defined".to_string(),
194 });
195 }
196
197 ValidationResult {
198 success: true,
199 errors: Vec::new(),
200 warnings,
201 file_path: absolute_path.to_string_lossy().to_string(),
202 file_type: "marketplace".to_string(),
203 }
204}