Skip to main content

paramodel_elements/
dependency.rs

1// Copyright (c) Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Typed dependency edges between elements.
5//!
6//! Per SRD-0002 R1 and SRD-0007 D3/D4, every edge in the Element Graph
7//! is a `Dependency { target, relationship }` pair. The relationship
8//! drives compiler decisions (serialisation, coalescing, lifecycle
9//! ordering, lifeline collapse); the composition rules live in the
10//! compilation SRD.
11
12use crate::ElementName;
13use serde::{Deserialize, Serialize};
14
15/// How a dependent element relates to its target.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum RelationshipType {
19    /// Concurrent access is allowed. The default edge shape.
20    Shared,
21    /// Serialise dependents against the target (reducto Rule 2
22    /// inserts serialisation barriers).
23    Exclusive,
24    /// One target instance per dependent — the target gets coalesced
25    /// with its owner by the compiler.
26    Dedicated,
27    /// Full-lifecycle ordering within the same trial scope: the
28    /// target must complete before the dependent starts.
29    Linear,
30    /// Target's teardown subsumes the dependent's — the dependent
31    /// has no explicit teardown step.
32    Lifeline,
33}
34
35impl RelationshipType {
36    /// `true` for relationships that require the compiler to insert
37    /// a serialisation barrier around the target.
38    #[must_use]
39    pub const fn requires_serialization_barrier(&self) -> bool {
40        matches!(self, Self::Exclusive)
41    }
42
43    /// `true` for relationships that require a dedicated target
44    /// instance per dependent (coalesced with its owner).
45    #[must_use]
46    pub const fn requires_dedicated_instance(&self) -> bool {
47        matches!(self, Self::Dedicated)
48    }
49
50    /// `true` for relationships whose teardown is folded into the
51    /// target's.
52    #[must_use]
53    pub const fn implies_lifecycle_coupling(&self) -> bool {
54        matches!(self, Self::Lifeline)
55    }
56
57    /// `true` for the `Linear` full-lifecycle-ordering relationship.
58    #[must_use]
59    pub const fn is_linear(&self) -> bool {
60        matches!(self, Self::Linear)
61    }
62}
63
64/// One edge in the Element Graph.
65#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub struct Dependency {
67    /// The element this dependency points at.
68    pub target:       ElementName,
69    /// The relationship kind.
70    pub relationship: RelationshipType,
71}
72
73impl Dependency {
74    /// Shared dependency — the default relationship.
75    #[must_use]
76    pub const fn shared(target: ElementName) -> Self {
77        Self {
78            target,
79            relationship: RelationshipType::Shared,
80        }
81    }
82
83    /// Exclusive dependency — serialise dependents against `target`.
84    #[must_use]
85    pub const fn exclusive(target: ElementName) -> Self {
86        Self {
87            target,
88            relationship: RelationshipType::Exclusive,
89        }
90    }
91
92    /// Dedicated dependency — one target instance per dependent.
93    #[must_use]
94    pub const fn dedicated(target: ElementName) -> Self {
95        Self {
96            target,
97            relationship: RelationshipType::Dedicated,
98        }
99    }
100
101    /// Linear dependency — full lifecycle ordering.
102    #[must_use]
103    pub const fn linear(target: ElementName) -> Self {
104        Self {
105            target,
106            relationship: RelationshipType::Linear,
107        }
108    }
109
110    /// Lifeline dependency — teardown folded into `target`.
111    #[must_use]
112    pub const fn lifeline(target: ElementName) -> Self {
113        Self {
114            target,
115            relationship: RelationshipType::Lifeline,
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    fn name(s: &str) -> ElementName {
125        ElementName::new(s).unwrap()
126    }
127
128    #[test]
129    fn helper_predicates_match_variant() {
130        assert!(RelationshipType::Exclusive.requires_serialization_barrier());
131        assert!(!RelationshipType::Shared.requires_serialization_barrier());
132        assert!(RelationshipType::Dedicated.requires_dedicated_instance());
133        assert!(RelationshipType::Lifeline.implies_lifecycle_coupling());
134        assert!(RelationshipType::Linear.is_linear());
135        assert!(!RelationshipType::Shared.is_linear());
136    }
137
138    #[test]
139    fn constructors_set_the_right_relationship() {
140        let t = name("db");
141        assert_eq!(Dependency::shared(t.clone()).relationship, RelationshipType::Shared);
142        assert_eq!(Dependency::exclusive(t.clone()).relationship, RelationshipType::Exclusive);
143        assert_eq!(Dependency::dedicated(t.clone()).relationship, RelationshipType::Dedicated);
144        assert_eq!(Dependency::linear(t.clone()).relationship, RelationshipType::Linear);
145        assert_eq!(Dependency::lifeline(t).relationship, RelationshipType::Lifeline);
146    }
147
148    #[test]
149    fn serde_roundtrip() {
150        let d = Dependency::exclusive(name("db"));
151        let json = serde_json::to_string(&d).unwrap();
152        let back: Dependency = serde_json::from_str(&json).unwrap();
153        assert_eq!(d, back);
154    }
155}