diff --git a/src/validation/rules/defaults.rs b/src/validation/rules/defaults.rs index d26bc2e..647a273 100644 --- a/src/validation/rules/defaults.rs +++ b/src/validation/rules/defaults.rs @@ -7,7 +7,7 @@ use super::{ OverlappingFieldsCanBeMerged, PossibleFragmentSpreads, ProvidedRequiredArguments, SingleFieldSubscriptions, UniqueArgumentNames, UniqueDirectivesPerLocation, UniqueFragmentNames, UniqueOperationNames, UniqueVariableNames, ValuesOfCorrectType, - VariablesAreInputTypes, VariablesInAllowedPosition, + VariablesAreInputTypes, VariablesInAllowedPosition, KnownOperationTypes }; pub fn default_rules_validation_plan() -> ValidationPlan { @@ -37,6 +37,8 @@ pub fn default_rules_validation_plan() -> ValidationPlan { plan.add_rule(Box::new(VariablesInAllowedPosition::new())); plan.add_rule(Box::new(ValuesOfCorrectType::new())); plan.add_rule(Box::new(UniqueDirectivesPerLocation::new())); + plan.add_rule(Box::new(UniqueDirectivesPerLocation::new())); + plan.add_rule(Box::new(KnownOperationTypes::new())); plan } diff --git a/src/validation/rules/known_operation_types.rs b/src/validation/rules/known_operation_types.rs new file mode 100644 index 0000000..abcc41f --- /dev/null +++ b/src/validation/rules/known_operation_types.rs @@ -0,0 +1,139 @@ +use super::ValidationRule; +use crate::ast::{ visit_document, OperationVisitor, OperationVisitorContext, SchemaDocumentExtension, + }; +use crate::static_graphql::query::*; +use crate::validation::utils::{ValidationError, ValidationErrorContext}; + +/// Known operation types +/// +/// A GraphQL operation is only valid if the operation type is within the schema. +/// +/// See https://github.com/graphql/graphql-spec/pull/947 +pub struct KnownOperationTypes; + +impl KnownOperationTypes { + pub fn new() -> Self { + KnownOperationTypes + } +} + +fn build_error_message(operation_type: &str) -> String { + format!("The {} operation is not supported by the schema.", operation_type) +} + +impl<'a> OperationVisitor<'a, ValidationErrorContext> for KnownOperationTypes { + fn enter_operation_definition( + &mut self, + visitor_context: &mut OperationVisitorContext, + user_context: &mut ValidationErrorContext, + operation_definition: &OperationDefinition, + ) { + match operation_definition { + OperationDefinition::Mutation(mutation) => { + if let None = visitor_context.schema.mutation_type() { + user_context.report_error(ValidationError { + locations: vec![mutation.position], + message: build_error_message("mutation"), + }); + } + }, + OperationDefinition::Subscription(subscription) => { + if let None = visitor_context.schema.subscription_type() { + user_context.report_error(ValidationError { + locations: vec![subscription.position], + message: build_error_message("subscription"), + }); + } + }, + _ => {} + } + } +} + +impl ValidationRule for KnownOperationTypes { + fn validate<'a>( + &self, + ctx: &'a mut OperationVisitorContext, + error_collector: &mut ValidationErrorContext, + ) { + visit_document( + &mut KnownOperationTypes::new(), + &ctx.operation, + ctx, + error_collector, + ); + } +} + +#[test] +fn one_known_operation() { + use crate::validation::test_utils::*; + + let mut plan = create_plan_from_rule(Box::new(KnownOperationTypes {})); + let errors = test_operation_with_schema( + "{ field }", + TEST_SCHEMA, + &mut plan, + ); + + assert_eq!(get_messages(&errors).len(), 0); +} + +#[test] +fn unknown_mutation_operation() { + use crate::validation::test_utils::*; + + let mut plan = create_plan_from_rule(Box::new(KnownOperationTypes {})); + let errors = test_operation_with_schema( + "mutation { field }", + "type Query { _: String }", + &mut plan, + ); + + let messages = get_messages(&errors); + assert_eq!(messages.len(), 1); + assert_eq!( + messages, + vec!["The mutation operation is not supported by the schema."] + ); +} + +#[test] +fn unknown_subscription_operation() { + use crate::validation::test_utils::*; + + let mut plan = create_plan_from_rule(Box::new(KnownOperationTypes {})); + let errors = test_operation_with_schema( + "subscription { field }", + "type Query { _: String }", + &mut plan, + ); + + let messages = get_messages(&errors); + assert_eq!(messages.len(), 1); + assert_eq!( + messages, + vec!["The subscription operation is not supported by the schema."] + ); +} + +#[test] +fn mixture_of_known_and_unknown_operations() { + use crate::validation::test_utils::*; + + let mut plan = create_plan_from_rule(Box::new(KnownOperationTypes {})); + let errors = test_operation_with_schema( + "query { field } + mutation { field } + subscription { field }", + "type Query { field: String }", + &mut plan, + ); + + let messages = get_messages(&errors); + assert_eq!(messages.len(), 2); + assert_eq!( + messages, + vec!["The mutation operation is not supported by the schema.", "The subscription operation is not supported by the schema."] + ); +} diff --git a/src/validation/rules/mod.rs b/src/validation/rules/mod.rs index 44446e1..c722119 100644 --- a/src/validation/rules/mod.rs +++ b/src/validation/rules/mod.rs @@ -25,6 +25,7 @@ pub mod unique_variable_names; pub mod values_of_correct_type; pub mod variables_are_input_types; pub mod variables_in_allowed_position; +pub mod known_operation_types; pub use self::defaults::*; pub use self::rule::*; @@ -53,3 +54,4 @@ pub use self::unique_variable_names::*; pub use self::values_of_correct_type::*; pub use self::variables_are_input_types::*; pub use self::variables_in_allowed_position::*; +pub use self::known_operation_types::*;