apollo_federation/
api_schema.rs1use 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
16fn remove_core_feature_elements(schema: &mut FederationSchema) -> Result<(), FederationError> {
18 let Some(metadata) = schema.metadata() else {
19 return Ok(());
20 };
21
22 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 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 let mut api_schema = FederationSchema::from(schema);
120
121 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}