use std::error::Error; use std::fmt::Display; use crate::version::{Argument, CompatibilityRule, CompleteVersion, FeatureMatcher, Library, OSRestriction, RuleAction}; use super::SystemInfo; #[derive(Debug)] pub struct IncompatibleError { what: &'static str, reason: Option } impl Display for IncompatibleError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(reason) = self.reason.as_ref() { write!(f, "{} incompatible: {}", self.what, reason) } else { write!(f, "{} incompatible", self.what) } } } impl Error for IncompatibleError {} mod seal { pub trait CompatCheckInner { const WHAT: &'static str; fn get_rules(&self) -> Option>; fn get_incompatibility_reason(&self) -> Option<&str>; } } pub trait CompatCheck: seal::CompatCheckInner { fn rules_apply(&self, system: &SystemInfo, feature_matcher: &impl FeatureMatcher) -> Result<(), IncompatibleError> { let Some(rules) = self.get_rules() else { return Ok(()) }; let mut action = RuleAction::Disallow; fn match_os(os: &OSRestriction, system: &SystemInfo) -> bool { os.os.is_none_or(|o| system.is_our_os(o)) && os.version.as_ref().is_none_or(|v| v.is_match(system.os_version.as_str())) && os.arch.as_ref().is_none_or(|a| a.is_match(system.arch.as_str())) } for rule in rules { if rule.os.as_ref().is_none_or(|o| match_os(o, system)) && rule.features_match(feature_matcher) { action = rule.action; } } if action == RuleAction::Disallow { Err(IncompatibleError { what: Self::WHAT, reason: self.get_incompatibility_reason().map(|s| s.to_owned()) }) } else { Ok(()) } } } // trivial impl seal::CompatCheckInner for CompatibilityRule { const WHAT: &'static str = "rule"; fn get_rules(&self) -> Option> { Some(Some(self)) } fn get_incompatibility_reason(&self) -> Option<&str> { None } } impl seal::CompatCheckInner for CompleteVersion { const WHAT: &'static str = "version"; fn get_rules(&self) -> Option> { self.compatibility_rules.as_ref() } fn get_incompatibility_reason(&self) -> Option<&str> { self.incompatibility_reason.as_deref() } } impl seal::CompatCheckInner for Library { const WHAT: &'static str = "library"; fn get_rules(&self) -> Option> { self.rules.as_ref() } fn get_incompatibility_reason(&self) -> Option<&str> { None } } impl seal::CompatCheckInner for Argument { const WHAT: &'static str = "argument"; fn get_rules(&self) -> Option> { self.rules.as_ref() } fn get_incompatibility_reason(&self) -> Option<&str> { None } } impl CompatCheck for CompatibilityRule {} impl CompatCheck for CompleteVersion {} impl CompatCheck for Library {} impl CompatCheck for Argument {}