1use serde::{Deserialize, Serialize};
4
5use super::field::FieldName;
6use super::item::{ItemId, ItemType};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum RelationshipType {
12 Refines,
14 IsRefinedBy,
16 Derives,
18 DerivesFrom,
20 Satisfies,
22 IsSatisfiedBy,
24 DependsOn,
26 IsRequiredBy,
28 Justifies,
30 IsJustifiedBy,
32 Supersedes,
34 IsSupersededBy,
36}
37
38impl RelationshipType {
39 #[must_use]
41 pub const fn inverse(&self) -> Self {
42 match self {
43 Self::Refines => Self::IsRefinedBy,
44 Self::IsRefinedBy => Self::Refines,
45 Self::Derives => Self::DerivesFrom,
46 Self::DerivesFrom => Self::Derives,
47 Self::Satisfies => Self::IsSatisfiedBy,
48 Self::IsSatisfiedBy => Self::Satisfies,
49 Self::DependsOn => Self::IsRequiredBy,
50 Self::IsRequiredBy => Self::DependsOn,
51 Self::Justifies => Self::IsJustifiedBy,
52 Self::IsJustifiedBy => Self::Justifies,
53 Self::Supersedes => Self::IsSupersededBy,
54 Self::IsSupersededBy => Self::Supersedes,
55 }
56 }
57
58 #[must_use]
61 pub const fn is_upstream(&self) -> bool {
62 matches!(
63 self,
64 Self::Refines | Self::DerivesFrom | Self::Satisfies | Self::Justifies
65 )
66 }
67
68 #[must_use]
70 pub const fn is_downstream(&self) -> bool {
71 matches!(
72 self,
73 Self::IsRefinedBy | Self::Derives | Self::IsSatisfiedBy | Self::IsJustifiedBy
74 )
75 }
76
77 #[must_use]
79 pub const fn is_peer(&self) -> bool {
80 matches!(
81 self,
82 Self::DependsOn | Self::IsRequiredBy | Self::Supersedes | Self::IsSupersededBy
83 )
84 }
85
86 #[must_use]
95 pub const fn is_primary(&self) -> bool {
96 matches!(
97 self,
98 Self::Refines
99 | Self::DerivesFrom
100 | Self::Satisfies
101 | Self::Justifies
102 | Self::DependsOn
103 | Self::Supersedes
104 )
105 }
106
107 #[must_use]
109 pub const fn field_name(&self) -> FieldName {
110 match self {
111 Self::Refines => FieldName::Refines,
112 Self::IsRefinedBy => FieldName::IsRefinedBy,
113 Self::Derives => FieldName::Derives,
114 Self::DerivesFrom => FieldName::DerivesFrom,
115 Self::Satisfies => FieldName::Satisfies,
116 Self::IsSatisfiedBy => FieldName::IsSatisfiedBy,
117 Self::DependsOn => FieldName::DependsOn,
118 Self::IsRequiredBy => FieldName::IsRequiredBy,
119 Self::Justifies => FieldName::Justifies,
120 Self::IsJustifiedBy => FieldName::JustifiedBy,
121 Self::Supersedes => FieldName::Supersedes,
122 Self::IsSupersededBy => FieldName::SupersededBy,
123 }
124 }
125}
126
127impl std::fmt::Display for RelationshipType {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 write!(f, "{}", self.field_name().as_str())
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct Relationship {
138 pub to: ItemId,
140 pub relationship_type: RelationshipType,
142}
143
144impl Relationship {
145 #[must_use]
147 pub fn new(to: ItemId, relationship_type: RelationshipType) -> Self {
148 Self {
149 to,
150 relationship_type,
151 }
152 }
153
154 #[must_use]
156 pub fn target(&self) -> &ItemId {
157 &self.to
158 }
159
160 #[must_use]
162 pub fn rel_type(&self) -> RelationshipType {
163 self.relationship_type
164 }
165}
166
167pub struct RelationshipRules;
169
170impl RelationshipRules {
171 #[must_use]
173 pub fn valid_upstream_for(item_type: ItemType) -> Option<(RelationshipType, Vec<ItemType>)> {
174 match item_type {
175 ItemType::Solution => None,
176 ItemType::UseCase => Some((RelationshipType::Refines, vec![ItemType::Solution])),
177 ItemType::Scenario => Some((RelationshipType::Refines, vec![ItemType::UseCase])),
178 ItemType::SystemRequirement => {
179 Some((RelationshipType::DerivesFrom, vec![ItemType::Scenario]))
180 }
181 ItemType::SystemArchitecture => Some((
182 RelationshipType::Satisfies,
183 vec![ItemType::SystemRequirement],
184 )),
185 ItemType::HardwareRequirement => Some((
186 RelationshipType::DerivesFrom,
187 vec![ItemType::SystemArchitecture],
188 )),
189 ItemType::SoftwareRequirement => Some((
190 RelationshipType::DerivesFrom,
191 vec![ItemType::SystemArchitecture],
192 )),
193 ItemType::HardwareDetailedDesign => Some((
194 RelationshipType::Satisfies,
195 vec![ItemType::HardwareRequirement],
196 )),
197 ItemType::SoftwareDetailedDesign => Some((
198 RelationshipType::Satisfies,
199 vec![ItemType::SoftwareRequirement],
200 )),
201 ItemType::ArchitectureDecisionRecord => Some((
202 RelationshipType::Justifies,
203 vec![
204 ItemType::SystemArchitecture,
205 ItemType::SoftwareDetailedDesign,
206 ItemType::HardwareDetailedDesign,
207 ],
208 )),
209 }
210 }
211
212 #[must_use]
214 pub fn valid_downstream_for(item_type: ItemType) -> Option<(RelationshipType, Vec<ItemType>)> {
215 match item_type {
216 ItemType::Solution => Some((RelationshipType::IsRefinedBy, vec![ItemType::UseCase])),
217 ItemType::UseCase => Some((RelationshipType::IsRefinedBy, vec![ItemType::Scenario])),
218 ItemType::Scenario => {
219 Some((RelationshipType::Derives, vec![ItemType::SystemRequirement]))
220 }
221 ItemType::SystemRequirement => Some((
222 RelationshipType::IsSatisfiedBy,
223 vec![ItemType::SystemArchitecture],
224 )),
225 ItemType::SystemArchitecture => Some((
226 RelationshipType::Derives,
227 vec![ItemType::HardwareRequirement, ItemType::SoftwareRequirement],
228 )),
229 ItemType::HardwareRequirement => Some((
230 RelationshipType::IsSatisfiedBy,
231 vec![ItemType::HardwareDetailedDesign],
232 )),
233 ItemType::SoftwareRequirement => Some((
234 RelationshipType::IsSatisfiedBy,
235 vec![ItemType::SoftwareDetailedDesign],
236 )),
237 ItemType::HardwareDetailedDesign | ItemType::SoftwareDetailedDesign => Some((
238 RelationshipType::IsJustifiedBy,
239 vec![ItemType::ArchitectureDecisionRecord],
240 )),
241 ItemType::ArchitectureDecisionRecord => None,
242 }
243 }
244
245 #[must_use]
247 pub const fn valid_peer_for(item_type: ItemType) -> Option<ItemType> {
248 match item_type {
249 ItemType::SystemRequirement => Some(ItemType::SystemRequirement),
250 ItemType::HardwareRequirement => Some(ItemType::HardwareRequirement),
251 ItemType::SoftwareRequirement => Some(ItemType::SoftwareRequirement),
252 ItemType::ArchitectureDecisionRecord => Some(ItemType::ArchitectureDecisionRecord),
253 _ => None,
254 }
255 }
256
257 #[must_use]
259 pub fn valid_justification_targets() -> Vec<ItemType> {
260 vec![
261 ItemType::SystemArchitecture,
262 ItemType::SoftwareDetailedDesign,
263 ItemType::HardwareDetailedDesign,
264 ]
265 }
266
267 #[must_use]
269 pub fn is_valid_justification(from_type: ItemType, to_type: ItemType) -> bool {
270 from_type == ItemType::ArchitectureDecisionRecord
271 && Self::valid_justification_targets().contains(&to_type)
272 }
273
274 #[must_use]
276 pub const fn is_valid_supersession(from_type: ItemType, to_type: ItemType) -> bool {
277 matches!(from_type, ItemType::ArchitectureDecisionRecord)
278 && matches!(to_type, ItemType::ArchitectureDecisionRecord)
279 }
280
281 #[must_use]
283 pub fn is_valid_relationship(
284 from_type: ItemType,
285 to_type: ItemType,
286 rel_type: RelationshipType,
287 ) -> bool {
288 match rel_type {
289 RelationshipType::Refines
291 | RelationshipType::DerivesFrom
292 | RelationshipType::Satisfies
293 | RelationshipType::Justifies => {
294 if let Some((expected_rel, valid_targets)) = Self::valid_upstream_for(from_type) {
295 expected_rel == rel_type && valid_targets.contains(&to_type)
296 } else {
297 false
298 }
299 }
300 RelationshipType::IsRefinedBy
302 | RelationshipType::Derives
303 | RelationshipType::IsSatisfiedBy => {
304 if let Some((expected_rel, valid_targets)) = Self::valid_downstream_for(from_type) {
305 expected_rel == rel_type && valid_targets.contains(&to_type)
306 } else {
307 false
308 }
309 }
310 RelationshipType::IsJustifiedBy => Self::is_valid_justification(to_type, from_type),
312 RelationshipType::DependsOn
314 | RelationshipType::IsRequiredBy
315 | RelationshipType::Supersedes
316 | RelationshipType::IsSupersededBy => Self::valid_peer_for(from_type) == Some(to_type),
317 }
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_relationship_type_inverse() {
327 assert_eq!(
328 RelationshipType::Refines.inverse(),
329 RelationshipType::IsRefinedBy
330 );
331 assert_eq!(
332 RelationshipType::Derives.inverse(),
333 RelationshipType::DerivesFrom
334 );
335 assert_eq!(
336 RelationshipType::Satisfies.inverse(),
337 RelationshipType::IsSatisfiedBy
338 );
339 assert_eq!(
340 RelationshipType::DependsOn.inverse(),
341 RelationshipType::IsRequiredBy
342 );
343 }
344
345 #[test]
346 fn test_relationship_type_direction() {
347 assert!(RelationshipType::Refines.is_upstream());
348 assert!(RelationshipType::DerivesFrom.is_upstream());
349 assert!(RelationshipType::Satisfies.is_upstream());
350
351 assert!(RelationshipType::IsRefinedBy.is_downstream());
352 assert!(RelationshipType::Derives.is_downstream());
353 assert!(RelationshipType::IsSatisfiedBy.is_downstream());
354
355 assert!(RelationshipType::DependsOn.is_peer());
356 assert!(RelationshipType::IsRequiredBy.is_peer());
357 }
358
359 #[test]
360 fn test_valid_relationships() {
361 assert!(RelationshipRules::is_valid_relationship(
363 ItemType::UseCase,
364 ItemType::Solution,
365 RelationshipType::Refines
366 ));
367
368 assert!(RelationshipRules::is_valid_relationship(
370 ItemType::Scenario,
371 ItemType::UseCase,
372 RelationshipType::Refines
373 ));
374
375 assert!(RelationshipRules::is_valid_relationship(
377 ItemType::SystemRequirement,
378 ItemType::Scenario,
379 RelationshipType::DerivesFrom
380 ));
381
382 assert!(!RelationshipRules::is_valid_relationship(
384 ItemType::Solution,
385 ItemType::UseCase,
386 RelationshipType::Refines
387 ));
388 }
389
390 #[test]
391 fn test_peer_dependencies() {
392 assert!(RelationshipRules::is_valid_relationship(
393 ItemType::SystemRequirement,
394 ItemType::SystemRequirement,
395 RelationshipType::DependsOn
396 ));
397
398 assert!(!RelationshipRules::is_valid_relationship(
399 ItemType::Solution,
400 ItemType::Solution,
401 RelationshipType::DependsOn
402 ));
403 }
404
405 #[test]
406 fn test_adr_justifies_relationship() {
407 assert!(RelationshipRules::is_valid_relationship(
409 ItemType::ArchitectureDecisionRecord,
410 ItemType::SystemArchitecture,
411 RelationshipType::Justifies
412 ));
413 assert!(RelationshipRules::is_valid_relationship(
414 ItemType::ArchitectureDecisionRecord,
415 ItemType::SoftwareDetailedDesign,
416 RelationshipType::Justifies
417 ));
418 assert!(RelationshipRules::is_valid_relationship(
419 ItemType::ArchitectureDecisionRecord,
420 ItemType::HardwareDetailedDesign,
421 RelationshipType::Justifies
422 ));
423
424 assert!(!RelationshipRules::is_valid_relationship(
426 ItemType::ArchitectureDecisionRecord,
427 ItemType::SystemRequirement,
428 RelationshipType::Justifies
429 ));
430 }
431
432 #[test]
433 fn test_adr_supersession_relationship() {
434 assert!(RelationshipRules::is_valid_relationship(
436 ItemType::ArchitectureDecisionRecord,
437 ItemType::ArchitectureDecisionRecord,
438 RelationshipType::Supersedes
439 ));
440 assert!(RelationshipRules::is_valid_relationship(
441 ItemType::ArchitectureDecisionRecord,
442 ItemType::ArchitectureDecisionRecord,
443 RelationshipType::IsSupersededBy
444 ));
445
446 assert!(!RelationshipRules::is_valid_relationship(
448 ItemType::ArchitectureDecisionRecord,
449 ItemType::SystemArchitecture,
450 RelationshipType::Supersedes
451 ));
452 }
453
454 #[test]
455 fn test_adr_relationship_direction() {
456 assert!(RelationshipType::Justifies.is_upstream());
458 assert!(RelationshipType::IsJustifiedBy.is_downstream());
460 assert!(RelationshipType::Supersedes.is_peer());
462 assert!(RelationshipType::IsSupersededBy.is_peer());
463 }
464}