apollo_federation/subgraph/
mod.rs1use std::collections::BTreeMap;
2use std::fmt::Formatter;
3use std::sync::Arc;
4
5use apollo_compiler::Node;
6use apollo_compiler::Schema;
7use apollo_compiler::collections::IndexMap;
8use apollo_compiler::collections::IndexSet;
9use apollo_compiler::name;
10use apollo_compiler::schema::ComponentName;
11use apollo_compiler::schema::ExtendedType;
12use apollo_compiler::schema::ObjectType;
13use apollo_compiler::validation::Valid;
14use indexmap::map::Entry;
15
16use crate::ValidFederationSubgraph;
17use crate::error::FederationError;
18use crate::link::DEFAULT_LINK_NAME;
19use crate::link::Link;
20use crate::link::LinkError;
21use crate::link::spec::Identity;
22use crate::subgraph::spec::ANY_SCALAR_NAME;
23use crate::subgraph::spec::AppliedFederationLink;
24use crate::subgraph::spec::CONTEXTFIELDVALUE_SCALAR_NAME;
25use crate::subgraph::spec::ENTITIES_QUERY;
26use crate::subgraph::spec::ENTITY_UNION_NAME;
27use crate::subgraph::spec::FEDERATION_V2_DIRECTIVE_NAMES;
28use crate::subgraph::spec::FederationSpecDefinitions;
29use crate::subgraph::spec::KEY_DIRECTIVE_NAME;
30use crate::subgraph::spec::LinkSpecDefinitions;
31use crate::subgraph::spec::SERVICE_SDL_QUERY;
32use crate::subgraph::spec::SERVICE_TYPE;
33
34mod database;
35pub mod spec;
36
37pub struct Subgraph {
38 pub name: String,
39 pub url: String,
40 pub schema: Schema,
41}
42
43impl Subgraph {
44 pub fn new(name: &str, url: &str, schema_str: &str) -> Result<Self, FederationError> {
45 let schema = Schema::parse(schema_str, name)?;
46 Ok(Self {
48 name: name.to_string(),
49 url: url.to_string(),
50 schema,
51 })
52 }
53
54 pub fn parse_and_expand(
55 name: &str,
56 url: &str,
57 schema_str: &str,
58 ) -> Result<ValidSubgraph, FederationError> {
59 let mut schema = Schema::builder()
60 .adopt_orphan_extensions()
61 .parse(schema_str, name)
62 .build()?;
63
64 let mut imported_federation_definitions: Option<FederationSpecDefinitions> = None;
65 let mut imported_link_definitions: Option<LinkSpecDefinitions> = None;
66 let default_link_name = DEFAULT_LINK_NAME;
67 let link_directives = schema
68 .schema_definition
69 .directives
70 .get_all(&default_link_name);
71
72 for directive in link_directives {
73 let link_directive = Link::from_directive_application(directive)?;
74 if link_directive.url.identity == Identity::federation_identity() {
75 if imported_federation_definitions.is_some() {
76 let msg = "invalid graphql schema - multiple @link imports for the federation specification are not supported";
77 return Err(LinkError::BootstrapError(msg.to_owned()).into());
78 }
79
80 imported_federation_definitions =
81 Some(FederationSpecDefinitions::from_link(link_directive)?);
82 } else if link_directive.url.identity == Identity::link_identity() {
83 if imported_link_definitions.is_some() {
85 let msg = "invalid graphql schema - multiple @link imports for the link specification are not supported";
86 return Err(LinkError::BootstrapError(msg.to_owned()).into());
87 }
88
89 imported_link_definitions = Some(LinkSpecDefinitions::new(link_directive));
90 }
91 }
92
93 Self::populate_missing_type_definitions(
95 &mut schema,
96 imported_federation_definitions,
97 imported_link_definitions,
98 )?;
99 let schema = schema.validate()?;
100 Ok(ValidSubgraph {
101 name: name.to_owned(),
102 url: url.to_owned(),
103 schema,
104 })
105 }
106
107 fn populate_missing_type_definitions(
108 schema: &mut Schema,
109 imported_federation_definitions: Option<FederationSpecDefinitions>,
110 imported_link_definitions: Option<LinkSpecDefinitions>,
111 ) -> Result<(), FederationError> {
112 let link_spec_definitions = match imported_link_definitions {
114 Some(definitions) => definitions,
115 None => {
116 let defaults = LinkSpecDefinitions::default();
118 schema
119 .schema_definition
120 .make_mut()
121 .directives
122 .push(defaults.applied_link_directive());
123 defaults
124 }
125 };
126 Self::populate_missing_link_definitions(schema, link_spec_definitions)?;
127
128 let fed_definitions = match imported_federation_definitions {
130 Some(definitions) => definitions,
131 None => {
132 let defaults = FederationSpecDefinitions::default()?;
135 schema
136 .schema_definition
137 .make_mut()
138 .directives
139 .push(defaults.applied_link_directive());
140 defaults
141 }
142 };
143 Self::populate_missing_federation_directive_definitions(schema, &fed_definitions)?;
144 Self::populate_missing_federation_types(schema, &fed_definitions)
145 }
146
147 fn populate_missing_link_definitions(
148 schema: &mut Schema,
149 link_spec_definitions: LinkSpecDefinitions,
150 ) -> Result<(), FederationError> {
151 let purpose_enum_name = &link_spec_definitions.purpose_enum_name;
152 schema
153 .types
154 .entry(purpose_enum_name.clone())
155 .or_insert_with(|| {
156 link_spec_definitions
157 .link_purpose_enum_definition(purpose_enum_name.clone())
158 .into()
159 });
160 let import_scalar_name = &link_spec_definitions.import_scalar_name;
161 schema
162 .types
163 .entry(import_scalar_name.clone())
164 .or_insert_with(|| {
165 link_spec_definitions
166 .import_scalar_definition(import_scalar_name.clone())
167 .into()
168 });
169 if let Entry::Vacant(entry) = schema.directive_definitions.entry(DEFAULT_LINK_NAME) {
170 entry.insert(link_spec_definitions.link_directive_definition()?.into());
171 }
172 Ok(())
173 }
174
175 fn populate_missing_federation_directive_definitions(
176 schema: &mut Schema,
177 fed_definitions: &FederationSpecDefinitions,
178 ) -> Result<(), FederationError> {
179 let fieldset_scalar_name = &fed_definitions.fieldset_scalar_name;
181 schema
182 .types
183 .entry(fieldset_scalar_name.clone())
184 .or_insert_with(|| {
185 fed_definitions
186 .fieldset_scalar_definition(fieldset_scalar_name.clone())
187 .into()
188 });
189
190 let namespaced_contextfieldvalue_scalar_name =
192 fed_definitions.namespaced_type_name(&CONTEXTFIELDVALUE_SCALAR_NAME, false);
193 if let Entry::Vacant(entry) = schema
194 .types
195 .entry(namespaced_contextfieldvalue_scalar_name.clone())
196 {
197 let type_definition = fed_definitions.contextfieldvalue_scalar_definition(&Some(
198 namespaced_contextfieldvalue_scalar_name,
199 ));
200 entry.insert(type_definition.into());
201 }
202
203 for directive_name in &FEDERATION_V2_DIRECTIVE_NAMES {
204 let namespaced_directive_name =
205 fed_definitions.namespaced_type_name(directive_name, true);
206 if let Entry::Vacant(entry) = schema
207 .directive_definitions
208 .entry(namespaced_directive_name.clone())
209 {
210 let directive_definition = fed_definitions.directive_definition(
211 directive_name,
212 &Some(namespaced_directive_name.to_owned()),
213 )?;
214 entry.insert(directive_definition.into());
215 }
216 }
217 Ok(())
218 }
219
220 fn populate_missing_federation_types(
221 schema: &mut Schema,
222 fed_definitions: &FederationSpecDefinitions,
223 ) -> Result<(), FederationError> {
224 schema
225 .types
226 .entry(SERVICE_TYPE)
227 .or_insert_with(|| fed_definitions.service_object_type_definition());
228
229 let entities = Self::locate_entities(schema, fed_definitions);
230 let entities_present = !entities.is_empty();
231 if entities_present {
232 schema
233 .types
234 .entry(ENTITY_UNION_NAME)
235 .or_insert_with(|| fed_definitions.entity_union_definition(entities));
236 schema
237 .types
238 .entry(ANY_SCALAR_NAME)
239 .or_insert_with(|| fed_definitions.any_scalar_definition());
240 }
241
242 let query_type_name = schema
243 .schema_definition
244 .make_mut()
245 .query
246 .get_or_insert(ComponentName::from(name!("Query")));
247 if let ExtendedType::Object(query_type) = schema
248 .types
249 .entry(query_type_name.name.clone())
250 .or_insert(ExtendedType::Object(Node::new(ObjectType {
251 description: None,
252 name: query_type_name.name.clone(),
253 directives: Default::default(),
254 fields: IndexMap::default(),
255 implements_interfaces: IndexSet::default(),
256 })))
257 {
258 let query_type = query_type.make_mut();
259 query_type
260 .fields
261 .entry(SERVICE_SDL_QUERY)
262 .or_insert_with(|| fed_definitions.service_sdl_query_field());
263 if entities_present {
264 query_type
266 .fields
267 .entry(ENTITIES_QUERY)
268 .or_insert_with(|| fed_definitions.entities_query_field());
269 }
270 }
271 Ok(())
272 }
273
274 fn locate_entities(
275 schema: &mut Schema,
276 fed_definitions: &FederationSpecDefinitions,
277 ) -> IndexSet<ComponentName> {
278 let mut entities = Vec::new();
279 let immutable_type_map = schema.types.to_owned();
280 for (named_type, extended_type) in immutable_type_map.iter() {
281 let is_entity = extended_type
282 .directives()
283 .iter()
284 .find(|d| {
285 d.name
286 == fed_definitions
287 .namespaced_type_name(&KEY_DIRECTIVE_NAME, true)
288 .as_str()
289 })
290 .map(|_| true)
291 .unwrap_or(false);
292 if is_entity {
293 entities.push(named_type);
294 }
295 }
296 let entity_set: IndexSet<ComponentName> =
297 entities.iter().map(|e| ComponentName::from(*e)).collect();
298 entity_set
299 }
300}
301
302impl std::fmt::Debug for Subgraph {
303 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
304 write!(f, r#"name: {}, urL: {}"#, self.name, self.url)
305 }
306}
307
308pub struct Subgraphs {
309 subgraphs: BTreeMap<String, Arc<Subgraph>>,
310}
311
312#[allow(clippy::new_without_default)]
313impl Subgraphs {
314 pub fn new() -> Self {
315 Subgraphs {
316 subgraphs: BTreeMap::new(),
317 }
318 }
319
320 pub fn add(&mut self, subgraph: Subgraph) -> Result<(), String> {
321 if self.subgraphs.contains_key(&subgraph.name) {
322 return Err(format!("A subgraph named {} already exists", subgraph.name));
323 }
324 self.subgraphs
325 .insert(subgraph.name.clone(), Arc::new(subgraph));
326 Ok(())
327 }
328
329 pub fn get(&self, name: &str) -> Option<Arc<Subgraph>> {
330 self.subgraphs.get(name).cloned()
331 }
332}
333
334pub struct ValidSubgraph {
335 pub name: String,
336 pub url: String,
337 pub schema: Valid<Schema>,
338}
339
340impl std::fmt::Debug for ValidSubgraph {
341 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
342 write!(f, r#"name: {}, url: {}"#, self.name, self.url)
343 }
344}
345
346impl From<ValidFederationSubgraph> for ValidSubgraph {
347 fn from(value: ValidFederationSubgraph) -> Self {
348 Self {
349 name: value.name,
350 url: value.url,
351 schema: value.schema.schema().clone(),
352 }
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::subgraph::database::keys;
360
361 #[test]
362 fn can_inspect_a_type_key() {
363 let schema = r#"
370 extend schema
371 @link(url: "https://specs.apollo.dev/link/v1.0", import: ["Import"])
372 @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
373
374 type Query {
375 t: T
376 }
377
378 type T @key(fields: "id") {
379 id: ID!
380 x: Int
381 }
382
383 enum link__Purpose {
384 SECURITY
385 EXECUTION
386 }
387
388 scalar Import
389
390 directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA
391 "#;
392
393 let subgraph = Subgraph::new("S1", "http://s1", schema).unwrap();
394 let keys = keys(&subgraph.schema, &name!("T"));
395 assert_eq!(keys.len(), 1);
396 assert_eq!(keys.first().unwrap().type_name, name!("T"));
397
398 }
400}