use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::{ReviewActor, ReviewRequestId, ReviewTeam};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ReviewTarget {
Actor(ReviewActor),
Team(ReviewTeam),
Unavailable,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ReviewRequestTarget {
User(String),
Bot(String),
Team(String),
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ReviewRequestTargetInspection {
Matching(ReviewTarget),
KindMismatch(ReviewTarget),
NotResolvable,
}
impl ReviewRequestTargetInspection {
#[must_use]
pub fn from_observation(requested: &ReviewRequestTarget, observed: ReviewTarget) -> Self {
let matching = match (requested, &observed) {
(
ReviewRequestTarget::User(_),
ReviewTarget::Actor(ReviewActor {
kind: super::ReviewActorKind::User | super::ReviewActorKind::EnterpriseUser,
..
}),
)
| (
ReviewRequestTarget::Bot(_),
ReviewTarget::Actor(ReviewActor {
kind: super::ReviewActorKind::Bot,
..
}),
)
| (ReviewRequestTarget::Team(_), ReviewTarget::Team(_)) => true,
(_, ReviewTarget::Unavailable) => return Self::NotResolvable,
_ => false,
};
if matching {
Self::Matching(observed)
} else {
Self::KindMismatch(observed)
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ReviewRequest {
pub id: ReviewRequestId,
pub target: ReviewTarget,
pub request_target: Option<ReviewRequestTarget>,
pub requested_at: Option<DateTime<Utc>>,
pub as_code_owner: bool,
}
#[cfg(test)]
mod tests {
use chrono::TimeZone;
use super::*;
use crate::{ReviewActorId, ReviewActorKind, ReviewTeamId, ReviewTeamKind};
fn actor(kind: ReviewActorKind) -> ReviewTarget {
ReviewTarget::Actor(ReviewActor {
id: ReviewActorId::new("actor").expect("actor id"),
login: "reviewer".to_owned(),
kind,
})
}
#[test]
fn inspection_classifies_actual_actor_kinds() {
for kind in [ReviewActorKind::User, ReviewActorKind::EnterpriseUser] {
let observed = actor(kind);
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::User("reviewer".to_owned()),
observed.clone(),
),
ReviewRequestTargetInspection::Matching(observed)
);
}
for kind in [
ReviewActorKind::Bot,
ReviewActorKind::Placeholder,
ReviewActorKind::Organization,
] {
let observed = actor(kind);
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::User("reviewer".to_owned()),
observed.clone(),
),
ReviewRequestTargetInspection::KindMismatch(observed)
);
}
let bot = actor(ReviewActorKind::Bot);
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::Bot("reviewer".to_owned()),
bot.clone(),
),
ReviewRequestTargetInspection::Matching(bot)
);
for kind in [
ReviewActorKind::User,
ReviewActorKind::EnterpriseUser,
ReviewActorKind::Placeholder,
ReviewActorKind::Organization,
] {
let observed = actor(kind);
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::Bot("reviewer".to_owned()),
observed.clone(),
),
ReviewRequestTargetInspection::KindMismatch(observed)
);
}
}
#[test]
fn inspection_classifies_actor_team_and_unavailable_boundaries() {
let team = ReviewTarget::Team(ReviewTeam {
id: ReviewTeamId::new("team").expect("team id"),
slug: "maintainers".to_owned(),
name: "Maintainers".to_owned(),
kind: ReviewTeamKind::Organization,
});
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::Team("example/maintainers".to_owned()),
team.clone(),
),
ReviewRequestTargetInspection::Matching(team.clone())
);
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::Bot("maintainers".to_owned()),
team.clone(),
),
ReviewRequestTargetInspection::KindMismatch(team)
);
let user = actor(ReviewActorKind::User);
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::Team("example/maintainers".to_owned()),
user.clone(),
),
ReviewRequestTargetInspection::KindMismatch(user)
);
assert_eq!(
ReviewRequestTargetInspection::from_observation(
&ReviewRequestTarget::Team("example/maintainers".to_owned()),
ReviewTarget::Unavailable,
),
ReviewRequestTargetInspection::NotResolvable
);
}
#[test]
fn observed_target_kind_and_request_address_are_independent() {
let organization_team = ReviewRequest {
id: ReviewRequestId::new("request-organization").expect("request id"),
target: ReviewTarget::Team(ReviewTeam {
id: ReviewTeamId::new("team-organization").expect("team id"),
slug: "maintainers".to_owned(),
name: "Maintainers".to_owned(),
kind: ReviewTeamKind::Organization,
}),
request_target: None,
requested_at: None,
as_code_owner: false,
};
let enterprise_team = ReviewRequest {
id: ReviewRequestId::new("request-enterprise").expect("request id"),
target: ReviewTarget::Team(ReviewTeam {
id: ReviewTeamId::new("team-enterprise").expect("team id"),
slug: "security".to_owned(),
name: "Security".to_owned(),
kind: ReviewTeamKind::Enterprise,
}),
request_target: Some(ReviewRequestTarget::Team("security".to_owned())),
requested_at: Utc.timestamp_opt(3, 0).single(),
as_code_owner: false,
};
assert_eq!(organization_team.request_target, None);
assert_eq!(
enterprise_team.request_target,
Some(ReviewRequestTarget::Team("security".to_owned()))
);
assert_eq!(organization_team.requested_at, None);
assert_eq!(
enterprise_team.requested_at,
Utc.timestamp_opt(3, 0).single()
);
}
}