use crate::fix::FixStrategyEntry;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct KonveyorRuleset {
pub name: String,
pub description: String,
pub labels: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KonveyorRule {
#[serde(rename = "ruleID")]
pub rule_id: String,
pub labels: Vec<String>,
pub effort: u32,
pub category: String,
pub description: String,
pub message: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub links: Vec<KonveyorLink>,
pub when: KonveyorCondition,
#[serde(skip)]
pub fix_strategy: Option<FixStrategyEntry>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KonveyorLink {
pub url: String,
pub title: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum KonveyorCondition {
FileContent {
#[serde(rename = "builtin.filecontent")]
filecontent: FileContentFields,
},
Json {
#[serde(rename = "builtin.json")]
json: JsonFields,
},
FrontendReferenced {
#[serde(rename = "frontend.referenced")]
referenced: FrontendReferencedFields,
},
FrontendCssClass {
#[serde(rename = "frontend.cssclass")]
cssclass: FrontendPatternFields,
},
FrontendCssVar {
#[serde(rename = "frontend.cssvar")]
cssvar: FrontendPatternFields,
},
FrontendDependency {
#[serde(rename = "frontend.dependency")]
dependency: FrontendDependencyFields,
},
JavaReferenced {
#[serde(rename = "java.referenced")]
referenced: JavaReferencedFields,
},
JavaDependency {
#[serde(rename = "java.dependency")]
dependency: JavaDependencyFields,
},
Or {
or: Vec<KonveyorCondition>,
},
And {
and: Vec<KonveyorCondition>,
},
FileContentNegated {
#[serde(rename = "not")]
negated: bool,
#[serde(rename = "builtin.filecontent")]
filecontent: FileContentFields,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FrontendPatternFields {
pub pattern: String,
#[serde(
rename = "filePattern",
skip_serializing_if = "Option::is_none",
default
)]
pub file_pattern: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FrontendDependencyFields {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub nameregex: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub upperbound: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub lowerbound: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FileContentFields {
pub pattern: String,
#[serde(rename = "filePattern")]
pub file_pattern: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct JsonFields {
pub xpath: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub filepaths: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FrontendReferencedFields {
pub pattern: String,
pub location: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub component: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub parent: Option<String>,
#[serde(rename = "notParent", skip_serializing_if = "Option::is_none", default)]
pub not_parent: Option<String>,
#[serde(
rename = "parentFrom",
skip_serializing_if = "Option::is_none",
default
)]
pub parent_from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub child: Option<String>,
#[serde(rename = "notChild", skip_serializing_if = "Option::is_none", default)]
pub not_child: Option<String>,
#[serde(
rename = "requiresChild",
skip_serializing_if = "Option::is_none",
default
)]
pub requires_child: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub from: Option<String>,
#[serde(
rename = "filePattern",
skip_serializing_if = "Option::is_none",
default
)]
pub file_pattern: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct JavaReferencedFields {
pub pattern: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub annotated: Option<JavaAnnotatedFields>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct JavaAnnotatedFields {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub pattern: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub elements: Vec<JavaAnnotationElement>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct JavaAnnotationElement {
pub name: String,
pub value: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct JavaDependencyFields {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub nameregex: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub upperbound: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub lowerbound: Option<String>,
}
pub fn extract_frontend_refs(condition: &KonveyorCondition) -> Vec<&FrontendReferencedFields> {
match condition {
KonveyorCondition::FrontendReferenced { referenced } => vec![referenced],
KonveyorCondition::Or { or } => or.iter().flat_map(extract_frontend_refs).collect(),
KonveyorCondition::And { and } => and.iter().flat_map(extract_frontend_refs).collect(),
_ => vec![],
}
}
pub fn extract_file_pattern_from_condition(condition: &KonveyorCondition) -> Option<String> {
match condition {
KonveyorCondition::FileContent { filecontent } => Some(filecontent.file_pattern.clone()),
KonveyorCondition::Or { or } => or.first().and_then(extract_file_pattern_from_condition),
_ => None,
}
}
pub fn dedup_conditions(conditions: Vec<KonveyorCondition>) -> Vec<KonveyorCondition> {
use std::collections::BTreeSet;
let mut seen = BTreeSet::new();
let mut unique = Vec::new();
for cond in conditions {
let key = serde_json::to_string(&cond).unwrap_or_default();
if seen.insert(key) {
unique.push(cond);
}
}
unique
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frontend_dependency_serializes_to_yaml() {
let condition = KonveyorCondition::FrontendDependency {
dependency: FrontendDependencyFields {
name: Some("@patternfly/react-core".into()),
nameregex: None,
upperbound: Some("5.99.99".into()),
lowerbound: None,
},
};
let yaml = serde_yaml::to_string(&condition).unwrap();
assert!(
yaml.contains("frontend.dependency"),
"Should serialize with frontend.dependency key"
);
assert!(yaml.contains("@patternfly/react-core"));
assert!(yaml.contains("5.99.99"));
assert!(!yaml.contains("nameregex"));
assert!(!yaml.contains("lowerbound"));
}
#[test]
fn test_frontend_dependency_roundtrips() {
let condition = KonveyorCondition::FrontendDependency {
dependency: FrontendDependencyFields {
name: Some("@patternfly/react-core".into()),
nameregex: None,
upperbound: Some("5.99.99".into()),
lowerbound: Some("4.0.0".into()),
},
};
let yaml = serde_yaml::to_string(&condition).unwrap();
let deserialized: KonveyorCondition = serde_yaml::from_str(&yaml).unwrap();
match deserialized {
KonveyorCondition::FrontendDependency { dependency } => {
assert_eq!(dependency.name, Some("@patternfly/react-core".into()));
assert_eq!(dependency.upperbound, Some("5.99.99".into()));
assert_eq!(dependency.lowerbound, Some("4.0.0".into()));
assert_eq!(dependency.nameregex, None);
}
_ => panic!("Should deserialize as FrontendDependency"),
}
}
#[test]
fn test_frontend_dependency_in_rule_yaml() {
let rule = KonveyorRule {
rule_id: "test-dep-rule".into(),
labels: vec!["source=test".into()],
effort: 1,
category: "mandatory".into(),
description: "Update dep".into(),
message: "Update this dependency".into(),
links: vec![],
when: KonveyorCondition::FrontendDependency {
dependency: FrontendDependencyFields {
name: Some("@patternfly/react-core".into()),
nameregex: None,
upperbound: Some("5.99.99".into()),
lowerbound: None,
},
},
fix_strategy: None,
};
let yaml = serde_yaml::to_string(&rule).unwrap();
assert!(yaml.contains("frontend.dependency:"));
assert!(
yaml.contains("name: '@patternfly/react-core'")
|| yaml.contains("name: \"@patternfly/react-core\"")
|| yaml.contains("name: '@patternfly/react-core'")
);
}
}