use serde::{Deserialize, Serialize};
use super::field::FieldName;
use super::item::{ItemId, ItemType};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RelationshipType {
Refines,
IsRefinedBy,
Derives,
DerivesFrom,
Satisfies,
IsSatisfiedBy,
DependsOn,
IsRequiredBy,
Justifies,
IsJustifiedBy,
Supersedes,
IsSupersededBy,
}
impl RelationshipType {
#[must_use]
pub const fn inverse(&self) -> Self {
match self {
Self::Refines => Self::IsRefinedBy,
Self::IsRefinedBy => Self::Refines,
Self::Derives => Self::DerivesFrom,
Self::DerivesFrom => Self::Derives,
Self::Satisfies => Self::IsSatisfiedBy,
Self::IsSatisfiedBy => Self::Satisfies,
Self::DependsOn => Self::IsRequiredBy,
Self::IsRequiredBy => Self::DependsOn,
Self::Justifies => Self::IsJustifiedBy,
Self::IsJustifiedBy => Self::Justifies,
Self::Supersedes => Self::IsSupersededBy,
Self::IsSupersededBy => Self::Supersedes,
}
}
#[must_use]
pub const fn is_upstream(&self) -> bool {
matches!(
self,
Self::Refines | Self::DerivesFrom | Self::Satisfies | Self::Justifies
)
}
#[must_use]
pub const fn is_downstream(&self) -> bool {
matches!(
self,
Self::IsRefinedBy | Self::Derives | Self::IsSatisfiedBy | Self::IsJustifiedBy
)
}
#[must_use]
pub const fn is_peer(&self) -> bool {
matches!(
self,
Self::DependsOn | Self::IsRequiredBy | Self::Supersedes | Self::IsSupersededBy
)
}
#[must_use]
pub const fn is_primary(&self) -> bool {
matches!(
self,
Self::Refines
| Self::DerivesFrom
| Self::Satisfies
| Self::Justifies
| Self::DependsOn
| Self::Supersedes
)
}
#[must_use]
pub const fn field_name(&self) -> FieldName {
match self {
Self::Refines => FieldName::Refines,
Self::IsRefinedBy => FieldName::IsRefinedBy,
Self::Derives => FieldName::Derives,
Self::DerivesFrom => FieldName::DerivesFrom,
Self::Satisfies => FieldName::Satisfies,
Self::IsSatisfiedBy => FieldName::IsSatisfiedBy,
Self::DependsOn => FieldName::DependsOn,
Self::IsRequiredBy => FieldName::IsRequiredBy,
Self::Justifies => FieldName::Justifies,
Self::IsJustifiedBy => FieldName::JustifiedBy,
Self::Supersedes => FieldName::Supersedes,
Self::IsSupersededBy => FieldName::SupersededBy,
}
}
}
impl std::fmt::Display for RelationshipType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.field_name().as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Relationship {
pub to: ItemId,
pub relationship_type: RelationshipType,
}
impl Relationship {
#[must_use]
pub fn new(to: ItemId, relationship_type: RelationshipType) -> Self {
Self {
to,
relationship_type,
}
}
#[must_use]
pub fn target(&self) -> &ItemId {
&self.to
}
#[must_use]
pub fn rel_type(&self) -> RelationshipType {
self.relationship_type
}
}
pub struct RelationshipRules;
impl RelationshipRules {
#[must_use]
pub fn valid_upstream_for(item_type: ItemType) -> Option<(RelationshipType, Vec<ItemType>)> {
match item_type {
ItemType::Solution => None,
ItemType::UseCase => Some((RelationshipType::Refines, vec![ItemType::Solution])),
ItemType::Scenario => Some((RelationshipType::Refines, vec![ItemType::UseCase])),
ItemType::SystemRequirement => {
Some((RelationshipType::DerivesFrom, vec![ItemType::Scenario]))
}
ItemType::SystemArchitecture => Some((
RelationshipType::Satisfies,
vec![ItemType::SystemRequirement],
)),
ItemType::HardwareRequirement => Some((
RelationshipType::DerivesFrom,
vec![ItemType::SystemArchitecture],
)),
ItemType::SoftwareRequirement => Some((
RelationshipType::DerivesFrom,
vec![ItemType::SystemArchitecture],
)),
ItemType::HardwareDetailedDesign => Some((
RelationshipType::Satisfies,
vec![ItemType::HardwareRequirement],
)),
ItemType::SoftwareDetailedDesign => Some((
RelationshipType::Satisfies,
vec![ItemType::SoftwareRequirement],
)),
ItemType::ArchitectureDecisionRecord => Some((
RelationshipType::Justifies,
vec![
ItemType::SystemArchitecture,
ItemType::SoftwareDetailedDesign,
ItemType::HardwareDetailedDesign,
],
)),
}
}
#[must_use]
pub fn valid_downstream_for(item_type: ItemType) -> Option<(RelationshipType, Vec<ItemType>)> {
match item_type {
ItemType::Solution => Some((RelationshipType::IsRefinedBy, vec![ItemType::UseCase])),
ItemType::UseCase => Some((RelationshipType::IsRefinedBy, vec![ItemType::Scenario])),
ItemType::Scenario => {
Some((RelationshipType::Derives, vec![ItemType::SystemRequirement]))
}
ItemType::SystemRequirement => Some((
RelationshipType::IsSatisfiedBy,
vec![ItemType::SystemArchitecture],
)),
ItemType::SystemArchitecture => Some((
RelationshipType::Derives,
vec![ItemType::HardwareRequirement, ItemType::SoftwareRequirement],
)),
ItemType::HardwareRequirement => Some((
RelationshipType::IsSatisfiedBy,
vec![ItemType::HardwareDetailedDesign],
)),
ItemType::SoftwareRequirement => Some((
RelationshipType::IsSatisfiedBy,
vec![ItemType::SoftwareDetailedDesign],
)),
ItemType::HardwareDetailedDesign | ItemType::SoftwareDetailedDesign => Some((
RelationshipType::IsJustifiedBy,
vec![ItemType::ArchitectureDecisionRecord],
)),
ItemType::ArchitectureDecisionRecord => None,
}
}
#[must_use]
pub const fn valid_peer_for(item_type: ItemType) -> Option<ItemType> {
match item_type {
ItemType::SystemRequirement => Some(ItemType::SystemRequirement),
ItemType::HardwareRequirement => Some(ItemType::HardwareRequirement),
ItemType::SoftwareRequirement => Some(ItemType::SoftwareRequirement),
ItemType::ArchitectureDecisionRecord => Some(ItemType::ArchitectureDecisionRecord),
_ => None,
}
}
#[must_use]
pub fn valid_justification_targets() -> Vec<ItemType> {
vec![
ItemType::SystemArchitecture,
ItemType::SoftwareDetailedDesign,
ItemType::HardwareDetailedDesign,
]
}
#[must_use]
pub fn is_valid_justification(from_type: ItemType, to_type: ItemType) -> bool {
from_type == ItemType::ArchitectureDecisionRecord
&& Self::valid_justification_targets().contains(&to_type)
}
#[must_use]
pub const fn is_valid_supersession(from_type: ItemType, to_type: ItemType) -> bool {
matches!(from_type, ItemType::ArchitectureDecisionRecord)
&& matches!(to_type, ItemType::ArchitectureDecisionRecord)
}
#[must_use]
pub fn is_valid_relationship(
from_type: ItemType,
to_type: ItemType,
rel_type: RelationshipType,
) -> bool {
match rel_type {
RelationshipType::Refines
| RelationshipType::DerivesFrom
| RelationshipType::Satisfies
| RelationshipType::Justifies => {
if let Some((expected_rel, valid_targets)) = Self::valid_upstream_for(from_type) {
expected_rel == rel_type && valid_targets.contains(&to_type)
} else {
false
}
}
RelationshipType::IsRefinedBy
| RelationshipType::Derives
| RelationshipType::IsSatisfiedBy => {
if let Some((expected_rel, valid_targets)) = Self::valid_downstream_for(from_type) {
expected_rel == rel_type && valid_targets.contains(&to_type)
} else {
false
}
}
RelationshipType::IsJustifiedBy => Self::is_valid_justification(to_type, from_type),
RelationshipType::DependsOn
| RelationshipType::IsRequiredBy
| RelationshipType::Supersedes
| RelationshipType::IsSupersededBy => Self::valid_peer_for(from_type) == Some(to_type),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relationship_type_inverse() {
assert_eq!(
RelationshipType::Refines.inverse(),
RelationshipType::IsRefinedBy
);
assert_eq!(
RelationshipType::Derives.inverse(),
RelationshipType::DerivesFrom
);
assert_eq!(
RelationshipType::Satisfies.inverse(),
RelationshipType::IsSatisfiedBy
);
assert_eq!(
RelationshipType::DependsOn.inverse(),
RelationshipType::IsRequiredBy
);
}
#[test]
fn test_relationship_type_direction() {
assert!(RelationshipType::Refines.is_upstream());
assert!(RelationshipType::DerivesFrom.is_upstream());
assert!(RelationshipType::Satisfies.is_upstream());
assert!(RelationshipType::IsRefinedBy.is_downstream());
assert!(RelationshipType::Derives.is_downstream());
assert!(RelationshipType::IsSatisfiedBy.is_downstream());
assert!(RelationshipType::DependsOn.is_peer());
assert!(RelationshipType::IsRequiredBy.is_peer());
}
#[test]
fn test_valid_relationships() {
assert!(RelationshipRules::is_valid_relationship(
ItemType::UseCase,
ItemType::Solution,
RelationshipType::Refines
));
assert!(RelationshipRules::is_valid_relationship(
ItemType::Scenario,
ItemType::UseCase,
RelationshipType::Refines
));
assert!(RelationshipRules::is_valid_relationship(
ItemType::SystemRequirement,
ItemType::Scenario,
RelationshipType::DerivesFrom
));
assert!(!RelationshipRules::is_valid_relationship(
ItemType::Solution,
ItemType::UseCase,
RelationshipType::Refines
));
}
#[test]
fn test_peer_dependencies() {
assert!(RelationshipRules::is_valid_relationship(
ItemType::SystemRequirement,
ItemType::SystemRequirement,
RelationshipType::DependsOn
));
assert!(!RelationshipRules::is_valid_relationship(
ItemType::Solution,
ItemType::Solution,
RelationshipType::DependsOn
));
}
#[test]
fn test_adr_justifies_relationship() {
assert!(RelationshipRules::is_valid_relationship(
ItemType::ArchitectureDecisionRecord,
ItemType::SystemArchitecture,
RelationshipType::Justifies
));
assert!(RelationshipRules::is_valid_relationship(
ItemType::ArchitectureDecisionRecord,
ItemType::SoftwareDetailedDesign,
RelationshipType::Justifies
));
assert!(RelationshipRules::is_valid_relationship(
ItemType::ArchitectureDecisionRecord,
ItemType::HardwareDetailedDesign,
RelationshipType::Justifies
));
assert!(!RelationshipRules::is_valid_relationship(
ItemType::ArchitectureDecisionRecord,
ItemType::SystemRequirement,
RelationshipType::Justifies
));
}
#[test]
fn test_adr_supersession_relationship() {
assert!(RelationshipRules::is_valid_relationship(
ItemType::ArchitectureDecisionRecord,
ItemType::ArchitectureDecisionRecord,
RelationshipType::Supersedes
));
assert!(RelationshipRules::is_valid_relationship(
ItemType::ArchitectureDecisionRecord,
ItemType::ArchitectureDecisionRecord,
RelationshipType::IsSupersededBy
));
assert!(!RelationshipRules::is_valid_relationship(
ItemType::ArchitectureDecisionRecord,
ItemType::SystemArchitecture,
RelationshipType::Supersedes
));
}
#[test]
fn test_adr_relationship_direction() {
assert!(RelationshipType::Justifies.is_upstream());
assert!(RelationshipType::IsJustifiedBy.is_downstream());
assert!(RelationshipType::Supersedes.is_peer());
assert!(RelationshipType::IsSupersededBy.is_peer());
}
}