1use std::collections::HashMap;
7
8use super::{
9 super::{CompiledSchema, MutationDefinition, QueryDefinition, SubscriptionDefinition},
10 directive_builder::{build_custom_directives, builtin_directives},
11 field_resolver::{build_arg_input_value, type_ref},
12 type_resolver::{
13 build_enum_type, build_input_object_type, build_interface_type, build_object_type,
14 build_union_type, builtin_scalars,
15 },
16 types::{
17 IntrospectionField, IntrospectionInputValue, IntrospectionSchema, IntrospectionType,
18 IntrospectionTypeRef, TypeKind,
19 },
20};
21
22#[must_use = "call .build() to construct the final value"]
28pub struct IntrospectionBuilder;
29
30impl IntrospectionBuilder {
31 #[must_use]
33 pub fn build(schema: &CompiledSchema) -> IntrospectionSchema {
34 let mut types = Vec::new();
35
36 types.extend(builtin_scalars());
38
39 for type_def in &schema.types {
41 types.push(build_object_type(type_def));
42 }
43
44 for enum_def in &schema.enums {
46 types.push(build_enum_type(enum_def));
47 }
48
49 for input_def in &schema.input_types {
51 types.push(build_input_object_type(input_def));
52 }
53
54 for interface_def in &schema.interfaces {
56 types.push(build_interface_type(interface_def, schema));
57 }
58
59 for union_def in &schema.unions {
61 types.push(build_union_type(union_def));
62 }
63
64 types.push(build_query_type(schema));
66
67 if !schema.mutations.is_empty() {
69 types.push(build_mutation_type(schema));
70 }
71
72 if !schema.subscriptions.is_empty() {
74 types.push(build_subscription_type(schema));
75 }
76
77 let mut directives = builtin_directives();
79 directives.extend(build_custom_directives(&schema.directives));
80
81 IntrospectionSchema {
82 description: Some("FraiseQL GraphQL Schema".to_string()),
83 types,
84 query_type: IntrospectionTypeRef {
85 name: "Query".to_string(),
86 },
87 mutation_type: if schema.mutations.is_empty() {
88 None
89 } else {
90 Some(IntrospectionTypeRef {
91 name: "Mutation".to_string(),
92 })
93 },
94 subscription_type: if schema.subscriptions.is_empty() {
95 None
96 } else {
97 Some(IntrospectionTypeRef {
98 name: "Subscription".to_string(),
99 })
100 },
101 directives,
102 }
103 }
104
105 #[must_use]
107 pub fn build_type_map(schema: &IntrospectionSchema) -> HashMap<String, IntrospectionType> {
108 let mut map = HashMap::new();
109 for t in &schema.types {
110 if let Some(ref name) = t.name {
111 map.insert(name.clone(), t.clone());
112 }
113 }
114 map
115 }
116
117 #[must_use]
119 pub fn type_ref(name: &str) -> IntrospectionType {
120 type_ref(name)
121 }
122}
123
124fn build_query_type(schema: &CompiledSchema) -> IntrospectionType {
130 let mut fields: Vec<IntrospectionField> =
131 schema.queries.iter().map(build_query_field).collect();
132
133 let has_relay_types =
135 schema.types.iter().any(|t| t.relay) || schema.interfaces.iter().any(|i| i.name == "Node");
136 if has_relay_types && !fields.iter().any(|f| f.name == "node") {
137 fields.push(build_node_query_field());
138 }
139
140 IntrospectionType {
141 kind: TypeKind::Object,
142 name: Some("Query".to_string()),
143 description: Some("Root query type".to_string()),
144 fields: Some(fields),
145 interfaces: Some(vec![]),
146 possible_types: None,
147 enum_values: None,
148 input_fields: None,
149 of_type: None,
150 specified_by_u_r_l: None,
151 }
152}
153
154fn build_mutation_type(schema: &CompiledSchema) -> IntrospectionType {
156 let fields: Vec<IntrospectionField> =
157 schema.mutations.iter().map(build_mutation_field).collect();
158
159 IntrospectionType {
160 kind: TypeKind::Object,
161 name: Some("Mutation".to_string()),
162 description: Some("Root mutation type".to_string()),
163 fields: Some(fields),
164 interfaces: Some(vec![]),
165 possible_types: None,
166 enum_values: None,
167 input_fields: None,
168 of_type: None,
169 specified_by_u_r_l: None,
170 }
171}
172
173fn build_subscription_type(schema: &CompiledSchema) -> IntrospectionType {
175 let fields: Vec<IntrospectionField> =
176 schema.subscriptions.iter().map(build_subscription_field).collect();
177
178 IntrospectionType {
179 kind: TypeKind::Object,
180 name: Some("Subscription".to_string()),
181 description: Some("Root subscription type".to_string()),
182 fields: Some(fields),
183 interfaces: Some(vec![]),
184 possible_types: None,
185 enum_values: None,
186 input_fields: None,
187 of_type: None,
188 specified_by_u_r_l: None,
189 }
190}
191
192fn build_query_field(query: &QueryDefinition) -> IntrospectionField {
198 if query.relay {
201 return build_relay_query_field(query);
202 }
203
204 let return_type = type_ref(&query.return_type);
205 let return_type = if query.returns_list {
206 IntrospectionType {
207 kind: TypeKind::List,
208 name: None,
209 description: None,
210 fields: None,
211 interfaces: None,
212 possible_types: None,
213 enum_values: None,
214 input_fields: None,
215 of_type: Some(Box::new(return_type)),
216 specified_by_u_r_l: None,
217 }
218 } else {
219 return_type
220 };
221
222 let return_type = if query.nullable {
223 return_type
224 } else {
225 IntrospectionType {
226 kind: TypeKind::NonNull,
227 name: None,
228 description: None,
229 fields: None,
230 interfaces: None,
231 possible_types: None,
232 enum_values: None,
233 input_fields: None,
234 of_type: Some(Box::new(return_type)),
235 specified_by_u_r_l: None,
236 }
237 };
238
239 let args: Vec<IntrospectionInputValue> =
241 query.arguments.iter().map(build_arg_input_value).collect();
242
243 IntrospectionField {
244 name: query.name.clone(),
245 description: query.description.clone(),
246 args,
247 field_type: return_type,
248 is_deprecated: query.is_deprecated(),
249 deprecation_reason: query.deprecation_reason().map(ToString::to_string),
250 }
251}
252
253fn build_relay_query_field(query: &QueryDefinition) -> IntrospectionField {
260 let connection_type = format!("{}Connection", query.return_type);
261
262 let return_type = IntrospectionType {
264 kind: TypeKind::NonNull,
265 name: None,
266 description: None,
267 fields: None,
268 interfaces: None,
269 possible_types: None,
270 enum_values: None,
271 input_fields: None,
272 of_type: Some(Box::new(type_ref(&connection_type))),
273 specified_by_u_r_l: None,
274 };
275
276 let nullable_int = || IntrospectionType {
278 kind: TypeKind::Scalar,
279 name: Some("Int".to_string()),
280 description: None,
281 fields: None,
282 interfaces: None,
283 possible_types: None,
284 enum_values: None,
285 input_fields: None,
286 of_type: None,
287 specified_by_u_r_l: None,
288 };
289 let nullable_string = || IntrospectionType {
290 kind: TypeKind::Scalar,
291 name: Some("String".to_string()),
292 description: None,
293 fields: None,
294 interfaces: None,
295 possible_types: None,
296 enum_values: None,
297 input_fields: None,
298 of_type: None,
299 specified_by_u_r_l: None,
300 };
301 let relay_args = vec![
302 IntrospectionInputValue {
303 name: "first".to_string(),
304 description: Some("Return the first N items.".to_string()),
305 input_type: nullable_int(),
306 default_value: None,
307 is_deprecated: false,
308 deprecation_reason: None,
309 validation_rules: vec![],
310 },
311 IntrospectionInputValue {
312 name: "after".to_string(),
313 description: Some("Cursor: return items after this position.".to_string()),
314 input_type: nullable_string(),
315 default_value: None,
316 is_deprecated: false,
317 deprecation_reason: None,
318 validation_rules: vec![],
319 },
320 IntrospectionInputValue {
321 name: "last".to_string(),
322 description: Some("Return the last N items.".to_string()),
323 input_type: nullable_int(),
324 default_value: None,
325 is_deprecated: false,
326 deprecation_reason: None,
327 validation_rules: vec![],
328 },
329 IntrospectionInputValue {
330 name: "before".to_string(),
331 description: Some("Cursor: return items before this position.".to_string()),
332 input_type: nullable_string(),
333 default_value: None,
334 is_deprecated: false,
335 deprecation_reason: None,
336 validation_rules: vec![],
337 },
338 ];
339
340 IntrospectionField {
341 name: query.name.clone(),
342 description: query.description.clone(),
343 args: relay_args,
344 field_type: return_type,
345 is_deprecated: query.is_deprecated(),
346 deprecation_reason: query.deprecation_reason().map(ToString::to_string),
347 }
348}
349
350fn build_node_query_field() -> IntrospectionField {
354 let return_type = IntrospectionType {
358 kind: TypeKind::Interface,
359 name: Some("Node".to_string()),
360 description: None,
361 fields: None,
362 interfaces: None,
363 possible_types: None,
364 enum_values: None,
365 input_fields: None,
366 of_type: None,
367 specified_by_u_r_l: None,
368 };
369
370 let id_arg = IntrospectionInputValue {
372 name: "id".to_string(),
373 description: Some("Globally unique opaque identifier.".to_string()),
374 input_type: IntrospectionType {
375 kind: TypeKind::NonNull,
376 name: None,
377 description: None,
378 fields: None,
379 interfaces: None,
380 possible_types: None,
381 enum_values: None,
382 input_fields: None,
383 of_type: Some(Box::new(type_ref("ID"))),
384 specified_by_u_r_l: None,
385 },
386 default_value: None,
387 is_deprecated: false,
388 deprecation_reason: None,
389 validation_rules: vec![],
390 };
391
392 IntrospectionField {
393 name: "node".to_string(),
394 description: Some(
395 "Fetch any object that implements the Node interface by its global ID.".to_string(),
396 ),
397 args: vec![id_arg],
398 field_type: return_type,
399 is_deprecated: false,
400 deprecation_reason: None,
401 }
402}
403
404fn build_mutation_field(mutation: &MutationDefinition) -> IntrospectionField {
406 let return_type = type_ref(&mutation.return_type);
408
409 let args: Vec<IntrospectionInputValue> =
411 mutation.arguments.iter().map(build_arg_input_value).collect();
412
413 IntrospectionField {
414 name: mutation.name.clone(),
415 description: mutation.description.clone(),
416 args,
417 field_type: return_type,
418 is_deprecated: mutation.is_deprecated(),
419 deprecation_reason: mutation.deprecation_reason().map(ToString::to_string),
420 }
421}
422
423fn build_subscription_field(subscription: &SubscriptionDefinition) -> IntrospectionField {
425 let return_type = type_ref(&subscription.return_type);
427
428 let args: Vec<IntrospectionInputValue> =
430 subscription.arguments.iter().map(build_arg_input_value).collect();
431
432 IntrospectionField {
433 name: subscription.name.clone(),
434 description: subscription.description.clone(),
435 args,
436 field_type: return_type,
437 is_deprecated: subscription.is_deprecated(),
438 deprecation_reason: subscription.deprecation_reason().map(ToString::to_string),
439 }
440}
441
442#[derive(Debug, Clone)]
448pub struct IntrospectionResponses {
449 pub schema_response: String,
451 pub type_responses: HashMap<String, String>,
453}
454
455impl IntrospectionResponses {
456 pub fn build(schema: &CompiledSchema) -> Self {
460 let introspection = IntrospectionBuilder::build(schema);
461 let type_map = IntrospectionBuilder::build_type_map(&introspection);
462
463 let schema_response = serde_json::json!({
465 "data": {
466 "__schema": introspection
467 }
468 })
469 .to_string();
470
471 let mut type_responses = HashMap::new();
473 for (name, t) in type_map {
474 let response = serde_json::json!({
475 "data": {
476 "__type": t
477 }
478 })
479 .to_string();
480 type_responses.insert(name, response);
481 }
482
483 Self {
484 schema_response,
485 type_responses,
486 }
487 }
488
489 #[must_use]
491 pub fn get_type_response(&self, type_name: &str) -> String {
492 self.type_responses.get(type_name).cloned().unwrap_or_else(|| {
493 serde_json::json!({
494 "data": {
495 "__type": null
496 }
497 })
498 .to_string()
499 })
500 }
501}