use std::collections::HashSet;
use std::fmt;
use strum::{AsRefStr, Display, EnumIter, EnumString, IntoStaticStr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AddonScope {
Global,
Addons(HashSet<String>),
AllAddons,
}
impl AddonScope {
pub fn single(addon: impl Into<String>) -> Self {
Self::Addons(std::iter::once(addon.into()).collect())
}
pub fn multiple<I, S>(addons: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self::Addons(addons.into_iter().map(Into::into).collect())
}
pub fn applies_to(&self, active_addons: &HashSet<String>) -> bool {
match self {
Self::Global => true,
Self::AllAddons => !active_addons.is_empty(),
Self::Addons(required) => !required.is_disjoint(active_addons),
}
}
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
AsRefStr, // Provides as_ref() -> &str
Display, // Provides to_string()
EnumString, // Provides from_str()
IntoStaticStr, // Provides into() -> &'static str
EnumIter, // Provides iter() over all variants
)]
#[strum(serialize_all = "snake_case")]
pub enum CoreRuleId {
UndefinedInput,
DeprecatedInput,
RequiredInput,
InputNamingConvention,
CliInputOverride,
SensitiveData,
NoDefaultValues,
RequiredProductionInputs,
}
impl CoreRuleId {
pub fn addon_scope(&self) -> AddonScope {
use CoreRuleId::*;
match self {
UndefinedInput
| DeprecatedInput
| RequiredInput
| InputNamingConvention
| CliInputOverride
| SensitiveData
| NoDefaultValues
| RequiredProductionInputs => AddonScope::Global,
}
}
pub const fn description(&self) -> &'static str {
use CoreRuleId::*;
match self {
UndefinedInput => "Checks if input references exist in the manifest or CLI inputs",
DeprecatedInput => "Warns about deprecated input names",
RequiredInput => "Ensures required inputs are provided in production environments",
InputNamingConvention => "Validates that inputs follow naming conventions",
CliInputOverride => "Warns when CLI inputs override environment values",
SensitiveData => "Detects potential sensitive data in inputs",
NoDefaultValues => "Ensures production environments don't use default values",
RequiredProductionInputs => "Ensures required inputs are present in production",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RuleIdentifier {
Core(CoreRuleId),
#[allow(dead_code)] External(String),
}
impl RuleIdentifier {
pub fn as_str(&self) -> &str {
match self {
RuleIdentifier::Core(id) => id.as_ref(),
RuleIdentifier::External(name) => name.as_str(),
}
}
pub fn is_core(&self) -> bool {
matches!(self, RuleIdentifier::Core(_))
}
pub fn is_external(&self) -> bool {
matches!(self, RuleIdentifier::External(_))
}
}
impl fmt::Display for RuleIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<CoreRuleId> for RuleIdentifier {
fn from(id: CoreRuleId) -> Self {
RuleIdentifier::Core(id)
}
}
impl AsRef<str> for RuleIdentifier {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_addon_scope_applies_to() {
let mut active = HashSet::new();
active.insert("evm".to_string());
active.insert("svm".to_string());
assert!(AddonScope::Global.applies_to(&active));
assert!(AddonScope::Global.applies_to(&HashSet::new()));
assert!(AddonScope::AllAddons.applies_to(&active));
assert!(!AddonScope::AllAddons.applies_to(&HashSet::new()));
let evm_scope = AddonScope::single("evm");
assert!(evm_scope.applies_to(&active));
let multi_scope = AddonScope::multiple(["evm"]);
assert!(multi_scope.applies_to(&active)); }
#[test]
fn test_core_rule_id_display() {
assert_eq!(CoreRuleId::UndefinedInput.to_string(), "undefined_input");
assert_eq!(CoreRuleId::SensitiveData.to_string(), "sensitive_data");
}
#[test]
fn test_rule_identifier() {
let core_id = RuleIdentifier::Core(CoreRuleId::UndefinedInput);
assert!(core_id.is_core());
assert!(!core_id.is_external());
assert_eq!(core_id.as_str(), "undefined_input");
let external_id = RuleIdentifier::External("custom_rule".to_string());
assert!(!external_id.is_core());
assert!(external_id.is_external());
assert_eq!(external_id.as_str(), "custom_rule");
}
}