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 line_column_range: None,
277 })
278 }
279
280 pub fn namespaced_type_name(&self, name: &Name, is_directive: bool) -> Name {
281 if is_directive {
282 self.link.directive_name_in_schema(name)
283 } else {
284 self.link.type_name_in_schema(name)
285 }
286 }
287
288 pub fn directive_definition(
289 &self,
290 name: &Name,
291 alias: &Option<Name>,
292 ) -> Result<DirectiveDefinition, FederationSpecError> {
293 let Some(enum_name) = FEDERATION_DIRECTIVE_NAMES_TO_ENUM.get(name) else {
298 return Err(UnsupportedFederationDirective(name.to_string()));
299 };
300 Ok(match enum_name {
301 FederationDirectiveName::Compose => self.compose_directive_definition(alias),
302 FederationDirectiveName::Context => self.context_directive_definition(alias),
303 FederationDirectiveName::Key => self.key_directive_definition(alias)?,
304 FederationDirectiveName::Extends => self.extends_directive_definition(alias),
305 FederationDirectiveName::External => self.external_directive_definition(alias),
306 FederationDirectiveName::FromContext => self.from_context_directive_definition(alias),
307 FederationDirectiveName::Inaccessible => self.inaccessible_directive_definition(alias),
308 FederationDirectiveName::IntfObject => {
309 self.interface_object_directive_definition(alias)
310 }
311 FederationDirectiveName::Override => self.override_directive_definition(alias),
312 FederationDirectiveName::Provides => self.provides_directive_definition(alias)?,
313 FederationDirectiveName::Requires => self.requires_directive_definition(alias)?,
314 FederationDirectiveName::Shareable => self.shareable_directive_definition(alias),
315 FederationDirectiveName::Tag => self.tag_directive_definition(alias),
316 })
317 }
318
319 pub fn fieldset_scalar_definition(&self, name: Name) -> ScalarType {
321 ScalarType {
322 description: None,
323 name,
324 directives: Default::default(),
325 }
326 }
327
328 pub fn contextfieldvalue_scalar_definition(&self, alias: &Option<Name>) -> ScalarType {
330 ScalarType {
331 description: None,
332 name: alias.clone().unwrap_or(CONTEXTFIELDVALUE_SCALAR_NAME),
333 directives: Default::default(),
334 }
335 }
336
337 fn fields_argument_definition(&self) -> Result<InputValueDefinition, FederationSpecError> {
338 Ok(InputValueDefinition {
339 description: None,
340 name: name!("fields"),
341 ty: Type::Named(self.namespaced_type_name(&FIELDSET_SCALAR_NAME, false))
342 .non_null()
343 .into(),
344 default_value: None,
345 directives: Default::default(),
346 })
347 }
348
349 fn compose_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
351 DirectiveDefinition {
352 description: None,
353 name: alias.clone().unwrap_or(COMPOSE_DIRECTIVE_NAME),
354 arguments: vec![
355 InputValueDefinition {
356 description: None,
357 name: name!("name"),
358 ty: ty!(String).into(),
359 default_value: None,
360 directives: Default::default(),
361 }
362 .into(),
363 ],
364 repeatable: true,
365 locations: vec![DirectiveLocation::Schema],
366 }
367 }
368
369 fn context_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
371 DirectiveDefinition {
372 description: None,
373 name: alias.clone().unwrap_or(CONTEXT_DIRECTIVE_NAME),
374 arguments: vec![
375 InputValueDefinition {
376 description: None,
377 name: name!("name"),
378 ty: ty!(String!).into(),
379 default_value: None,
380 directives: Default::default(),
381 }
382 .into(),
383 ],
384 repeatable: true,
385 locations: vec![
386 DirectiveLocation::Interface,
387 DirectiveLocation::Object,
388 DirectiveLocation::Union,
389 ],
390 }
391 }
392
393 fn key_directive_definition(
395 &self,
396 alias: &Option<Name>,
397 ) -> Result<DirectiveDefinition, FederationSpecError> {
398 Ok(DirectiveDefinition {
399 description: None,
400 name: alias.clone().unwrap_or(KEY_DIRECTIVE_NAME),
401 arguments: vec![
402 self.fields_argument_definition()?.into(),
403 InputValueDefinition {
404 description: None,
405 name: name!("resolvable"),
406 ty: ty!(Boolean).into(),
407 default_value: Some(true.into()),
408 directives: Default::default(),
409 }
410 .into(),
411 ],
412 repeatable: true,
413 locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface],
414 })
415 }
416
417 fn extends_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
419 DirectiveDefinition {
420 description: None,
421 name: alias.clone().unwrap_or(EXTENDS_DIRECTIVE_NAME),
422 arguments: Vec::new(),
423 repeatable: false,
424 locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface],
425 }
426 }
427
428 fn external_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
430 DirectiveDefinition {
431 description: None,
432 name: alias.clone().unwrap_or(EXTERNAL_DIRECTIVE_NAME),
433 arguments: Vec::new(),
434 repeatable: false,
435 locations: vec![
436 DirectiveLocation::Object,
437 DirectiveLocation::FieldDefinition,
438 ],
439 }
440 }
441
442 #[allow(clippy::wrong_self_convention)]
448 fn from_context_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
449 DirectiveDefinition {
450 description: None,
451 name: alias.clone().unwrap_or(FROM_CONTEXT_DIRECTIVE_NAME),
452 arguments: vec![
453 InputValueDefinition {
454 description: None,
455 name: name!("field"),
456 ty: Type::Named(
457 self.namespaced_type_name(&CONTEXTFIELDVALUE_SCALAR_NAME, false),
458 )
459 .into(),
460 default_value: None,
461 directives: Default::default(),
462 }
463 .into(),
464 ],
465 repeatable: false,
466 locations: vec![DirectiveLocation::ArgumentDefinition],
467 }
468 }
469
470 fn inaccessible_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
482 DirectiveDefinition {
483 description: None,
484 name: alias.clone().unwrap_or(INACCESSIBLE_DIRECTIVE_NAME),
485 arguments: Vec::new(),
486 repeatable: false,
487 locations: vec![
488 DirectiveLocation::ArgumentDefinition,
489 DirectiveLocation::Enum,
490 DirectiveLocation::EnumValue,
491 DirectiveLocation::FieldDefinition,
492 DirectiveLocation::InputFieldDefinition,
493 DirectiveLocation::InputObject,
494 DirectiveLocation::Interface,
495 DirectiveLocation::Object,
496 DirectiveLocation::Scalar,
497 DirectiveLocation::Union,
498 ],
499 }
500 }
501
502 fn interface_object_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
504 DirectiveDefinition {
505 description: None,
506 name: alias.clone().unwrap_or(INTF_OBJECT_DIRECTIVE_NAME),
507 arguments: Vec::new(),
508 repeatable: false,
509 locations: vec![DirectiveLocation::Object],
510 }
511 }
512
513 fn override_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
515 DirectiveDefinition {
516 description: None,
517 name: alias.clone().unwrap_or(OVERRIDE_DIRECTIVE_NAME),
518 arguments: vec![
519 InputValueDefinition {
520 description: None,
521 name: name!("from"),
522 ty: ty!(String!).into(),
523 default_value: None,
524 directives: Default::default(),
525 }
526 .into(),
527 ],
528 repeatable: false,
529 locations: vec![DirectiveLocation::FieldDefinition],
530 }
531 }
532
533 fn provides_directive_definition(
535 &self,
536 alias: &Option<Name>,
537 ) -> Result<DirectiveDefinition, FederationSpecError> {
538 Ok(DirectiveDefinition {
539 description: None,
540 name: alias.clone().unwrap_or(PROVIDES_DIRECTIVE_NAME),
541 arguments: vec![self.fields_argument_definition()?.into()],
542 repeatable: false,
543 locations: vec![DirectiveLocation::FieldDefinition],
544 })
545 }
546
547 fn requires_directive_definition(
549 &self,
550 alias: &Option<Name>,
551 ) -> Result<DirectiveDefinition, FederationSpecError> {
552 Ok(DirectiveDefinition {
553 description: None,
554 name: alias.clone().unwrap_or(REQUIRES_DIRECTIVE_NAME),
555 arguments: vec![self.fields_argument_definition()?.into()],
556 repeatable: false,
557 locations: vec![DirectiveLocation::FieldDefinition],
558 })
559 }
560
561 fn shareable_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
563 DirectiveDefinition {
564 description: None,
565 name: alias.clone().unwrap_or(SHAREABLE_DIRECTIVE_NAME),
566 arguments: Vec::new(),
567 repeatable: true,
568 locations: vec![
569 DirectiveLocation::FieldDefinition,
570 DirectiveLocation::Object,
571 ],
572 }
573 }
574
575 fn tag_directive_definition(&self, alias: &Option<Name>) -> DirectiveDefinition {
587 DirectiveDefinition {
588 description: None,
589 name: alias.clone().unwrap_or(TAG_DIRECTIVE_NAME),
590 arguments: vec![
591 InputValueDefinition {
592 description: None,
593 name: name!("name"),
594 ty: ty!(String!).into(),
595 default_value: None,
596 directives: Default::default(),
597 }
598 .into(),
599 ],
600 repeatable: true,
601 locations: vec![
602 DirectiveLocation::ArgumentDefinition,
603 DirectiveLocation::Enum,
604 DirectiveLocation::EnumValue,
605 DirectiveLocation::FieldDefinition,
606 DirectiveLocation::InputFieldDefinition,
607 DirectiveLocation::InputObject,
608 DirectiveLocation::Interface,
609 DirectiveLocation::Object,
610 DirectiveLocation::Scalar,
611 DirectiveLocation::Union,
612 ],
613 }
614 }
615
616 pub(crate) fn any_scalar_definition(&self) -> ExtendedType {
617 let any_scalar = ScalarType {
618 description: None,
619 name: ANY_SCALAR_NAME,
620 directives: Default::default(),
621 };
622 ExtendedType::Scalar(Node::new(any_scalar))
623 }
624
625 pub(crate) fn entity_union_definition(
626 &self,
627 entities: IndexSet<ComponentName>,
628 ) -> ExtendedType {
629 let service_type = UnionType {
630 description: None,
631 name: ENTITY_UNION_NAME,
632 directives: Default::default(),
633 members: entities,
634 };
635 ExtendedType::Union(Node::new(service_type))
636 }
637 pub(crate) fn service_object_type_definition(&self) -> ExtendedType {
638 let mut service_type = ObjectType {
639 description: None,
640 name: SERVICE_TYPE,
641 directives: Default::default(),
642 fields: IndexMap::default(),
643 implements_interfaces: IndexSet::default(),
644 };
645 service_type.fields.insert(
646 name!("_sdl"),
647 Component::new(FieldDefinition {
648 name: name!("_sdl"),
649 description: None,
650 directives: Default::default(),
651 arguments: Vec::new(),
652 ty: ty!(String),
653 }),
654 );
655 ExtendedType::Object(Node::new(service_type))
656 }
657
658 pub(crate) fn entities_query_field(&self) -> Component<FieldDefinition> {
659 Component::new(FieldDefinition {
660 name: ENTITIES_QUERY,
661 description: None,
662 directives: Default::default(),
663 arguments: vec![Node::new(InputValueDefinition {
664 name: name!("representations"),
665 description: None,
666 directives: Default::default(),
667 ty: Node::new(Type::NonNullList(Box::new(Type::NonNullNamed(
668 ANY_SCALAR_NAME,
669 )))),
670 default_value: None,
671 })],
672 ty: Type::NonNullList(Box::new(Type::Named(ENTITY_UNION_NAME))),
673 })
674 }
675
676 pub(crate) fn service_sdl_query_field(&self) -> Component<FieldDefinition> {
677 Component::new(FieldDefinition {
678 name: SERVICE_SDL_QUERY,
679 description: None,
680 directives: Default::default(),
681 arguments: Vec::new(),
682 ty: Type::NonNullNamed(SERVICE_TYPE),
683 })
684 }
685}
686
687impl LinkSpecDefinitions {
688 pub fn new(link: Link) -> Self {
689 let import_scalar_name = link.type_name_in_schema(&DEFAULT_IMPORT_SCALAR_NAME);
690 let purpose_enum_name = link.type_name_in_schema(&DEFAULT_PURPOSE_ENUM_NAME);
691 Self {
692 link,
693 import_scalar_name,
694 purpose_enum_name,
695 }
696 }
697
698 pub fn import_scalar_definition(&self, name: Name) -> ScalarType {
700 ScalarType {
701 description: None,
702 name,
703 directives: Default::default(),
704 }
705 }
706
707 pub fn link_purpose_enum_definition(&self, name: Name) -> EnumType {
712 EnumType {
713 description: None,
714 name,
715 directives: Default::default(),
716 values: [
717 (
718 name!("SECURITY"),
719 EnumValueDefinition {
720 description: None,
721 value: name!("SECURITY"),
722 directives: Default::default(),
723 }
724 .into(),
725 ),
726 (
727 name!("EXECUTION"),
728 EnumValueDefinition {
729 description: None,
730 value: name!("EXECUTION"),
731 directives: Default::default(),
732 }
733 .into(),
734 ),
735 ]
736 .into_iter()
737 .collect(),
738 }
739 }
740
741 pub fn link_directive_definition(&self) -> Result<DirectiveDefinition, FederationSpecError> {
743 Ok(DirectiveDefinition {
744 description: None,
745 name: DEFAULT_LINK_NAME,
746 arguments: vec![
747 InputValueDefinition {
748 description: None,
749 name: name!("url"),
750 ty: ty!(String!).into(),
751 default_value: None,
752 directives: Default::default(),
753 }
754 .into(),
755 InputValueDefinition {
756 description: None,
757 name: name!("as"),
758 ty: ty!(String).into(),
759 default_value: None,
760 directives: Default::default(),
761 }
762 .into(),
763 InputValueDefinition {
764 description: None,
765 name: name!("import"),
766 ty: Type::Named(self.import_scalar_name.clone()).list().into(),
767 default_value: None,
768 directives: Default::default(),
769 }
770 .into(),
771 InputValueDefinition {
772 description: None,
773 name: name!("for"),
774 ty: Type::Named(self.purpose_enum_name.clone()).into(),
775 default_value: None,
776 directives: Default::default(),
777 }
778 .into(),
779 ],
780 repeatable: true,
781 locations: vec![DirectiveLocation::Schema],
782 })
783 }
784}
785
786impl Default for LinkSpecDefinitions {
787 fn default() -> Self {
788 let link = Link {
789 url: Url {
790 identity: Identity::link_identity(),
791 version: Version { major: 1, minor: 0 },
792 },
793 imports: vec![Arc::new(Import {
794 element: name!("Import"),
795 is_directive: false,
796 alias: None,
797 })],
798 purpose: None,
799 spec_alias: None,
800 line_column_range: None,
801 };
802 Self::new(link)
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809 use crate::link::spec::APOLLO_SPEC_DOMAIN;
810 use crate::link::spec::Identity;
811
812 fn federation_link_identity() -> Identity {
815 Identity {
816 domain: APOLLO_SPEC_DOMAIN.to_string(),
817 name: name!("federation"),
818 }
819 }
820
821 #[test]
822 fn handle_unsupported_federation_version() {
823 FederationSpecDefinitions::from_link(Link {
824 url: Url {
825 identity: federation_link_identity(),
826 version: Version {
827 major: 99,
828 minor: 99,
829 },
830 },
831 spec_alias: None,
832 imports: vec![],
833 purpose: None,
834 line_column_range: None,
835 })
836 .expect_err("federation version 99 is not yet supported");
837 }
838}