Skip to main content

apollo_federation/
api_schema.rs

1//! Implements API schema generation.
2use apollo_compiler::Node;
3use apollo_compiler::name;
4use apollo_compiler::schema::DirectiveDefinition;
5use apollo_compiler::schema::DirectiveLocation;
6use apollo_compiler::schema::InputValueDefinition;
7use apollo_compiler::ty;
8
9use crate::compat::coerce_and_validate_schema_values;
10use crate::error::FederationError;
11use crate::link::inaccessible_spec_definition::InaccessibleSpecDefinition;
12use crate::schema::FederationSchema;
13use crate::schema::ValidFederationSchema;
14use crate::schema::position;
15
16/// Remove types and directives imported by `@link`.
17fn remove_core_feature_elements(schema: &mut FederationSchema) -> Result<(), FederationError> {
18    let Some(metadata) = schema.metadata() else {
19        return Ok(());
20    };
21
22    // First collect the things to be removed so we do not hold any immutable references
23    // to the schema while mutating it below.
24    let types_for_removal = schema
25        .get_types()
26        .filter(|position| metadata.source_link_of_type(position.type_name()).is_some())
27        .collect::<Vec<_>>();
28
29    let directives_for_removal = schema
30        .get_directive_definitions()
31        .filter(|position| {
32            metadata
33                .source_link_of_directive(&position.directive_name)
34                .is_some()
35        })
36        .collect::<Vec<_>>();
37
38    // First remove children of elements that need to be removed, so there won't be outgoing
39    // references from the type.
40    for position in &types_for_removal {
41        match position {
42            position::TypeDefinitionPosition::Object(position) => {
43                let object = position.get(schema.schema())?.clone();
44                object
45                    .fields
46                    .keys()
47                    .map(|field_name| position.field(field_name.clone()))
48                    .try_for_each(|child| child.remove(schema))?;
49            }
50            position::TypeDefinitionPosition::Interface(position) => {
51                let interface = position.get(schema.schema())?.clone();
52                interface
53                    .fields
54                    .keys()
55                    .map(|field_name| position.field(field_name.clone()))
56                    .try_for_each(|child| child.remove(schema))?;
57            }
58            position::TypeDefinitionPosition::InputObject(position) => {
59                let input_object = position.get(schema.schema())?.clone();
60                input_object
61                    .fields
62                    .keys()
63                    .map(|field_name| position.field(field_name.clone()))
64                    .try_for_each(|child| child.remove(schema))?;
65            }
66            position::TypeDefinitionPosition::Enum(position) => {
67                let enum_ = position.get(schema.schema())?.clone();
68                enum_
69                    .values
70                    .keys()
71                    .map(|field_name| position.value(field_name.clone()))
72                    .try_for_each(|child| child.remove(schema))?;
73            }
74            _ => {}
75        }
76    }
77
78    for position in &directives_for_removal {
79        position.remove(schema)?;
80    }
81
82    for position in &types_for_removal {
83        match position {
84            position::TypeDefinitionPosition::Object(position) => {
85                position.remove(schema)?;
86            }
87            position::TypeDefinitionPosition::Interface(position) => {
88                position.remove(schema)?;
89            }
90            position::TypeDefinitionPosition::InputObject(position) => {
91                position.remove(schema)?;
92            }
93            position::TypeDefinitionPosition::Enum(position) => {
94                position.remove(schema)?;
95            }
96            position::TypeDefinitionPosition::Scalar(position) => {
97                position.remove(schema)?;
98            }
99            position::TypeDefinitionPosition::Union(position) => {
100                position.remove(schema)?;
101            }
102        }
103    }
104
105    Ok(())
106}
107
108#[derive(Debug, Default, Clone)]
109pub struct ApiSchemaOptions {
110    pub include_defer: bool,
111    pub include_stream: bool,
112}
113
114pub(crate) fn to_api_schema(
115    schema: ValidFederationSchema,
116    options: ApiSchemaOptions,
117) -> Result<ValidFederationSchema, FederationError> {
118    // Create a whole new federation schema that we can mutate.
119    let mut api_schema = FederationSchema::from(schema);
120
121    // As we compute the API schema of a supergraph, we want to ignore explicit definitions of `@defer` and `@stream` because
122    // those correspond to the merging of potential definitions from the subgraphs, but whether the supergraph API schema
123    // supports defer or not is unrelated to whether subgraphs support it.
124    if let Some(defer) = api_schema.get_directive_definition(&name!("defer")) {
125        defer.remove(&mut api_schema)?;
126    }
127    if let Some(stream) = api_schema.get_directive_definition(&name!("stream")) {
128        stream.remove(&mut api_schema)?;
129    }
130
131    if let Some(inaccessible_spec) = InaccessibleSpecDefinition::get_from_schema(&api_schema)? {
132        inaccessible_spec.validate_inaccessible(&api_schema)?;
133        inaccessible_spec.remove_inaccessible_elements(&mut api_schema)?;
134    }
135
136    remove_core_feature_elements(&mut api_schema)?;
137
138    let mut schema = api_schema.into_inner();
139
140    if options.include_defer {
141        schema
142            .directive_definitions
143            .insert(name!("defer"), defer_definition());
144    }
145
146    if options.include_stream {
147        schema
148            .directive_definitions
149            .insert(name!("stream"), stream_definition());
150    }
151
152    crate::compat::make_print_schema_compatible(&mut schema);
153
154    coerce_and_validate_schema_values(&mut schema)?;
155    ValidFederationSchema::new(schema.validate()?)
156}
157
158fn defer_definition() -> Node<DirectiveDefinition> {
159    Node::new(DirectiveDefinition {
160        description: None,
161        name: name!("defer"),
162        arguments: vec![
163            Node::new(InputValueDefinition {
164                description: None,
165                name: name!("label"),
166                ty: ty!(String).into(),
167                default_value: None,
168                directives: Default::default(),
169            }),
170            Node::new(InputValueDefinition {
171                description: None,
172                name: name!("if"),
173                ty: ty!(Boolean!).into(),
174                default_value: Some(true.into()),
175                directives: Default::default(),
176            }),
177        ],
178        repeatable: false,
179        locations: vec![
180            DirectiveLocation::FragmentSpread,
181            DirectiveLocation::InlineFragment,
182        ],
183    })
184}
185
186fn stream_definition() -> Node<DirectiveDefinition> {
187    Node::new(DirectiveDefinition {
188        description: None,
189        name: name!("stream"),
190        arguments: vec![
191            Node::new(InputValueDefinition {
192                description: None,
193                name: name!("label"),
194                ty: ty!(String).into(),
195                default_value: None,
196                directives: Default::default(),
197            }),
198            Node::new(InputValueDefinition {
199                description: None,
200                name: name!("if"),
201                ty: ty!(Boolean!).into(),
202                default_value: Some(true.into()),
203                directives: Default::default(),
204            }),
205            Node::new(InputValueDefinition {
206                description: None,
207                name: name!("initialCount"),
208                ty: ty!(Int).into(),
209                default_value: Some(0.into()),
210                directives: Default::default(),
211            }),
212        ],
213        repeatable: false,
214        locations: vec![DirectiveLocation::Field],
215    })
216}