Skip to main content

injectable_rs_graph/
node.rs

1//! Graph node representation for the dependency graph.
2
3/// A node in the dependency graph representing an injectable type.
4///
5/// Each node corresponds to a type annotated with `#[derive(Injectable)]`
6/// and records its direct dependencies and scope.
7///
8/// # Construction
9///
10/// Nodes are created by the proc macro from constructor parameter analysis:
11///
12/// ```rust,ignore
13/// // From:
14/// #[injectable(ctor)]
15/// pub async fn new(db: Inject<Database>, cache: Inject<Cache>) -> Self
16///
17/// // The macro generates:
18/// GraphNode {
19///     name: "UserService",
20///     dependencies: &["Database", "Cache"],
21///     scope: "singleton",
22/// }
23/// ```
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub struct GraphNode {
26    /// The fully qualified type name of this injectable.
27    pub name: &'static str,
28
29    /// The type names of direct dependencies.
30    pub dependencies: &'static [&'static str],
31
32    /// The scope of this injectable ("singleton" or "transient").
33    pub scope: &'static str,
34}
35
36impl GraphNode {
37    /// Create a new graph node with default singleton scope.
38    pub const fn new(name: &'static str, dependencies: &'static [&'static str]) -> Self {
39        Self {
40            name,
41            dependencies,
42            scope: "singleton",
43        }
44    }
45
46    /// Create a graph node with no dependencies (singleton scope by default).
47    pub const fn leaf(name: &'static str) -> Self {
48        Self {
49            name,
50            dependencies: &[],
51            scope: "singleton",
52        }
53    }
54
55    /// Create a graph node with an explicit scope.
56    pub const fn with_scope(
57        name: &'static str,
58        dependencies: &'static [&'static str],
59        scope: &'static str,
60    ) -> Self {
61        Self {
62            name,
63            dependencies,
64            scope,
65        }
66    }
67
68    /// Create a leaf graph node with an explicit scope.
69    pub const fn leaf_with_scope(name: &'static str, scope: &'static str) -> Self {
70        Self {
71            name,
72            dependencies: &[],
73            scope,
74        }
75    }
76
77    /// Returns `true` if this node has no dependencies.
78    pub fn is_leaf(&self) -> bool {
79        self.dependencies.is_empty()
80    }
81
82    /// Returns the number of direct dependencies.
83    pub fn dependency_count(&self) -> usize {
84        self.dependencies.len()
85    }
86
87    /// Returns `true` if this node is in singleton scope.
88    pub fn is_singleton(&self) -> bool {
89        self.scope == "singleton"
90    }
91
92    /// Returns `true` if this node is in transient scope.
93    pub fn is_transient(&self) -> bool {
94        self.scope == "transient"
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn leaf_node() {
104        let n = GraphNode::leaf("Database");
105        assert_eq!(n.name, "Database");
106        assert!(n.is_leaf());
107        assert_eq!(n.dependency_count(), 0);
108        assert!(n.is_singleton());
109        assert!(!n.is_transient());
110    }
111
112    #[test]
113    fn new_node_with_deps() {
114        let n = GraphNode::new("UserService", &["Database", "Cache"]);
115        assert_eq!(n.name, "UserService");
116        assert!(!n.is_leaf());
117        assert_eq!(n.dependency_count(), 2);
118        assert!(n.is_singleton());
119    }
120
121    #[test]
122    fn with_scope_transient() {
123        let n = GraphNode::with_scope("Logger", &["Config"], "transient");
124        assert!(n.is_transient());
125        assert!(!n.is_singleton());
126        assert_eq!(n.dependency_count(), 1);
127    }
128
129    #[test]
130    fn leaf_with_scope_singleton() {
131        let n = GraphNode::leaf_with_scope("Config", "singleton");
132        assert!(n.is_leaf());
133        assert!(n.is_singleton());
134        assert!(!n.is_transient());
135    }
136
137    #[test]
138    fn leaf_with_scope_transient() {
139        let n = GraphNode::leaf_with_scope("Req", "transient");
140        assert!(n.is_transient());
141        assert!(!n.is_singleton());
142    }
143
144    #[test]
145    fn derive_traits() {
146        let a = GraphNode::leaf("A");
147        let b = GraphNode::leaf("A");
148        let c = GraphNode::leaf("B");
149        assert_eq!(a, b);
150        assert_ne!(a, c);
151        let _ = format!("{a:?}");
152    }
153}