fraiseql_core/schema/dependency_graph/
builder.rs1use std::collections::{HashMap, HashSet};
4
5use super::graph::SchemaDependencyGraph;
6use crate::schema::{CompiledSchema, FieldType};
7
8impl SchemaDependencyGraph {
9 #[must_use]
14 #[allow(clippy::cognitive_complexity)] pub fn build(schema: &CompiledSchema) -> Self {
16 let mut outgoing: HashMap<String, HashSet<String>> = HashMap::new();
17 let mut incoming: HashMap<String, HashSet<String>> = HashMap::new();
18 let mut all_types: HashSet<String> = HashSet::new();
19 let mut root_types: HashSet<String> = HashSet::new();
20
21 for type_def in &schema.types {
23 all_types.insert(type_def.name.to_string());
24 outgoing.entry(type_def.name.to_string()).or_default();
25 incoming.entry(type_def.name.to_string()).or_default();
26 }
27
28 for enum_def in &schema.enums {
29 all_types.insert(enum_def.name.clone());
30 outgoing.entry(enum_def.name.clone()).or_default();
31 incoming.entry(enum_def.name.clone()).or_default();
32 }
33
34 for input_def in &schema.input_types {
35 all_types.insert(input_def.name.clone());
36 outgoing.entry(input_def.name.clone()).or_default();
37 incoming.entry(input_def.name.clone()).or_default();
38 }
39
40 for interface_def in &schema.interfaces {
41 all_types.insert(interface_def.name.clone());
42 outgoing.entry(interface_def.name.clone()).or_default();
43 incoming.entry(interface_def.name.clone()).or_default();
44 }
45
46 for union_def in &schema.unions {
47 all_types.insert(union_def.name.clone());
48 outgoing.entry(union_def.name.clone()).or_default();
49 incoming.entry(union_def.name.clone()).or_default();
50 }
51
52 if !schema.queries.is_empty() {
54 root_types.insert("Query".to_string());
55 all_types.insert("Query".to_string());
56 outgoing.entry("Query".to_string()).or_default();
57 incoming.entry("Query".to_string()).or_default();
58 }
59 if !schema.mutations.is_empty() {
60 root_types.insert("Mutation".to_string());
61 all_types.insert("Mutation".to_string());
62 outgoing.entry("Mutation".to_string()).or_default();
63 incoming.entry("Mutation".to_string()).or_default();
64 }
65 if !schema.subscriptions.is_empty() {
66 root_types.insert("Subscription".to_string());
67 all_types.insert("Subscription".to_string());
68 outgoing.entry("Subscription".to_string()).or_default();
69 incoming.entry("Subscription".to_string()).or_default();
70 }
71
72 for type_def in &schema.types {
74 for field in &type_def.fields {
75 if let Some(ref_type) = Self::extract_referenced_type(&field.field_type) {
76 if all_types.contains(&ref_type) {
77 outgoing
78 .entry(type_def.name.to_string())
79 .or_default()
80 .insert(ref_type.clone());
81 incoming
82 .entry(ref_type.clone())
83 .or_default()
84 .insert(type_def.name.to_string());
85 }
86 }
87 }
88
89 for interface_name in &type_def.implements {
91 if all_types.contains(interface_name) {
92 outgoing
93 .entry(type_def.name.to_string())
94 .or_default()
95 .insert(interface_name.clone());
96 incoming
97 .entry(interface_name.clone())
98 .or_default()
99 .insert(type_def.name.to_string());
100 }
101 }
102 }
103
104 for interface_def in &schema.interfaces {
106 for field in &interface_def.fields {
107 if let Some(ref_type) = Self::extract_referenced_type(&field.field_type) {
108 if all_types.contains(&ref_type) {
109 outgoing
110 .entry(interface_def.name.clone())
111 .or_default()
112 .insert(ref_type.clone());
113 incoming
114 .entry(ref_type.clone())
115 .or_default()
116 .insert(interface_def.name.clone());
117 }
118 }
119 }
120 }
121
122 for union_def in &schema.unions {
124 for member_type in &union_def.member_types {
125 if all_types.contains(member_type) {
126 outgoing.entry(union_def.name.clone()).or_default().insert(member_type.clone());
127 incoming.entry(member_type.clone()).or_default().insert(union_def.name.clone());
128 }
129 }
130 }
131
132 for input_def in &schema.input_types {
134 for field in &input_def.fields {
135 let parsed = FieldType::parse(&field.field_type);
137 if let Some(ref_type) = Self::extract_referenced_type(&parsed) {
138 if all_types.contains(&ref_type) {
139 outgoing
140 .entry(input_def.name.clone())
141 .or_default()
142 .insert(ref_type.clone());
143 incoming
144 .entry(ref_type.clone())
145 .or_default()
146 .insert(input_def.name.clone());
147 }
148 }
149 }
150 }
151
152 for query in &schema.queries {
154 let parsed = FieldType::parse(&query.return_type);
155 if let Some(ref_type) = Self::extract_referenced_type(&parsed) {
156 if all_types.contains(&ref_type) {
157 outgoing.entry("Query".to_string()).or_default().insert(ref_type.clone());
158 incoming.entry(ref_type.clone()).or_default().insert("Query".to_string());
159 }
160 }
161 }
162
163 for mutation in &schema.mutations {
165 let parsed = FieldType::parse(&mutation.return_type);
166 if let Some(ref_type) = Self::extract_referenced_type(&parsed) {
167 if all_types.contains(&ref_type) {
168 outgoing.entry("Mutation".to_string()).or_default().insert(ref_type.clone());
169 incoming.entry(ref_type.clone()).or_default().insert("Mutation".to_string());
170 }
171 }
172 }
173
174 for subscription in &schema.subscriptions {
176 let parsed = FieldType::parse(&subscription.return_type);
177 if let Some(ref_type) = Self::extract_referenced_type(&parsed) {
178 if all_types.contains(&ref_type) {
179 outgoing
180 .entry("Subscription".to_string())
181 .or_default()
182 .insert(ref_type.clone());
183 incoming
184 .entry(ref_type.clone())
185 .or_default()
186 .insert("Subscription".to_string());
187 }
188 }
189 }
190
191 Self {
192 outgoing,
193 incoming,
194 all_types,
195 root_types,
196 }
197 }
198
199 pub(super) fn extract_referenced_type(field_type: &FieldType) -> Option<String> {
201 match field_type {
202 FieldType::Object(name)
203 | FieldType::Enum(name)
204 | FieldType::Input(name)
205 | FieldType::Interface(name)
206 | FieldType::Union(name) => Some(name.clone()),
207 FieldType::List(inner) => Self::extract_referenced_type(inner),
208 _ => None, }
210 }
211}