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