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