graphgate_schema/
composed_schema.rs

1use std::collections::HashMap;
2use std::ops::Deref;
3
4use indexmap::{IndexMap, IndexSet};
5use parser::types::{
6    self, BaseType, ConstDirective, DirectiveDefinition, DirectiveLocation, DocumentOperations,
7    EnumType, InputObjectType, InterfaceType, ObjectType, SchemaDefinition, Selection,
8    SelectionSet, ServiceDocument, Type, TypeDefinition, TypeSystemDefinition, UnionType,
9};
10use parser::{Positioned, Result};
11use value::{ConstValue, Name};
12
13use crate::type_ext::TypeExt;
14use crate::CombineError;
15
16#[derive(Debug, Eq, PartialEq)]
17pub enum Deprecation {
18    NoDeprecated,
19    Deprecated { reason: Option<String> },
20}
21
22impl Deprecation {
23    #[inline]
24    pub fn is_deprecated(&self) -> bool {
25        matches!(self, Deprecation::Deprecated { .. })
26    }
27
28    #[inline]
29    pub fn reason(&self) -> Option<&str> {
30        match self {
31            Deprecation::NoDeprecated => None,
32            Deprecation::Deprecated { reason } => reason.as_deref(),
33        }
34    }
35}
36
37#[derive(Debug, Eq, PartialEq)]
38pub struct MetaField {
39    pub description: Option<String>,
40    pub name: Name,
41    pub arguments: IndexMap<Name, MetaInputValue>,
42    pub ty: Type,
43    pub deprecation: Deprecation,
44
45    pub service: Option<String>,
46    pub requires: Option<KeyFields>,
47    pub provides: Option<KeyFields>,
48}
49
50#[derive(Debug, Eq, PartialEq, Copy, Clone)]
51pub enum TypeKind {
52    Scalar,
53    Object,
54    Interface,
55    Union,
56    Enum,
57    InputObject,
58}
59
60#[derive(Debug, Eq, PartialEq)]
61pub struct KeyFields(IndexMap<Name, KeyFields>);
62
63impl Deref for KeyFields {
64    type Target = IndexMap<Name, KeyFields>;
65
66    fn deref(&self) -> &Self::Target {
67        &self.0
68    }
69}
70
71#[derive(Debug, Eq, PartialEq)]
72pub struct MetaEnumValue {
73    pub description: Option<String>,
74    pub value: Name,
75    pub deprecation: Deprecation,
76}
77
78#[derive(Debug, Eq, PartialEq)]
79pub struct MetaInputValue {
80    pub description: Option<String>,
81    pub name: Name,
82    pub ty: Type,
83    pub default_value: Option<ConstValue>,
84}
85
86#[derive(Debug, Eq, PartialEq)]
87pub struct MetaType {
88    pub description: Option<String>,
89    pub name: Name,
90    pub kind: TypeKind,
91    pub owner: Option<String>,
92    pub keys: HashMap<String, Vec<KeyFields>>,
93
94    pub implements: IndexSet<Name>,
95    pub fields: IndexMap<Name, MetaField>,
96    pub possible_types: IndexSet<Name>,
97    pub enum_values: IndexMap<Name, MetaEnumValue>,
98    pub input_fields: IndexMap<Name, MetaInputValue>,
99}
100
101impl MetaType {
102    #[inline]
103    pub fn field_by_name(&self, name: &str) -> Option<&MetaField> {
104        self.fields.get(name)
105    }
106
107    #[inline]
108    pub fn is_composite(&self) -> bool {
109        matches!(
110            self.kind,
111            TypeKind::Object | TypeKind::Interface | TypeKind::Union
112        )
113    }
114
115    #[inline]
116    pub fn is_abstract(&self) -> bool {
117        matches!(self.kind, TypeKind::Interface | TypeKind::Union)
118    }
119
120    #[inline]
121    pub fn is_leaf(&self) -> bool {
122        matches!(self.kind, TypeKind::Enum | TypeKind::Scalar)
123    }
124
125    #[inline]
126    pub fn is_input(&self) -> bool {
127        matches!(
128            self.kind,
129            TypeKind::Enum | TypeKind::Scalar | TypeKind::InputObject
130        )
131    }
132
133    #[inline]
134    pub fn is_possible_type(&self, type_name: &str) -> bool {
135        match self.kind {
136            TypeKind::Interface | TypeKind::Union => self.possible_types.contains(type_name),
137            TypeKind::Object => self.name == type_name,
138            _ => false,
139        }
140    }
141
142    pub fn type_overlap(&self, ty: &MetaType) -> bool {
143        if std::ptr::eq(self, ty) {
144            return true;
145        }
146
147        match (self.is_abstract(), ty.is_abstract()) {
148            (true, true) => self
149                .possible_types
150                .iter()
151                .any(|type_name| ty.is_possible_type(type_name)),
152            (true, false) => self.is_possible_type(&ty.name),
153            (false, true) => ty.is_possible_type(&self.name),
154            (false, false) => false,
155        }
156    }
157}
158
159#[derive(Debug)]
160pub struct MetaDirective {
161    pub name: Name,
162    pub description: Option<String>,
163    pub locations: Vec<DirectiveLocation>,
164    pub arguments: IndexMap<Name, MetaInputValue>,
165}
166
167#[derive(Debug, Default)]
168pub struct ComposedSchema {
169    pub query_type: Option<Name>,
170    pub mutation_type: Option<Name>,
171    pub subscription_type: Option<Name>,
172    pub types: IndexMap<Name, MetaType>,
173    pub directives: HashMap<Name, MetaDirective>,
174}
175
176impl ComposedSchema {
177    pub fn parse(document: &str) -> Result<ComposedSchema> {
178        Ok(Self::new(parser::parse_schema(document)?))
179    }
180
181    pub fn new(document: ServiceDocument) -> ComposedSchema {
182        let mut composed_schema = ComposedSchema::default();
183
184        for definition in document.definitions.into_iter() {
185            match definition {
186                TypeSystemDefinition::Schema(schema) => {
187                    convert_schema_definition(&mut composed_schema, schema.node);
188                }
189                TypeSystemDefinition::Type(type_definition) => {
190                    composed_schema.types.insert(
191                        type_definition.node.name.node.clone(),
192                        convert_type_definition(type_definition.node),
193                    );
194                }
195                TypeSystemDefinition::Directive(_) => {}
196            }
197        }
198
199        finish_schema(&mut composed_schema);
200        composed_schema
201    }
202
203    pub fn combine(
204        federation_sdl: impl IntoIterator<Item = (String, ServiceDocument)>,
205    ) -> ::std::result::Result<Self, CombineError> {
206        let mut composed_schema = ComposedSchema::default();
207        let root_objects = &["Query", "Mutation", "Subscription"];
208
209        for obj in root_objects {
210            let name = Name::new(obj);
211            composed_schema.types.insert(
212                name.clone(),
213                MetaType {
214                    description: None,
215                    name,
216                    kind: TypeKind::Object,
217                    owner: None,
218                    keys: Default::default(),
219                    implements: Default::default(),
220                    fields: Default::default(),
221                    possible_types: Default::default(),
222                    enum_values: Default::default(),
223                    input_fields: Default::default(),
224                },
225            );
226        }
227
228        composed_schema.query_type = Some(Name::new("Query"));
229        composed_schema.mutation_type = Some(Name::new("Mutation"));
230        composed_schema.subscription_type = Some(Name::new("Subscription"));
231
232        for (service, doc) in federation_sdl {
233            for definition in doc.definitions {
234                match definition {
235                    TypeSystemDefinition::Type(type_definition) => {
236                        if let types::TypeKind::Object(ObjectType { implements, fields }) =
237                            type_definition.node.kind
238                        {
239                            let name = type_definition.node.name.node.clone();
240                            let description = type_definition
241                                .node
242                                .description
243                                .map(|description| description.node);
244                            let is_extend =
245                                type_definition.node.extend || root_objects.contains(&&*name);
246                            let meta_type = composed_schema
247                                .types
248                                .entry(name.clone())
249                                .or_insert_with(|| MetaType {
250                                    description,
251                                    name,
252                                    kind: TypeKind::Object,
253                                    owner: None,
254                                    keys: Default::default(),
255                                    implements: Default::default(),
256                                    fields: Default::default(),
257                                    possible_types: Default::default(),
258                                    enum_values: Default::default(),
259                                    input_fields: Default::default(),
260                                });
261
262                            if !is_extend {
263                                meta_type.owner = Some(service.clone());
264                            };
265
266                            for directive in type_definition.node.directives {
267                                if directive.node.name.node.as_str() == "key" {
268                                    if let Some(fields) =
269                                        get_argument_str(&directive.node.arguments, "fields")
270                                    {
271                                        if let Some(selection_set) =
272                                            parse_fields(fields.node).map(|selection_set| {
273                                                Positioned::new(selection_set, directive.pos)
274                                            })
275                                        {
276                                            meta_type
277                                                .keys
278                                                .entry(service.clone())
279                                                .or_default()
280                                                .push(convert_key_fields(selection_set.node));
281                                        }
282                                    }
283                                }
284                            }
285
286                            meta_type
287                                .implements
288                                .extend(implements.into_iter().map(|implement| implement.node));
289
290                            for field in fields {
291                                if is_extend {
292                                    let is_external =
293                                        has_directive(&field.node.directives, "external");
294                                    if is_external {
295                                        continue;
296                                    }
297                                }
298
299                                if meta_type.fields.contains_key(&field.node.name.node) {
300                                    return Err(CombineError::FieldConflicted {
301                                        type_name: type_definition.node.name.node.to_string(),
302                                        field_name: field.node.name.node.to_string(),
303                                    });
304                                }
305                                let mut meta_field = convert_field_definition(field.node);
306                                if is_extend {
307                                    meta_field.service = Some(service.clone());
308                                }
309                                meta_type.fields.insert(meta_field.name.clone(), meta_field);
310                            }
311                        } else {
312                            let meta_type = convert_type_definition(type_definition.node);
313                            if let Some(meta_type2) = composed_schema.types.get(&meta_type.name) {
314                                if meta_type2 != &meta_type {
315                                    return Err(CombineError::DefinitionConflicted {
316                                        type_name: meta_type.name.to_string(),
317                                    });
318                                }
319                            }
320                            composed_schema
321                                .types
322                                .insert(meta_type.name.clone(), meta_type);
323                        }
324                    }
325                    TypeSystemDefinition::Schema(_schema_definition) => {
326                        return Err(CombineError::SchemaIsNotAllowed)
327                    }
328                    TypeSystemDefinition::Directive(_directive_definition) => {}
329                }
330            }
331        }
332
333        if let Some(mutation) = composed_schema.types.get("Mutation") {
334            if mutation.fields.is_empty() {
335                composed_schema.types.remove("Mutation");
336                composed_schema.mutation_type = None;
337            }
338        }
339
340        if let Some(subscription) = composed_schema.types.get("Subscription") {
341            if subscription.fields.is_empty() {
342                composed_schema.types.remove("Subscription");
343                composed_schema.subscription_type = None;
344            }
345        }
346
347        finish_schema(&mut composed_schema);
348        Ok(composed_schema)
349    }
350
351    #[inline]
352    pub fn query_type(&self) -> &str {
353        self.query_type
354            .as_ref()
355            .map(|name| name.as_str())
356            .unwrap_or("Query")
357    }
358
359    #[inline]
360    pub fn mutation_type(&self) -> Option<&str> {
361        self.mutation_type.as_ref().map(|name| name.as_str())
362    }
363
364    #[inline]
365    pub fn subscription_type(&self) -> Option<&str> {
366        self.subscription_type.as_ref().map(|name| name.as_str())
367    }
368
369    #[inline]
370    pub fn get_type(&self, ty: &Type) -> Option<&MetaType> {
371        let name = match &ty.base {
372            BaseType::Named(name) => name.as_str(),
373            BaseType::List(ty) => return self.get_type(ty),
374        };
375        self.types.get(name)
376    }
377
378    pub fn concrete_type_by_name(&self, ty: &Type) -> Option<&MetaType> {
379        self.types.get(ty.concrete_typename())
380    }
381}
382
383fn get_argument<'a>(
384    arguments: &'a [(Positioned<Name>, Positioned<ConstValue>)],
385    name: &str,
386) -> Option<&'a Positioned<ConstValue>> {
387    arguments.iter().find_map(|d| {
388        if d.0.node.as_str() == name {
389            Some(&d.1)
390        } else {
391            None
392        }
393    })
394}
395
396fn get_argument_str<'a>(
397    arguments: &'a [(Positioned<Name>, Positioned<ConstValue>)],
398    name: &str,
399) -> Option<Positioned<&'a str>> {
400    get_argument(arguments, name).and_then(|value| match &value.node {
401        ConstValue::String(s) => Some(Positioned::new(s.as_str(), value.pos)),
402        _ => None,
403    })
404}
405
406fn parse_fields(fields: &str) -> Option<SelectionSet> {
407    parser::parse_query(format!("{{{}}}", fields))
408        .ok()
409        .and_then(|document| match document.operations {
410            DocumentOperations::Single(op) => Some(op.node.selection_set.node),
411            DocumentOperations::Multiple(_) => None,
412        })
413}
414
415fn convert_schema_definition(
416    composed_schema: &mut ComposedSchema,
417    schema_definition: SchemaDefinition,
418) {
419    composed_schema.query_type = schema_definition.query.map(|name| name.node);
420    composed_schema.mutation_type = schema_definition.mutation.map(|name| name.node);
421    composed_schema.subscription_type = schema_definition.subscription.map(|name| name.node);
422}
423
424fn convert_type_definition(definition: TypeDefinition) -> MetaType {
425    let mut type_definition = MetaType {
426        description: definition.description.map(|description| description.node),
427        name: definition.name.node.clone(),
428        kind: TypeKind::Scalar,
429        owner: None,
430        keys: Default::default(),
431        implements: Default::default(),
432        fields: Default::default(),
433        possible_types: Default::default(),
434        enum_values: Default::default(),
435        input_fields: Default::default(),
436    };
437
438    match definition.kind {
439        types::TypeKind::Scalar => type_definition.kind = TypeKind::Scalar,
440        types::TypeKind::Object(ObjectType { implements, fields }) => {
441            type_definition.kind = TypeKind::Object;
442            type_definition.implements = implements
443                .into_iter()
444                .map(|implement| implement.node)
445                .collect();
446            type_definition
447                .fields
448                .extend(fields.into_iter().map(|field| {
449                    (
450                        field.node.name.node.clone(),
451                        convert_field_definition(field.node),
452                    )
453                }));
454        }
455        types::TypeKind::Interface(InterfaceType { implements, fields }) => {
456            type_definition.kind = TypeKind::Interface;
457            type_definition.implements = implements.into_iter().map(|name| name.node).collect();
458            type_definition.fields = fields
459                .into_iter()
460                .map(|field| {
461                    (
462                        field.node.name.node.clone(),
463                        convert_field_definition(field.node),
464                    )
465                })
466                .collect();
467        }
468        types::TypeKind::Union(UnionType { members }) => {
469            type_definition.kind = TypeKind::Union;
470            type_definition.possible_types = members.into_iter().map(|name| name.node).collect();
471        }
472        types::TypeKind::Enum(EnumType { values }) => {
473            type_definition.kind = TypeKind::Enum;
474            type_definition
475                .enum_values
476                .extend(values.into_iter().map(|value| {
477                    (
478                        value.node.value.node.clone(),
479                        MetaEnumValue {
480                            description: value.node.description.map(|description| description.node),
481                            value: value.node.value.node,
482                            deprecation: get_deprecated(&value.node.directives),
483                        },
484                    )
485                }));
486        }
487        types::TypeKind::InputObject(InputObjectType { fields }) => {
488            type_definition.kind = TypeKind::InputObject;
489            type_definition
490                .input_fields
491                .extend(fields.into_iter().map(|field| {
492                    (
493                        field.node.name.node.clone(),
494                        convert_input_value_definition(field.node),
495                    )
496                }));
497        }
498    }
499
500    for directive in definition.directives {
501        match directive.node.name.node.as_str() {
502            "owner" => {
503                if let Some(service) = get_argument_str(&directive.node.arguments, "service") {
504                    type_definition.owner = Some(service.node.to_string());
505                }
506            }
507            "key" => {
508                if let Some((fields, service)) =
509                    get_argument_str(&directive.node.arguments, "fields")
510                        .zip(get_argument_str(&directive.node.arguments, "service"))
511                {
512                    if let Some(selection_set) = parse_fields(fields.node)
513                        .map(|selection_set| Positioned::new(selection_set, directive.pos))
514                    {
515                        type_definition
516                            .keys
517                            .entry(service.node.to_string())
518                            .or_default()
519                            .push(convert_key_fields(selection_set.node));
520                    }
521                }
522            }
523            _ => {}
524        }
525    }
526
527    type_definition
528}
529
530fn convert_field_definition(definition: types::FieldDefinition) -> MetaField {
531    let mut field_definition = MetaField {
532        description: definition.description.map(|description| description.node),
533        name: definition.name.node,
534        arguments: definition
535            .arguments
536            .into_iter()
537            .map(|arg| {
538                (
539                    arg.node.name.node.clone(),
540                    convert_input_value_definition(arg.node),
541                )
542            })
543            .collect(),
544        ty: definition.ty.node,
545        deprecation: get_deprecated(&definition.directives),
546        service: None,
547        requires: None,
548        provides: None,
549    };
550
551    for directive in definition.directives {
552        match directive.node.name.node.as_str() {
553            "resolve" => {
554                if let Some(service) = get_argument_str(&directive.node.arguments, "service") {
555                    field_definition.service = Some(service.node.to_string());
556                }
557            }
558            "requires" => {
559                if let Some(fields) = get_argument_str(&directive.node.arguments, "fields") {
560                    field_definition.requires = parse_fields(fields.node).map(convert_key_fields);
561                }
562            }
563            "provides" => {
564                if let Some(fields) = get_argument_str(&directive.node.arguments, "fields") {
565                    field_definition.provides = parse_fields(fields.node).map(convert_key_fields);
566                }
567            }
568            _ => {}
569        }
570    }
571
572    field_definition
573}
574
575fn convert_key_fields(selection_set: SelectionSet) -> KeyFields {
576    KeyFields(
577        selection_set
578            .items
579            .into_iter()
580            .filter_map(|field| {
581                if let Selection::Field(field) = field.node {
582                    Some((
583                        field.node.name.node,
584                        convert_key_fields(field.node.selection_set.node),
585                    ))
586                } else {
587                    None
588                }
589            })
590            .collect(),
591    )
592}
593
594fn convert_input_value_definition(arg: parser::types::InputValueDefinition) -> MetaInputValue {
595    MetaInputValue {
596        description: arg.description.map(|description| description.node),
597        name: arg.name.node,
598        ty: arg.ty.node,
599        default_value: arg.default_value.map(|default_value| default_value.node),
600    }
601}
602
603fn convert_directive_definition(directive_definition: DirectiveDefinition) -> MetaDirective {
604    MetaDirective {
605        name: directive_definition.name.node,
606        description: directive_definition
607            .description
608            .map(|directive_definition| directive_definition.node),
609        locations: directive_definition
610            .locations
611            .into_iter()
612            .map(|location| location.node)
613            .collect(),
614        arguments: directive_definition
615            .arguments
616            .into_iter()
617            .map(|arg| {
618                (
619                    arg.node.name.node.clone(),
620                    convert_input_value_definition(arg.node),
621                )
622            })
623            .collect(),
624    }
625}
626
627fn get_deprecated(directives: &[Positioned<ConstDirective>]) -> Deprecation {
628    directives
629        .iter()
630        .find(|directive| directive.node.name.node.as_str() == "deprecated")
631        .map(|directive| Deprecation::Deprecated {
632            reason: get_argument_str(&directive.node.arguments, "reason")
633                .map(|reason| reason.node.to_string()),
634        })
635        .unwrap_or(Deprecation::NoDeprecated)
636}
637
638fn has_directive(directives: &[Positioned<ConstDirective>], name: &str) -> bool {
639    directives
640        .iter()
641        .any(|directive| directive.node.name.node.as_str() == name)
642}
643
644fn finish_schema(composed_schema: &mut ComposedSchema) {
645    for definition in parser::parse_schema(include_str!("builtin.graphql"))
646        .unwrap()
647        .definitions
648        .into_iter()
649    {
650        match definition {
651            TypeSystemDefinition::Type(type_definition) => {
652                let type_definition = convert_type_definition(type_definition.node);
653                composed_schema
654                    .types
655                    .insert(type_definition.name.clone(), type_definition);
656            }
657            TypeSystemDefinition::Directive(directive_definition) => {
658                composed_schema.directives.insert(
659                    directive_definition.node.name.node.clone(),
660                    convert_directive_definition(directive_definition.node),
661                );
662            }
663            TypeSystemDefinition::Schema(_) => {}
664        }
665    }
666
667    if let Some(query_type) = composed_schema.types.get_mut(
668        composed_schema
669            .query_type
670            .as_ref()
671            .map(|name| name.as_str())
672            .unwrap_or("Query"),
673    ) {
674        let name = Name::new("__type");
675        query_type.fields.insert(
676            name.clone(),
677            MetaField {
678                description: None,
679                name,
680                arguments: {
681                    let mut arguments = IndexMap::new();
682                    let name = Name::new("name");
683                    arguments.insert(
684                        name.clone(),
685                        MetaInputValue {
686                            description: None,
687                            name,
688                            ty: Type::new("String!").unwrap(),
689                            default_value: None,
690                        },
691                    );
692                    arguments
693                },
694                ty: Type::new("__Type").unwrap(),
695                deprecation: Deprecation::NoDeprecated,
696                service: None,
697                requires: None,
698                provides: None,
699            },
700        );
701
702        let name = Name::new("__schema");
703        query_type.fields.insert(
704            name.clone(),
705            MetaField {
706                description: None,
707                name,
708                arguments: Default::default(),
709                ty: Type::new("__Schema!").unwrap(),
710                deprecation: Deprecation::NoDeprecated,
711                service: None,
712                requires: None,
713                provides: None,
714            },
715        );
716    }
717
718    let mut possible_types: HashMap<Name, IndexSet<Name>> = Default::default();
719    for ty in composed_schema.types.values() {
720        if ty.kind == TypeKind::Object {
721            for implement in &ty.implements {
722                possible_types
723                    .entry(implement.clone())
724                    .or_default()
725                    .insert(ty.name.clone());
726            }
727        }
728    }
729    for (name, types) in possible_types {
730        if let Some(ty) = composed_schema.types.get_mut(&name) {
731            ty.possible_types = types;
732        }
733    }
734}