use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct OwnersOutput {
pub platform: Option<String>,
pub path: Option<String>,
pub header: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OwnerRule {
pub pattern: String,
pub owners: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub section: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Owners {
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<OwnersOutput>,
#[serde(default)]
pub rules: HashMap<String, OwnerRule>,
}
impl Owners {
#[must_use]
pub fn sorted_rules(&self) -> Vec<(&String, &OwnerRule)> {
let mut rule_entries: Vec<_> = self.rules.iter().collect();
rule_entries.sort_by(|a, b| {
let order_a = a.1.order.unwrap_or(i32::MAX);
let order_b = b.1.order.unwrap_or(i32::MAX);
order_a.cmp(&order_b).then_with(|| a.0.cmp(b.0))
});
rule_entries
}
#[must_use]
pub fn header(&self) -> Option<&str> {
self.output.as_ref().and_then(|o| o.header.as_deref())
}
#[must_use]
pub fn custom_path(&self) -> Option<&str> {
self.output.as_ref().and_then(|o| o.path.as_deref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sorted_rules() {
let mut rules = HashMap::new();
rules.insert(
"z-last".to_string(),
OwnerRule {
pattern: "*.last".to_string(),
owners: vec!["@team".to_string()],
description: None,
section: None,
order: Some(3),
},
);
rules.insert(
"a-first".to_string(),
OwnerRule {
pattern: "*.first".to_string(),
owners: vec!["@team".to_string()],
description: None,
section: None,
order: Some(1),
},
);
rules.insert(
"m-middle".to_string(),
OwnerRule {
pattern: "*.middle".to_string(),
owners: vec!["@team".to_string()],
description: None,
section: None,
order: Some(2),
},
);
let owners = Owners {
rules,
..Default::default()
};
let sorted = owners.sorted_rules();
assert_eq!(sorted.len(), 3);
assert_eq!(sorted[0].0, "a-first");
assert_eq!(sorted[1].0, "m-middle");
assert_eq!(sorted[2].0, "z-last");
}
#[test]
fn test_owners_header() {
let owners = Owners::default();
assert!(owners.header().is_none());
let owners = Owners {
output: Some(OwnersOutput {
platform: None,
path: None,
header: Some("Custom Header".to_string()),
}),
..Default::default()
};
assert_eq!(owners.header(), Some("Custom Header"));
}
#[test]
fn test_owners_custom_path() {
let owners = Owners::default();
assert!(owners.custom_path().is_none());
let owners = Owners {
output: Some(OwnersOutput {
platform: None,
path: Some("docs/CODEOWNERS".to_string()),
header: None,
}),
..Default::default()
};
assert_eq!(owners.custom_path(), Some("docs/CODEOWNERS"));
}
#[test]
fn test_owners_output_platform_string() {
let owners = Owners {
output: Some(OwnersOutput {
platform: Some("gitlab".to_string()),
path: None,
header: None,
}),
..Default::default()
};
assert_eq!(
owners.output.as_ref().unwrap().platform,
Some("gitlab".to_string())
);
}
}