diff --git a/src/lib.rs b/src/lib.rs index 6c5b4a60..5ba58264 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![allow(dead_code)] // TODO: This is fine while we're iterating, but should be removed later. -use apollo_compiler::ast::Directives; +use apollo_compiler::ast::DirectiveList; use apollo_compiler::schema::ExtendedType; use apollo_compiler::Schema; @@ -60,7 +60,7 @@ impl Supergraph { && !graphql_type .directives() .iter() - .any(|d| d.name.eq("inaccessible")) + .any(|d| d.name == "inaccessible") }); // remove directive applications for (_, graphql_type) in api_schema.types.iter_mut() { @@ -146,8 +146,8 @@ fn is_join_type(type_name: &str) -> bool { JOIN_TYPES.contains(&type_name) } -fn is_inaccessible_applied(directives: &Directives) -> bool { - directives.iter().any(|d| d.name.eq("inaccessible")) +fn is_inaccessible_applied(directives: &DirectiveList) -> bool { + directives.iter().any(|d| d.name == "inaccessible") } #[cfg(test)] diff --git a/src/link/database.rs b/src/link/database.rs index b21b8d69..0aac749e 100644 --- a/src/link/database.rs +++ b/src/link/database.rs @@ -134,10 +134,11 @@ fn parse_link_if_bootstrap_directive(schema: &Schema, directive: &Directive) -> .and_then(|value| value.as_str()) { let url = url.parse::(); + let default_link_name = DEFAULT_LINK_NAME; let expected_name = directive .argument_by_name("as") .and_then(|value| value.as_str()) - .unwrap_or(DEFAULT_LINK_NAME); + .unwrap_or(default_link_name.as_str()); return url.map_or(false, |url| { url.identity == Identity::link_identity() && directive.name == expected_name }); diff --git a/src/link/mod.rs b/src/link/mod.rs index 96806a38..981b0df2 100644 --- a/src/link/mod.rs +++ b/src/link/mod.rs @@ -4,6 +4,8 @@ use crate::link::spec::Identity; use crate::link::spec::Url; use crate::link::spec_definition::spec_definitions; use apollo_compiler::ast::{Directive, Value}; +use apollo_compiler::name; +use apollo_compiler::schema::Name; use std::fmt; use std::ops::Deref; use std::str; @@ -18,9 +20,9 @@ pub(crate) mod link_spec_definition; pub mod spec; pub(crate) mod spec_definition; -pub const DEFAULT_LINK_NAME: &str = "link"; -pub const DEFAULT_IMPORT_SCALAR_NAME: &str = "Import"; -pub const DEFAULT_PURPOSE_ENUM_NAME: &str = "Purpose"; +pub const DEFAULT_LINK_NAME: Name = name!("link"); +pub const DEFAULT_IMPORT_SCALAR_NAME: Name = name!("Import"); +pub const DEFAULT_PURPOSE_ENUM_NAME: Name = name!("Purpose"); // TODO: we should provide proper "diagnostic" here, linking to ast, accumulating more than one // error and whatnot. @@ -83,6 +85,15 @@ impl fmt::Display for Purpose { } } +impl From<&Purpose> for Name { + fn from(value: &Purpose) -> Self { + match value { + Purpose::SECURITY => name!("SECURITY"), + Purpose::EXECUTION => name!("EXECUTION"), + } + } +} + #[derive(Eq, PartialEq, Debug)] pub struct Import { /// The name of the element that is being imported. diff --git a/src/merge.rs b/src/merge.rs index 023d2bf8..2cfca39b 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::iter; -use apollo_compiler::ast::Directives; +use apollo_compiler::ast::DirectiveList; use apollo_compiler::ast::{ Argument, Directive, DirectiveDefinition, DirectiveLocation, EnumValueDefinition, FieldDefinition, NamedType, Type, Value, @@ -10,7 +10,7 @@ use apollo_compiler::schema::{ Component, EnumType, ExtendedType, InputObjectType, InputValueDefinition, InterfaceType, Name, ObjectType, ScalarType, UnionType, }; -use apollo_compiler::{Node, NodeStr, Schema}; +use apollo_compiler::{name, Node, NodeStr, Schema}; use indexmap::map::Entry::{Occupied, Vacant}; use indexmap::map::Iter; use indexmap::{IndexMap, IndexSet}; @@ -51,6 +51,24 @@ impl Merger { fn merge(&mut self, subgraphs: Vec<&Subgraph>) -> Result { let mut subgraphs = subgraphs.clone(); subgraphs.sort_by(|s1, s2| s1.name.cmp(&s2.name)); + let mut subgraphs_and_enum_values: Vec<(&Subgraph, Name)> = Vec::new(); + for subgraph in &subgraphs { + // TODO: Implement JS codebase's name transform (which always generates a valid GraphQL + // name and avoids collisions). + if let Ok(subgraph_name) = Name::new(&subgraph.name.to_uppercase()) { + subgraphs_and_enum_values.push((*subgraph, subgraph_name)); + } else { + self.errors + .push("Subgraph name couldn't be transformed into valid GraphQL name"); + } + } + if !self.errors.is_empty() { + return Err(MergeFailure { + schema: None, + composition_hints: self.composition_hints.to_owned(), + errors: self.errors.to_owned(), + }); + } let mut supergraph = Schema::new(); // TODO handle @compose @@ -58,11 +76,10 @@ impl Merger { // add core features // TODO verify federation versions across subgraphs add_core_feature_link(&mut supergraph); - add_core_feature_join(&mut supergraph, &subgraphs); + add_core_feature_join(&mut supergraph, &subgraphs_and_enum_values); // create stubs - for subgraph in &subgraphs { - let subgraph_name = subgraph.name.to_uppercase().clone(); + for (subgraph, subgraph_name) in &subgraphs_and_enum_values { self.merge_schema(&mut supergraph, subgraph); // TODO merge directives @@ -75,31 +92,31 @@ impl Merger { match value { ExtendedType::Enum(value) => self.merge_enum_type( &mut supergraph.types, - &subgraph_name, + subgraph_name.clone(), key.clone(), value, ), ExtendedType::InputObject(value) => self.merge_input_object_type( &mut supergraph.types, - &subgraph_name, + subgraph_name.clone(), key.clone(), value, ), ExtendedType::Interface(value) => self.merge_interface_type( &mut supergraph.types, - &subgraph_name, + subgraph_name.clone(), key.clone(), value, ), ExtendedType::Object(value) => self.merge_object_type( &mut supergraph.types, - &subgraph_name, + subgraph_name.clone(), key.clone(), value, ), ExtendedType::Union(value) => self.merge_union_type( &mut supergraph.types, - &subgraph_name, + subgraph_name.clone(), key.clone(), value, ), @@ -166,14 +183,14 @@ impl Merger { fn merge_enum_type( &mut self, types: &mut IndexMap, - subgraph_name: &str, + subgraph_name: Name, enum_name: NamedType, enum_type: &Node, ) { - let existing_type = types.entry(enum_name).or_insert(copy_enum_type(enum_type)); + let existing_type = types.entry(enum_name.clone()).or_insert(copy_enum_type(enum_name, enum_type)); if let ExtendedType::Enum(e) = existing_type { let join_type_directives = - join_type_applied_directive(subgraph_name, iter::empty(), false); + join_type_applied_directive(subgraph_name.clone(), iter::empty(), false); e.make_mut().directives.extend(join_type_directives); self.merge_descriptions(&mut e.make_mut().description, &enum_type.description); @@ -192,11 +209,11 @@ impl Merger { })); self.merge_descriptions(&mut ev.make_mut().description, &enum_value.description); ev.make_mut().directives.push(Node::new(Directive { - name: Name::new("join__enumValue"), + name: name!("join__enumValue"), arguments: vec![ (Node::new(Argument { - name: Name::new("graph"), - value: Node::new(Value::Enum(Name::new(subgraph_name))), + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name.clone())), })), ], })); @@ -209,13 +226,13 @@ impl Merger { fn merge_input_object_type( &mut self, types: &mut IndexMap, - subgraph_name: &str, + subgraph_name: Name, input_object_name: NamedType, input_object: &Node, ) { let existing_type = types - .entry(input_object_name) - .or_insert(copy_input_object_type(input_object)); + .entry(input_object_name.clone()) + .or_insert(copy_input_object_type(input_object_name, input_object)); if let ExtendedType::InputObject(obj) = existing_type { let join_type_directives = join_type_applied_directive(subgraph_name, iter::empty(), false); @@ -245,13 +262,13 @@ impl Merger { fn merge_interface_type( &mut self, types: &mut IndexMap, - subgraph_name: &str, + subgraph_name: Name, interface_name: NamedType, interface: &Node, ) { let existing_type = types .entry(interface_name.clone()) - .or_insert(copy_interface_type(interface)); + .or_insert(copy_interface_type(interface_name, interface)); if let ExtendedType::Interface(intf) = existing_type { let key_directives = interface.directives.get_all("key"); let join_type_directives = @@ -288,20 +305,20 @@ impl Merger { fn merge_object_type( &mut self, types: &mut IndexMap, - subgraph_name: &str, + subgraph_name: Name, object_name: NamedType, object: &Node, ) { let is_interface_object = object.directives.has("interfaceObject"); let existing_type = types .entry(object_name.clone()) - .or_insert(copy_object_type_stub(object, is_interface_object)); + .or_insert(copy_object_type_stub(object_name.clone(), object, is_interface_object)); if let ExtendedType::Object(obj) = existing_type { let key_fields: HashSet<&str> = parse_keys(object.directives.get_all("key")); - let is_join_field = !key_fields.is_empty() || object_name.eq("Query"); + let is_join_field = !key_fields.is_empty() || object_name == "Query"; let key_directives = object.directives.get_all("key"); let join_type_directives = - join_type_applied_directive(subgraph_name, key_directives, false); + join_type_applied_directive(subgraph_name.clone(), key_directives, false); let mutable_object = obj.make_mut(); mutable_object.directives.extend(join_type_directives); self.merge_descriptions(&mut mutable_object.description, &object.description); @@ -310,13 +327,14 @@ impl Merger { mutable_object .implements_interfaces .insert(intf_name.clone()); - let join_implements_directive = join_type_implements(subgraph_name, intf_name); + let join_implements_directive = + join_type_implements(subgraph_name.clone(), intf_name); mutable_object.directives.push(join_implements_directive); }); for (field_name, field) in object.fields.iter() { // skip federation built-in queries - if field_name.eq(&Name::new("_service")) || field_name.eq(&Name::new("_entities")) { + if field_name == "_service" || field_name == "_entities" { continue; } @@ -342,7 +360,7 @@ impl Merger { ); let mut existing_args = supergraph_field.arguments.iter(); for arg in field.arguments.iter() { - if let Some(_existing_arg) = &existing_args.find(|a| a.name.eq(&arg.name)) { + if let Some(_existing_arg) = &existing_args.find(|a| a.name == arg.name) { } else { // TODO mismatch no args } @@ -365,7 +383,7 @@ impl Merger { }); let external_field = field.directives.get_all("external").next().is_some(); let join_field_directive = join_field_applied_directive( - subgraph_name, + subgraph_name.clone(), requires_directive_option, provides_directive_option, external_field, @@ -391,31 +409,31 @@ impl Merger { fn merge_union_type( &mut self, types: &mut IndexMap, - subgraph_name: &str, + subgraph_name: Name, union_name: NamedType, union: &Node, ) { let existing_type = types .entry(union_name.clone()) - .or_insert(copy_union_type(&union_name, union.description.clone())); + .or_insert(copy_union_type(union_name.clone(), union.description.clone())); if let ExtendedType::Union(u) = existing_type { let join_type_directives = - join_type_applied_directive(subgraph_name, iter::empty(), false); + join_type_applied_directive(subgraph_name.clone(), iter::empty(), false); u.make_mut().directives.extend(join_type_directives); for union_member in union.members.iter() { // IndexSet::insert deduplicates u.make_mut().members.insert(union_member.clone()); u.make_mut().directives.push(Component::new(Directive { - name: Name::new("join__unionMember"), + name: name!("join__unionMember"), arguments: vec![ Node::new(Argument { - name: Name::new("graph"), - value: Node::new(Value::Enum(Name::new(subgraph_name))), + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name.clone())), }), Node::new(Argument { - name: Name::new("member"), - value: Node::new(Value::String(Name::new(union_member))), + name: name!("member"), + value: Node::new(Value::String(NodeStr::new(union_member))), }), ], })); @@ -451,17 +469,19 @@ fn is_mergeable_type(type_name: &str) -> bool { !FEDERATION_TYPES.contains(&type_name) } -fn copy_enum_type(enum_type: &Node) -> ExtendedType { +fn copy_enum_type(enum_name: Name, enum_type: &Node) -> ExtendedType { ExtendedType::Enum(Node::new(EnumType { description: enum_type.description.clone(), + name: enum_name, directives: Default::default(), values: IndexMap::new(), })) } -fn copy_input_object_type(input_object: &Node) -> ExtendedType { +fn copy_input_object_type(input_object_name: Name, input_object: &Node) -> ExtendedType { let mut new_input_object = InputObjectType { description: input_object.description.clone(), + name: input_object_name, directives: Default::default(), fields: IndexMap::new(), }; @@ -482,9 +502,10 @@ fn copy_input_object_type(input_object: &Node) -> ExtendedType ExtendedType::InputObject(Node::new(new_input_object)) } -fn copy_interface_type(interface: &Node) -> ExtendedType { +fn copy_interface_type(interface_name: Name, interface: &Node) -> ExtendedType { let new_interface = InterfaceType { description: interface.description.clone(), + name: interface_name, directives: Default::default(), fields: copy_fields(interface.fields.iter()), implements_interfaces: interface.implements_interfaces.clone(), @@ -492,10 +513,11 @@ fn copy_interface_type(interface: &Node) -> ExtendedType { ExtendedType::Interface(Node::new(new_interface)) } -fn copy_object_type_stub(object: &Node, is_interface_object: bool) -> ExtendedType { +fn copy_object_type_stub(object_name: Name, object: &Node, is_interface_object: bool) -> ExtendedType { if is_interface_object { let new_interface = InterfaceType { description: object.description.clone(), + name: object_name, directives: Default::default(), fields: copy_fields(object.fields.iter()), implements_interfaces: object.implements_interfaces.clone(), @@ -504,6 +526,7 @@ fn copy_object_type_stub(object: &Node, is_interface_object: bool) - } else { let new_object = ObjectType { description: object.description.clone(), + name: object_name, directives: Default::default(), fields: copy_fields(object.fields.iter()), implements_interfaces: object.implements_interfaces.clone(), @@ -518,7 +541,7 @@ fn copy_fields( let mut new_fields: IndexMap> = IndexMap::new(); for (field_name, field) in fields_to_copy { // skip federation built-in queries - if field_name.eq(&Name::new("_service")) || field_name.eq(&Name::new("_entities")) { + if field_name == "_service" || field_name == "_entities" { continue; } let args: Vec> = field @@ -547,29 +570,30 @@ fn copy_fields( new_fields } -fn copy_union_type(_name: &NamedType, description: Option) -> ExtendedType { +fn copy_union_type(union_name: Name, description: Option) -> ExtendedType { ExtendedType::Union(Node::new(UnionType { description, + name: union_name, directives: Default::default(), members: IndexSet::new(), })) } fn join_type_applied_directive<'a>( - subgraph_name: &str, + subgraph_name: Name, key_directives: impl Iterator> + Sized, is_interface_object: bool, ) -> Vec> { let mut join_type_directive = Directive { - name: Name::new("join__type"), + name: name!("join__type"), arguments: vec![Node::new(Argument { - name: Name::new("graph"), - value: Node::new(Value::Enum(Name::new(subgraph_name))), + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name)), })], }; if is_interface_object { join_type_directive.arguments.push(Node::new(Argument { - name: Name::new("isInterfaceObject"), + name: name!("isInterfaceObject"), value: Node::new(Value::Boolean(is_interface_object)), })); } @@ -581,7 +605,7 @@ fn join_type_applied_directive<'a>( join_type_directive_with_key .arguments .push(Node::new(Argument { - name: Name::new("key"), + name: name!("key"), value: Node::new(Value::String(NodeStr::new(field_set.as_str()))), })); @@ -590,7 +614,7 @@ fn join_type_applied_directive<'a>( join_type_directive_with_key .arguments .push(Node::new(Argument { - name: Name::new("resolvable"), + name: name!("resolvable"), value: Node::new(Value::Boolean(false)), })); } @@ -605,16 +629,16 @@ fn join_type_applied_directive<'a>( .collect::>>() } -fn join_type_implements(subgraph_name: &str, intf_name: &str) -> Component { +fn join_type_implements(subgraph_name: Name, intf_name: &str) -> Component { Component::new(Directive { - name: Name::new("join__implements"), + name: name!("join__implements"), arguments: vec![ Node::new(Argument { - name: Name::new("graph"), - value: Node::new(Value::String(NodeStr::new(subgraph_name))), + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name)), }), Node::new(Argument { - name: Name::new("interface"), + name: name!("interface"), value: Node::new(Value::String(NodeStr::new(intf_name))), }), ], @@ -657,9 +681,9 @@ fn add_core_feature_link(supergraph: &mut Schema) { .make_mut() .directives .push(Component::new(Directive { - name: Name::new("link"), + name: name!("link"), arguments: vec![Node::new(Argument { - name: Name::new("url"), + name: name!("url"), value: Node::new(Value::String(NodeStr::new( "https://specs.apollo.dev/link/v1.0", ))), @@ -670,52 +694,54 @@ fn add_core_feature_link(supergraph: &mut Schema) { supergraph.types.insert(name, link_purpose_enum.into()); // scalar Import + let link_import_name = name!("link__Import"); let link_import_scalar = ExtendedType::Scalar(Node::new(ScalarType { directives: Default::default(), + name: link_import_name.clone(), description: None, })); supergraph .types - .insert("link__Import".into(), link_import_scalar); + .insert(link_import_name, link_import_scalar); let link_directive_definition = link_directive_definition(); supergraph .directive_definitions - .insert(NamedType::new("link"), Node::new(link_directive_definition)); + .insert(name!("link"), Node::new(link_directive_definition)); } /// directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA fn link_directive_definition() -> DirectiveDefinition { DirectiveDefinition { - name: Name::new("link"), + name: name!("link"), description: None, arguments: vec![ Node::new(InputValueDefinition { - name: Name::new("url"), + name: name!("url"), description: None, directives: Default::default(), - ty: Type::new_named("String").into(), + ty: Type::Named(name!("String")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("as"), + name: name!("as"), description: None, directives: Default::default(), - ty: Type::new_named("String").into(), + ty: Type::Named(name!("String")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("for"), + name: name!("for"), description: None, directives: Default::default(), - ty: Type::new_named("link__Purpose").into(), + ty: Type::Named(name!("link__Purpose")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("import"), + name: name!("import"), description: None, directives: Default::default(), - ty: Type::new_named("link__Import").list().into(), + ty: Type::Named(name!("link__Import")).list().into(), default_value: None, }), ], @@ -736,8 +762,10 @@ fn link_directive_definition() -> DirectiveDefinition { /// EXECUTION /// } fn link_purpose_enum_type() -> (Name, EnumType) { + let link_purpose_name = name!("link__Purpose"); let mut link_purpose_enum = EnumType { description: None, + name: link_purpose_name.clone(), directives: Default::default(), values: IndexMap::new(), }; @@ -746,14 +774,14 @@ fn link_purpose_enum_type() -> (Name, EnumType) { r"SECURITY features provide metadata necessary to securely resolve fields.", )), directives: Default::default(), - value: Name::new("SECURITY"), + value: name!("SECURITY"), }; let link_purpose_execution_value = EnumValueDefinition { description: Some(NodeStr::new( r"EXECUTION features provide metadata necessary for operation execution.", )), directives: Default::default(), - value: Name::new("EXECUTION"), + value: name!("EXECUTION"), }; link_purpose_enum.values.insert( link_purpose_security_value.value.clone(), @@ -763,40 +791,45 @@ fn link_purpose_enum_type() -> (Name, EnumType) { link_purpose_execution_value.value.clone(), Component::new(link_purpose_execution_value), ); - (Name::new("link__Purpose"), link_purpose_enum) + (link_purpose_name, link_purpose_enum) } // TODO join spec -fn add_core_feature_join(supergraph: &mut Schema, subgraphs: &Vec<&Subgraph>) { +fn add_core_feature_join( + supergraph: &mut Schema, + subgraphs_and_enum_values: &Vec<(&Subgraph, Name)>, +) { // @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) supergraph .schema_definition .make_mut() .directives .push(Component::new(Directive { - name: Name::new("link"), + name: name!("link"), arguments: vec![ Node::new(Argument { - name: Name::new("url"), + name: name!("url"), value: Node::new(Value::String(NodeStr::new( "https://specs.apollo.dev/join/v0.3", ))), }), Node::new(Argument { - name: Name::new("for"), - value: Node::new(Value::Enum(NodeStr::new("EXECUTION"))), + name: name!("for"), + value: Node::new(Value::Enum(name!("EXECUTION"))), }), ], })); // scalar FieldSet + let join_field_set_name = name!("join__FieldSet"); let join_field_set_scalar = ExtendedType::Scalar(Node::new(ScalarType { directives: Default::default(), + name: join_field_set_name.clone(), description: None, })); supergraph .types - .insert("join__FieldSet".into(), join_field_set_scalar); + .insert(join_field_set_name, join_field_set_scalar); let join_graph_directive_definition = join_graph_directive_definition(); supergraph.directive_definitions.insert( @@ -834,20 +867,20 @@ fn add_core_feature_join(supergraph: &mut Schema, subgraphs: &Vec<&Subgraph>) { Node::new(join_enum_value_directive_definition), ); - let (name, join_graph_enum_type) = join_graph_enum_type(subgraphs); + let (name, join_graph_enum_type) = join_graph_enum_type(subgraphs_and_enum_values); supergraph.types.insert(name, join_graph_enum_type.into()); } /// directive @enumValue(graph: join__Graph!) repeatable on ENUM_VALUE fn join_enum_value_directive_definition() -> DirectiveDefinition { DirectiveDefinition { - name: Name::new("join__enumValue"), + name: name!("join__enumValue"), description: None, arguments: vec![Node::new(InputValueDefinition { - name: Name::new("graph"), + name: name!("graph"), description: None, directives: Default::default(), - ty: Type::new_named("join__Graph").non_null().into(), + ty: Type::Named(name!("join__Graph")).non_null().into(), default_value: None, })], locations: vec![DirectiveLocation::EnumValue], @@ -866,56 +899,56 @@ fn join_enum_value_directive_definition() -> DirectiveDefinition { /// ) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION fn join_field_directive_definition() -> DirectiveDefinition { DirectiveDefinition { - name: Name::new("join__field"), + name: name!("join__field"), description: None, arguments: vec![ Node::new(InputValueDefinition { - name: Name::new("graph"), + name: name!("graph"), description: None, directives: Default::default(), - ty: Type::new_named("join__Graph").into(), + ty: Type::Named(name!("join__Graph")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("requires"), + name: name!("requires"), description: None, directives: Default::default(), - ty: Type::new_named("join__FieldSet").into(), + ty: Type::Named(name!("join__FieldSet")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("provides"), + name: name!("provides"), description: None, directives: Default::default(), - ty: Type::new_named("join__FieldSet").into(), + ty: Type::Named(name!("join__FieldSet")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("type"), + name: name!("type"), description: None, directives: Default::default(), - ty: Type::new_named("String").into(), + ty: Type::Named(name!("String")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("external"), + name: name!("external"), description: None, directives: Default::default(), - ty: Type::new_named("Boolean").into(), + ty: Type::Named(name!("Boolean")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("override"), + name: name!("override"), description: None, directives: Default::default(), - ty: Type::new_named("String").into(), + ty: Type::Named(name!("String")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("usedOverridden"), + name: name!("usedOverridden"), description: None, directives: Default::default(), - ty: Type::new_named("Boolean").into(), + ty: Type::Named(name!("Boolean")).into(), default_value: None, }), ], @@ -928,33 +961,33 @@ fn join_field_directive_definition() -> DirectiveDefinition { } fn join_field_applied_directive( - subgraph_name: &str, + subgraph_name: Name, requires: Option<&str>, provides: Option<&str>, external: bool, ) -> Directive { let mut join_field_directive = Directive { - name: Name::new("join__field"), + name: name!("join__field"), arguments: vec![Node::new(Argument { - name: Name::new("graph"), - value: Node::new(Value::Enum(Name::new(subgraph_name))), + name: name!("graph"), + value: Node::new(Value::Enum(subgraph_name)), })], }; if let Some(required_fields) = requires { join_field_directive.arguments.push(Node::new(Argument { - name: Name::new("requires"), - value: Node::new(Value::String(Name::new(required_fields))), + name: name!("requires"), + value: Node::new(Value::String(NodeStr::new(required_fields))), })); } if let Some(provided_fields) = provides { join_field_directive.arguments.push(Node::new(Argument { - name: Name::new("provides"), - value: Node::new(Value::String(Name::new(provided_fields))), + name: name!("provides"), + value: Node::new(Value::String(NodeStr::new(provided_fields))), })); } if external { join_field_directive.arguments.push(Node::new(Argument { - name: Name::new("external"), + name: name!("external"), value: Node::new(Value::Boolean(external)), })); } @@ -964,21 +997,21 @@ fn join_field_applied_directive( /// directive @graph(name: String!, url: String!) on ENUM_VALUE fn join_graph_directive_definition() -> DirectiveDefinition { DirectiveDefinition { - name: Name::new("join__graph"), + name: name!("join__graph"), description: None, arguments: vec![ Node::new(InputValueDefinition { - name: Name::new("name"), + name: name!("name"), description: None, directives: Default::default(), - ty: Type::new_named("String").non_null().into(), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("url"), + name: name!("url"), description: None, directives: Default::default(), - ty: Type::new_named("String").non_null().into(), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, }), ], @@ -993,21 +1026,21 @@ fn join_graph_directive_definition() -> DirectiveDefinition { /// ) on OBJECT | INTERFACE fn join_implements_directive_definition() -> DirectiveDefinition { DirectiveDefinition { - name: Name::new("join__implements"), + name: name!("join__implements"), description: None, arguments: vec![ Node::new(InputValueDefinition { - name: Name::new("graph"), + name: name!("graph"), description: None, directives: Default::default(), - ty: Type::new_named("join__Graph").non_null().into(), + ty: Type::Named(name!("join__Graph")).non_null().into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("interface"), + name: name!("interface"), description: None, directives: Default::default(), - ty: Type::new_named("String").non_null().into(), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, }), ], @@ -1025,42 +1058,42 @@ fn join_implements_directive_definition() -> DirectiveDefinition { /// ) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR fn join_type_directive_definition() -> DirectiveDefinition { DirectiveDefinition { - name: Name::new("join__type"), + name: name!("join__type"), description: None, arguments: vec![ Node::new(InputValueDefinition { - name: Name::new("graph"), + name: name!("graph"), description: None, directives: Default::default(), - ty: Type::new_named("join__Graph").non_null().into(), + ty: Type::Named(name!("join__Graph")).non_null().into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("key"), + name: name!("key"), description: None, directives: Default::default(), - ty: Type::new_named("join__FieldSet").into(), + ty: Type::Named(name!("join__FieldSet")).into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("extension"), + name: name!("extension"), description: None, directives: Default::default(), - ty: Type::new_named("Boolean").non_null().into(), + ty: Type::Named(name!("Boolean")).non_null().into(), default_value: Some(Node::new(Value::Boolean(false))), }), Node::new(InputValueDefinition { - name: Name::new("resolvable"), + name: name!("resolvable"), description: None, directives: Default::default(), - ty: Type::new_named("Boolean").non_null().into(), + ty: Type::Named(name!("Boolean")).non_null().into(), default_value: Some(Node::new(Value::Boolean(true))), }), Node::new(InputValueDefinition { - name: Name::new("isInterfaceObject"), + name: name!("isInterfaceObject"), description: None, directives: Default::default(), - ty: Type::new_named("Boolean").non_null().into(), + ty: Type::Named(name!("Boolean")).non_null().into(), default_value: Some(Node::new(Value::Boolean(false))), }), ], @@ -1079,21 +1112,21 @@ fn join_type_directive_definition() -> DirectiveDefinition { /// directive @unionMember(graph: join__Graph!, member: String!) repeatable on UNION fn join_union_member_directive_definition() -> DirectiveDefinition { DirectiveDefinition { - name: Name::new("join__unionMember"), + name: name!("join__unionMember"), description: None, arguments: vec![ Node::new(InputValueDefinition { - name: Name::new("graph"), + name: name!("graph"), description: None, directives: Default::default(), - ty: Type::new_named("join__Graph").non_null().into(), + ty: Type::Named(name!("join__Graph")).non_null().into(), default_value: None, }), Node::new(InputValueDefinition { - name: Name::new("member"), + name: name!("member"), description: None, directives: Default::default(), - ty: Type::new_named("String").non_null().into(), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, }), ], @@ -1103,36 +1136,38 @@ fn join_union_member_directive_definition() -> DirectiveDefinition { } /// enum Graph -fn join_graph_enum_type(subgraphs: &Vec<&Subgraph>) -> (Name, EnumType) { +fn join_graph_enum_type(subgraphs_and_enum_values: &Vec<(&Subgraph, Name)>) -> (Name, EnumType) { + let join_graph_enum_name = name!("join__Graph"); let mut join_graph_enum_type = EnumType { description: None, + name: join_graph_enum_name.clone(), directives: Default::default(), values: IndexMap::new(), }; - for s in subgraphs { + for (s, subgraph_name) in subgraphs_and_enum_values { let join_graph_applied_directive = Directive { - name: Name::new("join__graph"), + name: name!("join__graph"), arguments: vec![ (Node::new(Argument { - name: Name::new("name"), + name: name!("name"), value: Node::new(Value::String(NodeStr::new(s.name.as_str()))), })), (Node::new(Argument { - name: Name::new("url"), + name: name!("url"), value: Node::new(Value::String(NodeStr::new(s.url.as_str()))), })), ], }; let graph = EnumValueDefinition { description: None, - directives: Directives(vec![Node::new(join_graph_applied_directive)]), - value: Name::new(s.name.to_uppercase().as_str()), + directives: DirectiveList(vec![Node::new(join_graph_applied_directive)]), + value: subgraph_name.clone(), }; join_graph_enum_type .values .insert(graph.value.clone(), Component::new(graph)); } - (Name::new("join__Graph"), join_graph_enum_type) + (join_graph_enum_name, join_graph_enum_type) } fn parse_keys<'a>( diff --git a/src/subgraph/mod.rs b/src/subgraph/mod.rs index 970e600f..1305365f 100644 --- a/src/subgraph/mod.rs +++ b/src/subgraph/mod.rs @@ -2,9 +2,9 @@ use std::collections::BTreeMap; use std::fmt::Formatter; use std::sync::Arc; -use apollo_compiler::ast::{Name, NamedType}; -use apollo_compiler::schema::{ComponentStr, ExtendedType, ObjectType}; -use apollo_compiler::{Node, Schema}; +use apollo_compiler::ast::Name; +use apollo_compiler::schema::{ComponentName, ExtendedType, ObjectType}; +use apollo_compiler::{name, Node, Schema}; use indexmap::map::Entry; use indexmap::{IndexMap, IndexSet}; @@ -29,8 +29,8 @@ pub struct SubgraphError { pub msg: String, } -impl From for SubgraphError { - fn from(value: apollo_compiler::Diagnostics) -> Self { +impl From for SubgraphError { + fn from(value: apollo_compiler::DiagnosticList) -> Self { SubgraphError { msg: value.to_string_no_color(), } @@ -53,6 +53,12 @@ impl From for SubgraphError { } } +pub(crate) fn graphql_name_or_subgraph_error(name: &str) -> Result { + Name::new(name).map_err(|_| SubgraphError { + msg: format!("Invalid GraphQL name \"{}\"", name), + }) +} + pub struct Subgraph { pub name: String, pub url: String, @@ -90,25 +96,22 @@ impl Subgraph { let mut imported_federation_definitions: Option = None; let mut imported_link_definitions: Option = None; + let default_link_name = DEFAULT_LINK_NAME; let link_directives = schema .schema_definition .directives - .get_all(DEFAULT_LINK_NAME); + .get_all(&default_link_name); for directive in link_directives { let link_directive = Link::from_directive_application(directive)?; - if link_directive - .url - .identity - .eq(&Identity::federation_identity()) - { + if link_directive.url.identity == Identity::federation_identity() { if imported_federation_definitions.is_some() { return Err(SubgraphError { msg: "invalid graphql schema - multiple @link imports for the federation specification are not supported".to_owned() }); } imported_federation_definitions = Some(FederationSpecDefinitions::from_link(link_directive)?); - } else if link_directive.url.identity.eq(&Identity::link_identity()) { + } else if link_directive.url.identity == Identity::link_identity() { // user manually imported @link specification if imported_link_definitions.is_some() { return Err(SubgraphError { msg: "invalid graphql schema - multiple @link imports for the link specification are not supported".to_owned() }); @@ -176,18 +179,29 @@ impl Subgraph { schema: &mut Schema, link_spec_definitions: LinkSpecDefinitions, ) -> Result<(), SubgraphError> { + let purpose_enum_name = + graphql_name_or_subgraph_error(&link_spec_definitions.purpose_enum_name)?; schema .types - .entry(link_spec_definitions.purpose_enum_name.as_str().into()) - .or_insert_with(|| link_spec_definitions.link_purpose_enum_definition().into()); + .entry(purpose_enum_name.clone()) + .or_insert_with(|| { + link_spec_definitions + .link_purpose_enum_definition(purpose_enum_name) + .into() + }); + let import_scalar_name = + graphql_name_or_subgraph_error(&link_spec_definitions.import_scalar_name)?; schema .types - .entry(link_spec_definitions.import_scalar_name.as_str().into()) - .or_insert_with(|| link_spec_definitions.import_scalar_definition().into()); - schema - .directive_definitions - .entry(DEFAULT_LINK_NAME.into()) - .or_insert_with(|| link_spec_definitions.link_directive_definition().into()); + .entry(import_scalar_name.clone()) + .or_insert_with(|| { + link_spec_definitions + .import_scalar_definition(import_scalar_name) + .into() + }); + if let Entry::Vacant(entry) = schema.directive_definitions.entry(DEFAULT_LINK_NAME) { + entry.insert(link_spec_definitions.link_directive_definition()?.into()); + } Ok(()) } @@ -195,17 +209,24 @@ impl Subgraph { schema: &mut Schema, fed_definitions: &FederationSpecDefinitions, ) -> Result<(), SubgraphError> { + let fieldset_scalar_name = + graphql_name_or_subgraph_error(&fed_definitions.fieldset_scalar_name)?; schema .types - .entry(fed_definitions.fieldset_scalar_name.as_str().into()) - .or_insert_with(|| fed_definitions.fieldset_scalar_definition().into()); + .entry(fieldset_scalar_name.clone()) + .or_insert_with(|| { + fed_definitions + .fieldset_scalar_definition(fieldset_scalar_name) + .into() + }); - for directive_name in FEDERATION_V2_DIRECTIVE_NAMES { - let namespaced_directive_name = - fed_definitions.namespaced_type_name(directive_name, true); + for directive_name in &FEDERATION_V2_DIRECTIVE_NAMES { + let namespaced_directive_name = graphql_name_or_subgraph_error( + &fed_definitions.namespaced_type_name(directive_name, true), + )?; if let Entry::Vacant(entry) = schema .directive_definitions - .entry(namespaced_directive_name.as_str().into()) + .entry(namespaced_directive_name.clone()) { let directive_definition = fed_definitions.directive_definition( directive_name, @@ -223,7 +244,7 @@ impl Subgraph { ) -> Result<(), SubgraphError> { schema .types - .entry(NamedType::new(SERVICE_TYPE)) + .entry(SERVICE_TYPE) .or_insert_with(|| fed_definitions.service_object_type_definition()); let entities = Self::locate_entities(schema, fed_definitions); @@ -231,11 +252,11 @@ impl Subgraph { if entities_present { schema .types - .entry(NamedType::new(ENTITY_UNION_NAME)) + .entry(ENTITY_UNION_NAME) .or_insert_with(|| fed_definitions.entity_union_definition(entities)); schema .types - .entry(NamedType::new(ANY_SCALAR_NAME)) + .entry(ANY_SCALAR_NAME) .or_insert_with(|| fed_definitions.any_scalar_definition()); } @@ -243,12 +264,13 @@ impl Subgraph { .schema_definition .make_mut() .query - .get_or_insert(ComponentStr::new("Query")); + .get_or_insert(ComponentName::from(name!("Query"))); if let ExtendedType::Object(query_type) = schema .types - .entry(NamedType::new(query_type_name.as_str())) + .entry(query_type_name.name.clone()) .or_insert(ExtendedType::Object(Node::new(ObjectType { description: None, + name: query_type_name.name.clone(), directives: Default::default(), fields: IndexMap::new(), implements_interfaces: IndexSet::new(), @@ -257,13 +279,13 @@ impl Subgraph { let query_type = query_type.make_mut(); query_type .fields - .entry(Name::new(SERVICE_SDL_QUERY)) + .entry(SERVICE_SDL_QUERY) .or_insert_with(|| fed_definitions.service_sdl_query_field()); if entities_present { // _entities(representations: [_Any!]!): [_Entity]! query_type .fields - .entry(Name::new(ENTITIES_QUERY)) + .entry(ENTITIES_QUERY) .or_insert_with(|| fed_definitions.entities_query_field()); } } @@ -273,7 +295,7 @@ impl Subgraph { fn locate_entities( schema: &mut Schema, fed_definitions: &FederationSpecDefinitions, - ) -> IndexSet { + ) -> IndexSet { let mut entities = Vec::new(); let immutable_type_map = schema.types.to_owned(); for (named_type, extended_type) in immutable_type_map.iter() { @@ -281,11 +303,10 @@ impl Subgraph { .directives() .iter() .find(|d| { - d.name.eq(&Name::new( - fed_definitions - .namespaced_type_name(KEY_DIRECTIVE_NAME, true) - .as_str(), - )) + d.name + == fed_definitions + .namespaced_type_name(&KEY_DIRECTIVE_NAME, true) + .as_str() }) .map(|_| true) .unwrap_or(false); @@ -293,10 +314,8 @@ impl Subgraph { entities.push(named_type); } } - let entity_set: IndexSet = entities - .iter() - .map(|e| ComponentStr::new(e.as_str())) - .collect(); + let entity_set: IndexSet = + entities.iter().map(|e| ComponentName::from(*e)).collect(); entity_set } } diff --git a/src/subgraph/spec.rs b/src/subgraph/spec.rs index 121a8ce5..11c4e382 100644 --- a/src/subgraph/spec.rs +++ b/src/subgraph/spec.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use apollo_compiler::ast::{ Argument, Directive, DirectiveDefinition, DirectiveLocation, EnumValueDefinition, - FieldDefinition, InputValueDefinition, Name, NamedType, Type, Value, + FieldDefinition, InputValueDefinition, Name, Type, Value, }; use apollo_compiler::schema::{ - Component, ComponentStr, EnumType, ExtendedType, ObjectType, ScalarType, UnionType, + Component, ComponentName, EnumType, ExtendedType, ObjectType, ScalarType, UnionType, }; -use apollo_compiler::Node; +use apollo_compiler::{name, Node}; use indexmap::{IndexMap, IndexSet}; +use lazy_static::lazy_static; use thiserror::Error; use crate::link::spec::{Identity, Url, Version}; @@ -19,28 +20,28 @@ use crate::subgraph::spec::FederationSpecError::{ UnsupportedFederationDirective, UnsupportedVersionError, }; -pub const COMPOSE_DIRECTIVE_NAME: &str = "composeDirective"; -pub const KEY_DIRECTIVE_NAME: &str = "key"; -pub const EXTENDS_DIRECTIVE_NAME: &str = "extends"; -pub const EXTERNAL_DIRECTIVE_NAME: &str = "external"; -pub const INACCESSIBLE_DIRECTIVE_NAME: &str = "inaccessible"; -pub const INTF_OBJECT_DIRECTIVE_NAME: &str = "interfaceObject"; -pub const OVERRIDE_DIRECTIVE_NAME: &str = "override"; -pub const PROVIDES_DIRECTIVE_NAME: &str = "provides"; -pub const REQUIRES_DIRECTIVE_NAME: &str = "requires"; -pub const SHAREABLE_DIRECTIVE_NAME: &str = "shareable"; -pub const TAG_DIRECTIVE_NAME: &str = "tag"; -pub const FIELDSET_SCALAR_NAME: &str = "FieldSet"; +pub const COMPOSE_DIRECTIVE_NAME: Name = name!("composeDirective"); +pub const KEY_DIRECTIVE_NAME: Name = name!("key"); +pub const EXTENDS_DIRECTIVE_NAME: Name = name!("extends"); +pub const EXTERNAL_DIRECTIVE_NAME: Name = name!("external"); +pub const INACCESSIBLE_DIRECTIVE_NAME: Name = name!("inaccessible"); +pub const INTF_OBJECT_DIRECTIVE_NAME: Name = name!("interfaceObject"); +pub const OVERRIDE_DIRECTIVE_NAME: Name = name!("override"); +pub const PROVIDES_DIRECTIVE_NAME: Name = name!("provides"); +pub const REQUIRES_DIRECTIVE_NAME: Name = name!("requires"); +pub const SHAREABLE_DIRECTIVE_NAME: Name = name!("shareable"); +pub const TAG_DIRECTIVE_NAME: Name = name!("tag"); +pub const FIELDSET_SCALAR_NAME: Name = name!("FieldSet"); // federated types -pub const ANY_SCALAR_NAME: &str = "_Any"; -pub const ENTITY_UNION_NAME: &str = "_Entity"; -pub const SERVICE_TYPE: &str = "_Service"; +pub const ANY_SCALAR_NAME: Name = name!("_Any"); +pub const ENTITY_UNION_NAME: Name = name!("_Entity"); +pub const SERVICE_TYPE: Name = name!("_Service"); -pub const ENTITIES_QUERY: &str = "_entities"; -pub const SERVICE_SDL_QUERY: &str = "_service"; +pub const ENTITIES_QUERY: Name = name!("_entities"); +pub const SERVICE_SDL_QUERY: Name = name!("_service"); -pub const FEDERATION_V1_DIRECTIVE_NAMES: [&str; 5] = [ +pub const FEDERATION_V1_DIRECTIVE_NAMES: [Name; 5] = [ KEY_DIRECTIVE_NAME, EXTENDS_DIRECTIVE_NAME, EXTERNAL_DIRECTIVE_NAME, @@ -48,7 +49,7 @@ pub const FEDERATION_V1_DIRECTIVE_NAMES: [&str; 5] = [ REQUIRES_DIRECTIVE_NAME, ]; -pub const FEDERATION_V2_DIRECTIVE_NAMES: [&str; 11] = [ +pub const FEDERATION_V2_DIRECTIVE_NAMES: [Name; 11] = [ COMPOSE_DIRECTIVE_NAME, KEY_DIRECTIVE_NAME, EXTENDS_DIRECTIVE_NAME, @@ -62,6 +63,40 @@ pub const FEDERATION_V2_DIRECTIVE_NAMES: [&str; 11] = [ TAG_DIRECTIVE_NAME, ]; +// This type and the subsequent IndexMap exist purely so we can use match with Names; see comment +// in FederationSpecDefinitions.directive_definition() for more information. +enum FederationDirectiveName { + Compose, + Key, + Extends, + External, + Inaccessible, + IntfObject, + Override, + Provides, + Requires, + Shareable, + Tag, +} + +lazy_static! { + static ref FEDERATION_DIRECTIVE_NAMES_TO_ENUM: IndexMap = { + IndexMap::from([ + (COMPOSE_DIRECTIVE_NAME,FederationDirectiveName::Compose), + (KEY_DIRECTIVE_NAME,FederationDirectiveName::Key), + (EXTENDS_DIRECTIVE_NAME,FederationDirectiveName::Extends), + (EXTERNAL_DIRECTIVE_NAME,FederationDirectiveName::External), + (INACCESSIBLE_DIRECTIVE_NAME,FederationDirectiveName::Inaccessible), + (INTF_OBJECT_DIRECTIVE_NAME,FederationDirectiveName::IntfObject), + (OVERRIDE_DIRECTIVE_NAME,FederationDirectiveName::Override), + (PROVIDES_DIRECTIVE_NAME,FederationDirectiveName::Provides), + (REQUIRES_DIRECTIVE_NAME,FederationDirectiveName::Requires), + (SHAREABLE_DIRECTIVE_NAME,FederationDirectiveName::Shareable), + (TAG_DIRECTIVE_NAME,FederationDirectiveName::Tag), + ]) + }; +} + const MIN_FEDERATION_VERSION: Version = Version { major: 2, minor: 0 }; const MAX_FEDERATION_VERSION: Version = Version { major: 2, minor: 5 }; @@ -77,6 +112,16 @@ pub enum FederationSpecError { }, #[error("Unsupported federation directive import {0}")] UnsupportedFederationDirective(String), + #[error("Invalid GraphQL name {0}")] + InvalidGraphQLName(String), +} + +pub(crate) fn graphql_name_or_federation_spec_error( + name: &str, +) -> Result { + Name::new(name).map_err(|_| { + FederationSpecError::InvalidGraphQLName(format!("Invalid GraphQL name \"{}\"", name)) + }) } #[derive(Debug)] @@ -108,8 +153,8 @@ macro_rules! applied_specification { .map(|i| { if i.alias.is_some() { Value::Object(vec![ - ("name".into(), i.element.as_str().into()), - ("as".into(), i.imported_display_name().into()), + (name!("name"), i.element.as_str().into()), + (name!("as"), i.imported_display_name().into()), ]) } else { i.imported_display_name().into() @@ -117,28 +162,28 @@ macro_rules! applied_specification { }) .collect::>>(); let mut applied_link_directive = Directive { - name: DEFAULT_LINK_NAME.into(), + name: DEFAULT_LINK_NAME, arguments: vec![ Argument { - name: "url".into(), + name: name!("url"), value: self.link.url.to_string().into(), }.into(), Argument { - name: "import".into(), + name: name!("import"), value: Value::List(imports).into(), }.into(), ] }; if let Some(spec_alias) = &self.link.spec_alias { applied_link_directive.arguments.push(Argument { - name: "as".into(), + name: name!("as"), value: spec_alias.into(), }.into()) } if let Some(purpose) = &self.link.purpose { applied_link_directive.arguments.push(Argument { - name: "for".into(), - value: Value::Enum(purpose.to_string().into()).into(), + name: name!("for"), + value: Value::Enum(purpose.into()).into(), }.into()) } applied_link_directive @@ -162,7 +207,7 @@ impl FederationSpecDefinitions { max: MAX_FEDERATION_VERSION.to_string(), }) } else { - let fieldset_scalar_name = link.type_name_in_schema(FIELDSET_SCALAR_NAME); + let fieldset_scalar_name = link.type_name_in_schema(&FIELDSET_SCALAR_NAME); Ok(Self { link, fieldset_scalar_name, @@ -201,54 +246,63 @@ impl FederationSpecDefinitions { pub fn directive_definition( &self, - name: &str, - alias: &Option, + name: &Name, + alias: &Option, ) -> Result { - match name { - COMPOSE_DIRECTIVE_NAME => Ok(self.compose_directive_definition(alias)), - KEY_DIRECTIVE_NAME => Ok(self.key_directive_definition(alias)), - EXTENDS_DIRECTIVE_NAME => Ok(self.extends_directive_definition(alias)), - EXTERNAL_DIRECTIVE_NAME => Ok(self.external_directive_definition(alias)), - INACCESSIBLE_DIRECTIVE_NAME => Ok(self.inaccessible_directive_definition(alias)), - INTF_OBJECT_DIRECTIVE_NAME => Ok(self.interface_object_directive_definition(alias)), - OVERRIDE_DIRECTIVE_NAME => Ok(self.override_directive_definition(alias)), - PROVIDES_DIRECTIVE_NAME => Ok(self.provides_directive_definition(alias)), - REQUIRES_DIRECTIVE_NAME => Ok(self.requires_directive_definition(alias)), - SHAREABLE_DIRECTIVE_NAME => Ok(self.shareable_directive_definition(alias)), - TAG_DIRECTIVE_NAME => Ok(self.tag_directive_definition(alias)), - _ => Err(UnsupportedFederationDirective(name.to_string())), - } + // TODO: NodeStr is not annotated with #[derive(PartialEq, Eq)], so Clippy warns it should + // not be used in pattern matching (as some future Rust version will likely turn this into + // a hard error). We resort instead to indexing into a static IndexMap to get an enum, which + // can be used in a match. + let Some(enum_name) = FEDERATION_DIRECTIVE_NAMES_TO_ENUM.get(name) else { + return Err(UnsupportedFederationDirective(name.to_string())); + }; + Ok(match enum_name { + FederationDirectiveName::Compose => self.compose_directive_definition(alias), + FederationDirectiveName::Key => self.key_directive_definition(alias)?, + FederationDirectiveName::Extends => self.extends_directive_definition(alias), + FederationDirectiveName::External => self.external_directive_definition(alias), + FederationDirectiveName::Inaccessible => self.inaccessible_directive_definition(alias), + FederationDirectiveName::IntfObject => self.interface_object_directive_definition(alias), + FederationDirectiveName::Override => self.override_directive_definition(alias), + FederationDirectiveName::Provides => self.provides_directive_definition(alias)?, + FederationDirectiveName::Requires => self.requires_directive_definition(alias)?, + FederationDirectiveName::Shareable => self.shareable_directive_definition(alias), + FederationDirectiveName::Tag => self.tag_directive_definition(alias), + }) } /// scalar FieldSet - pub fn fieldset_scalar_definition(&self) -> ScalarType { + pub fn fieldset_scalar_definition(&self, name: Name) -> ScalarType { ScalarType { description: None, + name, directives: Default::default(), } } - fn fields_argument_definition(&self) -> InputValueDefinition { - InputValueDefinition { + fn fields_argument_definition(&self) -> Result { + Ok(InputValueDefinition { description: None, - name: "fields".into(), - ty: Type::new_named(&self.namespaced_type_name(FIELDSET_SCALAR_NAME, false)) - .non_null() - .into(), + name: name!("fields"), + ty: Type::Named(graphql_name_or_federation_spec_error( + &self.namespaced_type_name(&FIELDSET_SCALAR_NAME, false), + )?) + .non_null() + .into(), default_value: None, directives: Default::default(), - } + }) } /// directive @composeDirective(name: String!) repeatable on SCHEMA - fn compose_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn compose_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(COMPOSE_DIRECTIVE_NAME).into(), + name: alias.clone().unwrap_or(COMPOSE_DIRECTIVE_NAME), arguments: vec![InputValueDefinition { description: None, - name: "name".into(), - ty: Type::new_named("String").non_null().into(), + name: name!("name"), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, directives: Default::default(), } @@ -259,16 +313,19 @@ impl FederationSpecDefinitions { } /// directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE - fn key_directive_definition(&self, alias: &Option) -> DirectiveDefinition { - DirectiveDefinition { + fn key_directive_definition( + &self, + alias: &Option, + ) -> Result { + Ok(DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(KEY_DIRECTIVE_NAME).into(), + name: alias.clone().unwrap_or(KEY_DIRECTIVE_NAME), arguments: vec![ - self.fields_argument_definition().into(), + self.fields_argument_definition()?.into(), InputValueDefinition { description: None, - name: "resolvable".into(), - ty: Type::new_named("Boolean").into(), + name: name!("resolvable"), + ty: Type::Named(name!("Boolean")).into(), default_value: Some(true.into()), directives: Default::default(), } @@ -276,14 +333,14 @@ impl FederationSpecDefinitions { ], repeatable: true, locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface], - } + }) } /// directive @extends on OBJECT | INTERFACE - fn extends_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn extends_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(EXTENDS_DIRECTIVE_NAME).into(), + name: alias.clone().unwrap_or(EXTENDS_DIRECTIVE_NAME), arguments: Vec::new(), repeatable: false, locations: vec![DirectiveLocation::Object, DirectiveLocation::Interface], @@ -291,10 +348,10 @@ impl FederationSpecDefinitions { } /// directive @external on OBJECT | FIELD_DEFINITION - fn external_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn external_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(EXTERNAL_DIRECTIVE_NAME).into(), + name: alias.clone().unwrap_or(EXTERNAL_DIRECTIVE_NAME), arguments: Vec::new(), repeatable: false, locations: vec![ @@ -315,13 +372,10 @@ impl FederationSpecDefinitions { /// | OBJECT /// | SCALAR /// | UNION - fn inaccessible_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn inaccessible_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias - .as_deref() - .unwrap_or(INACCESSIBLE_DIRECTIVE_NAME) - .into(), + name: alias.clone().unwrap_or(INACCESSIBLE_DIRECTIVE_NAME), arguments: Vec::new(), repeatable: false, locations: vec![ @@ -340,13 +394,10 @@ impl FederationSpecDefinitions { } /// directive @interfaceObject on OBJECT - fn interface_object_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn interface_object_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias - .as_deref() - .unwrap_or(INTF_OBJECT_DIRECTIVE_NAME) - .into(), + name: alias.clone().unwrap_or(INTF_OBJECT_DIRECTIVE_NAME), arguments: Vec::new(), repeatable: false, locations: vec![DirectiveLocation::Object], @@ -354,14 +405,14 @@ impl FederationSpecDefinitions { } /// directive @override(from: String!) on FIELD_DEFINITION - fn override_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn override_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(OVERRIDE_DIRECTIVE_NAME).into(), + name: alias.clone().unwrap_or(OVERRIDE_DIRECTIVE_NAME), arguments: vec![InputValueDefinition { description: None, - name: "from".into(), - ty: Type::new_named("String").non_null().into(), + name: name!("from"), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, directives: Default::default(), } @@ -372,32 +423,38 @@ impl FederationSpecDefinitions { } /// directive @provides(fields: FieldSet!) on FIELD_DEFINITION - fn provides_directive_definition(&self, alias: &Option) -> DirectiveDefinition { - DirectiveDefinition { + fn provides_directive_definition( + &self, + alias: &Option, + ) -> Result { + Ok(DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(PROVIDES_DIRECTIVE_NAME).into(), - arguments: vec![self.fields_argument_definition().into()], + name: alias.clone().unwrap_or(PROVIDES_DIRECTIVE_NAME), + arguments: vec![self.fields_argument_definition()?.into()], repeatable: false, locations: vec![DirectiveLocation::FieldDefinition], - } + }) } /// directive @requires(fields: FieldSet!) on FIELD_DEFINITION - fn requires_directive_definition(&self, alias: &Option) -> DirectiveDefinition { - DirectiveDefinition { + fn requires_directive_definition( + &self, + alias: &Option, + ) -> Result { + Ok(DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(REQUIRES_DIRECTIVE_NAME).into(), - arguments: vec![self.fields_argument_definition().into()], + name: alias.clone().unwrap_or(REQUIRES_DIRECTIVE_NAME), + arguments: vec![self.fields_argument_definition()?.into()], repeatable: false, locations: vec![DirectiveLocation::FieldDefinition], - } + }) } /// directive @shareable repeatable on FIELD_DEFINITION | OBJECT - fn shareable_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn shareable_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(SHAREABLE_DIRECTIVE_NAME).into(), + name: alias.clone().unwrap_or(SHAREABLE_DIRECTIVE_NAME), arguments: Vec::new(), repeatable: true, locations: vec![ @@ -418,14 +475,14 @@ impl FederationSpecDefinitions { /// | OBJECT /// | SCALAR /// | UNION - fn tag_directive_definition(&self, alias: &Option) -> DirectiveDefinition { + fn tag_directive_definition(&self, alias: &Option) -> DirectiveDefinition { DirectiveDefinition { description: None, - name: alias.as_deref().unwrap_or(TAG_DIRECTIVE_NAME).into(), + name: alias.clone().unwrap_or(TAG_DIRECTIVE_NAME), arguments: vec![InputValueDefinition { description: None, - name: "name".into(), - ty: Type::new_named("String").non_null().into(), + name: name!("name"), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, directives: Default::default(), } @@ -449,14 +506,19 @@ impl FederationSpecDefinitions { pub(crate) fn any_scalar_definition(&self) -> ExtendedType { let any_scalar = ScalarType { description: None, + name: ANY_SCALAR_NAME, directives: Default::default(), }; ExtendedType::Scalar(Node::new(any_scalar)) } - pub(crate) fn entity_union_definition(&self, entities: IndexSet) -> ExtendedType { + pub(crate) fn entity_union_definition( + &self, + entities: IndexSet, + ) -> ExtendedType { let service_type = UnionType { description: None, + name: ENTITY_UNION_NAME, directives: Default::default(), members: entities, }; @@ -465,18 +527,19 @@ impl FederationSpecDefinitions { pub(crate) fn service_object_type_definition(&self) -> ExtendedType { let mut service_type = ObjectType { description: None, + name: SERVICE_TYPE, directives: Default::default(), fields: IndexMap::new(), implements_interfaces: IndexSet::new(), }; service_type.fields.insert( - Name::new("_sdl"), + name!("_sdl"), Component::new(FieldDefinition { - name: Name::new("_sdl"), + name: name!("_sdl"), description: None, directives: Default::default(), arguments: Vec::new(), - ty: Type::Named(NamedType::new("String")), + ty: Type::Named(name!("String")), }), ); ExtendedType::Object(Node::new(service_type)) @@ -484,37 +547,37 @@ impl FederationSpecDefinitions { pub(crate) fn entities_query_field(&self) -> Component { Component::new(FieldDefinition { - name: Name::new(ENTITIES_QUERY), + name: ENTITIES_QUERY, description: None, directives: Default::default(), arguments: vec![Node::new(InputValueDefinition { - name: Name::new("representations"), + name: name!("representations"), description: None, directives: Default::default(), ty: Node::new(Type::NonNullList(Box::new(Type::NonNullNamed( - NamedType::new(ANY_SCALAR_NAME), + ANY_SCALAR_NAME, )))), default_value: None, })], - ty: Type::NonNullList(Box::new(Type::Named(NamedType::new(ENTITY_UNION_NAME)))), + ty: Type::NonNullList(Box::new(Type::Named(ENTITY_UNION_NAME))), }) } pub(crate) fn service_sdl_query_field(&self) -> Component { Component::new(FieldDefinition { - name: Name::new(SERVICE_SDL_QUERY), + name: SERVICE_SDL_QUERY, description: None, directives: Default::default(), arguments: Vec::new(), - ty: Type::NonNullNamed(NamedType::new(SERVICE_TYPE)), + ty: Type::NonNullNamed(SERVICE_TYPE), }) } } impl LinkSpecDefinitions { pub fn new(link: Link) -> Self { - let import_scalar_name = link.type_name_in_schema(DEFAULT_IMPORT_SCALAR_NAME); - let purpose_enum_name = link.type_name_in_schema(DEFAULT_PURPOSE_ENUM_NAME); + let import_scalar_name = link.type_name_in_schema(&DEFAULT_IMPORT_SCALAR_NAME); + let purpose_enum_name = link.type_name_in_schema(&DEFAULT_PURPOSE_ENUM_NAME); Self { link, import_scalar_name, @@ -540,9 +603,10 @@ impl LinkSpecDefinitions { } /// scalar Import - pub fn import_scalar_definition(&self) -> ScalarType { + pub fn import_scalar_definition(&self, name: Name) -> ScalarType { ScalarType { description: None, + name, directives: Default::default(), } } @@ -551,25 +615,26 @@ impl LinkSpecDefinitions { /// SECURITY /// EXECUTION /// } - pub fn link_purpose_enum_definition(&self) -> EnumType { + pub fn link_purpose_enum_definition(&self, name: Name) -> EnumType { EnumType { description: None, + name, directives: Default::default(), values: [ ( - "SECURITY".into(), + name!("SECURITY"), EnumValueDefinition { description: None, - value: "SECURITY".into(), + value: name!("SECURITY"), directives: Default::default(), } .into(), ), ( - "EXECUTION".into(), + name!("EXECUTION"), EnumValueDefinition { description: None, - value: "EXECUTION".into(), + value: name!("EXECUTION"), directives: Default::default(), } .into(), @@ -580,40 +645,47 @@ impl LinkSpecDefinitions { } /// directive @link(url: String, as: String, import: [Import], for: link__Purpose) repeatable on SCHEMA - pub fn link_directive_definition(&self) -> DirectiveDefinition { - DirectiveDefinition { + pub fn link_directive_definition(&self) -> Result { + Ok(DirectiveDefinition { description: None, - name: DEFAULT_LINK_NAME.into(), + name: DEFAULT_LINK_NAME, arguments: vec![ InputValueDefinition { description: None, - name: "url".into(), + name: name!("url"), // TODO: doc-comment disagrees with non-null here - ty: Type::new_named("String").non_null().into(), + ty: Type::Named(name!("String")).non_null().into(), default_value: None, directives: Default::default(), } .into(), InputValueDefinition { description: None, - name: "as".into(), - ty: Type::new_named("String").into(), + name: name!("as"), + ty: Type::Named(name!("String")).into(), default_value: None, directives: Default::default(), } .into(), InputValueDefinition { description: None, - name: "import".into(), - ty: Type::new_named(&self.import_scalar_name).list().into(), + name: name!("import"), + ty: Type::Named(graphql_name_or_federation_spec_error( + &self.import_scalar_name, + )?) + .list() + .into(), default_value: None, directives: Default::default(), } .into(), InputValueDefinition { description: None, - name: "for".into(), - ty: Type::new_named(&self.purpose_enum_name).into(), + name: name!("for"), + ty: Type::Named(graphql_name_or_federation_spec_error( + &self.purpose_enum_name, + )?) + .into(), default_value: None, directives: Default::default(), } @@ -621,7 +693,7 @@ impl LinkSpecDefinitions { ], repeatable: true, locations: vec![DirectiveLocation::Schema], - } + }) } }