apollo_compiler/validation/
interface.rs

1use crate::ast;
2use crate::collections::IndexSet;
3use crate::parser::SourceSpan;
4use crate::schema::validation::BuiltInScalars;
5use crate::schema::ComponentName;
6use crate::schema::InterfaceType;
7use crate::schema::Name;
8use crate::validation::diagnostics::DiagnosticData;
9use crate::validation::field::validate_field_definitions;
10use crate::validation::DiagnosticList;
11use crate::Node;
12
13pub(crate) fn validate_interface_definition(
14    diagnostics: &mut DiagnosticList,
15    schema: &crate::Schema,
16    built_in_scalars: &mut BuiltInScalars,
17    interface: &Node<InterfaceType>,
18) {
19    super::directive::validate_directives(
20        diagnostics,
21        Some(schema),
22        interface.directives.iter_ast(),
23        ast::DirectiveLocation::Interface,
24        // interfaces don't use variables
25        Default::default(),
26    );
27
28    // Interface must not implement itself.
29    //
30    // Return Recursive Definition error.
31    //
32    // NOTE(@lrlna): we should also check for more sneaky cyclic references for interfaces like this, for example:
33    //
34    // interface Node implements Named & Node {
35    //   id: ID!
36    //   name: String
37    // }
38    //
39    // interface Named implements Node & Named {
40    //   id: ID!
41    //   name: String
42    // }
43    for implements_interface in &interface.implements_interfaces {
44        if *implements_interface == interface.name {
45            diagnostics.push(
46                implements_interface.location(),
47                DiagnosticData::RecursiveInterfaceDefinition {
48                    name: implements_interface.name.clone(),
49                },
50            );
51        }
52    }
53
54    // Interface Type field validation.
55    validate_field_definitions(diagnostics, schema, built_in_scalars, &interface.fields);
56
57    // validate there is at least one field on the type
58    // https://spec.graphql.org/draft/#sel-HAHbnBFBABABxB4a
59    if interface.fields.is_empty() {
60        diagnostics.push(
61            interface.location(),
62            DiagnosticData::EmptyFieldSet {
63                type_name: interface.name.clone(),
64                type_location: interface.location(),
65                extensions_locations: interface
66                    .extensions()
67                    .iter()
68                    .map(|ext| ext.location())
69                    .collect(),
70            },
71        );
72    }
73
74    // Implements Interfaceds validation.
75    validate_implements_interfaces(
76        diagnostics,
77        schema,
78        &interface.name,
79        interface.location(),
80        &interface.implements_interfaces,
81    );
82
83    // When defining an interface that implements another interface, the
84    // implementing interface must define each field that is specified by
85    // the implemented interface.
86    //
87    // Returns a Missing Field error.
88    for implements_interface in &interface.implements_interfaces {
89        if let Some(super_interface) = schema.get_interface(implements_interface) {
90            for super_field in super_interface.fields.values() {
91                if interface.fields.contains_key(&super_field.name) {
92                    continue;
93                }
94                diagnostics.push(
95                    interface.location(),
96                    DiagnosticData::MissingInterfaceField {
97                        name: interface.name.clone(),
98                        implements_location: implements_interface.location(),
99                        interface: implements_interface.name.clone(),
100                        field: super_field.name.clone(),
101                        field_location: super_field.location(),
102                    },
103                );
104            }
105        }
106    }
107}
108
109pub(crate) fn validate_implements_interfaces(
110    diagnostics: &mut DiagnosticList,
111    schema: &crate::Schema,
112    implementor_name: &Name,
113    implementor_location: Option<SourceSpan>,
114    implements_interfaces: &IndexSet<ComponentName>,
115) {
116    let interface_definitions = implements_interfaces
117        .iter()
118        .filter_map(|name| {
119            schema
120                .get_interface(name)
121                .map(|interface| (name, interface))
122        })
123        .collect::<Vec<_>>();
124
125    // Implements Interfaces must be defined.
126    //
127    // Returns Undefined Definition error.
128    for interface_name in implements_interfaces {
129        if schema.get_interface(interface_name).is_some() {
130            continue;
131        }
132
133        // interface_name.loc should always be Some
134        let loc = interface_name.location();
135        diagnostics.push(
136            loc,
137            DiagnosticData::UndefinedDefinition {
138                name: interface_name.name.clone(),
139            },
140        );
141    }
142
143    // Transitively implemented interfaces must be defined on an implementing
144    // type or interface.
145    //
146    // Returns Transitive Implemented Interfaces error.
147    let transitive_interfaces = interface_definitions.iter().flat_map(|&(name, interface)| {
148        interface
149            .implements_interfaces
150            .iter()
151            .map(|component| &component.name)
152            .zip(std::iter::repeat(name))
153    });
154    for (transitive_interface, via_interface) in transitive_interfaces {
155        if implements_interfaces.contains(transitive_interface) {
156            continue;
157        }
158
159        let transitive_loc = transitive_interface.location();
160        diagnostics.push(
161            implementor_location,
162            DiagnosticData::TransitiveImplementedInterfaces {
163                interface: implementor_name.clone(),
164                via_interface: via_interface.name.clone(),
165                missing_interface: transitive_interface.clone(),
166                transitive_interface_location: transitive_loc,
167            },
168        );
169    }
170}