1#![allow(dead_code, unused_variables, unused_mut)]
3use std::{collections::BTreeMap, iter::successors, num::NonZeroUsize, sync::Arc};
4
5use async_graphql_parser::{
6 types::{ExecutableDocument, FieldDefinition, TypeDefinition, TypeKind},
7 Positioned,
8};
9use filters::make_filter_expr;
10use smallvec::SmallVec;
11
12use crate::{
13 graphql_query::{
14 directives::{FilterDirective, FoldGroup, RecurseDirective},
15 query::{parse_document, FieldConnection, FieldNode, Query},
16 },
17 ir::{
18 get_typename_meta_field, Argument, ContextField, EdgeParameters, Eid, FieldRef, FieldValue,
19 FoldSpecificField, FoldSpecificFieldKind, IREdge, IRFold, IRQuery, IRQueryComponent,
20 IRVertex, IndexedQuery, LocalField, Operation, Recursive, TransformationKind, Type, Vid,
21 TYPENAME_META_FIELD,
22 },
23 schema::{get_builtin_scalars, FieldOrigin, Schema},
24 util::{BTreeMapTryInsertExt, TryCollectUniqueKey},
25};
26
27use self::{
28 error::{DuplicatedNamesConflict, FilterTypeError, FrontendError, ValidationError},
29 outputs::OutputHandler,
30 tags::TagHandler,
31 util::{get_underlying_named_type, ComponentPath},
32 validation::validate_query_against_schema,
33};
34
35pub mod error;
36mod filters;
37mod outputs;
38mod tags;
39mod util;
40mod validation;
41
42pub fn parse(schema: &Schema, query: impl AsRef<str>) -> Result<Arc<IndexedQuery>, FrontendError> {
45 let ir_query = parse_to_ir(schema, query)?;
46
47 let indexed_query: IndexedQuery = ir_query.try_into().unwrap();
52
53 Ok(Arc::from(indexed_query))
54}
55
56pub fn parse_to_ir<T: AsRef<str>>(schema: &Schema, query: T) -> Result<IRQuery, FrontendError> {
58 let document = async_graphql_parser::parse_query(query)?;
59 let q = parse_document(&document)?;
60 make_ir_for_query(schema, &q)
61}
62
63pub fn parse_doc(schema: &Schema, document: &ExecutableDocument) -> Result<IRQuery, FrontendError> {
64 let q = parse_document(document)?;
65 make_ir_for_query(schema, &q)
66}
67
68fn get_field_name_and_type_from_schema<'a>(
69 defined_fields: &'a [Positioned<FieldDefinition>],
70 field_node: &FieldNode,
71) -> (&'a str, Arc<str>, Arc<str>, Type) {
72 if field_node.name.as_ref() == TYPENAME_META_FIELD {
73 let field_name = get_typename_meta_field();
74 return (
75 TYPENAME_META_FIELD,
76 field_name.clone(),
77 field_name.clone(),
78 Type::new_named_type("String", false),
79 );
80 }
81
82 for defined_field in defined_fields {
83 let field_name = &defined_field.node.name.node;
84 let field_raw_type = &defined_field.node.ty.node;
85 if field_name.as_ref() == field_node.name.as_ref() {
86 let pre_coercion_type_name: Arc<str> =
87 get_underlying_named_type(field_raw_type).to_string().into();
88 let post_coercion_type_name = if let Some(coerced_to) = &field_node.coerced_to {
89 coerced_to.clone()
90 } else {
91 pre_coercion_type_name.clone()
92 };
93 return (
94 field_name,
95 pre_coercion_type_name,
96 post_coercion_type_name,
97 Type::from_type(field_raw_type),
98 );
99 }
100 }
101
102 unreachable!()
103}
104
105fn get_vertex_type_definition_from_schema<'a>(
106 schema: &'a Schema,
107 vertex_type_name: &str,
108) -> Result<&'a TypeDefinition, FrontendError> {
109 schema.vertex_types.get(vertex_type_name).ok_or_else(|| {
110 FrontendError::ValidationError(ValidationError::NonExistentType(
111 vertex_type_name.to_owned(),
112 ))
113 })
114}
115
116fn get_edge_definition_from_schema<'a>(
117 schema: &'a Schema,
118 type_name: &str,
119 edge_name: &str,
120) -> &'a FieldDefinition {
121 let defined_fields = get_vertex_field_definitions(schema, type_name);
122
123 for defined_field in defined_fields {
124 let field_name = defined_field.node.name.node.as_str();
125 if field_name == edge_name {
126 return &defined_field.node;
127 }
128 }
129
130 unreachable!()
131}
132
133fn get_vertex_field_definitions<'a>(
134 schema: &'a Schema,
135 type_name: &str,
136) -> &'a Vec<Positioned<FieldDefinition>> {
137 match &schema.vertex_types[type_name].kind {
138 TypeKind::Object(o) => &o.fields,
139 TypeKind::Interface(i) => &i.fields,
140 _ => unreachable!(),
141 }
142}
143
144fn make_edge_parameters(
145 edge_definition: &FieldDefinition,
146 specified_arguments: &BTreeMap<Arc<str>, FieldValue>,
147) -> Result<EdgeParameters, Vec<FrontendError>> {
148 let mut errors: Vec<FrontendError> = vec![];
149
150 let mut edge_arguments: BTreeMap<Arc<str>, FieldValue> = BTreeMap::new();
151 for arg in &edge_definition.arguments {
152 let arg_name = arg.node.name.node.as_ref();
153 let specified_value = match specified_arguments.get(arg_name) {
154 None => {
155 arg.node
160 .default_value
161 .as_ref()
162 .map(|v| {
163 let value = FieldValue::try_from(v.node.clone()).unwrap();
164
165 assert!(Type::from_type(&arg.node.ty.node).is_valid_value(&value));
168
169 value
170 })
171 .or({
172 if arg.node.ty.node.nullable {
173 Some(FieldValue::Null)
174 } else {
175 None
176 }
177 })
178 }
179 Some(value) => {
180 if !Type::from_type(&arg.node.ty.node).is_valid_value(value) {
182 errors.push(FrontendError::InvalidEdgeParameterType(
183 arg_name.to_string(),
184 edge_definition.name.node.to_string(),
185 arg.node.ty.to_string(),
186 value.clone(),
187 ));
188 }
189 Some(value.clone())
190 }
191 };
192
193 match specified_value {
194 None => {
195 errors.push(FrontendError::MissingRequiredEdgeParameter(
196 arg_name.to_string(),
197 edge_definition.name.node.to_string(),
198 ));
199 }
200 Some(value) => {
201 edge_arguments.insert_or_error(arg_name.to_owned().into(), value).unwrap();
202 }
204 }
205 }
206
207 for specified_argument_name in specified_arguments.keys() {
209 if !edge_arguments.contains_key(specified_argument_name) {
210 errors.push(FrontendError::UnexpectedEdgeParameter(
213 specified_argument_name.to_string(),
214 edge_definition.name.node.to_string(),
215 ))
216 }
217 }
218
219 if !errors.is_empty() {
220 Err(errors)
221 } else {
222 Ok(EdgeParameters::new(Arc::new(edge_arguments)))
223 }
224}
225
226#[allow(clippy::too_many_arguments)]
227fn make_local_field_filter_expr(
228 schema: &Schema,
229 component_path: &ComponentPath,
230 tags: &mut TagHandler<'_>,
231 current_vertex_vid: Vid,
232 property_name: &Arc<str>,
233 property_type: &Type,
234 filter_directive: &FilterDirective,
235) -> Result<Operation<LocalField, Argument>, Vec<FrontendError>> {
236 let left = LocalField { field_name: property_name.clone(), field_type: property_type.clone() };
237
238 filters::make_filter_expr(
239 schema,
240 component_path,
241 tags,
242 current_vertex_vid,
243 left,
244 filter_directive,
245 )
246}
247
248pub fn make_ir_for_query(schema: &Schema, query: &Query) -> Result<IRQuery, FrontendError> {
249 validate_query_against_schema(schema, query)?;
250
251 let mut vid_maker = successors(Some(Vid::new(NonZeroUsize::new(1).unwrap())), |x| {
252 let inner_number = x.0.get();
253 Some(Vid::new(NonZeroUsize::new(inner_number.checked_add(1).unwrap()).unwrap()))
254 });
255 let mut eid_maker = successors(Some(Eid::new(NonZeroUsize::new(1).unwrap())), |x| {
256 let inner_number = x.0.get();
257 Some(Eid::new(NonZeroUsize::new(inner_number.checked_add(1).unwrap()).unwrap()))
258 });
259
260 let mut errors: Vec<FrontendError> = vec![];
261
262 let (root_field_name, root_field_pre_coercion_type, root_field_post_coercion_type, _) =
263 get_field_name_and_type_from_schema(&schema.query_type.fields, &query.root_field);
264 let starting_vid = vid_maker.next().unwrap();
265
266 let root_parameters = make_edge_parameters(
267 get_edge_definition_from_schema(schema, schema.query_type_name(), root_field_name),
268 &query.root_connection.arguments,
269 );
270
271 let mut component_path = ComponentPath::new(starting_vid);
272 let mut tags = Default::default();
273 let mut output_handler = OutputHandler::new(starting_vid, None);
274 let mut root_component = make_query_component(
275 schema,
276 query,
277 &mut vid_maker,
278 &mut eid_maker,
279 &mut component_path,
280 &mut output_handler,
281 &mut tags,
282 None,
283 starting_vid,
284 root_field_pre_coercion_type,
285 root_field_post_coercion_type,
286 &query.root_field,
287 );
288
289 if let Err(e) = &root_parameters {
290 errors.extend(e.iter().cloned());
291 }
292
293 let root_component = match root_component {
294 Ok(r) => r,
295 Err(e) => {
296 errors.extend(e);
297 return Err(errors.into());
298 }
299 };
300 let mut variables: BTreeMap<Arc<str>, Type> = Default::default();
301 if let Err(v) = fill_in_query_variables(&mut variables, &root_component) {
302 errors.extend(v.into_iter().map(|x| x.into()));
303 }
304
305 if let Err(e) = tags.finish() {
306 errors.push(FrontendError::UnusedTags(e.into_iter().map(String::from).collect()));
307 }
308
309 let all_outputs = output_handler.finish();
310 if let Err(duplicates) = check_for_duplicate_output_names(all_outputs) {
311 let all_vertices = collect_ir_vertices(&root_component);
312 let errs = make_duplicated_output_names_error(&all_vertices, duplicates);
313 errors.extend(errs);
314 }
315
316 if errors.is_empty() {
317 Ok(IRQuery {
318 root_name: root_field_name.into(),
319 root_parameters: root_parameters.unwrap(),
320 root_component: root_component.into(),
321 variables,
322 })
323 } else {
324 Err(errors.into())
325 }
326}
327
328fn collect_ir_vertices(root_component: &IRQueryComponent) -> BTreeMap<Vid, IRVertex> {
329 let mut result = Default::default();
330 collect_ir_vertices_recursive_step(&mut result, root_component);
331 result
332}
333
334fn collect_ir_vertices_recursive_step(
335 result: &mut BTreeMap<Vid, IRVertex>,
336 component: &IRQueryComponent,
337) {
338 result.extend(component.vertices.iter().map(|(k, v)| (*k, v.clone())));
339
340 component
341 .folds
342 .values()
343 .for_each(move |fold| collect_ir_vertices_recursive_step(result, &fold.component))
344}
345
346fn fill_in_query_variables(
347 variables: &mut BTreeMap<Arc<str>, Type>,
348 component: &IRQueryComponent,
349) -> Result<(), Vec<FilterTypeError>> {
350 let mut errors: Vec<FilterTypeError> = vec![];
351
352 let all_variable_uses = component
353 .vertices
354 .values()
355 .flat_map(|vertex| &vertex.filters)
356 .map(|filter| filter.right())
357 .chain(
358 component
359 .folds
360 .values()
361 .flat_map(|fold| &fold.post_filters)
362 .map(|filter| filter.right()),
363 )
364 .filter_map(|rhs| match rhs {
365 Some(Argument::Variable(vref)) => Some(vref),
366 _ => None,
367 });
368 for vref in all_variable_uses {
369 let existing_type = variables
370 .entry(vref.variable_name.clone())
371 .or_insert_with(|| vref.variable_type.clone());
372
373 match existing_type.intersect(&vref.variable_type) {
374 Some(intersection) => {
375 *existing_type = intersection;
376 }
377 None => {
378 errors.push(FilterTypeError::IncompatibleVariableTypeRequirements(
379 vref.variable_name.to_string(),
380 existing_type.to_string(),
381 vref.variable_type.to_string(),
382 ));
383 }
384 }
385 }
386
387 for fold in component.folds.values() {
388 if let Err(e) = fill_in_query_variables(variables, fold.component.as_ref()) {
389 errors.extend(e);
390 }
391 }
392
393 if errors.is_empty() {
394 Ok(())
395 } else {
396 Err(errors)
397 }
398}
399
400fn make_duplicated_output_names_error(
401 ir_vertices: &BTreeMap<Vid, IRVertex>,
402 duplicates: BTreeMap<Arc<str>, Vec<FieldRef>>,
403) -> Vec<FrontendError> {
404 let conflict_info = DuplicatedNamesConflict {
405 duplicates: duplicates
406 .iter()
407 .map(|(k, fields)| {
408 let duplicate_values = fields
409 .iter()
410 .map(|field| match field {
411 FieldRef::ContextField(field) => {
412 let vid = field.vertex_id;
413 (ir_vertices[&vid].type_name.to_string(), field.field_name.to_string())
414 }
415 FieldRef::FoldSpecificField(field) => {
416 let vid = field.fold_root_vid;
417 match field.kind {
418 FoldSpecificFieldKind::Count => (
419 ir_vertices[&vid].type_name.to_string(),
420 "fold count value".to_string(),
421 ),
422 }
423 }
424 })
425 .collect();
426 (k.to_string(), duplicate_values)
427 })
428 .collect(),
429 };
430 vec![FrontendError::MultipleOutputsWithSameName(conflict_info)]
431}
432
433#[allow(clippy::type_complexity)]
434fn check_for_duplicate_output_names(
435 maybe_duplicated_outputs: BTreeMap<Arc<str>, Vec<FieldRef>>,
436) -> Result<BTreeMap<Arc<str>, FieldRef>, BTreeMap<Arc<str>, Vec<FieldRef>>> {
437 maybe_duplicated_outputs
438 .into_iter()
439 .flat_map(|(name, outputs)| outputs.into_iter().map(move |o| (name.clone(), o)))
440 .try_collect_unique()
441}
442
443#[allow(clippy::too_many_arguments)]
444fn make_query_component<'schema, 'query, V, E>(
445 schema: &'schema Schema,
446 query: &'query Query,
447 vid_maker: &mut V,
448 eid_maker: &mut E,
449 component_path: &mut ComponentPath,
450 output_handler: &mut OutputHandler<'query>,
451 tags: &mut TagHandler<'query>,
452 parent_vid: Option<Vid>,
453 starting_vid: Vid,
454 pre_coercion_type: Arc<str>,
455 post_coercion_type: Arc<str>,
456 starting_field: &'query FieldNode,
457) -> Result<IRQueryComponent, Vec<FrontendError>>
458where
459 'schema: 'query,
460 V: Iterator<Item = Vid>,
461 E: Iterator<Item = Eid>,
462{
463 let mut errors: Vec<FrontendError> = vec![];
464
465 let mut vertices: BTreeMap<Vid, (Arc<str>, &'query FieldNode)> = Default::default();
467
468 let mut edges: BTreeMap<Eid, (Vid, Vid, &'query FieldConnection)> = Default::default();
470
471 let mut property_names_by_vertex: BTreeMap<Vid, Vec<Arc<str>>> = Default::default();
473
474 #[allow(clippy::type_complexity)]
476 let mut properties: BTreeMap<
477 (Vid, Arc<str>),
478 (Arc<str>, Type, SmallVec<[&'query FieldNode; 1]>),
479 > = Default::default();
480
481 output_handler.begin_subcomponent();
482
483 let mut folds: BTreeMap<Eid, Arc<IRFold>> = Default::default();
484 if let Err(e) = fill_in_vertex_data(
485 schema,
486 query,
487 vid_maker,
488 eid_maker,
489 &mut vertices,
490 &mut edges,
491 &mut folds,
492 &mut property_names_by_vertex,
493 &mut properties,
494 component_path,
495 output_handler,
496 tags,
497 None,
498 starting_vid,
499 pre_coercion_type,
500 post_coercion_type,
501 starting_field,
502 ) {
503 errors.extend(e);
504 }
505
506 let vertex_results = vertices.iter().map(|(vid, (uncoerced_type_name, field_node))| {
507 make_vertex(
508 schema,
509 &property_names_by_vertex,
510 &properties,
511 tags,
512 component_path,
513 *vid,
514 uncoerced_type_name,
515 field_node,
516 )
517 });
518
519 let ir_vertices: BTreeMap<Vid, IRVertex> = vertex_results
520 .filter_map(|res| match res {
521 Ok(v) => Some((v.vid, v)),
522 Err(e) => {
523 errors.extend(e);
524 None
525 }
526 })
527 .try_collect_unique()
528 .unwrap();
529 if !errors.is_empty() {
530 return Err(errors);
531 }
532
533 let mut ir_edges: BTreeMap<Eid, Arc<IREdge>> = BTreeMap::new();
534 for (eid, (from_vid, to_vid, field_connection)) in edges.iter() {
535 let from_vertex_type = &ir_vertices[from_vid].type_name;
536 let edge_definition = get_edge_definition_from_schema(
537 schema,
538 from_vertex_type.as_ref(),
539 field_connection.name.as_ref(),
540 );
541 let edge_name = edge_definition.name.node.as_ref().to_owned().into();
542
543 let parameters_result = make_edge_parameters(edge_definition, &field_connection.arguments);
544
545 let optional = field_connection.optional.is_some();
546 let recursive = match field_connection.recurse.as_ref() {
547 None => None,
548 Some(d) => {
549 match get_recurse_implicit_coercion(
550 schema,
551 &ir_vertices[from_vid],
552 edge_definition,
553 d,
554 ) {
555 Ok(coerce_to) => Some(Recursive::new(d.depth, coerce_to)),
556 Err(e) => {
557 errors.push(e);
558 None
559 }
560 }
561 }
562 };
563
564 match parameters_result {
565 Ok(parameters) => {
566 ir_edges.insert(
567 *eid,
568 IREdge {
569 eid: *eid,
570 from_vid: *from_vid,
571 to_vid: *to_vid,
572 edge_name,
573 parameters,
574 optional,
575 recursive,
576 }
577 .into(),
578 );
579 }
580 Err(e) => {
581 errors.extend(e);
582 }
583 }
584 }
585
586 if !errors.is_empty() {
587 return Err(errors);
588 }
589
590 let maybe_duplicated_outputs = output_handler.end_subcomponent();
591
592 let component_outputs = match check_for_duplicate_output_names(maybe_duplicated_outputs) {
593 Ok(outputs) => outputs,
594 Err(duplicates) => {
595 return Err(make_duplicated_output_names_error(&ir_vertices, duplicates))
596 }
597 };
598
599 let hacked_outputs = component_outputs
601 .into_iter()
602 .filter_map(|(k, v)| match v {
603 FieldRef::ContextField(c) => Some((k, c)),
604 FieldRef::FoldSpecificField(_) => None,
605 })
606 .collect();
607
608 Ok(IRQueryComponent {
609 root: starting_vid,
610 vertices: ir_vertices,
611 edges: ir_edges,
612 folds,
613 outputs: hacked_outputs,
614 })
615}
616
617fn get_recurse_implicit_coercion(
666 schema: &Schema,
667 from_vertex: &IRVertex,
668 edge_definition: &FieldDefinition,
669 d: &RecurseDirective,
670) -> Result<Option<Arc<str>>, FrontendError> {
671 let source_type = &from_vertex.type_name;
672 let destination_type = get_underlying_named_type(&edge_definition.ty.node).as_ref();
673
674 if !schema.is_named_type_subtype(destination_type, source_type) {
675 if !schema.is_named_type_subtype(source_type, destination_type) {
678 return Err(FrontendError::RecursingNonRecursableEdge(
680 edge_definition.name.node.to_string(),
681 source_type.to_string(),
682 destination_type.to_string(),
683 ));
684 } else {
685 return Err(FrontendError::RecursionToSubtype(
690 edge_definition.name.node.to_string(),
691 source_type.to_string(),
692 destination_type.to_string(),
693 ));
694 }
695 }
696
697 if source_type.as_ref() == destination_type {
698 return Ok(None);
700 }
701
702 let edge_name: Arc<str> = Arc::from(edge_definition.name.node.as_ref());
704 let destination_edge = schema.fields.get(&(Arc::from(destination_type), edge_name.clone()));
705 match destination_edge {
706 Some(destination_edge) => {
707 let edge_type = get_underlying_named_type(&destination_edge.ty.node).as_ref();
709 if edge_type == destination_type {
710 Ok(None)
712 } else {
713 Err(FrontendError::EdgeRecursionNeedingMultipleCoercions(edge_name.to_string()))
715 }
716 }
717 None => {
718 let edge_origin = &schema.field_origins[&(source_type.clone(), edge_name.clone())];
721 match edge_origin {
722 FieldOrigin::SingleAncestor(ancestor) => {
723 let ancestor_edge = &schema.fields[&(ancestor.clone(), edge_name.clone())];
725 let edge_type = get_underlying_named_type(&ancestor_edge.ty.node).as_ref();
726 if edge_type == destination_type {
727 Ok(Some(ancestor.clone()))
729 } else {
730 Err(FrontendError::EdgeRecursionNeedingMultipleCoercions(
731 edge_name.to_string(),
732 ))
733 }
734 }
735 FieldOrigin::MultipleAncestors(multiple) => {
736 Err(FrontendError::AmbiguousOriginEdgeRecursion(edge_name.to_string()))
738 }
739 }
740 }
741 }
742}
743
744#[allow(clippy::too_many_arguments)]
745#[allow(clippy::type_complexity)]
746fn make_vertex<'query>(
747 schema: &Schema,
748 property_names_by_vertex: &BTreeMap<Vid, Vec<Arc<str>>>,
749 properties: &BTreeMap<(Vid, Arc<str>), (Arc<str>, Type, SmallVec<[&'query FieldNode; 1]>)>,
750 tags: &mut TagHandler<'_>,
751 component_path: &ComponentPath,
752 vid: Vid,
753 uncoerced_type_name: &Arc<str>,
754 field_node: &'query FieldNode,
755) -> Result<IRVertex, Vec<FrontendError>> {
756 let mut errors: Vec<FrontendError> = vec![];
757
758 let is_fold_root = component_path.is_component_root(vid);
764 if !is_fold_root && !field_node.output.is_empty() {
765 errors.push(FrontendError::UnsupportedEdgeOutput(field_node.name.as_ref().to_owned()));
766 }
767
768 if let Some(first_filter) = field_node.filter.first() {
769 errors.push(FrontendError::UnsupportedEdgeFilter(field_node.name.as_ref().to_owned()));
771 }
772
773 if let Some(first_tag) = field_node.tag.first() {
774 errors.push(FrontendError::UnsupportedEdgeTag(field_node.name.as_ref().to_owned()));
776 }
777
778 let default_func = || {
779 Result::<(Arc<str>, Option<Arc<str>>), FrontendError>::Ok((
780 uncoerced_type_name.clone(),
781 None,
782 ))
783 };
784 let mapper_func = |coerced_to_type: Arc<str>| {
785 let coerced_type =
786 get_vertex_type_definition_from_schema(schema, coerced_to_type.as_ref())?;
787 Ok((coerced_type.name.node.as_ref().to_owned().into(), Some(uncoerced_type_name.clone())))
788 };
789 let (type_name, coerced_from_type) =
790 match field_node.coerced_to.clone().map_or_else(default_func, mapper_func) {
791 Ok(x) => x,
792 Err(e) => {
793 errors.push(e);
794 return Err(errors);
795 }
796 };
797
798 let mut filters = vec![];
799 for property_name in property_names_by_vertex.get(&vid).into_iter().flatten() {
800 let (_, property_type, property_fields) =
801 properties.get(&(vid, property_name.clone())).unwrap();
802
803 for property_field in property_fields.iter() {
804 for filter_directive in property_field.filter.iter() {
805 match make_local_field_filter_expr(
806 schema,
807 component_path,
808 tags,
809 vid,
810 property_name,
811 property_type,
812 filter_directive,
813 ) {
814 Ok(filter_operation) => {
815 filters.push(filter_operation);
816 }
817 Err(e) => {
818 errors.extend(e);
819 }
820 }
821 }
822 }
823 }
824
825 if errors.is_empty() {
826 Ok(IRVertex { vid, type_name, coerced_from_type, filters })
827 } else {
828 Err(errors)
829 }
830}
831
832#[allow(clippy::too_many_arguments)]
833#[allow(clippy::type_complexity)]
834fn fill_in_vertex_data<'schema, 'query, V, E>(
835 schema: &'schema Schema,
836 query: &'query Query,
837 vid_maker: &mut V,
838 eid_maker: &mut E,
839 vertices: &mut BTreeMap<Vid, (Arc<str>, &'query FieldNode)>,
840 edges: &mut BTreeMap<Eid, (Vid, Vid, &'query FieldConnection)>,
841 folds: &mut BTreeMap<Eid, Arc<IRFold>>,
842 property_names_by_vertex: &mut BTreeMap<Vid, Vec<Arc<str>>>,
843 properties: &mut BTreeMap<(Vid, Arc<str>), (Arc<str>, Type, SmallVec<[&'query FieldNode; 1]>)>,
844 component_path: &mut ComponentPath,
845 output_handler: &mut OutputHandler<'query>,
846 tags: &mut TagHandler<'query>,
847 parent_vid: Option<Vid>,
848 current_vid: Vid,
849 pre_coercion_type: Arc<str>,
850 post_coercion_type: Arc<str>,
851 current_field: &'query FieldNode,
852) -> Result<(), Vec<FrontendError>>
853where
854 'schema: 'query,
855 V: Iterator<Item = Vid>,
856 E: Iterator<Item = Eid>,
857{
858 let mut errors: Vec<FrontendError> = vec![];
859
860 vertices.insert_or_error(current_vid, (pre_coercion_type, current_field)).unwrap();
861
862 let defined_fields = get_vertex_field_definitions(schema, post_coercion_type.as_ref());
863
864 for (connection, subfield) in ¤t_field.connections {
865 let (
866 subfield_name,
867 subfield_pre_coercion_type,
868 subfield_post_coercion_type,
869 subfield_raw_type,
870 ) = get_field_name_and_type_from_schema(defined_fields, subfield);
871 if schema.vertex_types.contains_key(subfield_post_coercion_type.as_ref()) {
872 let next_vid = vid_maker.next().unwrap();
875 let next_eid = eid_maker.next().unwrap();
876 output_handler
877 .begin_nested_scope(next_vid, subfield.alias.as_ref().map(|x| x.as_ref()));
878
879 if let Some(fold_group) = &connection.fold {
880 if connection.optional.is_some() {
881 errors.push(FrontendError::UnsupportedDirectiveOnFoldedEdge(
882 subfield.name.to_string(),
883 "@optional".to_owned(),
884 ));
885 }
886 if connection.recurse.is_some() {
887 errors.push(FrontendError::UnsupportedDirectiveOnFoldedEdge(
888 subfield.name.to_string(),
889 "@recurse".to_owned(),
890 ));
891 }
892
893 let edge_definition = get_edge_definition_from_schema(
894 schema,
895 post_coercion_type.as_ref(),
896 connection.name.as_ref(),
897 );
898 match make_edge_parameters(edge_definition, &connection.arguments) {
899 Ok(edge_parameters) => {
900 match make_fold(
901 schema,
902 query,
903 vid_maker,
904 eid_maker,
905 component_path,
906 output_handler,
907 tags,
908 fold_group,
909 next_eid,
910 edge_definition.name.node.as_str().to_owned().into(),
911 edge_parameters,
912 current_vid,
913 next_vid,
914 subfield_pre_coercion_type,
915 subfield_post_coercion_type,
916 subfield,
917 ) {
918 Ok(fold) => {
919 folds.insert(next_eid, fold.into());
920 }
921 Err(e) => {
922 errors.extend(e);
923 }
924 }
925 }
926 Err(e) => {
927 errors.extend(e);
928 }
929 }
930 } else {
931 edges
932 .insert_or_error(next_eid, (current_vid, next_vid, connection))
933 .expect("Unexpectedly encountered duplicate eid");
934
935 if let Err(e) = fill_in_vertex_data(
936 schema,
937 query,
938 vid_maker,
939 eid_maker,
940 vertices,
941 edges,
942 folds,
943 property_names_by_vertex,
944 properties,
945 component_path,
946 output_handler,
947 tags,
948 Some(current_vid),
949 next_vid,
950 subfield_pre_coercion_type.clone(),
951 subfield_post_coercion_type.clone(),
952 subfield,
953 ) {
954 errors.extend(e);
955 }
956 }
957
958 output_handler.end_nested_scope(next_vid);
959 } else if get_builtin_scalars().contains(subfield_post_coercion_type.as_ref())
960 || schema.scalars.contains_key(subfield_post_coercion_type.as_ref())
961 || subfield_name == TYPENAME_META_FIELD
962 {
963 if connection.fold.is_some() {
967 errors.push(FrontendError::UnsupportedDirectiveOnProperty(
968 "@fold".into(),
969 subfield.name.to_string(),
970 ));
971 }
972
973 if connection.optional.is_some() {
975 errors.push(FrontendError::UnsupportedDirectiveOnProperty(
976 "@optional".into(),
977 subfield.name.to_string(),
978 ));
979 }
980
981 if connection.recurse.is_some() {
983 errors.push(FrontendError::UnsupportedDirectiveOnProperty(
984 "@recurse".into(),
985 subfield.name.to_string(),
986 ));
987 }
988
989 let subfield_name: Arc<str> = subfield_name.into();
990 let key = (current_vid, subfield_name.clone());
991 properties
992 .entry(key)
993 .and_modify(|(prior_name, prior_type, subfields)| {
994 assert_eq!(subfield_name.as_ref(), prior_name.as_ref());
995 assert_eq!(&subfield_raw_type, prior_type);
996 subfields.push(subfield);
997 })
998 .or_insert_with(|| {
999 property_names_by_vertex
1000 .entry(current_vid)
1001 .or_default()
1002 .push(subfield_name.clone());
1003
1004 (subfield_name, subfield_raw_type.clone(), SmallVec::from([subfield]))
1005 });
1006
1007 for output_directive in &subfield.output {
1008 let field_ref = FieldRef::ContextField(ContextField {
1010 vertex_id: current_vid,
1011 field_name: subfield.name.clone(),
1012 field_type: subfield_raw_type.clone(),
1013 });
1014
1015 if let Some(explicit_name) = output_directive.name.as_ref() {
1023 output_handler
1024 .register_explicitly_named_output(explicit_name.clone(), field_ref);
1025 } else {
1026 let local_name = subfield
1027 .alias
1028 .as_ref()
1029 .map(|x| x.as_ref())
1030 .unwrap_or_else(|| subfield.name.as_ref());
1031 output_handler.register_locally_named_output(local_name, None, field_ref);
1032 }
1033 }
1034
1035 for tag_directive in &subfield.tag {
1036 let tag_name =
1041 tag_directive.name.as_ref().map(|x| x.as_ref()).unwrap_or_else(|| {
1042 subfield
1043 .alias
1044 .as_ref()
1045 .map(|x| x.as_ref())
1046 .unwrap_or_else(|| subfield.name.as_ref())
1047 });
1048 let tag_field = ContextField {
1049 vertex_id: current_vid,
1050 field_name: subfield.name.clone(),
1051 field_type: subfield_raw_type.clone(),
1052 };
1053
1054 if let Err(e) =
1056 tags.register_tag(tag_name, FieldRef::ContextField(tag_field), component_path)
1057 {
1058 errors.push(FrontendError::MultipleTagsWithSameName(tag_name.to_string()));
1059 }
1060 }
1061 } else {
1062 unreachable!("field name: {}", subfield_name);
1063 }
1064 }
1065
1066 if errors.is_empty() {
1067 Ok(())
1068 } else {
1069 Err(errors)
1070 }
1071}
1072
1073#[allow(clippy::too_many_arguments)]
1074fn make_fold<'schema, 'query, V, E>(
1075 schema: &'schema Schema,
1076 query: &'query Query,
1077 vid_maker: &mut V,
1078 eid_maker: &mut E,
1079 component_path: &mut ComponentPath,
1080 output_handler: &mut OutputHandler<'query>,
1081 tags: &mut TagHandler<'query>,
1082 fold_group: &'query FoldGroup,
1083 fold_eid: Eid,
1084 edge_name: Arc<str>,
1085 edge_parameters: EdgeParameters,
1086 parent_vid: Vid,
1087 starting_vid: Vid,
1088 starting_pre_coercion_type: Arc<str>,
1089 starting_post_coercion_type: Arc<str>,
1090 starting_field: &'query FieldNode,
1091) -> Result<IRFold, Vec<FrontendError>>
1092where
1093 'schema: 'query,
1094 V: Iterator<Item = Vid>,
1095 E: Iterator<Item = Eid>,
1096{
1097 component_path.push(starting_vid);
1098 tags.begin_subcomponent(starting_vid);
1099
1100 let mut errors = vec![];
1101 let component = make_query_component(
1102 schema,
1103 query,
1104 vid_maker,
1105 eid_maker,
1106 component_path,
1107 output_handler,
1108 tags,
1109 Some(parent_vid),
1110 starting_vid,
1111 starting_pre_coercion_type,
1112 starting_post_coercion_type,
1113 starting_field,
1114 )?;
1115 component_path.pop(starting_vid);
1116 let imported_tags = tags.end_subcomponent(starting_vid);
1117
1118 if !starting_field.output.is_empty() {
1119 errors.push(FrontendError::UnsupportedEdgeOutput(starting_field.name.as_ref().to_owned()));
1122 }
1123
1124 let mut post_filters = vec![];
1125 let mut fold_specific_outputs = BTreeMap::new();
1126
1127 if let Some(transform_group) = &fold_group.transform {
1128 if transform_group.retransform.is_some() {
1129 unimplemented!("re-transforming a @fold @transform value is currently not supported");
1130 }
1131
1132 let fold_specific_field = match transform_group.transform.kind {
1133 TransformationKind::Count => FoldSpecificField {
1134 fold_eid,
1135 fold_root_vid: starting_vid,
1136 kind: FoldSpecificFieldKind::Count,
1137 },
1138 };
1139 let field_ref = FieldRef::FoldSpecificField(fold_specific_field.clone());
1140
1141 for filter_directive in &transform_group.filter {
1142 match make_filter_expr(
1143 schema,
1144 component_path,
1145 tags,
1146 starting_vid,
1147 fold_specific_field.kind,
1148 filter_directive,
1149 ) {
1150 Ok(filter) => post_filters.push(filter),
1151 Err(e) => errors.extend(e),
1152 }
1153 }
1154 for output in &transform_group.output {
1155 let final_output_name = match output.name.as_ref() {
1156 Some(explicit_name) => {
1157 output_handler
1158 .register_explicitly_named_output(explicit_name.clone(), field_ref.clone());
1159 explicit_name.clone()
1160 }
1161 None => {
1162 let local_name = if starting_field.alias.is_some() {
1163 ""
1166 } else {
1167 starting_field.name.as_ref()
1170 };
1171 output_handler.register_locally_named_output(
1172 local_name,
1173 Some(&[fold_specific_field.kind.transform_suffix()]),
1174 field_ref.clone(),
1175 )
1176 }
1177 };
1178
1179 let prior_output_by_that_name =
1180 fold_specific_outputs.insert(final_output_name.clone(), fold_specific_field.kind);
1181 if let Some(prior_output_kind) = prior_output_by_that_name {
1182 errors.push(FrontendError::MultipleOutputsWithSameName(DuplicatedNamesConflict {
1183 duplicates: btreemap! {
1184 final_output_name.to_string() => vec![
1185 (starting_field.name.to_string(), prior_output_kind.field_name().to_string()),
1186 (starting_field.name.to_string(), fold_specific_field.kind.field_name().to_string()),
1187 ]
1188 }
1189 }))
1190 }
1191 }
1192 for tag_directive in &transform_group.tag {
1193 let tag_name = tag_directive.name.as_ref().map(|x| x.as_ref());
1194 if let Some(tag_name) = tag_name {
1195 let field = FieldRef::FoldSpecificField(fold_specific_field.clone());
1196
1197 if let Err(e) = tags.register_tag(tag_name, field, component_path) {
1198 errors.push(FrontendError::MultipleTagsWithSameName(tag_name.to_string()));
1199 }
1200 } else {
1201 errors.push(FrontendError::ExplicitTagNameRequired(
1202 starting_field.name.as_ref().to_owned(),
1203 ))
1204 }
1205 }
1206 }
1207
1208 if !errors.is_empty() {
1209 return Err(errors);
1210 }
1211
1212 Ok(IRFold {
1213 eid: fold_eid,
1214 from_vid: parent_vid,
1215 to_vid: starting_vid,
1216 edge_name,
1217 parameters: edge_parameters,
1218 component: component.into(),
1219 imported_tags,
1220 post_filters,
1221 fold_specific_outputs,
1222 })
1223}
1224
1225#[cfg(test)]
1226mod tests {
1227 use std::{
1228 fs,
1229 path::{Path, PathBuf},
1230 sync::OnceLock,
1231 };
1232
1233 use trustfall_filetests_macros::parameterize;
1234
1235 use crate::{
1236 frontend::make_ir_for_query,
1237 schema::Schema,
1238 test_types::{TestIRQuery, TestIRQueryResult, TestParsedGraphQLQueryResult},
1239 };
1240
1241 static FILESYSTEM_SCHEMA: OnceLock<Schema> = OnceLock::new();
1242 static NUMBERS_SCHEMA: OnceLock<Schema> = OnceLock::new();
1243 static NULLABLES_SCHEMA: OnceLock<Schema> = OnceLock::new();
1244 static RECURSES_SCHEMA: OnceLock<Schema> = OnceLock::new();
1245
1246 fn get_filesystem_schema() -> &'static Schema {
1247 FILESYSTEM_SCHEMA.get_or_init(|| {
1248 Schema::parse(fs::read_to_string("test_data/schemas/filesystem.graphql").unwrap())
1249 .unwrap()
1250 })
1251 }
1252
1253 fn get_numbers_schema() -> &'static Schema {
1254 NUMBERS_SCHEMA.get_or_init(|| {
1255 Schema::parse(fs::read_to_string("test_data/schemas/numbers.graphql").unwrap()).unwrap()
1256 })
1257 }
1258
1259 fn get_nullables_schema() -> &'static Schema {
1260 NULLABLES_SCHEMA.get_or_init(|| {
1261 Schema::parse(fs::read_to_string("test_data/schemas/nullables.graphql").unwrap())
1262 .unwrap()
1263 })
1264 }
1265
1266 fn get_recurses_schema() -> &'static Schema {
1267 RECURSES_SCHEMA.get_or_init(|| {
1268 Schema::parse(fs::read_to_string("test_data/schemas/recurses.graphql").unwrap())
1269 .unwrap()
1270 })
1271 }
1272
1273 #[test]
1274 fn test_schemas_load_correctly() {
1275 assert!(get_filesystem_schema().vertex_types.len() > 3);
1278 assert!(!get_numbers_schema().vertex_types.is_empty());
1279 assert!(!get_nullables_schema().vertex_types.is_empty());
1280 assert!(!get_recurses_schema().vertex_types.is_empty());
1281 }
1282
1283 #[parameterize("trustfall_core/test_data/tests/frontend_errors")]
1284 fn frontend_errors(base: &Path, stem: &str) {
1285 parameterizable_tester(base, stem, ".frontend-error.ron")
1286 }
1287
1288 #[parameterize("trustfall_core/test_data/tests/execution_errors")]
1289 fn execution_errors(base: &Path, stem: &str) {
1290 parameterizable_tester(base, stem, ".ir.ron")
1291 }
1292
1293 #[parameterize("trustfall_core/test_data/tests/valid_queries")]
1294 fn valid_queries(base: &Path, stem: &str) {
1295 parameterizable_tester(base, stem, ".ir.ron")
1296 }
1297
1298 fn parameterizable_tester(base: &Path, stem: &str, check_file_suffix: &str) {
1299 let mut input_path = PathBuf::from(base);
1300 input_path.push(format!("{stem}.graphql-parsed.ron"));
1301
1302 let input_data = fs::read_to_string(input_path).unwrap();
1303 let test_query: TestParsedGraphQLQueryResult = ron::from_str(&input_data).unwrap();
1304 if test_query.is_err() {
1305 return;
1306 }
1307 let test_query = test_query.unwrap();
1308
1309 let schema: &Schema = match test_query.schema_name.as_str() {
1310 "filesystem" => get_filesystem_schema(),
1311 "numbers" => get_numbers_schema(),
1312 "nullables" => get_nullables_schema(),
1313 "recurses" => get_recurses_schema(),
1314 _ => unimplemented!("unrecognized schema name: {:?}", test_query.schema_name),
1315 };
1316
1317 let mut check_path = PathBuf::from(base);
1318 check_path.push(format!("{stem}{check_file_suffix}"));
1319 let check_data = fs::read_to_string(check_path).unwrap();
1320
1321 let arguments = test_query.arguments;
1322 let constructed_test_item =
1323 make_ir_for_query(schema, &test_query.query).map(move |ir_query| TestIRQuery {
1324 schema_name: test_query.schema_name,
1325 ir_query,
1326 arguments,
1327 });
1328
1329 let check_parsed: TestIRQueryResult = ron::from_str(&check_data).unwrap();
1330
1331 assert_eq!(check_parsed, constructed_test_item);
1332 }
1333}