1use std::sync::Arc;
2use std::sync::LazyLock;
3
4use apollo_compiler::InvalidNameError;
5use apollo_compiler::Name;
6use apollo_compiler::Node;
7use apollo_compiler::ast::Argument;
8use apollo_compiler::ast::Directive;
9use apollo_compiler::ast::DirectiveDefinition;
10use apollo_compiler::ast::DirectiveLocation;
11use apollo_compiler::ast::EnumValueDefinition;
12use apollo_compiler::ast::FieldDefinition;
13use apollo_compiler::ast::InputValueDefinition;
14use apollo_compiler::ast::Type;
15use apollo_compiler::ast::Value;
16use apollo_compiler::collections::IndexMap;
17use apollo_compiler::collections::IndexSet;
18use apollo_compiler::name;
19use apollo_compiler::schema::Component;
20use apollo_compiler::schema::ComponentName;
21use apollo_compiler::schema::EnumType;
22use apollo_compiler::schema::ExtendedType;
23use apollo_compiler::schema::ObjectType;
24use apollo_compiler::schema::ScalarType;
25use apollo_compiler::schema::UnionType;
26use apollo_compiler::ty;
27use thiserror::Error;
28
29use crate::link::DEFAULT_IMPORT_SCALAR_NAME;
30use crate::link::DEFAULT_LINK_NAME;
31use crate::link::DEFAULT_PURPOSE_ENUM_NAME;
32use crate::link::Import;
33use crate::link::Link;
34use crate::link::spec::Identity;
35use crate::link::spec::Url;
36use crate::link::spec::Version;
37use crate::subgraph::spec::FederationSpecError::UnsupportedFederationDirective;
38use crate::subgraph::spec::FederationSpecError::UnsupportedVersionError;
39
40pub const COMPOSE_DIRECTIVE_NAME: Name = name!("composeDirective");
41pub const CONTEXT_DIRECTIVE_NAME: Name = name!("context");
42pub const KEY_DIRECTIVE_NAME: Name = name!("key");
43pub const EXTENDS_DIRECTIVE_NAME: Name = name!("extends");
44pub const EXTERNAL_DIRECTIVE_NAME: Name = name!("external");
45pub const FROM_CONTEXT_DIRECTIVE_NAME: Name = name!("fromContext");
46pub const INACCESSIBLE_DIRECTIVE_NAME: Name = name!("inaccessible");
47pub const INTF_OBJECT_DIRECTIVE_NAME: Name = name!("interfaceObject");
48pub const OVERRIDE_DIRECTIVE_NAME: Name = name!("override");
49pub const PROVIDES_DIRECTIVE_NAME: Name = name!("provides");
50pub const REQUIRES_DIRECTIVE_NAME: Name = name!("requires");
51pub const SHAREABLE_DIRECTIVE_NAME: Name = name!("shareable");
52pub const TAG_DIRECTIVE_NAME: Name = name!("tag");
53pub const FIELDSET_SCALAR_NAME: Name = name!("FieldSet");
54pub const CONTEXTFIELDVALUE_SCALAR_NAME: Name = name!("ContextFieldValue");
55
56pub const ANY_SCALAR_NAME: Name = name!("_Any");
58pub const ENTITY_UNION_NAME: Name = name!("_Entity");
59pub const SERVICE_TYPE: Name = name!("_Service");
60
61pub const ENTITIES_QUERY: Name = name!("_entities");
62pub const SERVICE_SDL_QUERY: Name = name!("_service");
63
64pub const FEDERATION_V1_DIRECTIVE_NAMES: [Name; 5] = [
65 KEY_DIRECTIVE_NAME,
66 EXTENDS_DIRECTIVE_NAME,
67 EXTERNAL_DIRECTIVE_NAME,
68 PROVIDES_DIRECTIVE_NAME,
69 REQUIRES_DIRECTIVE_NAME,
70];
71
72pub const FEDERATION_V2_DIRECTIVE_NAMES: [Name; 13] = [
73 COMPOSE_DIRECTIVE_NAME,
74 CONTEXT_DIRECTIVE_NAME,
75 KEY_DIRECTIVE_NAME,
76 EXTENDS_DIRECTIVE_NAME,
77 EXTERNAL_DIRECTIVE_NAME,
78 FROM_CONTEXT_DIRECTIVE_NAME,
79 INACCESSIBLE_DIRECTIVE_NAME,
80 INTF_OBJECT_DIRECTIVE_NAME,
81 OVERRIDE_DIRECTIVE_NAME,
82 PROVIDES_DIRECTIVE_NAME,
83 REQUIRES_DIRECTIVE_NAME,
84 SHAREABLE_DIRECTIVE_NAME,
85 TAG_DIRECTIVE_NAME,
86];
87
88pub(crate) const FEDERATION_V2_ELEMENT_NAMES: [Name; 1] = [FIELDSET_SCALAR_NAME];
89
90enum FederationDirectiveName {
93 Compose,
94 Context,
95 Key,
96 Extends,
97 External,
98 FromContext,
99 Inaccessible,
100 IntfObject,
101 Override,
102 Provides,
103 Requires,
104 Shareable,
105 Tag,
106}
107
108static FEDERATION_DIRECTIVE_NAMES_TO_ENUM: LazyLock<IndexMap<Name, FederationDirectiveName>> =
109 LazyLock::new(|| {
110 IndexMap::from_iter([
111 (COMPOSE_DIRECTIVE_NAME, FederationDirectiveName::Compose),
112 (CONTEXT_DIRECTIVE_NAME, FederationDirectiveName::Context),
113 (KEY_DIRECTIVE_NAME, FederationDirectiveName::Key),
114 (EXTENDS_DIRECTIVE_NAME, FederationDirectiveName::Extends),
115 (EXTERNAL_DIRECTIVE_NAME, FederationDirectiveName::External),
116 (
117 FROM_CONTEXT_DIRECTIVE_NAME,
118 FederationDirectiveName::FromContext,
119 ),
120 (
121 INACCESSIBLE_DIRECTIVE_NAME,
122 FederationDirectiveName::Inaccessible,
123 ),
124 (
125 INTF_OBJECT_DIRECTIVE_NAME,
126 FederationDirectiveName::IntfObject,
127 ),
128 (OVERRIDE_DIRECTIVE_NAME, FederationDirectiveName::Override),
129 (PROVIDES_DIRECTIVE_NAME, FederationDirectiveName::Provides),
130 (REQUIRES_DIRECTIVE_NAME, FederationDirectiveName::Requires),
131 (SHAREABLE_DIRECTIVE_NAME, FederationDirectiveName::Shareable),
132 (TAG_DIRECTIVE_NAME, FederationDirectiveName::Tag),
133 ])
134 });
135
136const MIN_FEDERATION_VERSION: Version = Version { major: 2, minor: 0 };
137const MAX_FEDERATION_VERSION: Version = Version { major: 2, minor: 5 };
138
139#[derive(Error, Debug, PartialEq)]
140pub enum FederationSpecError {
141 #[error(
142 "Specified specification version {specified} is outside of supported range {min}-{max}"
143 )]
144 UnsupportedVersionError {
145 specified: String,
146 min: String,
147 max: String,
148 },
149 #[error("Unsupported federation directive import {0}")]
150 UnsupportedFederationDirective(String),
151 #[error(transparent)]
152 InvalidGraphQLName(InvalidNameError),
153}
154
155impl From<InvalidNameError> for FederationSpecError {
156 fn from(err: InvalidNameError) -> Self {
157 FederationSpecError::InvalidGraphQLName(err)
158 }
159}
160
161#[derive(Debug)]
162pub struct FederationSpecDefinitions {
163 link: Link,
164 pub fieldset_scalar_name: Name,
165}
166
167#[derive(Debug)]
168pub struct LinkSpecDefinitions {
169 link: Link,
170 pub import_scalar_name: Name,
171 pub purpose_enum_name: Name,
172}
173
174pub trait AppliedFederationLink {
175 fn applied_link_directive(&self) -> Directive;
176}
177
178macro_rules! applied_specification {
179 ($($t:ty),+) => {
180 $(impl AppliedFederationLink for $t {
181 fn applied_link_directive(&self) -> Directive {
185 let imports = self
186 .link
187 .imports
188 .iter()
189 .map(|i| {
190 if i.alias.is_some() {
191 Value::Object(vec![
192 (name!("name"), i.element.as_str().into()),
193 (name!("as"), i.imported_display_name().to_string().into()),
194 ])
195 } else {
196 i.imported_display_name().to_string().into()
197 }.into()
198 })
199 .collect::<Vec<Node<Value>>>();
200 let mut applied_link_directive = Directive {
201 name: DEFAULT_LINK_NAME,
202 arguments: vec![
203 Argument {
204 name: name!("url"),
205 value: self.link.url.to_string().into(),
206 }.into(),
207 Argument {
208 name: name!("import"),
209 value: Value::List(imports).into(),
210 }.into(),
211 ]
212 };
213 if let Some(spec_alias) = &self.link.spec_alias {
214 applied_link_directive.arguments.push(Argument {
215 name: name!("as"),
216 value: spec_alias.as_str().into(),
217 }.into())
218 }
219 if let Some(purpose) = &self.link.purpose {
220 applied_link_directive.arguments.push(Argument {
221 name: name!("for"),
222 value: Value::Enum(purpose.into()).into(),
223 }.into())
224 }
225 applied_link_directive
226 }
227 })+
228 }
229}
230
231applied_specification!(FederationSpecDefinitions, LinkSpecDefinitions);
232
233impl FederationSpecDefinitions {
234 pub fn from_link(link: Link) -> Result<Self, FederationSpecError> {
235 if !link
236 .url
237 .version
238 .satisfies_range(&MIN_FEDERATION_VERSION, &MAX_FEDERATION_VERSION)
239 {
240 Err(UnsupportedVersionError {
241 specified: link.url.version.to_string(),
242 min: MIN_FEDERATION_VERSION.to_string(),
243 max: MAX_FEDERATION_VERSION.to_string(),
244 })
245 } else {
246 let fieldset_scalar_name = link.type_name_in_schema(&FIELDSET_SCALAR_NAME);
247 Ok(Self {
248 link,
249 fieldset_scalar_name,
250 })
251 }
252 }
253
254 #[allow(clippy::should_implement_trait)]
256 pub fn default() -> Result<Self, FederationSpecError> {
257 Self::from_link(Link {
258 url: Url {
259 identity: Identity::federation_identity(),
260 version: MAX_FEDERATION_VERSION,
261 },
262 imports: FEDERATION_V1_DIRECTIVE_NAMES
263 .iter()
264 .map(|i| {
265 Arc::new(Import {
266 element: i.clone(),
267 alias: None,
268 is_directive: true,
269 })
270 })
271 .collect::<Vec<Arc<Import>>>(),
272 purpose: None,
273 spec_alias: None,
274 })
275 }
276
277 pub fn namespaced_type_name(&self, name: &Name, is_directive: bool) -> Name {
278 if is_directive {
279 self.link.directive_name_in_schema(name)
280 } else {
281 self.link.type_name_in_schema(name)
282 }
283 }
284
285 pub fn directive_definition(
286 &self,
287 name: &Name,
288 alias: &Option<Name>,
289 ) -> Result<DirectiveDefinition, FederationSpecError> {
290 let Some(enum_name) = FEDERATION_DIRECTIVE_NAMES_TO_ENUM.get(name) else {
295 return Err(UnsupportedFederationDirective(name.to_string()));
296 };
297 Ok(match enum_name {
298 FederationDirectiveName::Compose => self.compose_directive_definition(alias),
299 FederationDirectiveName::Context => self.context_directive_definition(alias),
300 FederationDirectiveName::Key => self.key_directive_definition(alias)?,
301 FederationDirectiveName::Extends => self.extends_directive_definition(alias),
302 FederationDirectiveName::External => self.external_directive_definition(alias),
303 FederationDirectiveName::FromContext => self.from_context_directive_definition(alias),
304 FederationDirectiveName::Inaccessible => self.inaccessible_directive_definition(alias),
305 FederationDirectiveName::IntfObject => {
306 self.interface_object_directive_definition(alias)
307 }
308 FederationDirectiveName::Override => self.override_directive_definition(alias),
309 FederationDirectiveName::Provides => self.provides_directive_definition(alias)?,
310 FederationDirectiveName::Requires => self.requires_directive_definition(alias)?,
311 FederationDirectiveName::Shareable => self.shareable_directive_definition(alias),
312 FederationDirectiveName::Tag => self.tag_directive_definition(alias),
313 })
314 }
315
316 pub fn fieldset_scalar_definition(&self, name: Name) -> ScalarType {
318 ScalarType {
319 description: None,
320 name,
321 directives: Default::default(),
322 }
323 }
324
325 pub fn contextfieldvalue_scalar_definition(&self, alias: &Option<Name>) -> ScalarType {
327 ScalarType {
328 description: None,
329 name: alias.clone().unwrap_or(CONTEXTFIELDVALUE_SCALAR_NAME),
330 directives: Default::default(),
331 }
332 }
333
334 fn fields_argument_definition(&self) -> Result<InputValueDefinition, FederationSpecError> {
335 Ok(InputValueDefinition {
336 description: None,
337 name: name!("fields"),
338 ty: Type::Named(self.namespaced_type_name(&FIELDSET_SCALAR_NAME, false))
339 .non_null()
340 .into(),
341 default_value: None,
342 directives: Default::default(),
343 })
344 }
345
346 fn compose_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
348 DirectiveDefinition {
349 description: None,
350 name: alias.clone().unwrap_or(COMPOSE_DIRECTIVE_NAME),
351 arguments: vec![
352 InputValueDefinition {
353 description: None,
354 name: name!("name"),
355 ty: ty!(String!).into(),
356 default_value: None,
357 directives: Default::default(),
358 }
359 .into(),
360 ],
361 repeatable: true,
362 locations: vec![DirectiveLocation::Schema],
363 }
364 }
365
366 fn context_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
368 DirectiveDefinition {
369 description: None,
370 name: alias.clone().unwrap_or(CONTEXT_DIRECTIVE_NAME),
371 arguments: vec![
372 InputValueDefinition {
373 description: None,
374 name: name!("name"),
375 ty: ty!(String!).into(),
376 default_value: None,
377 directives: Default::default(),
378 }
379 .into(),
380 ],
381 repeatable: true,
382 locations: vec![
383 DirectiveLocation::Interface,
384 DirectiveLocation::Object,
385 DirectiveLocation::Union,
386 ],
387 }
388 }
389
390 fn key_directive_definition(
392 &self,
393 alias: &Option<Name>,
394 ) -> Result<DirectiveDefinition, FederationSpecError> {
395 Ok(DirectiveDefinition {
396 description: None,
397 name: alias.clone().unwrap_or(KEY_DIRECTIVE_NAME),
398 arguments: vec![
399 self.fields_argument_definition()?.into(),
400 InputValueDefinition {
401 description: None,
402 name: name!("resolvable"),
403 ty: ty!(Boolean).into(),
404 default_value: Some(true.into()),
405 directives: Default::default(),
406 }
407 .into(),
408 ],
409 repeatable: true,
410 locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface],
411 })
412 }
413
414 fn extends_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
416 DirectiveDefinition {
417 description: None,
418 name: alias.clone().unwrap_or(EXTENDS_DIRECTIVE_NAME),
419 arguments: Vec::new(),
420 repeatable: false,
421 locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface],
422 }
423 }
424
425 fn external_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
427 DirectiveDefinition {
428 description: None,
429 name: alias.clone().unwrap_or(EXTERNAL_DIRECTIVE_NAME),
430 arguments: Vec::new(),
431 repeatable: false,
432 locations: vec![
433 DirectiveLocation::Object,
434 DirectiveLocation::FieldDefinition,
435 ],
436 }
437 }
438
439 #[allow(clippy::wrong_self_convention)]
445 fn from_context_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
446 DirectiveDefinition {
447 description: None,
448 name: alias.clone().unwrap_or(FROM_CONTEXT_DIRECTIVE_NAME),
449 arguments: vec![
450 InputValueDefinition {
451 description: None,
452 name: name!("field"),
453 ty: Type::Named(
454 self.namespaced_type_name(&CONTEXTFIELDVALUE_SCALAR_NAME, false),
455 )
456 .into(),
457 default_value: None,
458 directives: Default::default(),
459 }
460 .into(),
461 ],
462 repeatable: false,
463 locations: vec![DirectiveLocation::ArgumentDefinition],
464 }
465 }
466
467 fn inaccessible_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
479 DirectiveDefinition {
480 description: None,
481 name: alias.clone().unwrap_or(INACCESSIBLE_DIRECTIVE_NAME),
482 arguments: Vec::new(),
483 repeatable: false,
484 locations: vec![
485 DirectiveLocation::ArgumentDefinition,
486 DirectiveLocation::Enum,
487 DirectiveLocation::EnumValue,
488 DirectiveLocation::FieldDefinition,
489 DirectiveLocation::InputFieldDefinition,
490 DirectiveLocation::InputObject,
491 DirectiveLocation::Interface,
492 DirectiveLocation::Object,
493 DirectiveLocation::Scalar,
494 DirectiveLocation::Union,
495 ],
496 }
497 }
498
499 fn interface_object_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
501 DirectiveDefinition {
502 description: None,
503 name: alias.clone().unwrap_or(INTF_OBJECT_DIRECTIVE_NAME),
504 arguments: Vec::new(),
505 repeatable: false,
506 locations: vec![DirectiveLocation::Object],
507 }
508 }
509
510 fn override_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
512 DirectiveDefinition {
513 description: None,
514 name: alias.clone().unwrap_or(OVERRIDE_DIRECTIVE_NAME),
515 arguments: vec![
516 InputValueDefinition {
517 description: None,
518 name: name!("from"),
519 ty: ty!(String!).into(),
520 default_value: None,
521 directives: Default::default(),
522 }
523 .into(),
524 ],
525 repeatable: false,
526 locations: vec![DirectiveLocation::FieldDefinition],
527 }
528 }
529
530 fn provides_directive_definition(
532 &self,
533 alias: &Option<Name>,
534 ) -> Result<DirectiveDefinition, FederationSpecError> {
535 Ok(DirectiveDefinition {
536 description: None,
537 name: alias.clone().unwrap_or(PROVIDES_DIRECTIVE_NAME),
538 arguments: vec![self.fields_argument_definition()?.into()],
539 repeatable: false,
540 locations: vec![DirectiveLocation::FieldDefinition],
541 })
542 }
543
544 fn requires_directive_definition(
546 &self,
547 alias: &Option<Name>,
548 ) -> Result<DirectiveDefinition, FederationSpecError> {
549 Ok(DirectiveDefinition {
550 description: None,
551 name: alias.clone().unwrap_or(REQUIRES_DIRECTIVE_NAME),
552 arguments: vec![self.fields_argument_definition()?.into()],
553 repeatable: false,
554 locations: vec![DirectiveLocation::FieldDefinition],
555 })
556 }
557
558 fn shareable_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
560 DirectiveDefinition {
561 description: None,
562 name: alias.clone().unwrap_or(SHAREABLE_DIRECTIVE_NAME),
563 arguments: Vec::new(),
564 repeatable: true,
565 locations: vec![
566 DirectiveLocation::FieldDefinition,
567 DirectiveLocation::Object,
568 ],
569 }
570 }
571
572 fn tag_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
584 DirectiveDefinition {
585 description: None,
586 name: alias.clone().unwrap_or(TAG_DIRECTIVE_NAME),
587 arguments: vec![
588 InputValueDefinition {
589 description: None,
590 name: name!("name"),
591 ty: ty!(String!).into(),
592 default_value: None,
593 directives: Default::default(),
594 }
595 .into(),
596 ],
597 repeatable: true,
598 locations: vec![
599 DirectiveLocation::ArgumentDefinition,
600 DirectiveLocation::Enum,
601 DirectiveLocation::EnumValue,
602 DirectiveLocation::FieldDefinition,
603 DirectiveLocation::InputFieldDefinition,
604 DirectiveLocation::InputObject,
605 DirectiveLocation::Interface,
606 DirectiveLocation::Object,
607 DirectiveLocation::Scalar,
608 DirectiveLocation::Union,
609 ],
610 }
611 }
612
613 pub(crate) fn any_scalar_definition(&self) -> ExtendedType {
614 let any_scalar = ScalarType {
615 description: None,
616 name: ANY_SCALAR_NAME,
617 directives: Default::default(),
618 };
619 ExtendedType::Scalar(Node::new(any_scalar))
620 }
621
622 pub(crate) fn entity_union_definition(
623 &self,
624 entities: IndexSet<ComponentName>,
625 ) -> ExtendedType {
626 let service_type = UnionType {
627 description: None,
628 name: ENTITY_UNION_NAME,
629 directives: Default::default(),
630 members: entities,
631 };
632 ExtendedType::Union(Node::new(service_type))
633 }
634 pub(crate) fn service_object_type_definition(&self) -> ExtendedType {
635 let mut service_type = ObjectType {
636 description: None,
637 name: SERVICE_TYPE,
638 directives: Default::default(),
639 fields: IndexMap::default(),
640 implements_interfaces: IndexSet::default(),
641 };
642 service_type.fields.insert(
643 name!("_sdl"),
644 Component::new(FieldDefinition {
645 name: name!("_sdl"),
646 description: None,
647 directives: Default::default(),
648 arguments: Vec::new(),
649 ty: ty!(String),
650 }),
651 );
652 ExtendedType::Object(Node::new(service_type))
653 }
654
655 pub(crate) fn entities_query_field(&self) -> Component<FieldDefinition> {
656 Component::new(FieldDefinition {
657 name: ENTITIES_QUERY,
658 description: None,
659 directives: Default::default(),
660 arguments: vec![Node::new(InputValueDefinition {
661 name: name!("representations"),
662 description: None,
663 directives: Default::default(),
664 ty: Node::new(Type::NonNullList(Box::new(Type::NonNullNamed(
665 ANY_SCALAR_NAME,
666 )))),
667 default_value: None,
668 })],
669 ty: Type::NonNullList(Box::new(Type::Named(ENTITY_UNION_NAME))),
670 })
671 }
672
673 pub(crate) fn service_sdl_query_field(&self) -> Component<FieldDefinition> {
674 Component::new(FieldDefinition {
675 name: SERVICE_SDL_QUERY,
676 description: None,
677 directives: Default::default(),
678 arguments: Vec::new(),
679 ty: Type::NonNullNamed(SERVICE_TYPE),
680 })
681 }
682}
683
684impl LinkSpecDefinitions {
685 pub fn new(link: Link) -> Self {
686 let import_scalar_name = link.type_name_in_schema(&DEFAULT_IMPORT_SCALAR_NAME);
687 let purpose_enum_name = link.type_name_in_schema(&DEFAULT_PURPOSE_ENUM_NAME);
688 Self {
689 link,
690 import_scalar_name,
691 purpose_enum_name,
692 }
693 }
694
695 pub fn import_scalar_definition(&self, name: Name) -> ScalarType {
697 ScalarType {
698 description: None,
699 name,
700 directives: Default::default(),
701 }
702 }
703
704 pub fn link_purpose_enum_definition(&self, name: Name) -> EnumType {
709 EnumType {
710 description: None,
711 name,
712 directives: Default::default(),
713 values: [
714 (
715 name!("SECURITY"),
716 EnumValueDefinition {
717 description: None,
718 value: name!("SECURITY"),
719 directives: Default::default(),
720 }
721 .into(),
722 ),
723 (
724 name!("EXECUTION"),
725 EnumValueDefinition {
726 description: None,
727 value: name!("EXECUTION"),
728 directives: Default::default(),
729 }
730 .into(),
731 ),
732 ]
733 .into_iter()
734 .collect(),
735 }
736 }
737
738 pub fn link_directive_definition(&self) -> Result<DirectiveDefinition, FederationSpecError> {
740 Ok(DirectiveDefinition {
741 description: None,
742 name: DEFAULT_LINK_NAME,
743 arguments: vec![
744 InputValueDefinition {
745 description: None,
746 name: name!("url"),
747 ty: ty!(String!).into(),
748 default_value: None,
749 directives: Default::default(),
750 }
751 .into(),
752 InputValueDefinition {
753 description: None,
754 name: name!("as"),
755 ty: ty!(String).into(),
756 default_value: None,
757 directives: Default::default(),
758 }
759 .into(),
760 InputValueDefinition {
761 description: None,
762 name: name!("import"),
763 ty: Type::Named(self.import_scalar_name.clone()).list().into(),
764 default_value: None,
765 directives: Default::default(),
766 }
767 .into(),
768 InputValueDefinition {
769 description: None,
770 name: name!("for"),
771 ty: Type::Named(self.purpose_enum_name.clone()).into(),
772 default_value: None,
773 directives: Default::default(),
774 }
775 .into(),
776 ],
777 repeatable: true,
778 locations: vec![DirectiveLocation::Schema],
779 })
780 }
781}
782
783impl Default for LinkSpecDefinitions {
784 fn default() -> Self {
785 let link = Link {
786 url: Url {
787 identity: Identity::link_identity(),
788 version: Version { major: 1, minor: 0 },
789 },
790 imports: vec![Arc::new(Import {
791 element: name!("Import"),
792 is_directive: false,
793 alias: None,
794 })],
795 purpose: None,
796 spec_alias: None,
797 };
798 Self::new(link)
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805 use crate::link::spec::APOLLO_SPEC_DOMAIN;
806 use crate::link::spec::Identity;
807
808 fn federation_link_identity() -> Identity {
811 Identity {
812 domain: APOLLO_SPEC_DOMAIN.to_string(),
813 name: name!("federation"),
814 }
815 }
816
817 #[test]
818 fn handle_unsupported_federation_version() {
819 FederationSpecDefinitions::from_link(Link {
820 url: Url {
821 identity: federation_link_identity(),
822 version: Version {
823 major: 99,
824 minor: 99,
825 },
826 },
827 spec_alias: None,
828 imports: vec![],
829 purpose: None,
830 })
831 .expect_err("federation version 99 is not yet supported");
832 }
833}