Skip to main content

apollo_federation/utils/
normalize_schema.rs

1use std::cmp::Ordering;
2
3use apollo_compiler::Name;
4use apollo_compiler::Node;
5use apollo_compiler::Schema;
6use apollo_compiler::collections::IndexMap;
7use apollo_compiler::collections::IndexSet;
8use apollo_compiler::schema::Component;
9use apollo_compiler::schema::ComponentName;
10use apollo_compiler::schema::ComponentOrigin;
11use apollo_compiler::validation::Valid;
12use itertools::Itertools;
13use itertools::kmerge_by;
14
15/// For any two [Schema]s that are considered "equal", normalizing them with this function will make
16/// it such that:
17/// 1. They compare as equal via [PartialEq]/[Eq].
18/// 2. They serialize to the same string via [std::fmt::Display].
19///
20/// Schema "equality" in this context is invariant to the order of:
21/// - Schema definitions/extensions, type definitions/extensions, and directive definitions
22/// - Field definitions
23/// - Argument definitions
24/// - Input field definitions
25/// - Enum value definitions
26/// - Root operation types in schema definitions/extensions
27/// - Members in union definitions/extensions
28/// - Implemented interfaces in object/interface definitions/extensions
29/// - Locations in directive definitions
30/// - Directive applications
31/// - Arguments of directive applications
32/// - Input fields in arguments of directive applications
33/// - Input fields in default values of argument/input field definitions
34///
35/// Note that [PartialEq]/[Eq] ignores whether a component comes from a schema/type definition or a
36/// schema/type extension, while [std::fmt::Display] serializes components per-extension.
37/// Accordingly, it may be preferable to serialize via [std::fmt::Display] to check for equality if
38/// component origin is relevant. We support this by specifically sorting component containers (e.g.
39/// directive lists) by content first, and then by component origin (where component origin sort
40/// order is determined by the content with that origin).
41///
42/// Also note that [Schema] uses vectors for (and accordingly [PartialEq]/[Eq] does not ignore the
43/// order of):
44/// - Argument definitions
45/// - Locations in directive definitions
46/// - Directive applications
47/// - Arguments of directive applications
48/// - Input fields in arguments of directive applications
49/// - Input fields in default values of argument/input field definitions
50pub fn normalize_schema(mut schema: Schema) -> Normalized<Schema> {
51    sort_schema_definition(&mut schema.schema_definition);
52    schema
53        .types
54        .values_mut()
55        .for_each(sort_extended_type_definition);
56    schema.types.sort_unstable_keys();
57    schema
58        .directive_definitions
59        .values_mut()
60        .for_each(sort_directive_definition);
61    schema.directive_definitions.sort_unstable_keys();
62    Normalized(schema)
63}
64
65/// The same as [normalize_schema], but for [Valid] [Schema]s. See that function's doc comment for
66/// details.
67pub fn normalize_valid_schema(schema: Valid<Schema>) -> Normalized<Valid<Schema>> {
68    let schema = normalize_schema(schema.into_inner());
69    // Schema normalization is just sorting, and does not affect GraphQL spec validity.
70    Normalized(Valid::assume_valid(schema.into_inner()))
71}
72
73/// A marker wrapper that indicates the contained schema has been normalized via [normalize_schema].
74#[derive(Debug, Clone, Eq, PartialEq)]
75#[repr(transparent)]
76pub struct Normalized<T>(T);
77
78impl<T> Normalized<T> {
79    pub fn into_inner(self) -> T {
80        self.0
81    }
82}
83
84impl<T> std::ops::Deref for Normalized<T> {
85    type Target = T;
86
87    fn deref(&self) -> &Self::Target {
88        &self.0
89    }
90}
91
92impl<T> AsRef<T> for Normalized<T> {
93    fn as_ref(&self) -> &T {
94        &self.0
95    }
96}
97
98impl<T: std::fmt::Display> std::fmt::Display for Normalized<T> {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        self.0.fmt(f)
101    }
102}
103
104fn sort_schema_definition(definition: &mut Node<apollo_compiler::schema::SchemaDefinition>) {
105    let definition = definition.make_mut();
106    let grouped_query = group_components_by_origin_and_sort(
107        definition.query.take(),
108        |name| name.origin.clone(),
109        |_| {},
110        compare_component_names,
111    );
112    let grouped_mutation = group_components_by_origin_and_sort(
113        definition.mutation.take(),
114        |name| name.origin.clone(),
115        |_| {},
116        compare_component_names,
117    );
118    let grouped_subscription = group_components_by_origin_and_sort(
119        definition.subscription.take(),
120        |name| name.origin.clone(),
121        |_| {},
122        compare_component_names,
123    );
124    let grouped_directives = group_components_by_origin_and_sort(
125        definition.directives.drain(..),
126        |directive| directive.origin.clone(),
127        sort_component_directive,
128        compare_sorted_component_directives,
129    );
130    let mut origins: IndexSet<ComponentOrigin> = grouped_query
131        .keys()
132        .chain(grouped_mutation.keys())
133        .chain(grouped_subscription.keys())
134        .chain(grouped_directives.keys())
135        .cloned()
136        .collect();
137    sort_origins(&mut origins, |left, right| {
138        match compare_origins_by_sorted_components(
139            left,
140            right,
141            &grouped_query,
142            compare_component_names,
143        ) {
144            Ordering::Equal => (),
145            non_equal => return non_equal,
146        }
147        match compare_origins_by_sorted_components(
148            left,
149            right,
150            &grouped_mutation,
151            compare_component_names,
152        ) {
153            Ordering::Equal => (),
154            non_equal => return non_equal,
155        }
156        match compare_origins_by_sorted_components(
157            left,
158            right,
159            &grouped_subscription,
160            compare_component_names,
161        ) {
162            Ordering::Equal => (),
163            non_equal => return non_equal,
164        }
165        compare_origins_by_sorted_components(
166            left,
167            right,
168            &grouped_directives,
169            compare_sorted_component_directives,
170        )
171    });
172    definition.query = kmerge_sorted_components_and_origins(
173        grouped_query,
174        &origins,
175        |name| &name.origin,
176        compare_component_names,
177    )
178    .next();
179    definition.mutation = kmerge_sorted_components_and_origins(
180        grouped_mutation,
181        &origins,
182        |name| &name.origin,
183        compare_component_names,
184    )
185    .next();
186    definition.subscription = kmerge_sorted_components_and_origins(
187        grouped_subscription,
188        &origins,
189        |name| &name.origin,
190        compare_component_names,
191    )
192    .next();
193    definition
194        .directives
195        .extend(kmerge_sorted_components_and_origins(
196            grouped_directives,
197            &origins,
198            |directive| &directive.origin,
199            compare_sorted_component_directives,
200        ));
201}
202
203fn sort_extended_type_definition(definition: &mut apollo_compiler::schema::ExtendedType) {
204    use apollo_compiler::schema::ExtendedType;
205    match definition {
206        ExtendedType::Scalar(definition) => sort_scalar_type_definition(definition),
207        ExtendedType::Object(definition) => sort_object_type_definition(definition),
208        ExtendedType::Interface(definition) => sort_interface_type_definition(definition),
209        ExtendedType::Union(definition) => sort_union_type_definition(definition),
210        ExtendedType::Enum(definition) => sort_enum_type_definition(definition),
211        ExtendedType::InputObject(definition) => sort_input_object_type_definition(definition),
212    }
213}
214
215fn sort_scalar_type_definition(definition: &mut Node<apollo_compiler::schema::ScalarType>) {
216    let definition = definition.make_mut();
217    let grouped_directives = group_components_by_origin_and_sort(
218        definition.directives.drain(..),
219        |directive| directive.origin.clone(),
220        sort_component_directive,
221        compare_sorted_component_directives,
222    );
223    let mut origins: IndexSet<ComponentOrigin> = grouped_directives.keys().cloned().collect();
224    sort_origins(&mut origins, |left, right| {
225        compare_origins_by_sorted_components(
226            left,
227            right,
228            &grouped_directives,
229            compare_sorted_component_directives,
230        )
231    });
232    definition
233        .directives
234        .extend(kmerge_sorted_components_and_origins(
235            grouped_directives,
236            &origins,
237            |directive| &directive.origin,
238            compare_sorted_component_directives,
239        ));
240}
241
242fn sort_object_type_definition(definition: &mut Node<apollo_compiler::schema::ObjectType>) {
243    let definition = definition.make_mut();
244    let grouped_fields = group_components_by_origin_and_sort(
245        definition.fields.drain(..),
246        |(_, field)| field.origin.clone(),
247        sort_component_field_definition,
248        compare_sorted_component_field_definitions,
249    );
250    let grouped_implements_interfaces = group_components_by_origin_and_sort(
251        definition.implements_interfaces.drain(..),
252        |implements_interface| implements_interface.origin.clone(),
253        |_| {},
254        compare_component_names,
255    );
256    let grouped_directives = group_components_by_origin_and_sort(
257        definition.directives.drain(..),
258        |directive| directive.origin.clone(),
259        sort_component_directive,
260        compare_sorted_component_directives,
261    );
262    let mut origins: IndexSet<ComponentOrigin> = grouped_fields
263        .keys()
264        .chain(grouped_implements_interfaces.keys())
265        .chain(grouped_directives.keys())
266        .cloned()
267        .collect();
268    sort_origins(&mut origins, |left, right| {
269        match compare_origins_by_sorted_components(
270            left,
271            right,
272            &grouped_fields,
273            compare_sorted_component_field_definitions,
274        ) {
275            Ordering::Equal => (),
276            non_equal => return non_equal,
277        }
278        match compare_origins_by_sorted_components(
279            left,
280            right,
281            &grouped_implements_interfaces,
282            compare_component_names,
283        ) {
284            Ordering::Equal => (),
285            non_equal => return non_equal,
286        }
287        compare_origins_by_sorted_components(
288            left,
289            right,
290            &grouped_directives,
291            compare_sorted_component_directives,
292        )
293    });
294    definition
295        .fields
296        .extend(kmerge_sorted_components_and_origins(
297            grouped_fields,
298            &origins,
299            |(_, field)| &field.origin,
300            compare_sorted_component_field_definitions,
301        ));
302    definition
303        .implements_interfaces
304        .extend(kmerge_sorted_components_and_origins(
305            grouped_implements_interfaces,
306            &origins,
307            |implements_interface| &implements_interface.origin,
308            compare_component_names,
309        ));
310    definition
311        .directives
312        .extend(kmerge_sorted_components_and_origins(
313            grouped_directives,
314            &origins,
315            |directive| &directive.origin,
316            compare_sorted_component_directives,
317        ));
318}
319
320fn sort_interface_type_definition(definition: &mut Node<apollo_compiler::schema::InterfaceType>) {
321    let definition = definition.make_mut();
322    let grouped_fields = group_components_by_origin_and_sort(
323        definition.fields.drain(..),
324        |(_, field)| field.origin.clone(),
325        sort_component_field_definition,
326        compare_sorted_component_field_definitions,
327    );
328    let grouped_implements_interfaces = group_components_by_origin_and_sort(
329        definition.implements_interfaces.drain(..),
330        |implements_interface| implements_interface.origin.clone(),
331        |_| {},
332        compare_component_names,
333    );
334    let grouped_directives = group_components_by_origin_and_sort(
335        definition.directives.drain(..),
336        |directive| directive.origin.clone(),
337        sort_component_directive,
338        compare_sorted_component_directives,
339    );
340    let mut origins: IndexSet<ComponentOrigin> = grouped_fields
341        .keys()
342        .chain(grouped_implements_interfaces.keys())
343        .chain(grouped_directives.keys())
344        .cloned()
345        .collect();
346    sort_origins(&mut origins, |left, right| {
347        match compare_origins_by_sorted_components(
348            left,
349            right,
350            &grouped_fields,
351            compare_sorted_component_field_definitions,
352        ) {
353            Ordering::Equal => (),
354            non_equal => return non_equal,
355        }
356        match compare_origins_by_sorted_components(
357            left,
358            right,
359            &grouped_implements_interfaces,
360            compare_component_names,
361        ) {
362            Ordering::Equal => (),
363            non_equal => return non_equal,
364        }
365        compare_origins_by_sorted_components(
366            left,
367            right,
368            &grouped_directives,
369            compare_sorted_component_directives,
370        )
371    });
372    definition
373        .fields
374        .extend(kmerge_sorted_components_and_origins(
375            grouped_fields,
376            &origins,
377            |(_, field)| &field.origin,
378            compare_sorted_component_field_definitions,
379        ));
380    definition
381        .implements_interfaces
382        .extend(kmerge_sorted_components_and_origins(
383            grouped_implements_interfaces,
384            &origins,
385            |implements_interface| &implements_interface.origin,
386            compare_component_names,
387        ));
388    definition
389        .directives
390        .extend(kmerge_sorted_components_and_origins(
391            grouped_directives,
392            &origins,
393            |directive| &directive.origin,
394            compare_sorted_component_directives,
395        ));
396}
397
398fn sort_union_type_definition(definition: &mut Node<apollo_compiler::schema::UnionType>) {
399    let definition = definition.make_mut();
400    let grouped_members = group_components_by_origin_and_sort(
401        definition.members.drain(..),
402        |member| member.origin.clone(),
403        |_| {},
404        compare_component_names,
405    );
406    let grouped_directives = group_components_by_origin_and_sort(
407        definition.directives.drain(..),
408        |directive| directive.origin.clone(),
409        sort_component_directive,
410        compare_sorted_component_directives,
411    );
412    let mut origins: IndexSet<ComponentOrigin> = grouped_members
413        .keys()
414        .chain(grouped_directives.keys())
415        .cloned()
416        .collect();
417    sort_origins(&mut origins, |left, right| {
418        match compare_origins_by_sorted_components(
419            left,
420            right,
421            &grouped_members,
422            compare_component_names,
423        ) {
424            Ordering::Equal => (),
425            non_equal => return non_equal,
426        }
427        compare_origins_by_sorted_components(
428            left,
429            right,
430            &grouped_directives,
431            compare_sorted_component_directives,
432        )
433    });
434    definition
435        .members
436        .extend(kmerge_sorted_components_and_origins(
437            grouped_members,
438            &origins,
439            |member| &member.origin,
440            compare_component_names,
441        ));
442    definition
443        .directives
444        .extend(kmerge_sorted_components_and_origins(
445            grouped_directives,
446            &origins,
447            |directive| &directive.origin,
448            compare_sorted_component_directives,
449        ));
450}
451
452fn sort_enum_type_definition(definition: &mut Node<apollo_compiler::schema::EnumType>) {
453    let definition = definition.make_mut();
454    let grouped_values = group_components_by_origin_and_sort(
455        definition.values.drain(..),
456        |(_, value)| value.origin.clone(),
457        sort_component_enum_value_definition,
458        compare_sorted_component_enum_value_definitions,
459    );
460    let grouped_directives = group_components_by_origin_and_sort(
461        definition.directives.drain(..),
462        |directive| directive.origin.clone(),
463        sort_component_directive,
464        compare_sorted_component_directives,
465    );
466    let mut origins: IndexSet<ComponentOrigin> = grouped_values
467        .keys()
468        .chain(grouped_directives.keys())
469        .cloned()
470        .collect();
471    sort_origins(&mut origins, |left, right| {
472        match compare_origins_by_sorted_components(
473            left,
474            right,
475            &grouped_values,
476            compare_sorted_component_enum_value_definitions,
477        ) {
478            Ordering::Equal => (),
479            non_equal => return non_equal,
480        }
481        compare_origins_by_sorted_components(
482            left,
483            right,
484            &grouped_directives,
485            compare_sorted_component_directives,
486        )
487    });
488    definition
489        .values
490        .extend(kmerge_sorted_components_and_origins(
491            grouped_values,
492            &origins,
493            |(_, value)| &value.origin,
494            compare_sorted_component_enum_value_definitions,
495        ));
496    definition
497        .directives
498        .extend(kmerge_sorted_components_and_origins(
499            grouped_directives,
500            &origins,
501            |directive| &directive.origin,
502            compare_sorted_component_directives,
503        ));
504}
505
506fn sort_input_object_type_definition(
507    definition: &mut Node<apollo_compiler::schema::InputObjectType>,
508) {
509    let definition = definition.make_mut();
510    let grouped_fields = group_components_by_origin_and_sort(
511        definition.fields.drain(..),
512        |(_, field)| field.origin.clone(),
513        sort_component_input_value_definition,
514        compare_sorted_component_input_value_definitions,
515    );
516    let grouped_directives = group_components_by_origin_and_sort(
517        definition.directives.drain(..),
518        |directive| directive.origin.clone(),
519        sort_component_directive,
520        compare_sorted_component_directives,
521    );
522    let mut origins: IndexSet<ComponentOrigin> = grouped_fields
523        .keys()
524        .chain(grouped_directives.keys())
525        .cloned()
526        .collect();
527    sort_origins(&mut origins, |left, right| {
528        match compare_origins_by_sorted_components(
529            left,
530            right,
531            &grouped_fields,
532            compare_sorted_component_input_value_definitions,
533        ) {
534            Ordering::Equal => (),
535            non_equal => return non_equal,
536        }
537        compare_origins_by_sorted_components(
538            left,
539            right,
540            &grouped_directives,
541            compare_sorted_component_directives,
542        )
543    });
544    definition
545        .fields
546        .extend(kmerge_sorted_components_and_origins(
547            grouped_fields,
548            &origins,
549            |(_, value)| &value.origin,
550            compare_sorted_component_input_value_definitions,
551        ));
552    definition
553        .directives
554        .extend(kmerge_sorted_components_and_origins(
555            grouped_directives,
556            &origins,
557            |directive| &directive.origin,
558            compare_sorted_component_directives,
559        ));
560}
561
562fn sort_component_field_definition(
563    definition: &mut (Name, Component<apollo_compiler::schema::FieldDefinition>),
564) {
565    let definition = definition.1.make_mut();
566    sort_slice(
567        &mut definition.arguments,
568        sort_input_value_definition,
569        compare_sorted_input_value_definitions,
570    );
571    sort_slice(
572        &mut definition.directives,
573        sort_directive,
574        compare_sorted_directives,
575    );
576}
577
578fn compare_sorted_component_field_definitions(
579    left: &(Name, Component<apollo_compiler::schema::FieldDefinition>),
580    right: &(Name, Component<apollo_compiler::schema::FieldDefinition>),
581) -> Ordering {
582    let left = &left.1;
583    let right = &right.1;
584    match left.name.cmp(&right.name) {
585        Ordering::Equal => (),
586        non_equal => return non_equal,
587    }
588    match compare_types(&left.ty, &right.ty) {
589        Ordering::Equal => (),
590        non_equal => return non_equal,
591    }
592    match compare_slices(
593        &left.arguments,
594        &right.arguments,
595        compare_sorted_input_value_definitions,
596    ) {
597        Ordering::Equal => (),
598        non_equal => return non_equal,
599    }
600    match compare_slices(
601        &left.directives,
602        &right.directives,
603        compare_sorted_directives,
604    ) {
605        Ordering::Equal => (),
606        non_equal => return non_equal,
607    }
608    compare_options(&left.description, &right.description, compare_descriptions)
609}
610
611fn sort_component_input_value_definition(
612    definition: &mut (Name, Component<apollo_compiler::ast::InputValueDefinition>),
613) {
614    sort_input_value_definition(&mut definition.1);
615}
616
617fn compare_sorted_component_input_value_definitions(
618    left: &(Name, Component<apollo_compiler::ast::InputValueDefinition>),
619    right: &(Name, Component<apollo_compiler::ast::InputValueDefinition>),
620) -> Ordering {
621    compare_sorted_input_value_definitions(&left.1, &right.1)
622}
623
624fn sort_component_enum_value_definition(
625    definition: &mut (Name, Component<apollo_compiler::ast::EnumValueDefinition>),
626) {
627    sort_slice(
628        &mut definition.1.make_mut().directives,
629        sort_directive,
630        compare_sorted_directives,
631    );
632}
633
634fn compare_sorted_component_enum_value_definitions(
635    left: &(Name, Component<apollo_compiler::ast::EnumValueDefinition>),
636    right: &(Name, Component<apollo_compiler::ast::EnumValueDefinition>),
637) -> Ordering {
638    let left = &left.1;
639    let right = &right.1;
640    match left.value.cmp(&right.value) {
641        Ordering::Equal => (),
642        non_equal => return non_equal,
643    }
644    match compare_slices(
645        &left.directives,
646        &right.directives,
647        compare_sorted_directives,
648    ) {
649        Ordering::Equal => (),
650        non_equal => return non_equal,
651    }
652    compare_options(&left.description, &right.description, compare_descriptions)
653}
654
655fn sort_component_directive(directive: &mut Component<apollo_compiler::ast::Directive>) {
656    sort_directive(directive);
657}
658
659fn compare_sorted_component_directives(
660    left: &Component<apollo_compiler::ast::Directive>,
661    right: &Component<apollo_compiler::ast::Directive>,
662) -> Ordering {
663    compare_sorted_directives(left, right)
664}
665
666fn compare_component_names(left: &ComponentName, right: &ComponentName) -> Ordering {
667    left.name.cmp(&right.name)
668}
669
670fn group_components_by_origin_and_sort<T>(
671    iter: impl IntoIterator<Item = T>,
672    mut origin: impl FnMut(&T) -> ComponentOrigin,
673    mut sort: impl FnMut(&mut T),
674    mut compare: impl FnMut(&T, &T) -> Ordering,
675) -> IndexMap<ComponentOrigin, Vec<T>> {
676    iter.into_iter()
677        .chunk_by(|component| origin(component))
678        .into_iter()
679        .map(|(origin, chunk)| {
680            let mut chunk: Vec<T> = chunk.collect();
681            sort_slice(&mut chunk, &mut sort, &mut compare);
682            (origin, chunk)
683        })
684        .collect()
685}
686
687fn sort_origins(
688    origins: &mut IndexSet<ComponentOrigin>,
689    mut compare: impl FnMut(&ComponentOrigin, &ComponentOrigin) -> Ordering,
690) {
691    origins.sort_unstable_by(|left, right| {
692        match (left, right) {
693            (ComponentOrigin::Definition, ComponentOrigin::Extension(_)) => return Ordering::Less,
694            (ComponentOrigin::Extension(_), ComponentOrigin::Definition) => {
695                return Ordering::Greater;
696            }
697            _ => (),
698        }
699        compare(left, right)
700    })
701}
702
703fn compare_origins_by_sorted_components<T>(
704    left: &ComponentOrigin,
705    right: &ComponentOrigin,
706    components: &IndexMap<ComponentOrigin, Vec<T>>,
707    compare: impl FnMut(&T, &T) -> Ordering,
708) -> Ordering {
709    compare_slices(
710        components.get(left).unwrap_or(&vec![]),
711        components.get(right).unwrap_or(&vec![]),
712        compare,
713    )
714}
715
716fn kmerge_sorted_components_and_origins<T>(
717    components: IndexMap<ComponentOrigin, Vec<T>>,
718    origins: &IndexSet<ComponentOrigin>,
719    mut origin: impl FnMut(&T) -> &ComponentOrigin,
720    mut compare: impl FnMut(&T, &T) -> Ordering,
721) -> impl Iterator<Item = T> {
722    kmerge_by(components.into_values(), move |left: &T, right: &T| {
723        match compare(left, right) {
724            Ordering::Equal => (),
725            non_equal => return non_equal == Ordering::Less,
726        }
727        origins
728            .get_index_of(origin(left))
729            .cmp(&origins.get_index_of(origin(right)))
730            == Ordering::Less
731    })
732}
733
734fn sort_directive_definition(definition: &mut Node<apollo_compiler::ast::DirectiveDefinition>) {
735    let definition = definition.make_mut();
736    sort_slice(
737        &mut definition.arguments,
738        sort_input_value_definition,
739        compare_sorted_input_value_definitions,
740    );
741    definition
742        .locations
743        .sort_unstable_by_key(discriminant_directive_location);
744}
745
746fn sort_input_value_definition(definition: &mut Node<apollo_compiler::ast::InputValueDefinition>) {
747    let definition = definition.make_mut();
748    sort_option(&mut definition.default_value, sort_value);
749    sort_slice(
750        &mut definition.directives,
751        sort_directive,
752        compare_sorted_directives,
753    );
754}
755
756fn compare_sorted_input_value_definitions(
757    left: &Node<apollo_compiler::ast::InputValueDefinition>,
758    right: &Node<apollo_compiler::ast::InputValueDefinition>,
759) -> Ordering {
760    match left.name.cmp(&right.name) {
761        Ordering::Equal => (),
762        non_equal => return non_equal,
763    }
764    match compare_types(&left.ty, &right.ty) {
765        Ordering::Equal => (),
766        non_equal => return non_equal,
767    }
768    match compare_options(
769        &left.default_value,
770        &right.default_value,
771        compare_sorted_values,
772    ) {
773        Ordering::Equal => (),
774        non_equal => return non_equal,
775    }
776    match compare_slices(
777        &left.directives,
778        &right.directives,
779        compare_sorted_directives,
780    ) {
781        Ordering::Equal => (),
782        non_equal => return non_equal,
783    }
784    compare_options(&left.description, &right.description, compare_descriptions)
785}
786
787fn sort_directive(directive: &mut Node<apollo_compiler::ast::Directive>) {
788    sort_slice(
789        &mut directive.make_mut().arguments,
790        sort_argument,
791        compare_sorted_arguments,
792    );
793}
794
795fn compare_sorted_directives(
796    left: &Node<apollo_compiler::ast::Directive>,
797    right: &Node<apollo_compiler::ast::Directive>,
798) -> Ordering {
799    match left.name.cmp(&right.name) {
800        Ordering::Equal => (),
801        non_equal => return non_equal,
802    }
803    compare_slices(&left.arguments, &right.arguments, compare_sorted_arguments)
804}
805
806fn sort_argument(argument: &mut Node<apollo_compiler::ast::Argument>) {
807    sort_value(&mut argument.make_mut().value);
808}
809
810fn compare_sorted_arguments(
811    left: &Node<apollo_compiler::ast::Argument>,
812    right: &Node<apollo_compiler::ast::Argument>,
813) -> Ordering {
814    match left.name.cmp(&right.name) {
815        Ordering::Equal => (),
816        non_equal => return non_equal,
817    }
818    compare_sorted_values(&left.value, &right.value)
819}
820
821fn sort_value(value: &mut Node<apollo_compiler::ast::Value>) {
822    use apollo_compiler::ast::Value;
823    match value.make_mut() {
824        Value::Null
825        | Value::Enum(_)
826        | Value::Variable(_)
827        | Value::String(_)
828        | Value::Float(_)
829        | Value::Int(_)
830        | Value::Boolean(_) => {}
831        Value::List(values) => {
832            // Unlike most lists in schemas, order matters for value lists.
833            sort_ordered_slice(values, sort_value);
834        }
835        Value::Object(values) => {
836            sort_slice(values, sort_key_value, compare_sorted_key_values);
837        }
838    }
839}
840
841fn compare_sorted_values(
842    left: &Node<apollo_compiler::ast::Value>,
843    right: &Node<apollo_compiler::ast::Value>,
844) -> Ordering {
845    use apollo_compiler::ast::Value;
846    match (left.as_ref(), &right.as_ref()) {
847        (Value::Null, Value::Null) => Ordering::Equal,
848        (Value::Enum(left), Value::Enum(right)) => left.cmp(right),
849        (Value::Variable(left), Value::Variable(right)) => left.cmp(right),
850        (Value::String(left), Value::String(right)) => left.cmp(right),
851        (Value::Float(left), Value::Float(right)) => left.as_str().cmp(right.as_str()),
852        (Value::Int(left), Value::Int(right)) => left.as_str().cmp(right.as_str()),
853        (Value::Boolean(left), Value::Boolean(right)) => left.cmp(right),
854        (Value::List(left), Value::List(right)) => {
855            compare_slices(left, right, compare_sorted_values)
856        }
857        (Value::Object(left), Value::Object(right)) => {
858            compare_slices(left, right, compare_sorted_key_values)
859        }
860        (left, right) => discriminant_value(left).cmp(&discriminant_value(right)),
861    }
862}
863
864fn sort_key_value(key_value: &mut (Name, Node<apollo_compiler::ast::Value>)) {
865    sort_value(&mut key_value.1)
866}
867
868fn compare_sorted_key_values(
869    left: &(Name, Node<apollo_compiler::ast::Value>),
870    right: &(Name, Node<apollo_compiler::ast::Value>),
871) -> Ordering {
872    match left.0.cmp(&right.0) {
873        Ordering::Equal => (),
874        non_equal => return non_equal,
875    }
876    compare_sorted_values(&left.1, &right.1)
877}
878
879fn compare_types(
880    left: &apollo_compiler::ast::Type,
881    right: &apollo_compiler::ast::Type,
882) -> Ordering {
883    use apollo_compiler::ast::Type;
884    match (left, right) {
885        (Type::Named(left), Type::Named(right)) => left.cmp(right),
886        (Type::NonNullNamed(left), Type::NonNullNamed(right)) => left.cmp(right),
887        (Type::List(left), Type::List(right)) => compare_types(left, right),
888        (Type::NonNullList(left), Type::NonNullList(right)) => compare_types(left, right),
889        (left, right) => discriminant_type(left).cmp(&discriminant_type(right)),
890    }
891}
892
893fn compare_descriptions(left: &Node<str>, right: &Node<str>) -> Ordering {
894    left.cmp(right)
895}
896
897fn sort_slice<T>(
898    slice: &mut [T],
899    sort: impl FnMut(&mut T),
900    compare: impl FnMut(&T, &T) -> Ordering,
901) {
902    sort_ordered_slice(slice, sort);
903    slice.sort_unstable_by(compare);
904}
905
906fn sort_ordered_slice<T>(slice: &mut [T], sort: impl FnMut(&mut T)) {
907    slice.iter_mut().for_each(sort);
908}
909
910/// Based on the [PartialOrd] impl for slices.
911fn compare_slices<T>(
912    left: &[T],
913    right: &[T],
914    mut compare: impl FnMut(&T, &T) -> Ordering,
915) -> Ordering {
916    // Slice to the loop iteration range to enable bounds check elimination in the compiler.
917    let len_common = std::cmp::min(left.len(), right.len());
918    let left_common = &left[..len_common];
919    let right_common = &right[..len_common];
920    for i in 0..len_common {
921        match compare(&left_common[i], &right_common[i]) {
922            Ordering::Equal => continue,
923            non_equal => return non_equal,
924        }
925    }
926    left.len().cmp(&right.len())
927}
928
929fn sort_option<T>(option: &mut Option<T>, sort: impl FnMut(&mut T)) {
930    option.iter_mut().for_each(sort);
931}
932
933/// Based on the [PartialOrd] impl for [Option]s.
934fn compare_options<T>(
935    left: &Option<T>,
936    right: &Option<T>,
937    mut compare: impl FnMut(&T, &T) -> Ordering,
938) -> Ordering {
939    match (left, right) {
940        (Some(left), Some(right)) => compare(left, right),
941        (Some(_), None) => Ordering::Greater,
942        (None, Some(_)) => Ordering::Less,
943        (None, None) => Ordering::Equal,
944    }
945}
946
947fn discriminant_directive_location(location: &apollo_compiler::ast::DirectiveLocation) -> u8 {
948    use apollo_compiler::ast::DirectiveLocation;
949    match location {
950        DirectiveLocation::Query => 0,
951        DirectiveLocation::Mutation => 1,
952        DirectiveLocation::Subscription => 2,
953        DirectiveLocation::Field => 3,
954        DirectiveLocation::FragmentDefinition => 4,
955        DirectiveLocation::FragmentSpread => 5,
956        DirectiveLocation::InlineFragment => 6,
957        DirectiveLocation::VariableDefinition => 7,
958        DirectiveLocation::Schema => 8,
959        DirectiveLocation::Scalar => 9,
960        DirectiveLocation::Object => 10,
961        DirectiveLocation::FieldDefinition => 11,
962        DirectiveLocation::ArgumentDefinition => 12,
963        DirectiveLocation::Interface => 13,
964        DirectiveLocation::Union => 14,
965        DirectiveLocation::Enum => 15,
966        DirectiveLocation::EnumValue => 16,
967        DirectiveLocation::InputObject => 17,
968        DirectiveLocation::InputFieldDefinition => 18,
969    }
970}
971
972fn discriminant_value(value: &apollo_compiler::ast::Value) -> u8 {
973    use apollo_compiler::ast::Value;
974    match value {
975        Value::Null => 0,
976        Value::Enum(_) => 1,
977        Value::Variable(_) => 2,
978        Value::String(_) => 3,
979        Value::Float(_) => 4,
980        Value::Int(_) => 5,
981        Value::Boolean(_) => 6,
982        Value::List(_) => 7,
983        Value::Object(_) => 8,
984    }
985}
986
987fn discriminant_type(ty: &apollo_compiler::ast::Type) -> u8 {
988    use apollo_compiler::ast::Type;
989    match ty {
990        Type::Named(_) => 0,
991        Type::NonNullNamed(_) => 1,
992        Type::List(_) => 2,
993        Type::NonNullList(_) => 3,
994    }
995}
996
997#[cfg(test)]
998mod tests {
999    use apollo_compiler::schema::ExtendedType;
1000
1001    use super::*;
1002
1003    static TEST_SCHEMA: &str = r#"
1004    directive @d2(
1005      a2: I1
1006      a1: String
1007    ) repeatable on
1008      | FIELD_DEFINITION
1009      | SCALAR
1010      | ENUM
1011      | UNION
1012      | SUBSCRIPTION
1013      | ARGUMENT_DEFINITION
1014      | INTERFACE
1015      | FRAGMENT_DEFINITION
1016      | OBJECT
1017      | FIELD
1018      | INPUT_FIELD_DEFINITION
1019      | SCHEMA
1020      | INLINE_FRAGMENT
1021      | QUERY
1022      | INPUT_OBJECT
1023      | ENUM_VALUE
1024
1025    directive @d1(
1026      a1: String
1027      a3: String
1028      a2: I2
1029    ) repeatable on
1030      | ENUM
1031      | INPUT_OBJECT
1032      | SCHEMA
1033      | INTERFACE
1034      | FRAGMENT_SPREAD
1035      | FIELD_DEFINITION
1036      | ENUM_VALUE
1037      | MUTATION
1038      | INPUT_FIELD_DEFINITION
1039      | OBJECT
1040      | VARIABLE_DEFINITION
1041      | SCALAR
1042      | ARGUMENT_DEFINITION
1043      | UNION
1044
1045    extend schema @d1(a1: "0") {
1046      query: T3
1047    }
1048
1049    extend schema @d2(a1: "0") @d1(a1: "2")
1050
1051    schema @d1(a3: "2") {
1052      mutation: T1
1053    }
1054
1055    extend schema @d1(a1: "1") @d2(a1: "0") @d1(a3: "2", a1: "0")
1056
1057    extend schema {
1058      subscription: T2
1059    }
1060
1061    extend schema @d2(a1: "0") @d1(a1: "2")
1062
1063    extend input I1 {
1064      f1: String
1065      f3: [String!]
1066    }
1067
1068    type T3 implements N1 & N2 @d2(a1: "1") {
1069      f2(
1070        a2: I1 @d1(a2: { f3: "ID2", f1: "ID1" }) @d1(a1: "ID0")
1071        a1: I2 @d1(a2: { f3: "ID0", f1: "ID2" }) @d1(a2: { f1: "ID1", f3: "ID1" })
1072      ): T1! @d2(a1: "1") @d1(a1: "1")
1073    }
1074
1075    interface N2 @d2(a1: "1") @d1(a1: "2") {
1076      f3: ID!
1077    }
1078
1079    input I2 {
1080      f2: I1
1081      f3: String
1082      f1: ID
1083    }
1084
1085    extend type T3 implements N3 @d1(a1: "1") {
1086      f3: ID!
1087      f1(a1: Int): String
1088    }
1089
1090    scalar S2
1091
1092    type T1 {
1093      f2(a1: I2): E1
1094    }
1095
1096    extend interface N2 implements N1 @d2(a1: "2") @d1(a1: "1") {
1097      f1(a1: Int): String
1098    }
1099
1100    extend union U1 @d1 @d2 @d1 = T3
1101
1102    input I1 {
1103      f2: I2
1104    }
1105
1106    extend enum E1 @d1(a1: "0") @d1(a1: "0") {
1107      V3
1108      V1
1109    }
1110
1111    union U1 @d2 @d1 @d2 = T1 | T2
1112
1113    interface N3 {
1114      f2(a2: I1, a1: I2): T1
1115    }
1116
1117    scalar S1 @d1(a3: "2")
1118
1119    type T2 {
1120      f1: Float
1121    }
1122
1123    interface N1 {
1124      f3: ID!
1125    }
1126
1127    enum E1 @d1(a1: "0") @d1(a1: "0") {
1128      V2 @d2(a2: { f3: ["2", "1"] }) @d2(a2: { f3: ["1", "3"] })
1129    }
1130    "#;
1131
1132    fn remove_extensions(schema: &mut Schema) {
1133        fn handle_component<T>(component: &mut Component<T>) {
1134            component.origin = ComponentOrigin::Definition
1135        }
1136        fn handle_component_name(component: &mut ComponentName) {
1137            component.origin = ComponentOrigin::Definition
1138        }
1139        fn handle_indexset(components: &mut IndexSet<ComponentName>) {
1140            *components = components
1141                .drain(..)
1142                .map(|mut component| {
1143                    handle_component_name(&mut component);
1144                    component
1145                })
1146                .collect();
1147        }
1148        let schema_definition = schema.schema_definition.make_mut();
1149        schema_definition
1150            .query
1151            .iter_mut()
1152            .for_each(handle_component_name);
1153        schema_definition
1154            .mutation
1155            .iter_mut()
1156            .for_each(handle_component_name);
1157        schema_definition
1158            .subscription
1159            .iter_mut()
1160            .for_each(handle_component_name);
1161        schema_definition
1162            .directives
1163            .iter_mut()
1164            .for_each(handle_component);
1165        schema
1166            .types
1167            .values_mut()
1168            .for_each(|definition| match definition {
1169                ExtendedType::Scalar(definition) => {
1170                    let definition = definition.make_mut();
1171                    definition.directives.iter_mut().for_each(handle_component);
1172                }
1173                ExtendedType::Object(definition) => {
1174                    let definition = definition.make_mut();
1175                    definition.fields.values_mut().for_each(handle_component);
1176                    handle_indexset(&mut definition.implements_interfaces);
1177                    definition.directives.iter_mut().for_each(handle_component);
1178                }
1179                ExtendedType::Interface(definition) => {
1180                    let definition = definition.make_mut();
1181                    definition.fields.values_mut().for_each(handle_component);
1182                    handle_indexset(&mut definition.implements_interfaces);
1183                    definition.directives.iter_mut().for_each(handle_component);
1184                }
1185                ExtendedType::Union(definition) => {
1186                    let definition = definition.make_mut();
1187                    handle_indexset(&mut definition.members);
1188                    definition.directives.iter_mut().for_each(handle_component);
1189                }
1190                ExtendedType::Enum(definition) => {
1191                    let definition = definition.make_mut();
1192                    definition.values.values_mut().for_each(handle_component);
1193                    definition.directives.iter_mut().for_each(handle_component);
1194                }
1195                ExtendedType::InputObject(definition) => {
1196                    let definition = definition.make_mut();
1197                    definition.fields.values_mut().for_each(handle_component);
1198                    definition.directives.iter_mut().for_each(handle_component);
1199                }
1200            })
1201    }
1202
1203    #[test]
1204    fn test_round_trip_equality() {
1205        let schema = Schema::parse_and_validate(TEST_SCHEMA, "schema.graphql")
1206            .expect("Test schema unexpectedly invalid.");
1207
1208        // Snapshot what the normalized schema should look like.
1209        let normalized_schema = normalize_valid_schema(schema);
1210        let normalized_sdl = normalized_schema.to_string();
1211        insta::assert_snapshot!(normalized_sdl);
1212
1213        // Reparse the schema. Note that:
1214        // 1. At the time of writing this test, parsing-and-printing the normalized schema string
1215        //    results in the same schema string, but this isn't guaranteed in the future as it
1216        //    depends on parsing logic being aligned with printing logic in apollo-compiler, so we
1217        //    don't check that here.
1218        // 2. At the time of writing this test, the printed-and-parsed normalized Schema is not
1219        //    equal to the original normalized Schema. This is due to apollo-compiler parsing
1220        //    populating component containers in order of encountered definitions/extensions, while
1221        //    normalizing reorders component containers by their content. Since this parsing
1222        //    behavior may similarly change in the future, we similarly don't check that here.
1223        let reparsed_schema = Schema::parse_and_validate(&normalized_sdl, "schema.graphql")
1224            .expect("Reparsed test schema unexpectedly invalid.");
1225
1226        // Renormalize the reparsed schema, and confirm it's fully equal.
1227        let normalized_reparsed_schema = normalize_valid_schema(reparsed_schema);
1228        assert_eq!(normalized_reparsed_schema, normalized_schema);
1229        assert_eq!(normalized_reparsed_schema.to_string(), normalized_sdl);
1230    }
1231
1232    #[test]
1233    fn test_extension_equality() {
1234        let schema = Schema::parse_and_validate(TEST_SCHEMA, "schema.graphql")
1235            .expect("Test schema unexpectedly invalid.");
1236
1237        // Normalize the schema with extensions.
1238        let normalized_schema = normalize_valid_schema(schema.clone());
1239        let normalized_sdl = normalized_schema.to_string();
1240
1241        // Normalize the schema without extensions, and confirm it's still equal with PartialEq but
1242        // not with Display.
1243        let mut schema_no_exts = schema.into_inner();
1244        remove_extensions(&mut schema_no_exts);
1245        let schema_no_exts = schema_no_exts
1246            .validate()
1247            .expect("Test schema without extensions unexpectedly invalid.");
1248        let normalized_schema_no_exts = normalize_valid_schema(schema_no_exts);
1249        let normalized_sdl_no_exts = normalized_schema_no_exts.to_string();
1250        assert_eq!(normalized_schema_no_exts, normalized_schema);
1251        assert_ne!(normalized_sdl_no_exts, normalized_sdl);
1252
1253        // Remove extensions from the original normalized schema, and confirm it's fully equal.
1254        let mut normalized_schema = normalized_schema.into_inner().into_inner();
1255        remove_extensions(&mut normalized_schema);
1256        let normalized_schema = normalized_schema
1257            .validate()
1258            .expect("Test schema without extensions unexpectedly invalid.");
1259        assert_eq!(&normalized_schema, normalized_schema_no_exts.as_ref());
1260        assert_eq!(normalized_schema.to_string(), normalized_sdl_no_exts);
1261    }
1262}