1use std::sync::Arc;
2use std::sync::LazyLock;
3
4use apollo_compiler::Name;
5use apollo_compiler::Node;
6use apollo_compiler::Schema;
7use apollo_compiler::ast::Argument;
8use apollo_compiler::ast::Directive;
9use apollo_compiler::ast::DirectiveDefinition;
10use apollo_compiler::ast::DirectiveLocation;
11use apollo_compiler::ast::Type;
12use apollo_compiler::ast::Value;
13use apollo_compiler::name;
14use apollo_compiler::schema::Component;
15use apollo_compiler::ty;
16use itertools::Itertools;
17
18use crate::bail;
19use crate::error::FederationError;
20use crate::error::MultiTry;
21use crate::error::MultiTryAll;
22use crate::error::SingleFederationError;
23use crate::link::Import;
24use crate::link::Link;
25use crate::link::Purpose;
26use crate::link::argument::directive_optional_list_argument;
27use crate::link::argument::directive_optional_string_argument;
28use crate::link::spec::Identity;
29use crate::link::spec::Url;
30use crate::link::spec::Version;
31use crate::link::spec_definition::SpecDefinition;
32use crate::link::spec_definition::SpecDefinitions;
33use crate::schema::FederationSchema;
34use crate::schema::SchemaElement;
35use crate::schema::position::SchemaDefinitionPosition;
36use crate::schema::type_and_directive_specification::ArgumentSpecification;
37use crate::schema::type_and_directive_specification::DirectiveArgumentSpecification;
38use crate::schema::type_and_directive_specification::DirectiveSpecification;
39use crate::schema::type_and_directive_specification::EnumTypeSpecification;
40use crate::schema::type_and_directive_specification::EnumValueSpecification;
41use crate::schema::type_and_directive_specification::ScalarTypeSpecification;
42use crate::schema::type_and_directive_specification::TypeAndDirectiveSpecification;
43
44pub(crate) const LINK_DIRECTIVE_NAME_IN_SPEC: Name = name!("link");
45pub(crate) const LINK_DIRECTIVE_AS_ARGUMENT_NAME: Name = name!("as");
46pub(crate) const LINK_DIRECTIVE_URL_ARGUMENT_NAME: Name = name!("url");
47pub(crate) const LINK_DIRECTIVE_FOR_ARGUMENT_NAME: Name = name!("for");
48pub(crate) const LINK_DIRECTIVE_IMPORT_ARGUMENT_NAME: Name = name!("import");
49pub(crate) const LINK_DIRECTIVE_FEATURE_ARGUMENT_NAME: Name = name!("feature"); pub(crate) const IMPORT_TYPE_NAME_IN_SPEC: Name = name!("Import");
52pub(crate) const IMPORT_TYPE_NAME_FIELD_NAME: Name = name!("name");
53pub(crate) const IMPORT_TYPE_AS_FIELD_NAME: Name = name!("as");
54
55pub(crate) const PURPOSE_TYPE_NAME_IN_SPEC: Name = name!("Purpose");
56
57impl TryFrom<&Value> for Purpose {
58 type Error = FederationError;
59
60 fn try_from(value: &Value) -> Result<Self, Self::Error> {
61 let Some(purpose) = value.as_enum() else {
62 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
63 message: format!(
64 r#"@link(for:) argument `{}` must be an enum value"#,
65 value.serialize().no_indent()
66 ),
67 }
68 .into());
69 };
70 match purpose.as_str() {
71 "SECURITY" => Ok(Purpose::SECURITY),
72 "EXECUTION" => Ok(Purpose::EXECUTION),
73 _ => Err(SingleFederationError::InvalidLinkDirectiveUsage {
74 message: format!(
75 r#"@link(for:) argument `{}` is not a known enum value"#,
76 value.serialize().no_indent()
77 ),
78 }
79 .into()),
80 }
81 }
82}
83
84impl From<&Purpose> for Value {
85 fn from(value: &Purpose) -> Self {
86 match value {
87 Purpose::SECURITY => Value::Enum(name!("SECURITY")),
88 Purpose::EXECUTION => Value::Enum(name!("EXECUTION")),
89 }
90 }
91}
92
93impl TryFrom<&Value> for Import {
94 type Error = FederationError;
95
96 fn try_from(value: &Value) -> Result<Self, Self::Error> {
97 match value {
98 Value::String(str) => {
99 if let Some(directive_name) = str.strip_prefix('@') {
100 Ok(Import {
101 element: Name::new(directive_name).map_err(|_| SingleFederationError::InvalidLinkDirectiveUsage {
102 message: format!(r#"`{}` in @link(import:) argument is not a valid GraphQL name"#, value.serialize().no_indent()),
103 })?,
104 is_directive: true,
105 alias: None,
106 })
107 } else {
108 Ok(Import {
109 element: Name::new(str).map_err(|_| SingleFederationError::InvalidLinkDirectiveUsage {
110 message: format!(r#"`{}` in @link(import:) argument is not a valid GraphQL name"#, value.serialize().no_indent()),
111 })?,
112 is_directive: false,
113 alias: None,
114 })
115 }
116 }
117 Value::Object(fields) => {
118 let mut name: Option<&str> = None;
119 let mut alias: Option<&str> = None;
120 for (k, v) in fields {
121 match k.as_str() {
122 "name" => {
123 name = Some(v.as_str().ok_or_else(|| SingleFederationError::InvalidLinkDirectiveUsage {
124 message: format!(r#"For `{}` in @link(import:) argument, value for field "name" must be a string"#, value.serialize().no_indent()),
125 })?)
126 },
127 "as" => {
128 alias = Some(v.as_str().ok_or_else(|| SingleFederationError::InvalidLinkDirectiveUsage {
129 message: format!(r#"For `{}` in @link(import:) argument, value for field "as" must be a string"#, value.serialize().no_indent()),
130 })?)
131 },
132 _ => {
133 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
134 message: format!(r#"For `{}` in @link(import:) argument, field "{k}" is not a known field"#, value.serialize().no_indent()),
135 }.into());
136 }
137 }
138 }
139 let Some(element) = name else {
140 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
141 message: format!(r#"For `{}` in @link(import:) argument, missing required field "name""#, value.serialize().no_indent()),
142 }.into());
143 };
144 if let Some(directive_name) = element.strip_prefix('@') {
145 if let Some(alias_str) = alias.as_ref() {
146 let Some(alias_str) = alias_str.strip_prefix('@') else {
147 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
148 message: format!(r#"For `{}` in @link(import:) argument, value for field "as" must start with "@" since value for field "name" does ("@" indicates a directive import)"#, value.serialize().no_indent()),
149 }.into());
150 };
151 alias = Some(alias_str);
152 }
153 Ok(Import {
154 element: Name::new(directive_name).map_err(|_| SingleFederationError::InvalidLinkDirectiveUsage {
155 message: format!(r#"For `{}` in @link(import:) argument, value for field "name" is not a valid GraphQL name"#, value.serialize().no_indent()),
156 })?,
157 is_directive: true,
158 alias: alias.map(|alias| Name::new(alias).map_err(|_| SingleFederationError::InvalidLinkDirectiveUsage {
159 message: format!(r#"For `{}` in @link(import:) argument, value for field "as" is not a valid GraphQL name"#, value.serialize().no_indent()),
160 })).transpose()?,
161 })
162 } else {
163 if let Some(alias) = &alias
164 && alias.starts_with('@')
165 {
166 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
167 message: format!(r#"For `{}` in @link(import:) argument, value for field "as" must not start with "@" since value for field "name" does not ("@" indicates a directive import)"#, value.serialize().no_indent()),
168 }.into());
169 }
170 Ok(Import {
171 element: Name::new(element).map_err(|_| SingleFederationError::InvalidLinkDirectiveUsage {
172 message: format!(r#"For `{}` in @link(import:) argument, value for field "name" is not a valid GraphQL name"#, value.serialize().no_indent()),
173 })?,
174 is_directive: false,
175 alias: alias.map(|alias| Name::new(alias).map_err(|_| SingleFederationError::InvalidLinkDirectiveUsage {
176 message: format!(r#"For `{}` in @link(import:) argument, value for field "as" is not a valid GraphQL name"#, value.serialize().no_indent()),
177 })).transpose()?,
178 })
179 }
180 }
181 _ => Err(SingleFederationError::InvalidLinkDirectiveUsage {
182 message: format!(r#"`{}` in @link(import:) argument must either be a string `"<importedElement>"` or an object `{{ name: "<importedElement>", as: "<alias>" }}`"#, value.serialize().no_indent()),
183 }.into()),
184 }
185 }
186}
187
188impl From<&Import> for Value {
189 fn from(value: &Import) -> Self {
190 let element_string = value.element_name_in_spec().to_string();
191
192 if value.alias.is_some() {
193 let alias_string = value.element_name_in_schema().to_string();
194 Value::Object(vec![
195 (
196 IMPORT_TYPE_NAME_FIELD_NAME,
197 Node::new(Value::String(element_string)),
198 ),
199 (
200 IMPORT_TYPE_AS_FIELD_NAME,
201 Node::new(Value::String(alias_string)),
202 ),
203 ])
204 } else {
205 Value::String(element_string)
206 }
207 }
208}
209
210#[derive(Debug)]
211pub(crate) struct LinkSpecDefinition {
212 url: Url,
213 name: Name,
214 minimum_federation_version: Version,
215}
216
217impl LinkSpecDefinition {
218 pub(crate) fn new(
219 version: Version,
220 minimum_federation_version: Version,
221 is_link: bool,
222 ) -> Self {
223 Self {
224 url: Url {
225 identity: if is_link {
226 Identity::link_identity()
227 } else {
228 Identity::core_identity()
229 },
230 version,
231 },
232 name: if is_link {
233 Identity::LINK_NAME
234 } else {
235 Identity::CORE_NAME
236 },
237 minimum_federation_version,
238 }
239 }
240
241 pub(crate) fn name(&self) -> &Name {
242 &self.name
243 }
244
245 fn create_definition_argument_specifications(&self) -> Vec<DirectiveArgumentSpecification> {
246 let mut specs = vec![
247 DirectiveArgumentSpecification {
248 base_spec: ArgumentSpecification {
249 name: self.url_arg_name(),
250 get_type: |_, _| Ok(ty!(String)),
251 default_value: None,
252 },
253 composition_strategy: None,
254 },
255 DirectiveArgumentSpecification {
256 base_spec: ArgumentSpecification {
257 name: LINK_DIRECTIVE_AS_ARGUMENT_NAME,
258 get_type: |_, _| Ok(ty!(String)),
259 default_value: None,
260 },
261 composition_strategy: None,
262 },
263 ];
264 if self.supports_purpose() {
265 specs.push(DirectiveArgumentSpecification {
266 base_spec: ArgumentSpecification {
267 name: LINK_DIRECTIVE_FOR_ARGUMENT_NAME,
268 get_type: |_schema, link| {
269 let Some(link) = link else {
270 bail!(
271 "Type {PURPOSE_TYPE_NAME_IN_SPEC} shouldn't be added without being attached to a @link spec"
272 )
273 };
274 Ok(Type::Named(link.type_name_in_schema(&PURPOSE_TYPE_NAME_IN_SPEC)))
275 },
276 default_value: None,
277 },
278 composition_strategy: None,
279 });
280 }
281 if self.supports_import() {
282 specs.push(DirectiveArgumentSpecification {
283 base_spec: ArgumentSpecification {
284 name: LINK_DIRECTIVE_IMPORT_ARGUMENT_NAME,
285 get_type: |_, link| {
286 let Some(link) = link else {
287 bail!(
288 "Type {IMPORT_TYPE_NAME_IN_SPEC} shouldn't be added without being attached to a @link spec"
289 )
290 };
291 Ok(Type::List(Box::new(Type::Named(
292 link.type_name_in_schema(&IMPORT_TYPE_NAME_IN_SPEC),
293 ))))
294 },
295 default_value: None,
296 },
297 composition_strategy: None,
298 });
299 }
300 specs
301 }
302
303 fn supports_purpose(&self) -> bool {
304 self.version().gt(&Version { major: 0, minor: 1 })
305 }
306
307 fn supports_import(&self) -> bool {
308 self.version().satisfies(&Version { major: 1, minor: 0 })
309 }
310
311 pub(crate) fn url_arg_name(&self) -> Name {
312 if self.name == Identity::CORE_NAME {
313 LINK_DIRECTIVE_FEATURE_ARGUMENT_NAME
314 } else {
315 LINK_DIRECTIVE_URL_ARGUMENT_NAME
316 }
317 }
318
319 pub(crate) fn link_from_directive(
320 &self,
321 directive: &Node<Directive>,
322 schema: &Schema,
323 ) -> Result<Link, FederationError> {
324 let url = if let Some(value) = directive.specified_argument_by_name(&self.url_arg_name()) {
325 value
326 } else {
327 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
328 message: format!(
329 r#"`{}` missing required argument "{}""#,
330 directive.serialize().no_indent(),
331 self.url_arg_name(),
332 ),
333 }
334 .into());
335 };
336
337 let url = url
338 .as_str()
339 .ok_or_else(|| SingleFederationError::InvalidLinkDirectiveUsage {
340 message: format!(
341 r#"@{}({}:) argument `{}` must be a string"#,
342 self.name,
343 self.url_arg_name(),
344 url.serialize().no_indent()
345 ),
346 })?;
347 let url: Url = url.parse::<Url>()?;
348
349 let spec_alias = directive
350 .specified_argument_by_name("as")
351 .take_if(|arg| !arg.is_null())
352 .map(|arg| {
353 arg.as_str()
354 .ok_or_else(|| SingleFederationError::InvalidLinkDirectiveUsage {
355 message: format!(
356 r#"@{}(as:) argument `{}` must be a string or null"#,
357 self.name,
358 arg.serialize().no_indent()
359 ),
360 })
361 })
362 .transpose()?
363 .map(Name::new)
364 .transpose()?;
365
366 let purpose = if self.supports_purpose() {
367 directive
368 .specified_argument_by_name("for")
369 .take_if(|arg| !arg.is_null())
370 .map(|arg| Purpose::try_from(arg.as_ref()))
371 .transpose()?
372 } else {
373 None
374 };
375
376 let imports = if self.supports_import() {
377 directive
378 .specified_argument_by_name("import")
379 .take_if(|arg| !arg.is_null())
380 .map(|arg| {
381 if let Value::List(value) = arg.as_ref() {
384 value
385 } else {
386 std::slice::from_ref(arg)
387 }
388 })
389 .unwrap_or(&[])
390 .iter()
391 .map(|value| Ok(Arc::new(Import::try_from(value.as_ref())?)))
392 .collect::<Result<Vec<Arc<Import>>, FederationError>>()?
393 } else {
394 Default::default()
395 };
396
397 Ok(Link {
398 url,
399 spec_alias,
400 imports,
401 purpose,
402 line_column_range: directive.line_column_range(&schema.sources),
403 })
404 }
405
406 pub(crate) fn directive_from_link(&self, link: &Link) -> Directive {
407 let mut arguments = Vec::new();
408 arguments.push(Node::new(Argument {
409 name: self.url_arg_name().clone(),
410 value: Node::new(Value::String(link.url.to_string())),
411 }));
412 if let Some(spec_alias) = &link.spec_alias {
413 arguments.push(Node::new(Argument {
414 name: LINK_DIRECTIVE_AS_ARGUMENT_NAME.clone(),
415 value: Node::new(Value::String(spec_alias.to_string())),
416 }));
417 }
418 if self.supports_import() && !link.imports.is_empty() {
419 arguments.push(Node::new(Argument {
420 name: LINK_DIRECTIVE_IMPORT_ARGUMENT_NAME.clone(),
421 value: Node::new(Value::List(
422 link.imports
423 .iter()
424 .map(|import| Node::new(Value::from(import.as_ref())))
425 .collect(),
426 )),
427 }));
428 }
429 if self.supports_purpose()
430 && let Some(purpose) = &link.purpose
431 {
432 arguments.push(Node::new(Argument {
433 name: LINK_DIRECTIVE_FOR_ARGUMENT_NAME.clone(),
434 value: Node::new(Value::from(purpose)),
435 }));
436 }
437 Directive {
438 name: self.name.clone(),
439 arguments,
440 }
441 }
442
443 pub(super) fn is_bootstrap_directive(schema: &Schema, directive: &Directive) -> bool {
446 let Some(definition) = schema.directive_definitions.get(&directive.name) else {
447 return false;
448 };
449 if Self::is_link_directive_definition(definition) {
450 if let Some(url) = directive
451 .specified_argument_by_name("url")
452 .and_then(|value| value.as_str())
453 {
454 let url = url.parse::<Url>();
455 let default_link_name = LINK_DIRECTIVE_NAME_IN_SPEC;
456 let expected_name = directive
457 .specified_argument_by_name("as")
458 .and_then(|value| value.as_str())
459 .unwrap_or(default_link_name.as_str());
460 return url.is_ok_and(|url| {
461 url.identity == Identity::link_identity() && directive.name == expected_name
462 });
463 }
464 } else if Self::is_core_directive_definition(definition) {
465 if let Some(url) = directive
468 .specified_argument_by_name("feature")
469 .and_then(|value| value.as_str())
470 {
471 let url = url.parse::<Url>();
472 let expected_name = directive
473 .specified_argument_by_name("as")
474 .and_then(|value| value.as_str())
475 .unwrap_or("core");
476 return url.is_ok_and(|url| {
477 url.identity == Identity::core_identity() && directive.name == expected_name
478 });
479 }
480 };
481 false
482 }
483
484 fn is_link_directive_definition(definition: &DirectiveDefinition) -> bool {
494 definition.repeatable
495 && definition.locations == [DirectiveLocation::Schema]
496 && definition.argument_by_name("url").is_some_and(|argument| {
497 *argument.ty == ty!(String!) || *argument.ty == ty!(String)
503 })
504 && definition
505 .argument_by_name("as")
506 .is_none_or(|argument| *argument.ty == ty!(String))
507 }
508
509 fn is_core_directive_definition(definition: &DirectiveDefinition) -> bool {
519 definition.repeatable
522 && definition.locations == [DirectiveLocation::Schema]
523 && definition
524 .argument_by_name("feature")
525 .is_some_and(|argument| {
526 *argument.ty == ty!(String!) || *argument.ty == ty!(String)
532 })
533 && definition
534 .argument_by_name("as")
535 .is_none_or(|argument| *argument.ty == ty!(String))
536 }
537
538 pub(crate) fn add_to_schema(
542 &self,
543 schema: &mut FederationSchema,
544 alias: Option<Name>,
545 ) -> Result<(), FederationError> {
546 self.add_definitions_to_schema(schema, alias.clone(), vec![])?;
547
548 let name = alias.as_ref().unwrap_or(&self.name).clone();
578 let mut arguments = vec![Node::new(Argument {
579 name: self.url_arg_name(),
580 value: self.url.to_string().into(),
581 })];
582 if let Some(alias) = alias {
583 arguments.push(Node::new(Argument {
584 name: LINK_DIRECTIVE_AS_ARGUMENT_NAME,
585 value: alias.to_string().into(),
586 }));
587 }
588
589 let schema_definition = SchemaDefinitionPosition.get(schema.schema());
590 SchemaDefinitionPosition.insert_directive_at(
591 schema,
592 Component {
593 origin: schema_definition.origin_to_use(),
594 node: Node::new(Directive { name, arguments }),
595 },
596 0, )?;
598 Ok(())
599 }
600
601 pub(crate) fn extract_alias_and_imports_on_missing_link_directive_definition(
602 application: &Component<Directive>,
603 ) -> Result<(Option<Name>, Vec<Arc<Import>>), FederationError> {
604 let url =
609 directive_optional_string_argument(application, &LINK_DIRECTIVE_URL_ARGUMENT_NAME)?;
610 if let Some(url) = url
611 && url.starts_with(&LinkSpecDefinition::latest().url.identity.to_string())
612 {
613 let alias =
614 directive_optional_string_argument(application, &LINK_DIRECTIVE_AS_ARGUMENT_NAME)?
615 .map(Name::new)
616 .transpose()?;
617 let imports = directive_optional_list_argument(
618 application,
619 &LINK_DIRECTIVE_IMPORT_ARGUMENT_NAME,
620 )?
621 .into_iter()
622 .flatten()
623 .map(|value| Ok::<_, FederationError>(Arc::new(Import::try_from(value.as_ref())?)))
624 .process_results(|r| r.collect::<Vec<_>>())?;
625 return Ok((alias, imports));
626 }
627 Ok((None, vec![]))
628 }
629
630 pub(crate) fn add_definitions_to_schema(
631 &self,
632 schema: &mut FederationSchema,
633 alias: Option<Name>,
634 imports: Vec<Arc<Import>>,
635 ) -> Result<(), FederationError> {
636 if let Some(metadata) = schema.metadata() {
637 let link_spec_def = metadata.link_spec_definition();
638 if link_spec_def.url.identity == *self.identity() {
639 return Ok(());
641 }
642 let self_fmt = format!("{}/{}", self.identity(), self.version());
643 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
644 message: format!(
645 "Cannot add link spec {self_fmt} to the schema, it already has {existing_def}",
646 existing_def = link_spec_def.url
647 ),
648 }
649 .into());
650 }
651
652 let mock_link = Arc::new(Link {
657 url: self.url.clone(),
658 spec_alias: alias,
659 imports,
660 purpose: None,
661 line_column_range: None,
662 });
663 Ok(())
664 .and_try(
665 self.type_specs()
666 .into_iter()
667 .try_for_all(|spec| spec.check_or_add(schema, Some(&mock_link))),
668 )
669 .and_try(
670 self.directive_specs()
671 .into_iter()
672 .try_for_all(|spec| spec.check_or_add(schema, Some(&mock_link))),
673 )
674 }
675
676 pub(crate) fn apply_feature_to_schema(
677 &self,
678 schema: &mut FederationSchema,
679 feature: &dyn SpecDefinition,
680 alias: Option<Name>,
681 purpose: Option<Purpose>,
682 imports: Option<Vec<Import>>,
683 mut on_apply_error: impl FnMut(FederationError) -> Result<(), FederationError>,
684 ) -> Result<(), FederationError> {
685 let Some(metadata) = schema.metadata() else {
686 bail!("Schema unexpectedly not a link schema (add @link first)");
687 };
688 if metadata.link_itself().url != self.url {
689 bail!(
690 "Cannot use this version of @link ({}), the schema uses version {}",
691 self.url,
692 metadata.link_itself().url,
693 );
694 }
695 let mut directive = Directive::new(metadata.link_itself().spec_name_in_schema());
696 directive.arguments.push(Node::new(Argument {
697 name: self.url_arg_name(),
698 value: Node::new(feature.to_string().into()),
699 }));
700 if let Some(alias) = alias {
701 directive.arguments.push(Node::new(Argument {
702 name: LINK_DIRECTIVE_AS_ARGUMENT_NAME,
703 value: Node::new(alias.to_string().into()),
704 }));
705 }
706 if let Some(purpose) = &purpose {
707 if self.supports_purpose() {
708 directive.arguments.push(Node::new(Argument {
709 name: LINK_DIRECTIVE_FOR_ARGUMENT_NAME,
710 value: Node::new(purpose.into()),
711 }));
712 } else {
713 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
714 message: format!(
715 "Cannot apply feature {} with purpose since the schema's @core/@link version does not support it.", feature.to_string()
716 ),
717 }.into());
718 }
719 }
720 if let Some(imports) = imports
721 && !imports.is_empty()
722 {
723 if self.supports_import() {
724 directive.arguments.push(Node::new(Argument {
725 name: LINK_DIRECTIVE_IMPORT_ARGUMENT_NAME,
726 value: Node::new(Value::List(
727 imports
728 .into_iter()
729 .map(|i| Node::new((&i).into()))
730 .collect(),
731 )),
732 }))
733 } else {
734 return Err(SingleFederationError::InvalidLinkDirectiveUsage {
735 message: format!(
736 "Cannot apply feature {} with imports since the schema's @core/@link version does not support it.",
737 feature.to_string()
738 ),
739 }.into());
740 }
741 }
742
743 if let Err(error) =
744 SchemaDefinitionPosition.insert_directive(schema, Component::new(directive))
745 {
746 on_apply_error(error)?;
747 };
748 feature.add_elements_to_schema(schema)?;
749
750 Ok(())
751 }
752
753 pub(crate) fn fed1_latest() -> &'static Self {
754 let latest_version = CORE_VERSIONS.versions().last().unwrap();
757 CORE_VERSIONS.find(latest_version).unwrap()
758 }
759
760 pub(crate) fn latest() -> &'static Self {
762 let latest_version = LINK_VERSIONS.versions().last().unwrap();
765 LINK_VERSIONS.find(latest_version).unwrap()
766 }
767}
768
769impl SpecDefinition for LinkSpecDefinition {
770 fn url(&self) -> &Url {
771 &self.url
772 }
773
774 fn directive_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
775 vec![Box::new(DirectiveSpecification::new(
776 self.name().clone(),
777 &self.create_definition_argument_specifications(),
778 true,
779 &[DirectiveLocation::Schema],
780 None,
781 ))]
782 }
783
784 fn type_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
785 let mut specs: Vec<Box<dyn TypeAndDirectiveSpecification>> = Vec::with_capacity(2);
786 if self.supports_purpose() {
787 specs.push(Box::new(create_link_purpose_type_spec()))
788 }
789 if self.supports_import() {
790 specs.push(Box::new(create_link_import_type_spec()))
791 }
792 specs
793 }
794
795 fn minimum_federation_version(&self) -> &Version {
796 &self.minimum_federation_version
797 }
798
799 fn add_elements_to_schema(
800 &self,
801 _schema: &mut FederationSchema,
802 ) -> Result<(), FederationError> {
803 Ok(())
805 }
806
807 fn purpose(&self) -> Option<Purpose> {
808 None
809 }
810}
811
812fn create_link_purpose_type_spec() -> EnumTypeSpecification {
813 EnumTypeSpecification {
814 name: PURPOSE_TYPE_NAME_IN_SPEC,
815 values: vec![
816 EnumValueSpecification {
817 name: name!("SECURITY"),
818 description: Some(
819 "`SECURITY` features provide metadata necessary to securely resolve fields."
820 .to_string(),
821 ),
822 },
823 EnumValueSpecification {
824 name: name!("EXECUTION"),
825 description: Some(
826 "`EXECUTION` features provide metadata necessary for operation execution."
827 .to_string(),
828 ),
829 },
830 ],
831 }
832}
833
834fn create_link_import_type_spec() -> ScalarTypeSpecification {
835 ScalarTypeSpecification {
836 name: IMPORT_TYPE_NAME_IN_SPEC,
837 }
838}
839
840pub(crate) static CORE_VERSIONS: LazyLock<SpecDefinitions<LinkSpecDefinition>> =
841 LazyLock::new(|| {
842 let mut definitions = SpecDefinitions::new(Identity::core_identity());
843 definitions.add(LinkSpecDefinition::new(
844 Version { major: 0, minor: 1 },
845 Version { major: 1, minor: 0 },
846 false,
847 ));
848 definitions.add(LinkSpecDefinition::new(
849 Version { major: 0, minor: 2 },
850 Version { major: 2, minor: 0 },
851 false,
852 ));
853 definitions
854 });
855pub(crate) static LINK_VERSIONS: LazyLock<SpecDefinitions<LinkSpecDefinition>> =
856 LazyLock::new(|| {
857 let mut definitions = SpecDefinitions::new(Identity::link_identity());
858 definitions.add(LinkSpecDefinition::new(
859 Version { major: 1, minor: 0 },
860 Version { major: 2, minor: 0 },
861 true,
862 ));
863 definitions
864 });