diff --git a/graph/src/data/graphql/ext.rs b/graph/src/data/graphql/ext.rs index ac7c956d97e..2af85f18315 100644 --- a/graph/src/data/graphql/ext.rs +++ b/graph/src/data/graphql/ext.rs @@ -225,6 +225,19 @@ impl DocumentExt for Document { } } +pub trait DefinitionExt { + fn is_root_query_type(&self) -> bool; +} + +impl DefinitionExt for Definition { + fn is_root_query_type(&self) -> bool { + match self { + Definition::TypeDefinition(TypeDefinition::Object(t)) => t.name == "Query", + _ => false, + } + } +} + pub trait TypeExt { fn get_base_type(&self) -> &str; fn is_list(&self) -> bool; diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 264bab1c221..2c8a40e031e 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -203,7 +203,7 @@ pub mod prelude { }); static_graphql!(s, schema, { Field, Directive, InterfaceType, ObjectType, Value, TypeDefinition, - EnumType, Type, Document, ScalarType, InputValue, DirectiveDefinition, + EnumType, Type, Definition, Document, ScalarType, InputValue, DirectiveDefinition, UnionType, InputObjectType, EnumValue, }); diff --git a/graph/src/schema/api.rs b/graph/src/schema/api.rs index 3d084f79c92..6d120ade266 100644 --- a/graph/src/schema/api.rs +++ b/graph/src/schema/api.rs @@ -6,10 +6,10 @@ use inflector::Inflector; use lazy_static::lazy_static; use crate::components::store::EntityType; -use crate::data::graphql::ObjectOrInterface; +use crate::data::graphql::{ObjectOrInterface, ObjectTypeExt}; use crate::schema::{ast, META_FIELD_NAME, META_FIELD_TYPE}; -use crate::data::graphql::ext::{DirectiveExt, DocumentExt, ValueExt}; +use crate::data::graphql::ext::{DefinitionExt, DirectiveExt, DocumentExt, ValueExt}; use crate::prelude::s::{Value, *}; use crate::prelude::*; use thiserror::Error; @@ -223,7 +223,7 @@ impl ApiSchema { } #[cfg(debug_assertions)] - pub fn definitions(&self) -> impl Iterator> { + pub fn definitions(&self) -> impl Iterator { self.schema.document.definitions.iter() } } @@ -233,8 +233,24 @@ lazy_static! { let schema = include_str!("introspection.graphql"); parse_schema(schema).expect("the schema `introspection.graphql` is invalid") }; + pub static ref INTROSPECTION_QUERY_TYPE: ast::ObjectType = { + let root_query_type = INTROSPECTION_SCHEMA + .get_root_query_type() + .expect("Schema does not have a root query type"); + ast::ObjectType::from(Arc::new(root_query_type.clone())) + }; } +pub fn is_introspection_field(name: &str) -> bool { + INTROSPECTION_QUERY_TYPE.field(name).is_some() +} + +/// Extend `schema` with the definitions from the introspection schema and +/// modify the root query type to contain the fields from the introspection +/// schema's root query type. +/// +/// This results in a schema that combines the original schema with the +/// introspection schema fn add_introspection_schema(schema: &mut Document) { fn introspection_fields() -> Vec { // Generate fields for the root query fields in an introspection schema, @@ -274,9 +290,16 @@ fn add_introspection_schema(schema: &mut Document) { ] } - schema - .definitions - .extend(INTROSPECTION_SCHEMA.definitions.iter().cloned()); + // Add all definitions from the introspection schema to the schema, + // except for the root query type as that qould clobber the 'real' root + // query type + schema.definitions.extend( + INTROSPECTION_SCHEMA + .definitions + .iter() + .filter(|dfn| !dfn.is_root_query_type()) + .cloned(), + ); let query_type = schema .definitions diff --git a/graph/src/schema/introspection.graphql b/graph/src/schema/introspection.graphql index c3d2c1b8842..d34b4d67e5b 100644 --- a/graph/src/schema/introspection.graphql +++ b/graph/src/schema/introspection.graphql @@ -1,9 +1,12 @@ # A GraphQL introspection schema for inclusion in a subgraph's API schema. -# The schema differs from the 'standard' introspection schema in that it -# doesn't have a Query type nor scalar declarations as they come from the -# API schema. + +type Query { + __schema: __Schema! + __type(name: String!): __Type +} type __Schema { + description: String types: [__Type!]! queryType: __Type! mutationType: __Type @@ -33,12 +36,15 @@ type __Type { # NON_NULL and LIST only ofType: __Type + + # may be non-null for custom SCALAR, otherwise null. + specifiedByURL: String } type __Field { name: String! description: String - args: [__InputValue!]! + args(includeDeprecated: Boolean = false): [__InputValue!]! type: __Type! isDeprecated: Boolean! deprecationReason: String @@ -49,6 +55,8 @@ type __InputValue { description: String type: __Type! defaultValue: String + isDeprecated: Boolean! + deprecationReason: String } type __EnumValue { @@ -73,7 +81,8 @@ type __Directive { name: String! description: String locations: [__DirectiveLocation!]! - args: [__InputValue!]! + args(includeDeprecated: Boolean = false): [__InputValue!]! + isRepeatable: Boolean! } enum __DirectiveLocation { @@ -84,6 +93,7 @@ enum __DirectiveLocation { FRAGMENT_DEFINITION FRAGMENT_SPREAD INLINE_FRAGMENT + VARIABLE_DEFINITION SCHEMA SCALAR OBJECT @@ -95,4 +105,4 @@ enum __DirectiveLocation { ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION -} \ No newline at end of file +} diff --git a/graph/src/schema/mod.rs b/graph/src/schema/mod.rs index 5d4a3a0789a..4361255debf 100644 --- a/graph/src/schema/mod.rs +++ b/graph/src/schema/mod.rs @@ -31,7 +31,7 @@ pub mod ast; mod fulltext; mod input_schema; -pub use api::{api_schema, APISchemaError}; +pub use api::{api_schema, is_introspection_field, APISchemaError, INTROSPECTION_QUERY_TYPE}; pub use api::{ApiSchema, ErrorPolicy}; pub use fulltext::{FulltextAlgorithm, FulltextConfig, FulltextDefinition, FulltextLanguage}; diff --git a/graphql/src/execution/execution.rs b/graphql/src/execution/execution.rs index 2e256cb76e6..8e819293938 100644 --- a/graphql/src/execution/execution.rs +++ b/graphql/src/execution/execution.rs @@ -7,7 +7,7 @@ use graph::{ value::{Object, Word}, }, prelude::{s, CheapClone}, - schema::META_FIELD_NAME, + schema::{is_introspection_field, INTROSPECTION_QUERY_TYPE, META_FIELD_NAME}, util::{lfu_cache::EvictStats, timed_rw_lock::TimedMutex}, }; use lazy_static::lazy_static; @@ -24,7 +24,6 @@ use graph::util::{lfu_cache::LfuCache, stable_hash_glue::impl_stable_hash}; use super::QueryHash; use crate::execution::ast as a; -use crate::introspection::{is_introspection_field, INTROSPECTION_QUERY_TYPE}; use crate::prelude::*; lazy_static! { diff --git a/graphql/src/introspection/mod.rs b/graphql/src/introspection/mod.rs index 16b751284ee..7f4ccde25bd 100644 --- a/graphql/src/introspection/mod.rs +++ b/graphql/src/introspection/mod.rs @@ -1,5 +1,3 @@ mod resolver; -mod schema; pub use self::resolver::IntrospectionResolver; -pub use self::schema::{is_introspection_field, INTROSPECTION_DOCUMENT, INTROSPECTION_QUERY_TYPE}; diff --git a/graphql/src/introspection/schema.rs b/graphql/src/introspection/schema.rs deleted file mode 100644 index 303c46f36d5..00000000000 --- a/graphql/src/introspection/schema.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::sync::Arc; - -use graphql_parser; - -use graph::data::graphql::ext::DocumentExt; -use graph::data::graphql::ext::ObjectTypeExt; -use graph::prelude::s::Document; - -use lazy_static::lazy_static; - -use graph::schema::ast as sast; - -const INTROSPECTION_SCHEMA: &str = " -scalar Boolean -scalar Float -scalar Int -scalar ID -scalar String - -type Query { - __schema: __Schema! - __type(name: String!): __Type -} - -type __Schema { - types: [__Type!]! - queryType: __Type! - mutationType: __Type - subscriptionType: __Type - directives: [__Directive!]! -} - -type __Type { - kind: __TypeKind! - name: String - description: String - - # OBJECT and INTERFACE only - fields(includeDeprecated: Boolean = false): [__Field!] - - # OBJECT only - interfaces: [__Type!] - - # INTERFACE and UNION only - possibleTypes: [__Type!] - - # ENUM only - enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - - # INPUT_OBJECT only - inputFields: [__InputValue!] - - # NON_NULL and LIST only - ofType: __Type -} - -type __Field { - name: String! - description: String - args: [__InputValue!]! - type: __Type! - isDeprecated: Boolean! - deprecationReason: String -} - -type __InputValue { - name: String! - description: String - type: __Type! - defaultValue: String -} - -type __EnumValue { - name: String! - description: String - isDeprecated: Boolean! - deprecationReason: String -} - -enum __TypeKind { - SCALAR - OBJECT - INTERFACE - UNION - ENUM - INPUT_OBJECT - LIST - NON_NULL -} - -type __Directive { - name: String! - description: String - locations: [__DirectiveLocation!]! - args: [__InputValue!]! -} - -enum __DirectiveLocation { - QUERY - MUTATION - SUBSCRIPTION - FIELD - FRAGMENT_DEFINITION - FRAGMENT_SPREAD - INLINE_FRAGMENT - SCHEMA - SCALAR - OBJECT - FIELD_DEFINITION - ARGUMENT_DEFINITION - INTERFACE - UNION - ENUM - ENUM_VALUE - INPUT_OBJECT - INPUT_FIELD_DEFINITION -}"; - -lazy_static! { - pub static ref INTROSPECTION_DOCUMENT: Document = - graphql_parser::parse_schema(INTROSPECTION_SCHEMA).unwrap(); - pub static ref INTROSPECTION_QUERY_TYPE: sast::ObjectType = sast::ObjectType::from(Arc::new( - INTROSPECTION_DOCUMENT - .get_root_query_type() - .unwrap() - .clone() - )); -} - -pub fn is_introspection_field(name: &str) -> bool { - INTROSPECTION_QUERY_TYPE.field(name).is_some() -}