1use crate::apiversioning::Version;
5use crate::error::{CoreError, CoreResult, ErrorContext};
6use std::collections::{HashMap, HashSet};
7use std::sync::{Arc, RwLock};
8use std::time::{Duration, Instant};
9static GLOBAL_VALIDATOR: std::sync::OnceLock<Arc<EcosystemValidator>> = std::sync::OnceLock::new();
11#[derive(Debug)]
13pub struct EcosystemValidator {
14 registry: Arc<RwLock<ModuleRegistry>>,
15 compatibilitymatrix: Arc<RwLock<CompatibilityMatrix>>,
16 validation_cache: Arc<RwLock<ValidationCache>>,
17 policies: Arc<RwLock<ValidationPolicies>>,
18}
19#[allow(dead_code)]
20impl EcosystemValidator {
21 pub fn new() -> CoreResult<Self> {
23 Ok(Self {
24 registry: Arc::new(RwLock::new(ModuleRegistry::new())),
25 compatibilitymatrix: Arc::new(RwLock::new(CompatibilityMatrix::new())),
26 validation_cache: Arc::new(RwLock::new(ValidationCache::new())),
27 policies: Arc::new(RwLock::new(ValidationPolicies::default())),
28 })
29 }
30 pub fn global() -> CoreResult<Arc<Self>> {
32 Ok(GLOBAL_VALIDATOR
33 .get_or_init(|| Arc::new(Self::new().expect("Operation failed")))
34 .clone())
35 }
36 pub fn register_module(&self, module: ModuleInfo) -> CoreResult<()> {
38 let mut registry = self.registry.write().map_err(|_| {
39 CoreError::InvalidState(crate::error::ErrorContext {
40 message: "Failed to acquire registry lock".to_string(),
41 location: None,
42 cause: None,
43 })
44 })?;
45 registry.register(module)?;
46 let mut cache = self.validation_cache.write().map_err(|_| {
47 CoreError::InvalidState(ErrorContext {
48 message: "Failed to acquire cache lock".to_string(),
49 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
50 cause: None,
51 })
52 })?;
53 cache.invalidate_module_related_cache();
54 Ok(())
55 }
56 pub fn validate_ecosystem(&self) -> CoreResult<EcosystemValidationResult> {
58 let start_time = Instant::now();
59 {
60 let cache = self.validation_cache.read().map_err(|_| {
61 CoreError::InvalidState(ErrorContext {
62 message: "Failed to acquire cache lock".to_string(),
63 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
64 cause: None,
65 })
66 })?;
67 if let Some(cachedresult) = cache.get_ecosystem_validation() {
68 if cachedresult.is_recent(Duration::from_secs(300)) {
69 return Ok(cachedresult.result.clone());
70 }
71 }
72 }
73 let registry = self.registry.read().map_err(|_| {
74 CoreError::InvalidState(crate::error::ErrorContext {
75 message: "Failed to acquire registry lock".to_string(),
76 location: None,
77 cause: None,
78 })
79 })?;
80 let policies = self.policies.read().map_err(|_| {
81 CoreError::InvalidState(ErrorContext {
82 message: "Failed to acquire policies lock".to_string(),
83 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
84 cause: None,
85 })
86 })?;
87 let mut result = EcosystemValidationResult::new();
88 for module in registry.all_modules() {
89 let moduleresult = self.validate_module_internal(®istry, module, &policies)?;
90 result.add_moduleresult(module.name.clone(), moduleresult);
91 }
92 let compatibilityresult = self.validate_inter_module_compatibility(®istry, &policies)?;
93 result.add_compatibilityresult(compatibilityresult);
94 let api_stabilityresult = self.validate_api_stability(®istry, &policies)?;
95 result.add_api_stabilityresult(api_stabilityresult);
96 let version_consistencyresult = self.validate_version_consistency(®istry)?;
97 result.add_version_consistencyresult(version_consistencyresult);
98 result.validation_time = start_time.elapsed();
99 result.timestamp = Instant::now();
100 {
101 let mut cache = self.validation_cache.write().map_err(|_| {
102 CoreError::InvalidState(ErrorContext {
103 message: "Failed to acquire cache lock".to_string(),
104 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
105 cause: None,
106 })
107 })?;
108 cache.cache_ecosystem_validation(result.clone());
109 }
110 Ok(result)
111 }
112 pub fn validate_module(&self, modulename: &str) -> CoreResult<ModuleValidationResult> {
114 let registry = self.registry.read().map_err(|_| {
115 CoreError::InvalidState(crate::error::ErrorContext {
116 message: "Failed to acquire registry lock".to_string(),
117 location: None,
118 cause: None,
119 })
120 })?;
121 let policies = self.policies.read().map_err(|_| {
122 CoreError::InvalidState(ErrorContext {
123 message: "Failed to acquire policies lock".to_string(),
124 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
125 cause: None,
126 })
127 })?;
128 let module = registry.get_module(modulename).ok_or_else(|| {
129 CoreError::ValidationError(ErrorContext {
130 message: format!("Module '{modulename}' not found in registry"),
131 location: None,
132 cause: None,
133 })
134 })?;
135 self.validate_module_internal(®istry, module, &policies)
136 }
137 fn validate_module_internal(
138 &self,
139 registry: &ModuleRegistry,
140 module: &ModuleInfo,
141 policies: &ValidationPolicies,
142 ) -> CoreResult<ModuleValidationResult> {
143 let mut result = ModuleValidationResult::new(module.name.clone());
144 if let Err(e) = Version::parse(&module.version) {
145 result.adderror(ValidationError::new(
146 ValidationErrorType::InvalidVersion,
147 format!("Invalid _version format '{}': {}", module.version, e),
148 ));
149 }
150 for dep in &module.dependencies {
151 let depresult = self.validate_dependencypolicies(registry, module, dep, policies)?;
152 if !depresult.is_valid() {
153 result.adderror(ValidationError::new(
154 ValidationErrorType::DependencyError,
155 format!("Dependency validation failed for '{}'", dep.name),
156 ));
157 }
158 }
159 let apiresult = validate_apisurface(&module.apisurface);
160 if !apiresult.is_valid() {
161 for breaking_change in &apiresult.breakingchanges {
162 result.adderror(ValidationError::new(
163 ValidationErrorType::ApiCompatibility,
164 breaking_change.clone(),
165 ));
166 }
167 }
168 for feature in &module.features {
169 if !self.is_feature_compatible(feature, policies)? {
170 result.add_warning(ValidationWarning::new(
171 ValidationWarningType::FeatureCompatibility,
172 format!("Feature '{feature}' may have compatibility issues"),
173 ));
174 }
175 }
176 if policies.enforce_security_checks {
177 let securityresult = self.validate_module_security(module)?;
178 if !securityresult.is_secure() {
179 result.adderror(ValidationError::new(
180 ValidationErrorType::SecurityViolation,
181 "Module failed security validation".to_string(),
182 ));
183 }
184 }
185 Ok(result)
186 }
187 fn validate_dependencypolicies(
188 &self,
189 registry: &ModuleRegistry,
190 module: &ModuleInfo,
191 dep: &DependencyInfo,
192 _policies: &ValidationPolicies,
193 ) -> CoreResult<DependencyValidationResult> {
194 let mut result = DependencyValidationResult::new(dep.name.clone());
195 if let Some(dep_module) = registry.get_module(&dep.name) {
196 let dep_version = Version::parse(&dep_module.version).map_err(|e| {
197 CoreError::ValidationError(ErrorContext {
198 message: format!("Invalid dependency version: {e}"),
199 location: None,
200 cause: None,
201 })
202 })?;
203 if !dep.version_requirement.version(&dep_version) {
204 result.add_incompatibility(format!(
205 "Version mismatch: required {}, found {}",
206 dep.version_requirement, dep_version
207 ));
208 }
209 if self.has_circular_dependency(registry, &module.name, &dep.name) {
210 result.add_incompatibility("Circular dependency detected".to_string());
211 }
212 } else {
213 result.add_incompatibility("Dependency not found in ecosystem".to_string());
214 }
215 Ok(result)
216 }
217 fn validate_inter_module_compatibility(
218 &self,
219 registry: &ModuleRegistry,
220 policies: &ValidationPolicies,
221 ) -> CoreResult<CompatibilityValidationResult> {
222 let mut result = CompatibilityValidationResult::new();
223 let modules = registry.all_modules();
224 let mut matrix = self.compatibilitymatrix.write().map_err(|_| {
225 CoreError::InvalidState(ErrorContext::new(
226 "Failed to acquire matrix lock".to_string(),
227 ))
228 })?;
229 for module_a in &modules {
230 for module_b in &modules {
231 if module_a.name != module_b.name {
232 let compatibility =
233 self.check_module_compatibility(module_a, module_b, policies)?;
234 (*matrix).b(&module_a.name, &module_b.name, compatibility.clone());
235 if !compatibility.is_compatible() {
236 result.add_incompatibility(format!(
237 "Modules '{}' and '{}' are incompatible: {}",
238 module_a.name,
239 module_b.name,
240 compatibility.reason_2()
241 ));
242 }
243 }
244 }
245 }
246 Ok(result)
247 }
248 fn check_module_compatibility(
249 &self,
250 module_a: &ModuleInfo,
251 module_b: &ModuleInfo,
252 policies: &ValidationPolicies,
253 ) -> CoreResult<ModuleCompatibility> {
254 let version_a = Version::parse(&module_a.version).map_err(|e| {
255 CoreError::ValidationError(ErrorContext {
256 message: format!("Invalid _version for module '{}': {}", module_a.name, e),
257 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
258 cause: None,
259 })
260 })?;
261 let version_b = Version::parse(&module_b.version).map_err(|e| {
262 CoreError::ValidationError(ErrorContext {
263 message: format!("Invalid _version for module '{}': {}", module_b.name, e),
264 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
265 cause: None,
266 })
267 })?;
268 if !self.areversions_compatible(&version_a, &version_b, policies) {
269 return Ok(ModuleCompatibility::incompatible(format!(
270 "Version incompatibility: {version_a} vs {version_b}"
271 )));
272 }
273 if !self.are_apis_compatible(&module_a.apisurface, &module_b.apisurface) {
274 return Ok(ModuleCompatibility::incompatible(
275 "API incompatibility".to_string(),
276 ));
277 }
278 if !self.are_features_compatible(&module_a.features, &module_b.features, policies) {
279 return Ok(ModuleCompatibility::incompatible(
280 "Feature incompatibility".to_string(),
281 ));
282 }
283 Ok(ModuleCompatibility::compatible())
284 }
285 fn validate_api_stability(
286 &self,
287 registry: &ModuleRegistry,
288 policies: &ValidationPolicies,
289 ) -> CoreResult<ApiStabilityResult> {
290 let mut result = ApiStabilityResult::new();
291 for module in registry.all_modules() {
292 let previous_apisurface = registry
293 .get_previous_version(&module.name)
294 .map(|m| &m.apisurface);
295 let stability_check = self.check_api_stability(previous_apisurface, &module.apisurface);
296 if policies.enforce_semver && !stability_check.is_stable() {
297 result.add_breaking_change(
298 module.name.clone(),
299 stability_check.breakingchanges().to_vec(),
300 );
301 }
302 if !self.is_api_properly_versioned(&module.apisurface) {
303 result.add_versioning_violation(
304 module.name.clone(),
305 "API not properly versioned".to_string(),
306 );
307 }
308 }
309 Ok(result)
310 }
311 fn validate_version_consistency(
312 &self,
313 registry: &ModuleRegistry,
314 ) -> CoreResult<VersionConsistencyResult> {
315 let mut result = VersionConsistencyResult::new();
316 let modules = registry.all_modules();
317 let mut version_map: HashMap<String, Vec<Version>> = HashMap::new();
318 for module in &modules {
319 let version = Version::parse(&module.version).map_err(|e| {
320 CoreError::ValidationError(ErrorContext {
321 message: format!("Invalid version for module '{}': {}", module.name, e),
322 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
323 cause: None,
324 })
325 })?;
326 version_map
327 .entry(module.name.clone())
328 .or_default()
329 .push(version);
330 }
331 for (modulename, versions) in version_map {
332 if versions.len() > 1 {
333 result.add_conflict(modulename, versions);
334 }
335 }
336 for module in &modules {
337 for dep in &module.dependencies {
338 if let Some(dep_module) = registry.get_module(&dep.name) {
339 let dep_version = Version::parse(&dep_module.version).map_err(|e| {
340 CoreError::ValidationError(ErrorContext {
341 message: format!(
342 "Invalid _version format for dependency {}: {}",
343 dep.name, e
344 ),
345 location: Some(crate::error::ErrorLocation::new(file!(), line!())),
346 cause: None,
347 })
348 })?;
349 if !dep.version_requirement.version(&dep_version) {
350 result.add_dependency_mismatch(
351 module.name.clone(),
352 dep.name.clone(),
353 dep.version_requirement.clone(),
354 dep_version,
355 );
356 }
357 }
358 }
359 }
360 Ok(result)
361 }
362 #[allow(dead_code)]
363 fn surface(
364 &self,
365 apisurface: &ApiSurface,
366 policies: &ValidationPolicies,
367 ) -> CoreResult<ApiValidationResult> {
368 let mut result = ApiValidationResult::new();
369 for api in &apisurface.public_apis {
370 if !self.is_api_properly_documented(api)? {
371 result.add_documentation_issue(api.name.clone());
372 }
373 if policies.enforce_semver && !self.is_api_semver_compliant(api)? {
374 result.add_semver_violation(api.name.clone());
375 }
376 }
377 for api in &apisurface.deprecated_apis {
378 if !api.has_migration_path() {
379 result.add_deprecation_issue(
380 api.name.clone(),
381 "No migration path provided".to_string(),
382 );
383 }
384 }
385 Ok(result)
386 }
387 fn validate_module_security(
388 &self,
389 module: &ModuleInfo,
390 ) -> CoreResult<SecurityValidationResult> {
391 let mut result = SecurityValidationResult::new(module.name.clone());
392 for dep in &module.dependencies {
393 match self.has_known_vulnerabilities(&dep.name) {
394 VulnerabilityStatus::Unsupported => {
395 result.add_unchecked_dependency(dep.name.clone());
396 }
397 }
398 }
399 if !module.features.contains(&"security".to_string())
400 && self.requires_security_features(module)?
401 {
402 result.add_security_issue("Module should enable security features".to_string());
403 }
404 Ok(result)
405 }
406 fn is_feature_compatible(
407 &self,
408 feature: &str,
409 policies: &ValidationPolicies,
410 ) -> CoreResult<bool> {
411 Ok(!policies.incompatible_features.contains(feature))
412 }
413 #[allow(dead_code)]
414 fn is_api_properly_documented(&self, api: &ApiInfo) -> CoreResult<bool> {
415 Ok(!api.documentation.is_empty())
416 }
417 #[allow(dead_code)]
418 fn is_api_semver_compliant(&self, api: &ApiInfo) -> CoreResult<bool> {
419 Ok(api.since_version.is_some())
420 }
421 fn requires_security_features(&self, module: &ModuleInfo) -> CoreResult<bool> {
422 Ok(module.name.contains("network") || module.name.contains("auth"))
423 }
424 pub fn updatepolicies(&self, newpolicies: ValidationPolicies) -> CoreResult<()> {
426 let mut policies = self.policies.write().map_err(|_| {
427 CoreError::InvalidState(ErrorContext::new(
428 "Failed to acquire policies lock".to_string(),
429 ))
430 })?;
431 *policies = newpolicies;
432 let mut cache = self.validation_cache.write().map_err(|_| {
433 CoreError::InvalidState(ErrorContext::new(
434 "Failed to acquire cache lock".to_string(),
435 ))
436 })?;
437 cache.clear();
438 Ok(())
439 }
440 pub fn get_ecosystem_health(&self) -> CoreResult<EcosystemHealth> {
442 let validationresult = self.validate_ecosystem()?;
443 Ok(EcosystemHealth::from_validationresult(&validationresult))
444 }
445 pub fn has_circular_dependency(
460 &self,
461 registry: &ModuleRegistry,
462 module: &str,
463 dependency: &str,
464 ) -> bool {
465 if module == dependency {
466 return true;
467 }
468 let mut visited: HashSet<&str> = HashSet::new();
469 let mut stack: Vec<&str> = vec![dependency];
470 while let Some(current) = stack.pop() {
471 if current == module {
472 return true;
473 }
474 if !visited.insert(current) {
475 continue;
476 }
477 if let Some(info) = registry.get_module(current) {
478 stack.extend(info.dependencies.iter().map(|dep| dep.name.as_str()));
479 }
480 }
481 false
482 }
483 pub fn areversions_compatible(
490 &self,
491 version_a: &Version,
492 version_b: &Version,
493 policies: &ValidationPolicies,
494 ) -> bool {
495 if policies.strict_version_matching {
496 version_a == version_b
497 } else {
498 version_a.major == version_b.major
499 }
500 }
501 pub fn are_apis_compatible(&self, api_a: &ApiSurface, apib: &ApiSurface) -> bool {
506 api_a.public_apis.iter().all(|a| {
507 apib.public_apis
508 .iter()
509 .find(|b| b.name == a.name)
510 .is_none_or(|b| b.signature == a.signature)
511 })
512 }
513 pub fn are_features_compatible(
519 &self,
520 features_a: &[String],
521 featuresb: &[String],
522 policies: &ValidationPolicies,
523 ) -> bool {
524 let has_blacklisted = |features: &[String]| {
525 features
526 .iter()
527 .any(|f| policies.incompatible_features.contains(f))
528 };
529 !has_blacklisted(features_a) && !has_blacklisted(featuresb)
530 }
531 pub fn check_api_stability(
537 &self,
538 previous: Option<&ApiSurface>,
539 current: &ApiSurface,
540 ) -> ApiStabilityCheck {
541 let Some(previous) = previous else {
542 return ApiStabilityCheck::new(true, vec![]);
543 };
544 let mut breakingchanges = Vec::new();
545 for prev_api in &previous.public_apis {
546 if !current
547 .public_apis
548 .iter()
549 .any(|api| api.name == prev_api.name)
550 {
551 breakingchanges.push(format!("API '{}' was removed", prev_api.name));
552 }
553 }
554 for current_api in ¤t.public_apis {
555 if let Some(prev_api) = previous
556 .public_apis
557 .iter()
558 .find(|api| api.name == current_api.name)
559 {
560 if current_api.signature != prev_api.signature {
561 breakingchanges.push(format!("API '{}' signature changed", current_api.name));
562 }
563 }
564 }
565 ApiStabilityCheck::new(breakingchanges.is_empty(), breakingchanges)
566 }
567 pub fn is_api_properly_versioned(&self, apisurface: &ApiSurface) -> bool {
570 apisurface
571 .public_apis
572 .iter()
573 .all(|api| api.since_version.is_some())
574 }
575 pub fn has_known_vulnerabilities(&self, _dependency_name: &str) -> VulnerabilityStatus {
584 VulnerabilityStatus::Unsupported
585 }
586}
587#[derive(Debug, Clone, Copy, PartialEq, Eq)]
591#[non_exhaustive]
592pub enum VulnerabilityStatus {
593 Unsupported,
596}
597#[derive(Debug)]
599pub struct ModuleRegistry {
600 modules: HashMap<String, ModuleInfo>,
601 previousversions: HashMap<String, ModuleInfo>,
602}
603impl ModuleRegistry {
604 pub fn new() -> Self {
605 Self {
606 modules: HashMap::new(),
607 previousversions: HashMap::new(),
608 }
609 }
610 pub fn register(&mut self, module: ModuleInfo) -> CoreResult<()> {
611 if let Some(existing) = self.modules.get(&module.name) {
612 self.previousversions
613 .insert(module.name.clone(), existing.clone());
614 }
615 self.modules.insert(module.name.clone(), module);
616 Ok(())
617 }
618 pub fn get_module(&self, name: &str) -> Option<&ModuleInfo> {
619 self.modules.get(name)
620 }
621 pub fn get_previous_version(&self, name: &str) -> Option<&ModuleInfo> {
622 self.previousversions.get(name)
623 }
624 pub fn all_modules(&self) -> Vec<&ModuleInfo> {
625 self.modules.values().collect()
626 }
627 pub fn module_count(&self) -> usize {
628 self.modules.len()
629 }
630}
631impl Default for ModuleRegistry {
632 fn default() -> Self {
633 Self::new()
634 }
635}
636#[derive(Debug, Clone)]
638pub struct ModuleInfo {
639 pub name: String,
640 pub version: String,
641 pub dependencies: Vec<DependencyInfo>,
642 pub apisurface: ApiSurface,
643 pub features: Vec<String>,
644 pub metadata: ModuleMetadata,
645}
646#[derive(Debug, Clone)]
647pub struct DependencyInfo {
648 pub name: String,
649 pub version_requirement: VersionRequirement,
650 pub optional: bool,
651}
652#[derive(Debug, Clone)]
653pub struct VersionRequirement {
654 pub requirement: String,
655}
656impl VersionRequirement {
657 pub fn new(requirement: &str) -> Self {
658 Self {
659 requirement: requirement.to_string(),
660 }
661 }
662 pub fn version(&self, version: &Version) -> bool {
670 let requirement = self.requirement.trim();
671 if requirement.is_empty() || requirement == "*" {
672 return true;
673 }
674 requirement
675 .split(',')
676 .map(str::trim)
677 .filter(|clause| !clause.is_empty())
678 .all(|clause| Self::clause_matches(clause, version))
679 }
680 fn clause_matches(clause: &str, version: &Version) -> bool {
682 let (op, rest) = Self::split_operator(clause);
683 match Version::parse(rest.trim()) {
684 Ok(required) => match op {
685 VersionOp::Caret => Self::caret_matches(&required, version),
686 VersionOp::Tilde => Self::tilde_matches(&required, version),
687 VersionOp::Exact => *version == required,
688 VersionOp::Ge => *version >= required,
689 VersionOp::Le => *version <= required,
690 VersionOp::Gt => *version > required,
691 VersionOp::Lt => *version < required,
692 },
693 Err(_) => false,
694 }
695 }
696 fn split_operator(clause: &str) -> (VersionOp, &str) {
699 if let Some(rest) = clause.strip_prefix(">=") {
700 (VersionOp::Ge, rest)
701 } else if let Some(rest) = clause.strip_prefix("<=") {
702 (VersionOp::Le, rest)
703 } else if let Some(rest) = clause.strip_prefix('>') {
704 (VersionOp::Gt, rest)
705 } else if let Some(rest) = clause.strip_prefix('<') {
706 (VersionOp::Lt, rest)
707 } else if let Some(rest) = clause.strip_prefix('=') {
708 (VersionOp::Exact, rest)
709 } else if let Some(rest) = clause.strip_prefix('^') {
710 (VersionOp::Caret, rest)
711 } else if let Some(rest) = clause.strip_prefix('~') {
712 (VersionOp::Tilde, rest)
713 } else {
714 (VersionOp::Caret, clause)
715 }
716 }
717 fn caret_matches(required: &Version, version: &Version) -> bool {
720 if version < required {
721 return false;
722 }
723 if required.major > 0 {
724 version.major == required.major
725 } else if required.minor > 0 {
726 version.major == 0 && version.minor == required.minor
727 } else {
728 version.major == 0 && version.minor == 0 && version.patch == required.patch
729 }
730 }
731 fn tilde_matches(required: &Version, version: &Version) -> bool {
733 version >= required && version.major == required.major && version.minor == required.minor
734 }
735}
736#[derive(Debug, Clone, Copy, PartialEq, Eq)]
738enum VersionOp {
739 Caret,
740 Tilde,
741 Exact,
742 Ge,
743 Le,
744 Gt,
745 Lt,
746}
747impl std::fmt::Display for VersionRequirement {
748 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749 write!(f, "{}", self.requirement)
750 }
751}
752#[derive(Debug, Clone)]
753pub struct ApiSurface {
754 pub public_apis: Vec<ApiInfo>,
755 pub deprecated_apis: Vec<DeprecatedApiInfo>,
756}
757#[derive(Debug, Clone)]
758pub struct ApiInfo {
759 pub name: String,
760 pub signature: String,
761 pub documentation: String,
762 pub since_version: Option<Version>,
763 pub stability: ApiStability,
764}
765#[derive(Debug, Clone)]
766pub struct DeprecatedApiInfo {
767 pub name: String,
768 pub deprecated_since: Version,
769 pub removal_version: Option<Version>,
770 pub migration_path: Option<String>,
771}
772impl DeprecatedApiInfo {
773 pub fn has_migration_path(&self) -> bool {
774 self.migration_path.is_some()
775 }
776}
777#[derive(Debug, Clone, Copy, PartialEq, Eq)]
778pub enum ApiStability {
779 Stable,
780 Unstable,
781 Experimental,
782}
783#[derive(Debug, Clone)]
784pub struct ModuleMetadata {
785 pub author: String,
786 pub description: String,
787 pub license: String,
788 pub repository: Option<String>,
789 pub build_time: Option<String>,
790}
791#[derive(Debug)]
793pub struct CompatibilityMatrix {
794 matrix: HashMap<(String, String), ModuleCompatibility>,
795}
796impl CompatibilityMatrix {
797 pub fn new() -> Self {
798 Self {
799 matrix: HashMap::new(),
800 }
801 }
802 pub fn b(&mut self, module_a: &str, moduleb: &str, compatibility: ModuleCompatibility) {
803 self.matrix
804 .insert((module_a.to_string(), moduleb.to_string()), compatibility);
805 }
806 pub fn b_2(&self, module_a: &str, moduleb: &str) -> Option<&ModuleCompatibility> {
807 self.matrix
808 .get(&(module_a.to_string(), moduleb.to_string()))
809 }
810}
811impl Default for CompatibilityMatrix {
812 fn default() -> Self {
813 Self::new()
814 }
815}
816#[derive(Debug, Clone)]
817pub struct ModuleCompatibility {
818 compatible: bool,
819 reason: String,
820}
821impl ModuleCompatibility {
822 pub fn compatible() -> Self {
823 Self {
824 compatible: true,
825 reason: String::new(),
826 }
827 }
828 pub fn incompatible(reason: String) -> Self {
829 Self {
830 compatible: false,
831 reason,
832 }
833 }
834}
835impl ModuleCompatibility {
836 pub fn is_compatible(&self) -> bool {
837 self.compatible
838 }
839 pub fn reason_2(&self) -> &str {
840 &self.reason
841 }
842}
843#[derive(Debug)]
845pub struct ValidationCache {
846 ecosystem_validation: Option<CachedValidationResult>,
847 module_validations: HashMap<String, CachedModuleValidation>,
848}
849impl ValidationCache {
850 pub fn new() -> Self {
851 Self {
852 ecosystem_validation: None,
853 module_validations: HashMap::new(),
854 }
855 }
856 pub fn cache_ecosystem_validation(&mut self, result: EcosystemValidationResult) {
857 self.ecosystem_validation = Some(CachedValidationResult {
858 result,
859 timestamp: Instant::now(),
860 });
861 }
862 pub fn get_ecosystem_validation(&self) -> Option<&CachedValidationResult> {
863 self.ecosystem_validation.as_ref()
864 }
865 pub fn invalidate_module_related_cache(&mut self) {
866 self.ecosystem_validation = None;
867 self.module_validations.clear();
868 }
869 pub fn clear(&mut self) {
870 self.ecosystem_validation = None;
871 self.module_validations.clear();
872 }
873}
874impl Default for ValidationCache {
875 fn default() -> Self {
876 Self::new()
877 }
878}
879#[derive(Debug, Clone)]
880pub struct CachedValidationResult {
881 pub result: EcosystemValidationResult,
882 pub timestamp: Instant,
883}
884impl CachedValidationResult {
885 pub fn age(&self, maxage: Duration) -> bool {
886 self.timestamp.elapsed() < maxage
887 }
888 pub fn is_recent(&self, maxage: Duration) -> bool {
889 self.age(maxage)
890 }
891}
892#[derive(Debug)]
893pub struct CachedModuleValidation {
894 pub result: ModuleValidationResult,
895 pub timestamp: Instant,
896}
897#[derive(Debug, Clone)]
899pub struct ValidationPolicies {
900 pub enforce_semver: bool,
901 pub strict_version_matching: bool,
902 pub enforce_security_checks: bool,
903 pub allow_deprecated_apis: bool,
904 pub max_dependency_depth: usize,
905 pub incompatible_features: HashSet<String>,
906 pub required_features: HashSet<String>,
907}
908impl Default for ValidationPolicies {
909 fn default() -> Self {
910 Self {
911 enforce_semver: true,
912 strict_version_matching: false,
913 enforce_security_checks: true,
914 allow_deprecated_apis: true,
915 max_dependency_depth: 10,
916 incompatible_features: HashSet::new(),
917 required_features: HashSet::new(),
918 }
919 }
920}
921#[derive(Debug, Clone)]
923pub struct EcosystemValidationResult {
924 pub timestamp: Instant,
925 pub validation_time: Duration,
926 pub moduleresults: HashMap<String, ModuleValidationResult>,
927 pub compatibilityresult: CompatibilityValidationResult,
928 pub api_stabilityresult: ApiStabilityResult,
929 pub version_consistencyresult: VersionConsistencyResult,
930 pub overall_status: ValidationStatus,
931}
932impl EcosystemValidationResult {
933 pub fn new() -> Self {
934 Self {
935 timestamp: Instant::now(),
936 validation_time: Duration::ZERO,
937 moduleresults: HashMap::new(),
938 compatibilityresult: CompatibilityValidationResult::new(),
939 api_stabilityresult: ApiStabilityResult::new(),
940 version_consistencyresult: VersionConsistencyResult::new(),
941 overall_status: ValidationStatus::Unknown,
942 }
943 }
944 pub fn name(&mut self, modulename: String, result: ModuleValidationResult) {
945 self.moduleresults.insert(modulename, result);
946 self.update_overall_status();
947 }
948 pub fn add_moduleresult(&mut self, modulename: String, result: ModuleValidationResult) {
949 self.moduleresults.insert(modulename, result);
950 self.update_overall_status();
951 }
952 pub fn add_compatibilityresult(&mut self, result: CompatibilityValidationResult) {
953 self.compatibilityresult = result;
954 self.update_overall_status();
955 }
956 pub fn add_api_stabilityresult(&mut self, result: ApiStabilityResult) {
957 self.api_stabilityresult = result;
958 self.update_overall_status();
959 }
960 pub fn add_version_consistencyresult(&mut self, result: VersionConsistencyResult) {
961 self.version_consistencyresult = result;
962 self.update_overall_status();
963 }
964 fn update_overall_status(&mut self) {
965 let haserrors = self.moduleresults.values().any(|r| !r.errors.is_empty())
966 || !self.compatibilityresult.incompatibilities.is_empty()
967 || !self.api_stabilityresult.breakingchanges.is_empty()
968 || !self.version_consistencyresult.conflicts.is_empty();
969 let has_warnings = self.moduleresults.values().any(|r| !r.warnings.is_empty());
970 self.overall_status = if haserrors {
971 ValidationStatus::Failed
972 } else if has_warnings {
973 ValidationStatus::Warning
974 } else {
975 ValidationStatus::Passed
976 };
977 }
978 pub fn is_valid(&self) -> bool {
979 matches!(
980 self.overall_status,
981 ValidationStatus::Passed | ValidationStatus::Warning
982 )
983 }
984 pub fn error_count(&self) -> usize {
985 self.moduleresults
986 .values()
987 .map(|r| r.errors.len())
988 .sum::<usize>()
989 + self.compatibilityresult.incompatibilities.len()
990 + self.api_stabilityresult.breakingchanges.len()
991 + self.version_consistencyresult.conflicts.len()
992 }
993 pub fn warning_count(&self) -> usize {
994 self.moduleresults.values().map(|r| r.warnings.len()).sum()
995 }
996}
997impl Default for EcosystemValidationResult {
998 fn default() -> Self {
999 Self::new()
1000 }
1001}
1002#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1003pub enum ValidationStatus {
1004 Unknown,
1005 Passed,
1006 Warning,
1007 Failed,
1008}
1009#[derive(Debug, Clone)]
1011pub struct ModuleValidationResult {
1012 pub modulename: String,
1013 pub errors: Vec<ValidationError>,
1014 pub warnings: Vec<ValidationWarning>,
1015 pub status: ValidationStatus,
1016}
1017impl ModuleValidationResult {
1018 pub fn new(modulename: String) -> Self {
1019 Self {
1020 modulename,
1021 errors: Vec::new(),
1022 warnings: Vec::new(),
1023 status: ValidationStatus::Unknown,
1024 }
1025 }
1026 pub fn adderror(&mut self, error: ValidationError) {
1027 self.errors.push(error);
1028 self.status = ValidationStatus::Failed;
1029 }
1030 pub fn add_warning(&mut self, warning: ValidationWarning) {
1031 self.warnings.push(warning);
1032 if self.status == ValidationStatus::Unknown {
1033 self.status = ValidationStatus::Warning;
1034 }
1035 }
1036 pub fn is_valid(&self) -> bool {
1037 self.errors.is_empty()
1038 }
1039}
1040#[derive(Debug, Clone)]
1041pub struct ValidationError {
1042 pub errortype: ValidationErrorType,
1043 pub message: String,
1044 pub context: Option<String>,
1045}
1046impl ValidationError {
1047 pub fn new(errortype: ValidationErrorType, message: String) -> Self {
1048 Self {
1049 errortype,
1050 message,
1051 context: None,
1052 }
1053 }
1054}
1055#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1056pub enum ValidationErrorType {
1057 InvalidVersion,
1058 DependencyError,
1059 ApiCompatibility,
1060 SecurityViolation,
1061 FeatureConflict,
1062}
1063#[derive(Debug, Clone)]
1064pub struct ValidationWarning {
1065 pub warningtype: ValidationWarningType,
1066 pub message: String,
1067}
1068impl ValidationWarning {
1069 pub fn new(warningtype: ValidationWarningType, message: String) -> Self {
1070 Self {
1071 warningtype,
1072 message,
1073 }
1074 }
1075}
1076#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1077pub enum ValidationWarningType {
1078 FeatureCompatibility,
1079 PerformanceImpact,
1080 DeprecationWarning,
1081}
1082#[derive(Debug, Clone)]
1084pub struct CompatibilityValidationResult {
1085 pub incompatibilities: Vec<String>,
1086}
1087impl CompatibilityValidationResult {
1088 pub fn new() -> Self {
1089 Self {
1090 incompatibilities: Vec::new(),
1091 }
1092 }
1093 pub fn add_incompatibility(&mut self, incompatibility: String) {
1094 self.incompatibilities.push(incompatibility);
1095 }
1096}
1097impl Default for CompatibilityValidationResult {
1098 fn default() -> Self {
1099 Self::new()
1100 }
1101}
1102#[derive(Debug, Clone)]
1103pub struct ApiStabilityResult {
1104 pub breakingchanges: HashMap<String, Vec<String>>,
1105 pub versioning_violations: HashMap<String, String>,
1106}
1107impl ApiStabilityResult {
1108 pub fn new() -> Self {
1109 Self {
1110 breakingchanges: HashMap::new(),
1111 versioning_violations: HashMap::new(),
1112 }
1113 }
1114 pub fn add_breaking_change(&mut self, module: String, changes: Vec<String>) {
1115 self.breakingchanges.insert(module, changes);
1116 }
1117 pub fn add_versioning_violation(&mut self, module: String, violation: String) {
1118 self.versioning_violations.insert(module, violation);
1119 }
1120}
1121impl Default for ApiStabilityResult {
1122 fn default() -> Self {
1123 Self::new()
1124 }
1125}
1126#[derive(Debug, Clone)]
1127pub struct VersionConsistencyResult {
1128 pub conflicts: HashMap<String, Vec<Version>>,
1129 pub dependency_mismatches: Vec<DependencyMismatch>,
1130}
1131impl VersionConsistencyResult {
1132 pub fn new() -> Self {
1133 Self {
1134 conflicts: HashMap::new(),
1135 dependency_mismatches: Vec::new(),
1136 }
1137 }
1138 pub fn add_conflict(&mut self, module: String, versions: Vec<Version>) {
1139 self.conflicts.insert(module, versions);
1140 }
1141 pub fn add_dependency_mismatch(
1142 &mut self,
1143 module: String,
1144 dependency: String,
1145 required: VersionRequirement,
1146 found: Version,
1147 ) {
1148 self.dependency_mismatches.push(DependencyMismatch {
1149 module,
1150 dependency,
1151 required,
1152 found,
1153 });
1154 }
1155}
1156impl Default for VersionConsistencyResult {
1157 fn default() -> Self {
1158 Self::new()
1159 }
1160}
1161#[derive(Debug, Clone)]
1162pub struct DependencyMismatch {
1163 pub module: String,
1164 pub dependency: String,
1165 pub required: VersionRequirement,
1166 pub found: Version,
1167}
1168#[derive(Debug, Clone)]
1170pub struct DependencyValidationResult {
1171 pub dependency_name: String,
1172 pub incompatibilities: Vec<String>,
1173}
1174impl DependencyValidationResult {
1175 pub fn new(modulename: String) -> Self {
1176 Self {
1177 dependency_name: modulename,
1178 incompatibilities: Vec::new(),
1179 }
1180 }
1181 pub fn add_incompatibility(&mut self, incompatibility: String) {
1182 self.incompatibilities.push(incompatibility);
1183 }
1184 pub fn is_valid(&self) -> bool {
1185 self.incompatibilities.is_empty()
1186 }
1187}
1188#[derive(Debug, Clone)]
1189pub struct ApiValidationResult {
1190 pub documentation_issues: Vec<String>,
1191 pub semver_violations: Vec<String>,
1192 pub deprecation_issues: HashMap<String, String>,
1193}
1194impl ApiValidationResult {
1195 pub fn new() -> Self {
1196 Self {
1197 documentation_issues: Vec::new(),
1198 semver_violations: Vec::new(),
1199 deprecation_issues: HashMap::new(),
1200 }
1201 }
1202 pub fn name(&mut self, apiname: String) {
1203 self.documentation_issues.push(apiname);
1204 }
1205 pub fn add_documentation_issue(&mut self, apiname: String) {
1206 self.documentation_issues.push(apiname);
1207 }
1208 pub fn name_2(&mut self, apiname: String) {
1209 self.semver_violations.push(apiname);
1210 }
1211 pub fn add_semver_violation(&mut self, apiname: String) {
1212 self.semver_violations.push(apiname);
1213 }
1214 pub fn name_3(&mut self, apiname: String, issue: String) {
1215 self.deprecation_issues.insert(apiname, issue);
1216 }
1217 pub fn add_deprecation_issue(&mut self, apiname: String, issue: String) {
1218 self.deprecation_issues.insert(apiname, issue);
1219 }
1220 pub fn is_valid(&self) -> bool {
1221 self.documentation_issues.is_empty()
1222 && self.semver_violations.is_empty()
1223 && self.deprecation_issues.is_empty()
1224 }
1225}
1226impl Default for ApiValidationResult {
1227 fn default() -> Self {
1228 Self::new()
1229 }
1230}
1231#[derive(Debug, Clone)]
1232pub struct SecurityValidationResult {
1233 pub modulename: String,
1234 pub vulnerabilities: Vec<String>,
1235 pub security_issues: Vec<String>,
1236 pub unchecked_dependencies: Vec<String>,
1241}
1242impl SecurityValidationResult {
1243 pub fn new(modulename: String) -> Self {
1244 Self {
1245 modulename,
1246 vulnerabilities: Vec::new(),
1247 security_issues: Vec::new(),
1248 unchecked_dependencies: Vec::new(),
1249 }
1250 }
1251 pub fn add_vulnerability(&mut self, vulnerability: String) {
1252 self.vulnerabilities.push(vulnerability);
1253 }
1254 pub fn add_security_issue(&mut self, issue: String) {
1255 self.security_issues.push(issue);
1256 }
1257 pub fn add_unchecked_dependency(&mut self, dependency_name: String) {
1258 self.unchecked_dependencies.push(dependency_name);
1259 }
1260 pub fn is_secure(&self) -> bool {
1264 self.vulnerabilities.is_empty() && self.security_issues.is_empty()
1265 }
1266 pub fn is_fully_verified(&self) -> bool {
1271 self.unchecked_dependencies.is_empty()
1272 }
1273}
1274#[derive(Debug, Clone)]
1275pub struct ApiStabilityCheck {
1276 pub is_stable: bool,
1277 pub breakingchanges: Vec<String>,
1278}
1279impl ApiStabilityCheck {
1280 pub fn new(is_stable: bool, breakingchanges: Vec<String>) -> Self {
1281 Self {
1282 is_stable,
1283 breakingchanges,
1284 }
1285 }
1286 pub fn is_stable(&self) -> bool {
1287 self.is_stable
1288 }
1289 pub fn breakingchanges(&self) -> &[String] {
1290 &self.breakingchanges
1291 }
1292 pub fn is_valid(&self) -> bool {
1293 self.is_stable && self.breakingchanges.is_empty()
1294 }
1295}
1296#[derive(Debug, Clone)]
1298pub struct EcosystemHealth {
1299 pub overall_status: HealthStatus,
1300 pub module_count: usize,
1301 pub error_count: usize,
1302 pub warning_count: usize,
1303 pub compatibility_score: f64,
1304 pub recommendations: Vec<String>,
1305}
1306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1307pub enum HealthStatus {
1308 Excellent,
1309 Good,
1310 Fair,
1311 Poor,
1312 Critical,
1313}
1314impl EcosystemHealth {
1315 pub fn from_validationresult(result: &EcosystemValidationResult) -> Self {
1316 let module_count = result.moduleresults.len();
1317 let error_count = result.error_count();
1318 let warning_count = result.warning_count();
1319 let compatibility_score = if module_count == 0 {
1320 1.0
1321 } else {
1322 1.0 - (error_count as f64 / (module_count as f64 * 10.0))
1323 };
1324 let overall_status = match error_count {
1325 0 => {
1326 if warning_count == 0 {
1327 HealthStatus::Excellent
1328 } else {
1329 HealthStatus::Good
1330 }
1331 }
1332 1..=5 => HealthStatus::Fair,
1333 6..=15 => HealthStatus::Poor,
1334 _ => HealthStatus::Critical,
1335 };
1336 let recommendations = Self::generate_recommendations(result);
1337 Self {
1338 overall_status,
1339 module_count,
1340 error_count,
1341 warning_count,
1342 compatibility_score: compatibility_score.clamp(0.0, 1.0),
1343 recommendations,
1344 }
1345 }
1346 fn generate_recommendations(result: &EcosystemValidationResult) -> Vec<String> {
1347 let mut recommendations = Vec::new();
1348 if result.error_count() > 0 {
1349 recommendations
1350 .push("Address validation errors before production deployment".to_string());
1351 }
1352 if !result.api_stabilityresult.breakingchanges.is_empty() {
1353 recommendations
1354 .push("Review API breaking changes and update version numbers".to_string());
1355 }
1356 if !result.version_consistencyresult.conflicts.is_empty() {
1357 recommendations.push("Resolve version conflicts between modules".to_string());
1358 }
1359 if result.warning_count() > 10 {
1360 recommendations
1361 .push("Consider addressing warnings to improve ecosystem stability".to_string());
1362 }
1363 recommendations
1364 }
1365}
1366fn validate_api_name_format(name: &str, signature: &str) -> Option<String> {
1372 if name.is_empty() {
1373 return Some("API name must not be empty".to_string());
1374 }
1375 let t = signature.trim_start();
1376 let is_function = t.starts_with("fn ") || t.contains(" fn ");
1377 if is_function {
1378 let valid = name
1379 .chars()
1380 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_');
1381 if !valid {
1382 return Some(format!(
1383 "Function '{}' must use snake_case naming convention",
1384 name
1385 ));
1386 }
1387 } else {
1388 let starts_upper = name.chars().next().is_some_and(|c| c.is_ascii_uppercase());
1389 let no_underscores = !name.contains('_');
1390 if !starts_upper || !no_underscores {
1391 return Some(format!(
1392 "Type/trait '{}' must use CamelCase naming convention",
1393 name
1394 ));
1395 }
1396 }
1397 None
1398}
1399pub fn validate_apisurface(apisurface: &ApiSurface) -> ApiStabilityCheck {
1410 let mut breaking_changes: Vec<String> = Vec::new();
1411 let public_names: HashSet<&str> = apisurface
1412 .public_apis
1413 .iter()
1414 .map(|a| a.name.as_str())
1415 .collect();
1416 let mut seen_names: HashSet<&str> = HashSet::new();
1417 for api in &apisurface.public_apis {
1418 if !seen_names.insert(api.name.as_str()) {
1419 breaking_changes.push(format!(
1420 "Duplicate API name '{}' found in public surface",
1421 api.name
1422 ));
1423 }
1424 }
1425 for api in &apisurface.public_apis {
1426 if api.name.is_empty() {
1427 breaking_changes.push("An API entry has an empty name".to_string());
1428 }
1429 if api.signature.is_empty() {
1430 breaking_changes.push(format!("API '{}' has an empty signature", api.name));
1431 }
1432 if let Some(msg) = validate_api_name_format(&api.name, &api.signature) {
1433 breaking_changes.push(msg);
1434 }
1435 if api.stability == ApiStability::Stable {
1436 if api.since_version.is_none() {
1437 breaking_changes.push(format!(
1438 "Stable API '{}' is missing a 'since_version' annotation",
1439 api.name
1440 ));
1441 }
1442 if api.documentation.is_empty() {
1443 breaking_changes.push(format!(
1444 "Stable API '{}' is missing documentation",
1445 api.name
1446 ));
1447 }
1448 }
1449 }
1450 for dep_api in &apisurface.deprecated_apis {
1451 if public_names.contains(dep_api.name.as_str()) {
1452 breaking_changes.push(format!(
1453 "Deprecated API '{}' collides with an active public API of the same name",
1454 dep_api.name
1455 ));
1456 }
1457 }
1458 let is_stable = breaking_changes.is_empty();
1459 ApiStabilityCheck::new(is_stable, breaking_changes)
1460}
1461#[allow(dead_code)]
1463pub fn initialize_ecosystem_validation() -> CoreResult<()> {
1464 let validator = EcosystemValidator::global()?;
1465 validator.register_module(create_core_module_info())?;
1466 Ok(())
1467}
1468#[allow(dead_code)]
1469fn create_core_module_info() -> ModuleInfo {
1470 ModuleInfo {
1471 name: "scirs2-core".to_string(),
1472 version: "1.0.0".to_string(),
1473 dependencies: Vec::new(),
1474 apisurface: ApiSurface {
1475 public_apis: vec![ApiInfo {
1476 name: "validate_ecosystem".to_string(),
1477 signature: "fn validate_ecosystem() -> CoreResult<EcosystemValidationResult>"
1478 .to_string(),
1479 documentation: "Validates the entire ecosystem compatibility".to_string(),
1480 since_version: Some(Version::new(1, 0, 0)),
1481 stability: ApiStability::Stable,
1482 }],
1483 deprecated_apis: Vec::new(),
1484 },
1485 features: vec!["validation".to_string(), "ecosystem".to_string()],
1486 metadata: ModuleMetadata {
1487 author: "SciRS2 Team".to_string(),
1488 description: "Core utilities for SciRS2 ecosystem".to_string(),
1489 license: "Apache-2.0".to_string(),
1490 repository: Some("https://github.com/cool-japan/scirs".to_string()),
1491 build_time: None,
1492 },
1493 }
1494}
1495
1496#[cfg(test)]
1497mod tests;