apollo_supergraph/
merge.rs

1use apollo_compiler::ast::Directives;
2use apollo_compiler::ast::{
3    Argument, Directive, DirectiveDefinition, DirectiveLocation, EnumValueDefinition,
4    FieldDefinition, NamedType, Type, Value,
5};
6use apollo_compiler::schema::{
7    Component, EnumType, ExtendedType, InputObjectType, InputValueDefinition, InterfaceType, Name,
8    ObjectType, ScalarType, UnionType,
9};
10use apollo_compiler::{Node, NodeStr, Schema};
11use apollo_subgraph::Subgraph;
12use indexmap::map::Entry::{Occupied, Vacant};
13use indexmap::map::Iter;
14use indexmap::{IndexMap, IndexSet};
15use std::collections::HashSet;
16use std::iter;
17
18type MergeWarning = &'static str;
19type MergeError = &'static str;
20
21struct Merger {
22    errors: Vec<MergeError>,
23    composition_hints: Vec<MergeWarning>,
24}
25
26pub struct MergeSuccess {
27    pub schema: Schema,
28    pub composition_hints: Vec<MergeWarning>,
29}
30
31pub struct MergeFailure {
32    pub schema: Option<Schema>,
33    pub errors: Vec<MergeError>,
34    pub composition_hints: Vec<MergeWarning>,
35}
36
37pub fn merge_subgraphs(subgraphs: Vec<&Subgraph>) -> Result<MergeSuccess, MergeFailure> {
38    let mut merger = Merger::new();
39    merger.merge(subgraphs)
40}
41
42impl Merger {
43    fn new() -> Self {
44        Merger {
45            composition_hints: Vec::new(),
46            errors: Vec::new(),
47        }
48    }
49    fn merge(&mut self, subgraphs: Vec<&Subgraph>) -> Result<MergeSuccess, MergeFailure> {
50        let mut subgraphs = subgraphs.clone();
51        subgraphs.sort_by(|s1, s2| s1.name.cmp(&s2.name));
52
53        let mut supergraph = Schema::new();
54        // TODO handle @compose
55
56        // add core features
57        // TODO verify federation versions across subgraphs
58        add_core_feature_link(&mut supergraph);
59        add_core_feature_join(&mut supergraph, &subgraphs);
60
61        // create stubs
62        for subgraph in &subgraphs {
63            let subgraph_name = subgraph.name.to_uppercase().clone();
64            self.merge_schema(&mut supergraph, subgraph);
65            // TODO merge directives
66
67            for (key, value) in &subgraph.schema.types {
68                if value.is_built_in() || !is_mergeable_type(key) {
69                    // skip built-ins and federation specific types
70                    continue;
71                }
72
73                match value {
74                    ExtendedType::Enum(value) => self.merge_enum_type(
75                        &mut supergraph.types,
76                        &subgraph_name,
77                        key.clone(),
78                        value,
79                    ),
80                    ExtendedType::InputObject(value) => self.merge_input_object_type(
81                        &mut supergraph.types,
82                        &subgraph_name,
83                        key.clone(),
84                        value,
85                    ),
86                    ExtendedType::Interface(value) => self.merge_interface_type(
87                        &mut supergraph.types,
88                        &subgraph_name,
89                        key.clone(),
90                        value,
91                    ),
92                    ExtendedType::Object(value) => self.merge_object_type(
93                        &mut supergraph.types,
94                        &subgraph_name,
95                        key.clone(),
96                        value,
97                    ),
98                    ExtendedType::Union(value) => self.merge_union_type(
99                        &mut supergraph.types,
100                        &subgraph_name,
101                        key.clone(),
102                        value,
103                    ),
104                    ExtendedType::Scalar(_value) => {
105                        // DO NOTHING
106                    }
107                }
108            }
109
110            // merge executable directives
111            for (_, directive) in subgraph.schema.directive_definitions.iter() {
112                if is_executable_directive(directive) {
113                    merge_directive(&mut supergraph.directive_definitions, directive);
114                }
115            }
116        }
117
118        if self.errors.is_empty() {
119            Ok(MergeSuccess {
120                schema: supergraph,
121                composition_hints: self.composition_hints.to_owned(),
122            })
123        } else {
124            Err(MergeFailure {
125                schema: Some(supergraph),
126                composition_hints: self.composition_hints.to_owned(),
127                errors: self.errors.to_owned(),
128            })
129        }
130    }
131
132    fn merge_descriptions<T: Eq + Clone>(&mut self, merged: &mut Option<T>, new: &Option<T>) {
133        match (&mut *merged, new) {
134            (_, None) => {}
135            (None, Some(_)) => *merged = new.clone(),
136            (Some(a), Some(b)) => {
137                if a != b {
138                    // TODO add info about type and from/to subgraph
139                    self.composition_hints.push("conflicting descriptions");
140                }
141            }
142        }
143    }
144
145    fn merge_schema(&mut self, supergraph_schema: &mut Schema, subgraph: &Subgraph) {
146        let supergraph_def = &mut supergraph_schema.schema_definition.make_mut();
147        let subgraph_def = &subgraph.schema.schema_definition;
148        self.merge_descriptions(&mut supergraph_def.description, &subgraph_def.description);
149
150        if subgraph_def.query.is_some() {
151            supergraph_def.query = subgraph_def.query.clone();
152            // TODO mismatch on query types
153        }
154        if subgraph_def.mutation.is_some() {
155            supergraph_def.mutation = subgraph_def.mutation.clone();
156            // TODO mismatch on mutation types
157        }
158        if subgraph_def.subscription.is_some() {
159            supergraph_def.subscription = subgraph_def.subscription.clone();
160            // TODO mismatch on subscription types
161        }
162    }
163
164    fn merge_enum_type(
165        &mut self,
166        types: &mut IndexMap<NamedType, ExtendedType>,
167        subgraph_name: &str,
168        enum_name: NamedType,
169        enum_type: &Node<EnumType>,
170    ) {
171        let existing_type = types.entry(enum_name).or_insert(copy_enum_type(enum_type));
172        if let ExtendedType::Enum(e) = existing_type {
173            let join_type_directives =
174                join_type_applied_directive(subgraph_name, iter::empty(), false);
175            e.make_mut().directives.extend(join_type_directives);
176
177            self.merge_descriptions(&mut e.make_mut().description, &enum_type.description);
178
179            // TODO we need to merge those fields LAST so we know whether enum is used as input/output/both as different merge rules will apply
180            // below logic only works for output enums
181            for (enum_value_name, enum_value) in enum_type.values.iter() {
182                let ev = e
183                    .make_mut()
184                    .values
185                    .entry(enum_value_name.clone())
186                    .or_insert(Component::new(EnumValueDefinition {
187                        value: enum_value.value.clone(),
188                        description: None,
189                        directives: Default::default(),
190                    }));
191                self.merge_descriptions(&mut ev.make_mut().description, &enum_value.description);
192                ev.make_mut().directives.push(Node::new(Directive {
193                    name: Name::new("join__enumValue"),
194                    arguments: vec![
195                        (Node::new(Argument {
196                            name: Name::new("graph"),
197                            value: Node::new(Value::Enum(Name::new(subgraph_name))),
198                        })),
199                    ],
200                }));
201            }
202        } else {
203            // TODO - conflict
204        }
205    }
206
207    fn merge_input_object_type(
208        &mut self,
209        types: &mut IndexMap<NamedType, ExtendedType>,
210        subgraph_name: &str,
211        input_object_name: NamedType,
212        input_object: &Node<InputObjectType>,
213    ) {
214        let existing_type = types
215            .entry(input_object_name)
216            .or_insert(copy_input_object_type(input_object));
217        if let ExtendedType::InputObject(obj) = existing_type {
218            let join_type_directives =
219                join_type_applied_directive(subgraph_name, iter::empty(), false);
220            let mutable_object = obj.make_mut();
221            mutable_object.directives.extend(join_type_directives);
222
223            for (field_name, _field) in input_object.fields.iter() {
224                let existing_field = mutable_object.fields.entry(field_name.clone());
225                match existing_field {
226                    Vacant(_i) => {
227                        // TODO warning - mismatch on input fields
228                    }
229                    Occupied(_i) => {
230                        // merge_options(&i.get_mut().description, &field.description);
231                        // TODO check description
232                        // TODO check type
233                        // TODO check default value
234                        // TODO process directives
235                    }
236                }
237            }
238        } else {
239            // TODO conflict on type
240        }
241    }
242
243    fn merge_interface_type(
244        &mut self,
245        types: &mut IndexMap<NamedType, ExtendedType>,
246        subgraph_name: &str,
247        interface_name: NamedType,
248        interface: &Node<InterfaceType>,
249    ) {
250        let existing_type = types
251            .entry(interface_name.clone())
252            .or_insert(copy_interface_type(interface));
253        if let ExtendedType::Interface(intf) = existing_type {
254            let key_directives = interface.directives.get_all("key");
255            let join_type_directives =
256                join_type_applied_directive(subgraph_name, key_directives, false);
257            let mutable_intf = intf.make_mut();
258            mutable_intf.directives.extend(join_type_directives);
259
260            for (field_name, field) in interface.fields.iter() {
261                let existing_field = mutable_intf.fields.entry(field_name.clone());
262                match existing_field {
263                    Vacant(i) => {
264                        // TODO warning mismatch missing fields
265                        i.insert(Component::new(FieldDefinition {
266                            name: field.name.clone(),
267                            description: field.description.clone(),
268                            arguments: vec![],
269                            ty: field.ty.clone(),
270                            directives: Default::default(),
271                        }));
272                    }
273                    Occupied(_i) => {
274                        // TODO check description
275                        // TODO check type
276                        // TODO check default value
277                        // TODO process directives
278                    }
279                }
280            }
281        } else {
282            // TODO conflict on type
283        }
284    }
285
286    fn merge_object_type(
287        &mut self,
288        types: &mut IndexMap<NamedType, ExtendedType>,
289        subgraph_name: &str,
290        object_name: NamedType,
291        object: &Node<ObjectType>,
292    ) {
293        let is_interface_object = object.directives.has("interfaceObject");
294        let existing_type = types
295            .entry(object_name.clone())
296            .or_insert(copy_object_type_stub(object, is_interface_object));
297        if let ExtendedType::Object(obj) = existing_type {
298            let key_fields: HashSet<&str> = parse_keys(object.directives.get_all("key"));
299            let is_join_field = !key_fields.is_empty() || object_name.eq("Query");
300            let key_directives = object.directives.get_all("key");
301            let join_type_directives =
302                join_type_applied_directive(subgraph_name, key_directives, false);
303            let mutable_object = obj.make_mut();
304            mutable_object.directives.extend(join_type_directives);
305            self.merge_descriptions(&mut mutable_object.description, &object.description);
306            object.implements_interfaces.iter().for_each(|intf_name| {
307                // IndexSet::insert deduplicates
308                mutable_object
309                    .implements_interfaces
310                    .insert(intf_name.clone());
311                let join_implements_directive = join_type_implements(subgraph_name, intf_name);
312                mutable_object.directives.push(join_implements_directive);
313            });
314
315            for (field_name, field) in object.fields.iter() {
316                // skip federation built-in queries
317                if field_name.eq(&Name::new("_service")) || field_name.eq(&Name::new("_entities")) {
318                    continue;
319                }
320
321                let existing_field = mutable_object.fields.entry(field_name.clone());
322                let supergraph_field = match existing_field {
323                    Occupied(f) => {
324                        // check description
325                        // check type
326                        // check args
327                        f.into_mut()
328                    }
329                    Vacant(f) => f.insert(Component::new(FieldDefinition {
330                        name: field.name.clone(),
331                        description: field.description.clone(),
332                        arguments: vec![],
333                        directives: Default::default(),
334                        ty: field.ty.clone(),
335                    })),
336                };
337                self.merge_descriptions(
338                    &mut supergraph_field.make_mut().description,
339                    &field.description,
340                );
341                let mut existing_args = supergraph_field.arguments.iter();
342                for arg in field.arguments.iter() {
343                    if let Some(_existing_arg) = &existing_args.find(|a| a.name.eq(&arg.name)) {
344                    } else {
345                        // TODO mismatch no args
346                    }
347                }
348
349                if is_join_field {
350                    let is_key_field = key_fields.contains(field_name.as_str());
351                    if !is_key_field {
352                        let requires_directive_option =
353                            Option::and_then(field.directives.get_all("requires").next(), |p| {
354                                let requires_fields =
355                                    directive_string_arg_value(p, "fields").unwrap();
356                                Some(requires_fields.as_str())
357                            });
358                        let provides_directive_option =
359                            Option::and_then(field.directives.get_all("provides").next(), |p| {
360                                let provides_fields =
361                                    directive_string_arg_value(p, "fields").unwrap();
362                                Some(provides_fields.as_str())
363                            });
364                        let external_field = field.directives.get_all("external").next().is_some();
365                        let join_field_directive = join_field_applied_directive(
366                            subgraph_name,
367                            requires_directive_option,
368                            provides_directive_option,
369                            external_field,
370                        );
371
372                        supergraph_field
373                            .make_mut()
374                            .directives
375                            .push(Node::new(join_field_directive));
376                    }
377                }
378            }
379        } else if let ExtendedType::Interface(intf) = existing_type {
380            // TODO support interface object
381            let key_directives = object.directives.get_all("key");
382            let join_type_directives =
383                join_type_applied_directive(subgraph_name, key_directives, true);
384            intf.make_mut().directives.extend(join_type_directives);
385        };
386        // TODO merge fields
387    }
388
389    fn merge_union_type(
390        &mut self,
391        types: &mut IndexMap<NamedType, ExtendedType>,
392        subgraph_name: &str,
393        union_name: NamedType,
394        union: &Node<UnionType>,
395    ) {
396        let existing_type = types
397            .entry(union_name.clone())
398            .or_insert(copy_union_type(&union_name, union.description.clone()));
399        if let ExtendedType::Union(u) = existing_type {
400            let join_type_directives =
401                join_type_applied_directive(subgraph_name, iter::empty(), false);
402            u.make_mut().directives.extend(join_type_directives);
403
404            for union_member in union.members.iter() {
405                // IndexSet::insert deduplicates
406                u.make_mut().members.insert(union_member.clone());
407                u.make_mut().directives.push(Component::new(Directive {
408                    name: Name::new("join__unionMember"),
409                    arguments: vec![
410                        Node::new(Argument {
411                            name: Name::new("graph"),
412                            value: Node::new(Value::Enum(Name::new(subgraph_name))),
413                        }),
414                        Node::new(Argument {
415                            name: Name::new("member"),
416                            value: Node::new(Value::String(Name::new(union_member))),
417                        }),
418                    ],
419                }));
420            }
421        }
422    }
423}
424
425const EXECUTABLE_DIRECTIVE_LOCATIONS: [DirectiveLocation; 8] = [
426    DirectiveLocation::Query,
427    DirectiveLocation::Mutation,
428    DirectiveLocation::Subscription,
429    DirectiveLocation::Field,
430    DirectiveLocation::FragmentDefinition,
431    DirectiveLocation::FragmentSpread,
432    DirectiveLocation::InlineFragment,
433    DirectiveLocation::VariableDefinition,
434];
435fn is_executable_directive(directive: &Node<DirectiveDefinition>) -> bool {
436    directive
437        .locations
438        .iter()
439        .any(|loc| EXECUTABLE_DIRECTIVE_LOCATIONS.contains(loc))
440}
441
442// TODO handle federation specific types - skip if any of the link/fed spec
443// TODO this info should be coming from other module
444const FEDERATION_TYPES: [&str; 4] = ["_Any", "_Entity", "_Service", "@key"];
445fn is_mergeable_type(type_name: &str) -> bool {
446    if type_name.starts_with("federation__") || type_name.starts_with("link__") {
447        return false;
448    }
449    !FEDERATION_TYPES.contains(&type_name)
450}
451
452fn copy_enum_type(enum_type: &Node<EnumType>) -> ExtendedType {
453    ExtendedType::Enum(Node::new(EnumType {
454        description: enum_type.description.clone(),
455        directives: Default::default(),
456        values: IndexMap::new(),
457    }))
458}
459
460fn copy_input_object_type(input_object: &Node<InputObjectType>) -> ExtendedType {
461    let mut new_input_object = InputObjectType {
462        description: input_object.description.clone(),
463        directives: Default::default(),
464        fields: IndexMap::new(),
465    };
466
467    for (field_name, input_field) in input_object.fields.iter() {
468        new_input_object.fields.insert(
469            field_name.clone(),
470            Component::new(InputValueDefinition {
471                name: input_field.name.clone(),
472                description: input_field.description.clone(),
473                directives: Default::default(),
474                ty: input_field.ty.clone(),
475                default_value: input_field.default_value.clone(),
476            }),
477        );
478    }
479
480    ExtendedType::InputObject(Node::new(new_input_object))
481}
482
483fn copy_interface_type(interface: &Node<InterfaceType>) -> ExtendedType {
484    let new_interface = InterfaceType {
485        description: interface.description.clone(),
486        directives: Default::default(),
487        fields: copy_fields(interface.fields.iter()),
488        implements_interfaces: interface.implements_interfaces.clone(),
489    };
490    ExtendedType::Interface(Node::new(new_interface))
491}
492
493fn copy_object_type_stub(object: &Node<ObjectType>, is_interface_object: bool) -> ExtendedType {
494    if is_interface_object {
495        let new_interface = InterfaceType {
496            description: object.description.clone(),
497            directives: Default::default(),
498            fields: copy_fields(object.fields.iter()),
499            implements_interfaces: object.implements_interfaces.clone(),
500        };
501        ExtendedType::Interface(Node::new(new_interface))
502    } else {
503        let new_object = ObjectType {
504            description: object.description.clone(),
505            directives: Default::default(),
506            fields: copy_fields(object.fields.iter()),
507            implements_interfaces: object.implements_interfaces.clone(),
508        };
509        ExtendedType::Object(Node::new(new_object))
510    }
511}
512
513fn copy_fields(
514    fields_to_copy: Iter<Name, Component<FieldDefinition>>,
515) -> IndexMap<Name, Component<FieldDefinition>> {
516    let mut new_fields: IndexMap<Name, Component<FieldDefinition>> = IndexMap::new();
517    for (field_name, field) in fields_to_copy {
518        // skip federation built-in queries
519        if field_name.eq(&Name::new("_service")) || field_name.eq(&Name::new("_entities")) {
520            continue;
521        }
522        let args: Vec<Node<InputValueDefinition>> = field
523            .arguments
524            .iter()
525            .map(|a| {
526                Node::new(InputValueDefinition {
527                    name: a.name.clone(),
528                    description: a.description.clone(),
529                    directives: Default::default(),
530                    ty: a.ty.clone(),
531                    default_value: a.default_value.clone(),
532                })
533            })
534            .collect();
535        let new_field = Component::new(FieldDefinition {
536            name: field.name.clone(),
537            description: field.description.clone(),
538            directives: Default::default(),
539            arguments: args,
540            ty: field.ty.clone(),
541        });
542
543        new_fields.insert(field_name.clone(), new_field);
544    }
545    new_fields
546}
547
548fn copy_union_type(_name: &NamedType, description: Option<NodeStr>) -> ExtendedType {
549    ExtendedType::Union(Node::new(UnionType {
550        description,
551        directives: Default::default(),
552        members: IndexSet::new(),
553    }))
554}
555
556fn join_type_applied_directive<'a>(
557    subgraph_name: &str,
558    key_directives: impl Iterator<Item = &'a Component<Directive>> + Sized,
559    is_interface_object: bool,
560) -> Vec<Component<Directive>> {
561    let mut join_type_directive = Directive {
562        name: Name::new("join__type"),
563        arguments: vec![Node::new(Argument {
564            name: Name::new("graph"),
565            value: Node::new(Value::Enum(Name::new(subgraph_name))),
566        })],
567    };
568    if is_interface_object {
569        join_type_directive.arguments.push(Node::new(Argument {
570            name: Name::new("isInterfaceObject"),
571            value: Node::new(Value::Boolean(is_interface_object)),
572        }));
573    }
574
575    let mut result = vec![];
576    for key_directive in key_directives {
577        let mut join_type_directive_with_key = join_type_directive.clone();
578        let field_set = directive_string_arg_value(key_directive, "fields").unwrap();
579        join_type_directive_with_key
580            .arguments
581            .push(Node::new(Argument {
582                name: Name::new("key"),
583                value: Node::new(Value::String(NodeStr::new(field_set.as_str()))),
584            }));
585
586        let resolvable = directive_bool_arg_value(key_directive, "resolvable").unwrap_or(&true);
587        if !resolvable {
588            join_type_directive_with_key
589                .arguments
590                .push(Node::new(Argument {
591                    name: Name::new("resolvable"),
592                    value: Node::new(Value::Boolean(false)),
593                }));
594        }
595        result.push(join_type_directive_with_key)
596    }
597    if result.is_empty() {
598        result.push(join_type_directive)
599    }
600    result
601        .into_iter()
602        .map(Component::new)
603        .collect::<Vec<Component<Directive>>>()
604}
605
606fn join_type_implements(subgraph_name: &str, intf_name: &str) -> Component<Directive> {
607    Component::new(Directive {
608        name: Name::new("join__implements"),
609        arguments: vec![
610            Node::new(Argument {
611                name: Name::new("graph"),
612                value: Node::new(Value::String(NodeStr::new(subgraph_name))),
613            }),
614            Node::new(Argument {
615                name: Name::new("interface"),
616                value: Node::new(Value::String(NodeStr::new(intf_name))),
617            }),
618        ],
619    })
620}
621
622fn directive_arg_value<'a>(directive: &'a Directive, arg_name: &'static str) -> Option<&'a Value> {
623    directive
624        .arguments
625        .iter()
626        .find(|arg| arg.name == arg_name)
627        .map(|arg| arg.value.as_ref())
628}
629
630fn directive_string_arg_value<'a>(
631    directive: &'a Directive,
632    arg_name: &'static str,
633) -> Option<&'a NodeStr> {
634    match directive_arg_value(directive, arg_name) {
635        Some(Value::String(value)) => Some(value),
636        _ => None,
637    }
638}
639
640fn directive_bool_arg_value<'a>(
641    directive: &'a Directive,
642    arg_name: &'static str,
643) -> Option<&'a bool> {
644    match directive_arg_value(directive, arg_name) {
645        Some(Value::Boolean(value)) => Some(value),
646        _ => None,
647    }
648}
649
650// TODO link spec
651fn add_core_feature_link(supergraph: &mut Schema) {
652    // @link(url: "https://specs.apollo.dev/link/v1.0")
653    supergraph
654        .schema_definition
655        .make_mut()
656        .directives
657        .push(Component::new(Directive {
658            name: Name::new("link"),
659            arguments: vec![Node::new(Argument {
660                name: Name::new("url"),
661                value: Node::new(Value::String(NodeStr::new(
662                    "https://specs.apollo.dev/link/v1.0",
663                ))),
664            })],
665        }));
666
667    let (name, link_purpose_enum) = link_purpose_enum_type();
668    supergraph.types.insert(name, link_purpose_enum.into());
669
670    // scalar Import
671    let link_import_scalar = ExtendedType::Scalar(Node::new(ScalarType {
672        directives: Default::default(),
673        description: None,
674    }));
675    supergraph
676        .types
677        .insert("link__Import".into(), link_import_scalar);
678
679    let link_directive_definition = link_directive_definition();
680    supergraph
681        .directive_definitions
682        .insert(NamedType::new("link"), Node::new(link_directive_definition));
683}
684
685/// directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA
686fn link_directive_definition() -> DirectiveDefinition {
687    DirectiveDefinition {
688        name: Name::new("link"),
689        description: None,
690        arguments: vec![
691            Node::new(InputValueDefinition {
692                name: Name::new("url"),
693                description: None,
694                directives: Default::default(),
695                ty: Type::new_named("String").into(),
696                default_value: None,
697            }),
698            Node::new(InputValueDefinition {
699                name: Name::new("as"),
700                description: None,
701                directives: Default::default(),
702                ty: Type::new_named("String").into(),
703                default_value: None,
704            }),
705            Node::new(InputValueDefinition {
706                name: Name::new("for"),
707                description: None,
708                directives: Default::default(),
709                ty: Type::new_named("link__Purpose").into(),
710                default_value: None,
711            }),
712            Node::new(InputValueDefinition {
713                name: Name::new("import"),
714                description: None,
715                directives: Default::default(),
716                ty: Type::new_named("link__Import").list().into(),
717                default_value: None,
718            }),
719        ],
720        locations: vec![DirectiveLocation::Schema],
721        repeatable: true,
722    }
723}
724
725/// enum link__Purpose {
726///   """
727///   \`SECURITY\` features provide metadata necessary to securely resolve fields.
728///   """
729///   SECURITY
730///
731///   """
732///   \`EXECUTION\` features provide metadata necessary for operation execution.
733///   """
734///   EXECUTION
735/// }
736fn link_purpose_enum_type() -> (Name, EnumType) {
737    let mut link_purpose_enum = EnumType {
738        description: None,
739        directives: Default::default(),
740        values: IndexMap::new(),
741    };
742    let link_purpose_security_value = EnumValueDefinition {
743        description: Some(NodeStr::new(
744            r"SECURITY features provide metadata necessary to securely resolve fields.",
745        )),
746        directives: Default::default(),
747        value: Name::new("SECURITY"),
748    };
749    let link_purpose_execution_value = EnumValueDefinition {
750        description: Some(NodeStr::new(
751            r"EXECUTION features provide metadata necessary for operation execution.",
752        )),
753        directives: Default::default(),
754        value: Name::new("EXECUTION"),
755    };
756    link_purpose_enum.values.insert(
757        link_purpose_security_value.value.clone(),
758        Component::new(link_purpose_security_value),
759    );
760    link_purpose_enum.values.insert(
761        link_purpose_execution_value.value.clone(),
762        Component::new(link_purpose_execution_value),
763    );
764    (Name::new("link__Purpose"), link_purpose_enum)
765}
766
767// TODO join spec
768fn add_core_feature_join(supergraph: &mut Schema, subgraphs: &Vec<&Subgraph>) {
769    // @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
770    supergraph
771        .schema_definition
772        .make_mut()
773        .directives
774        .push(Component::new(Directive {
775            name: Name::new("link"),
776            arguments: vec![
777                Node::new(Argument {
778                    name: Name::new("url"),
779                    value: Node::new(Value::String(NodeStr::new(
780                        "https://specs.apollo.dev/join/v0.3",
781                    ))),
782                }),
783                Node::new(Argument {
784                    name: Name::new("for"),
785                    value: Node::new(Value::Enum(NodeStr::new("EXECUTION"))),
786                }),
787            ],
788        }));
789
790    // scalar FieldSet
791    let join_field_set_scalar = ExtendedType::Scalar(Node::new(ScalarType {
792        directives: Default::default(),
793        description: None,
794    }));
795    supergraph
796        .types
797        .insert("join__FieldSet".into(), join_field_set_scalar);
798
799    let join_graph_directive_definition = join_graph_directive_definition();
800    supergraph.directive_definitions.insert(
801        join_graph_directive_definition.name.clone(),
802        Node::new(join_graph_directive_definition),
803    );
804
805    let join_type_directive_definition = join_type_directive_definition();
806    supergraph.directive_definitions.insert(
807        join_type_directive_definition.name.clone(),
808        Node::new(join_type_directive_definition),
809    );
810
811    let join_field_directive_definition = join_field_directive_definition();
812    supergraph.directive_definitions.insert(
813        join_field_directive_definition.name.clone(),
814        Node::new(join_field_directive_definition),
815    );
816
817    let join_implements_directive_definition = join_implements_directive_definition();
818    supergraph.directive_definitions.insert(
819        join_implements_directive_definition.name.clone(),
820        Node::new(join_implements_directive_definition),
821    );
822
823    let join_union_member_directive_definition = join_union_member_directive_definition();
824    supergraph.directive_definitions.insert(
825        join_union_member_directive_definition.name.clone(),
826        Node::new(join_union_member_directive_definition),
827    );
828
829    let join_enum_value_directive_definition = join_enum_value_directive_definition();
830    supergraph.directive_definitions.insert(
831        join_enum_value_directive_definition.name.clone(),
832        Node::new(join_enum_value_directive_definition),
833    );
834
835    let (name, join_graph_enum_type) = join_graph_enum_type(subgraphs);
836    supergraph.types.insert(name, join_graph_enum_type.into());
837}
838
839/// directive @enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
840fn join_enum_value_directive_definition() -> DirectiveDefinition {
841    DirectiveDefinition {
842        name: Name::new("join__enumValue"),
843        description: None,
844        arguments: vec![Node::new(InputValueDefinition {
845            name: Name::new("graph"),
846            description: None,
847            directives: Default::default(),
848            ty: Type::new_named("join__Graph").non_null().into(),
849            default_value: None,
850        })],
851        locations: vec![DirectiveLocation::EnumValue],
852        repeatable: true,
853    }
854}
855
856/// directive @field(
857///   graph: Graph,
858///   requires: FieldSet,
859///   provides: FieldSet,
860///   type: String,
861///   external: Boolean,
862///   override: String,
863///   usedOverridden: Boolean
864/// ) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
865fn join_field_directive_definition() -> DirectiveDefinition {
866    DirectiveDefinition {
867        name: Name::new("join__field"),
868        description: None,
869        arguments: vec![
870            Node::new(InputValueDefinition {
871                name: Name::new("graph"),
872                description: None,
873                directives: Default::default(),
874                ty: Type::new_named("join__Graph").into(),
875                default_value: None,
876            }),
877            Node::new(InputValueDefinition {
878                name: Name::new("requires"),
879                description: None,
880                directives: Default::default(),
881                ty: Type::new_named("join__FieldSet").into(),
882                default_value: None,
883            }),
884            Node::new(InputValueDefinition {
885                name: Name::new("provides"),
886                description: None,
887                directives: Default::default(),
888                ty: Type::new_named("join__FieldSet").into(),
889                default_value: None,
890            }),
891            Node::new(InputValueDefinition {
892                name: Name::new("type"),
893                description: None,
894                directives: Default::default(),
895                ty: Type::new_named("String").into(),
896                default_value: None,
897            }),
898            Node::new(InputValueDefinition {
899                name: Name::new("external"),
900                description: None,
901                directives: Default::default(),
902                ty: Type::new_named("Boolean").into(),
903                default_value: None,
904            }),
905            Node::new(InputValueDefinition {
906                name: Name::new("override"),
907                description: None,
908                directives: Default::default(),
909                ty: Type::new_named("String").into(),
910                default_value: None,
911            }),
912            Node::new(InputValueDefinition {
913                name: Name::new("usedOverridden"),
914                description: None,
915                directives: Default::default(),
916                ty: Type::new_named("Boolean").into(),
917                default_value: None,
918            }),
919        ],
920        locations: vec![
921            DirectiveLocation::FieldDefinition,
922            DirectiveLocation::InputFieldDefinition,
923        ],
924        repeatable: true,
925    }
926}
927
928fn join_field_applied_directive(
929    subgraph_name: &str,
930    requires: Option<&str>,
931    provides: Option<&str>,
932    external: bool,
933) -> Directive {
934    let mut join_field_directive = Directive {
935        name: Name::new("join__field"),
936        arguments: vec![Node::new(Argument {
937            name: Name::new("graph"),
938            value: Node::new(Value::Enum(Name::new(subgraph_name))),
939        })],
940    };
941    if let Some(required_fields) = requires {
942        join_field_directive.arguments.push(Node::new(Argument {
943            name: Name::new("requires"),
944            value: Node::new(Value::String(Name::new(required_fields))),
945        }));
946    }
947    if let Some(provided_fields) = provides {
948        join_field_directive.arguments.push(Node::new(Argument {
949            name: Name::new("provides"),
950            value: Node::new(Value::String(Name::new(provided_fields))),
951        }));
952    }
953    if external {
954        join_field_directive.arguments.push(Node::new(Argument {
955            name: Name::new("external"),
956            value: Node::new(Value::Boolean(external)),
957        }));
958    }
959    join_field_directive
960}
961
962/// directive @graph(name: String!, url: String!) on ENUM_VALUE
963fn join_graph_directive_definition() -> DirectiveDefinition {
964    DirectiveDefinition {
965        name: Name::new("join__graph"),
966        description: None,
967        arguments: vec![
968            Node::new(InputValueDefinition {
969                name: Name::new("name"),
970                description: None,
971                directives: Default::default(),
972                ty: Type::new_named("String").non_null().into(),
973                default_value: None,
974            }),
975            Node::new(InputValueDefinition {
976                name: Name::new("url"),
977                description: None,
978                directives: Default::default(),
979                ty: Type::new_named("String").non_null().into(),
980                default_value: None,
981            }),
982        ],
983        locations: vec![DirectiveLocation::EnumValue],
984        repeatable: false,
985    }
986}
987
988/// directive @implements(
989///   graph: Graph!,
990///   interface: String!
991/// ) on OBJECT | INTERFACE
992fn join_implements_directive_definition() -> DirectiveDefinition {
993    DirectiveDefinition {
994        name: Name::new("join__implements"),
995        description: None,
996        arguments: vec![
997            Node::new(InputValueDefinition {
998                name: Name::new("graph"),
999                description: None,
1000                directives: Default::default(),
1001                ty: Type::new_named("join__Graph").non_null().into(),
1002                default_value: None,
1003            }),
1004            Node::new(InputValueDefinition {
1005                name: Name::new("interface"),
1006                description: None,
1007                directives: Default::default(),
1008                ty: Type::new_named("String").non_null().into(),
1009                default_value: None,
1010            }),
1011        ],
1012        locations: vec![DirectiveLocation::Interface, DirectiveLocation::Object],
1013        repeatable: true,
1014    }
1015}
1016
1017/// directive @type(
1018///   graph: Graph!,
1019///   key: FieldSet,
1020///   extension: Boolean! = false,
1021///   resolvable: Boolean = true,
1022///   isInterfaceObject: Boolean = false
1023/// ) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
1024fn join_type_directive_definition() -> DirectiveDefinition {
1025    DirectiveDefinition {
1026        name: Name::new("join__type"),
1027        description: None,
1028        arguments: vec![
1029            Node::new(InputValueDefinition {
1030                name: Name::new("graph"),
1031                description: None,
1032                directives: Default::default(),
1033                ty: Type::new_named("join__Graph").non_null().into(),
1034                default_value: None,
1035            }),
1036            Node::new(InputValueDefinition {
1037                name: Name::new("key"),
1038                description: None,
1039                directives: Default::default(),
1040                ty: Type::new_named("join__FieldSet").into(),
1041                default_value: None,
1042            }),
1043            Node::new(InputValueDefinition {
1044                name: Name::new("extension"),
1045                description: None,
1046                directives: Default::default(),
1047                ty: Type::new_named("Boolean").non_null().into(),
1048                default_value: Some(Node::new(Value::Boolean(false))),
1049            }),
1050            Node::new(InputValueDefinition {
1051                name: Name::new("resolvable"),
1052                description: None,
1053                directives: Default::default(),
1054                ty: Type::new_named("Boolean").non_null().into(),
1055                default_value: Some(Node::new(Value::Boolean(true))),
1056            }),
1057            Node::new(InputValueDefinition {
1058                name: Name::new("isInterfaceObject"),
1059                description: None,
1060                directives: Default::default(),
1061                ty: Type::new_named("Boolean").non_null().into(),
1062                default_value: Some(Node::new(Value::Boolean(false))),
1063            }),
1064        ],
1065        locations: vec![
1066            DirectiveLocation::Enum,
1067            DirectiveLocation::InputObject,
1068            DirectiveLocation::Interface,
1069            DirectiveLocation::Object,
1070            DirectiveLocation::Scalar,
1071            DirectiveLocation::Union,
1072        ],
1073        repeatable: true,
1074    }
1075}
1076
1077/// directive @unionMember(graph: join__Graph!, member: String!) repeatable on UNION
1078fn join_union_member_directive_definition() -> DirectiveDefinition {
1079    DirectiveDefinition {
1080        name: Name::new("join__unionMember"),
1081        description: None,
1082        arguments: vec![
1083            Node::new(InputValueDefinition {
1084                name: Name::new("graph"),
1085                description: None,
1086                directives: Default::default(),
1087                ty: Type::new_named("join__Graph").non_null().into(),
1088                default_value: None,
1089            }),
1090            Node::new(InputValueDefinition {
1091                name: Name::new("member"),
1092                description: None,
1093                directives: Default::default(),
1094                ty: Type::new_named("String").non_null().into(),
1095                default_value: None,
1096            }),
1097        ],
1098        locations: vec![DirectiveLocation::Union],
1099        repeatable: true,
1100    }
1101}
1102
1103/// enum Graph
1104fn join_graph_enum_type(subgraphs: &Vec<&Subgraph>) -> (Name, EnumType) {
1105    let mut join_graph_enum_type = EnumType {
1106        description: None,
1107        directives: Default::default(),
1108        values: IndexMap::new(),
1109    };
1110    for s in subgraphs {
1111        let join_graph_applied_directive = Directive {
1112            name: Name::new("join__graph"),
1113            arguments: vec![
1114                (Node::new(Argument {
1115                    name: Name::new("name"),
1116                    value: Node::new(Value::String(NodeStr::new(s.name.as_str()))),
1117                })),
1118                (Node::new(Argument {
1119                    name: Name::new("url"),
1120                    value: Node::new(Value::String(NodeStr::new(s.url.as_str()))),
1121                })),
1122            ],
1123        };
1124        let graph = EnumValueDefinition {
1125            description: None,
1126            directives: Directives(vec![Node::new(join_graph_applied_directive)]),
1127            value: Name::new(s.name.to_uppercase().as_str()),
1128        };
1129        join_graph_enum_type
1130            .values
1131            .insert(graph.value.clone(), Component::new(graph));
1132    }
1133    (Name::new("join__Graph"), join_graph_enum_type)
1134}
1135
1136fn parse_keys<'a>(
1137    directives: impl Iterator<Item = &'a Component<Directive>> + Sized,
1138) -> HashSet<&'a str> {
1139    HashSet::from_iter(
1140        directives
1141            .flat_map(|k| {
1142                let field_set = directive_string_arg_value(k, "fields").unwrap();
1143                field_set.split_whitespace()
1144            })
1145            .collect::<Vec<&str>>(),
1146    )
1147}
1148
1149fn merge_directive(
1150    supergraph_directives: &mut IndexMap<Name, Node<DirectiveDefinition>>,
1151    directive: &Node<DirectiveDefinition>,
1152) {
1153    if !supergraph_directives.contains_key(&directive.name.clone()) {
1154        supergraph_directives.insert(directive.name.clone(), directive.clone());
1155    }
1156}