diff --git a/crates/apollo-compiler/src/executable/mod.rs b/crates/apollo-compiler/src/executable/mod.rs index bc7ce5b69..36ff1b168 100644 --- a/crates/apollo-compiler/src/executable/mod.rs +++ b/crates/apollo-compiler/src/executable/mod.rs @@ -266,6 +266,14 @@ pub(crate) enum BuildError { /// Name of the introspection field field: Name, }, + #[error( + "{} can not specify @skip or @include on root fields", + subscription_name_or_anonymous(name) + )] + SubscriptionUsesConditionalSelection { + /// Name of the operation + name: Option, + }, #[error("{0}")] ConflictingFieldType(Box), diff --git a/crates/apollo-compiler/src/validation/mod.rs b/crates/apollo-compiler/src/validation/mod.rs index 837054a18..78bb87898 100644 --- a/crates/apollo-compiler/src/validation/mod.rs +++ b/crates/apollo-compiler/src/validation/mod.rs @@ -1,11 +1,6 @@ //! Supporting APIs for [GraphQL validation](https://spec.graphql.org/October2021/#sec-Validation) //! and other kinds of errors. -use crate::coordinate::SchemaCoordinate; -#[cfg(doc)] -use crate::ExecutableDocument; -use crate::Schema; - pub(crate) mod argument; pub(crate) mod diagnostics; pub(crate) mod directive; @@ -26,6 +21,7 @@ pub(crate) mod variable; use crate::collections::HashMap; use crate::collections::HashSet; use crate::collections::IndexSet; +use crate::coordinate::SchemaCoordinate; use crate::diagnostic::CliReport; use crate::diagnostic::Diagnostic; use crate::diagnostic::ToCliReport; @@ -41,6 +37,7 @@ use crate::schema::BuildError as SchemaBuildError; use crate::schema::Implementers; use crate::Name; use crate::Node; +use crate::Schema; use std::fmt; use std::sync::Arc; use std::sync::OnceLock; @@ -338,6 +335,9 @@ impl DiagnosticData { ExecutableBuildError::SubscriptionUsesIntrospection { .. } => { "SubscriptionUsesIntrospection" } + ExecutableBuildError::SubscriptionUsesConditionalSelection { .. } => { + "SubscriptionUsesConditionalSelection" + } ExecutableBuildError::ConflictingFieldType(_) => "ConflictingFieldType", ExecutableBuildError::ConflictingFieldName(_) => "ConflictingFieldName", ExecutableBuildError::ConflictingFieldArgument(_) => "ConflictingFieldArgument", @@ -586,6 +586,18 @@ impl DiagnosticData { .to_string()) } } + ExecutableBuildError::SubscriptionUsesConditionalSelection { name, .. } => { + if let Some(name) = name { + Some(format!( + r#"Subscription "{name}" can not specify @skip or @include on root fields."# + )) + } else { + Some( + "Anonymous Subscription can not specify @skip or @include on root fields." + .to_string(), + ) + } + } ExecutableBuildError::ConflictingFieldType(inner) => { let ConflictingFieldType { alias, @@ -839,6 +851,9 @@ impl ToCliReport for DiagnosticData { format_args!("{field} is an introspection field"), ); } + ExecutableBuildError::SubscriptionUsesConditionalSelection { .. } => { + report.with_label_opt(self.location, "conditional directive used here"); + } ExecutableBuildError::ConflictingFieldType(inner) => { let ConflictingFieldType { alias, diff --git a/crates/apollo-compiler/src/validation/operation.rs b/crates/apollo-compiler/src/validation/operation.rs index ed86d5581..a598e47ce 100644 --- a/crates/apollo-compiler/src/validation/operation.rs +++ b/crates/apollo-compiler/src/validation/operation.rs @@ -1,51 +1,117 @@ +use crate::collections::HashSet; use crate::executable; +use crate::validation::diagnostics::DiagnosticData; use crate::validation::DiagnosticList; use crate::validation::ExecutableValidationContext; +use crate::validation::RecursionLimitError; use crate::ExecutableDocument; +use crate::Name; use crate::Node; +/// Iterate all selections in the selection set. +/// +/// This includes fields, fragment spreads, and inline fragments. For fragments, both the spread +/// and the fragment's nested selections are reported. +/// +/// Does not recurse into nested fields. +fn walk_selections<'doc>( + document: &'doc ExecutableDocument, + selections: &'doc executable::SelectionSet, + mut f: impl FnMut(&'doc executable::Selection), +) -> Result<(), RecursionLimitError> { + fn walk_selections_inner<'doc>( + document: &'doc ExecutableDocument, + selection_set: &'doc executable::SelectionSet, + seen: &mut HashSet<&'doc Name>, + f: &mut dyn FnMut(&'doc executable::Selection), + ) -> Result<(), RecursionLimitError> { + for selection in &selection_set.selections { + f(selection); + match selection { + executable::Selection::Field(_) => { + // Nothing to do + } + executable::Selection::FragmentSpread(fragment) => { + let new = seen.insert(&fragment.fragment_name); + if !new { + continue; + } + + // If the fragment doesn't exist, that error is reported elsewhere. + if let Some(fragment_definition) = + document.fragments.get(&fragment.fragment_name) + { + walk_selections_inner( + document, + &fragment_definition.selection_set, + seen, + f, + )?; + } + } + executable::Selection::InlineFragment(fragment) => { + walk_selections_inner(document, &fragment.selection_set, seen, f)?; + } + } + } + Ok(()) + } + + walk_selections_inner(document, selections, &mut HashSet::default(), &mut f) +} + pub(crate) fn validate_subscription( document: &executable::ExecutableDocument, operation: &Node, diagnostics: &mut DiagnosticList, ) { - if operation.is_subscription() { - let fields = super::selection::expand_selections( - &document.fragments, - std::iter::once(&operation.selection_set), - ); + if !operation.is_subscription() { + return; + } - if fields.len() > 1 { - diagnostics.push( - operation.location(), - executable::BuildError::SubscriptionUsesMultipleFields { - name: operation.name.clone(), - fields: fields - .iter() - .map(|field| field.field.name.clone()) - .collect(), - }, - ); + let mut field_names = vec![]; + + let walked = walk_selections(document, &operation.selection_set, |selection| { + if let executable::Selection::Field(field) = selection { + field_names.push(field.name.clone()); + if matches!(field.name.as_str(), "__type" | "__schema" | "__typename") { + diagnostics.push( + field.location(), + executable::BuildError::SubscriptionUsesIntrospection { + name: operation.name.clone(), + field: field.name.clone(), + }, + ); + } } - let has_introspection_fields = fields + if let Some(conditional_directive) = selection + .directives() .iter() - .find(|field| { - matches!( - field.field.name.as_str(), - "__type" | "__schema" | "__typename" - ) - }) - .map(|field| &field.field); - if let Some(field) = has_introspection_fields { + .find(|d| matches!(d.name.as_str(), "skip" | "include")) + { diagnostics.push( - field.location(), - executable::BuildError::SubscriptionUsesIntrospection { + conditional_directive.location(), + executable::BuildError::SubscriptionUsesConditionalSelection { name: operation.name.clone(), - field: field.name.clone(), }, ); } + }); + + if walked.is_err() { + diagnostics.push(None, DiagnosticData::RecursionError {}); + return; + } + + if field_names.len() > 1 { + diagnostics.push( + operation.location(), + executable::BuildError::SubscriptionUsesMultipleFields { + name: operation.name.clone(), + fields: field_names, + }, + ); } } diff --git a/crates/apollo-compiler/src/validation/selection.rs b/crates/apollo-compiler/src/validation/selection.rs index da07bf78b..ba3de7b7d 100644 --- a/crates/apollo-compiler/src/validation/selection.rs +++ b/crates/apollo-compiler/src/validation/selection.rs @@ -64,7 +64,7 @@ impl<'a> FieldSelection<'a> { } /// Expand one or more selection sets to a list of all fields selected. -pub(crate) fn expand_selections<'doc>( +fn expand_selections<'doc>( fragments: &'doc IndexMap>, selection_sets: impl Iterator, ) -> Vec> { diff --git a/crates/apollo-compiler/src/validation/variable.rs b/crates/apollo-compiler/src/validation/variable.rs index 0976dd4db..53212c3d3 100644 --- a/crates/apollo-compiler/src/validation/variable.rs +++ b/crates/apollo-compiler/src/validation/variable.rs @@ -80,7 +80,14 @@ pub(crate) fn validate_variable_definitions( } } -/// Named fragments are "deduplicated": only visited once even if spread multiple times +/// Call a function for every selection that is reachable from the given selection set. +/// +/// This includes fields, fragment spreads, and inline fragments. For fragments, both the spread +/// and the fragment's nested selections are reported. For fields, nested selections are also +/// reported. +/// +/// Named fragments are "deduplicated": only visited once even if spread multiple times *in +/// different locations*. This is only appropriate for certain kinds of validations, so reuser beware. fn walk_selections_with_deduped_fragments<'doc>( document: &'doc ExecutableDocument, selections: &'doc executable::SelectionSet, diff --git a/crates/apollo-compiler/test_data/diagnostics/0120_conditional_subscriptions.graphql b/crates/apollo-compiler/test_data/diagnostics/0120_conditional_subscriptions.graphql new file mode 100644 index 000000000..e43c3d8a1 --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0120_conditional_subscriptions.graphql @@ -0,0 +1,11 @@ +subscription ConditionalSub($condition: Boolean = true) { + ticker @include(if: $condition) +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} \ No newline at end of file diff --git a/crates/apollo-compiler/test_data/diagnostics/0120_conditional_subscriptions.txt b/crates/apollo-compiler/test_data/diagnostics/0120_conditional_subscriptions.txt new file mode 100644 index 000000000..1c9bcf3bc --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0120_conditional_subscriptions.txt @@ -0,0 +1,8 @@ +Error: subscription `ConditionalSub` can not specify @skip or @include on root fields + ╭─[ 0120_conditional_subscriptions.graphql:2:12 ] + │ + 2 │ ticker @include(if: $condition) + │ ────────────┬─────────── + │ ╰───────────── conditional directive used here +───╯ + diff --git a/crates/apollo-compiler/test_data/diagnostics/0121_conditional_subscriptions_with_inline_fragment.graphql b/crates/apollo-compiler/test_data/diagnostics/0121_conditional_subscriptions_with_inline_fragment.graphql new file mode 100644 index 000000000..91d883a0b --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0121_conditional_subscriptions_with_inline_fragment.graphql @@ -0,0 +1,13 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ... @include(if: $condition) { + ticker + } +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} \ No newline at end of file diff --git a/crates/apollo-compiler/test_data/diagnostics/0121_conditional_subscriptions_with_inline_fragment.txt b/crates/apollo-compiler/test_data/diagnostics/0121_conditional_subscriptions_with_inline_fragment.txt new file mode 100644 index 000000000..4be10ccf6 --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0121_conditional_subscriptions_with_inline_fragment.txt @@ -0,0 +1,8 @@ +Error: subscription `ConditionalInlineSub` can not specify @skip or @include on root fields + ╭─[ 0121_conditional_subscriptions_with_inline_fragment.graphql:2:9 ] + │ + 2 │ ... @include(if: $condition) { + │ ────────────┬─────────── + │ ╰───────────── conditional directive used here +───╯ + diff --git a/crates/apollo-compiler/test_data/diagnostics/0122_conditional_subscriptions_with_named_fragment.graphql b/crates/apollo-compiler/test_data/diagnostics/0122_conditional_subscriptions_with_named_fragment.graphql new file mode 100644 index 000000000..f9961b28b --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0122_conditional_subscriptions_with_named_fragment.graphql @@ -0,0 +1,15 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ...mySubscription @include(if: $condition) +} + +fragment mySubscription on Subscription { + ticker +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} \ No newline at end of file diff --git a/crates/apollo-compiler/test_data/diagnostics/0122_conditional_subscriptions_with_named_fragment.txt b/crates/apollo-compiler/test_data/diagnostics/0122_conditional_subscriptions_with_named_fragment.txt new file mode 100644 index 000000000..db28ea3f5 --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0122_conditional_subscriptions_with_named_fragment.txt @@ -0,0 +1,8 @@ +Error: subscription `ConditionalInlineSub` can not specify @skip or @include on root fields + ╭─[ 0122_conditional_subscriptions_with_named_fragment.graphql:2:23 ] + │ + 2 │ ...mySubscription @include(if: $condition) + │ ────────────┬─────────── + │ ╰───────────── conditional directive used here +───╯ + diff --git a/crates/apollo-compiler/test_data/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.graphql b/crates/apollo-compiler/test_data/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.graphql new file mode 100644 index 000000000..011599e70 --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.graphql @@ -0,0 +1,13 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ... { + ticker @include(if: $condition) + } +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} \ No newline at end of file diff --git a/crates/apollo-compiler/test_data/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.txt b/crates/apollo-compiler/test_data/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.txt new file mode 100644 index 000000000..b0c564814 --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.txt @@ -0,0 +1,8 @@ +Error: subscription `ConditionalInlineSub` can not specify @skip or @include on root fields + ╭─[ 0123_conditional_subscriptions_inside_inline_fragment.graphql:3:16 ] + │ + 3 │ ticker @include(if: $condition) + │ ────────────┬─────────── + │ ╰───────────── conditional directive used here +───╯ + diff --git a/crates/apollo-compiler/test_data/diagnostics/0124_conditional_subscriptions_inside_named_fragment.graphql b/crates/apollo-compiler/test_data/diagnostics/0124_conditional_subscriptions_inside_named_fragment.graphql new file mode 100644 index 000000000..e6553a09f --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0124_conditional_subscriptions_inside_named_fragment.graphql @@ -0,0 +1,15 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ...mySubscription +} + +fragment mySubscription on Subscription { + ticker @include(if: $condition) +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} \ No newline at end of file diff --git a/crates/apollo-compiler/test_data/diagnostics/0124_conditional_subscriptions_inside_named_fragment.txt b/crates/apollo-compiler/test_data/diagnostics/0124_conditional_subscriptions_inside_named_fragment.txt new file mode 100644 index 000000000..48e295de9 --- /dev/null +++ b/crates/apollo-compiler/test_data/diagnostics/0124_conditional_subscriptions_inside_named_fragment.txt @@ -0,0 +1,8 @@ +Error: subscription `ConditionalInlineSub` can not specify @skip or @include on root fields + ╭─[ 0124_conditional_subscriptions_inside_named_fragment.graphql:6:12 ] + │ + 6 │ ticker @include(if: $condition) + │ ────────────┬─────────── + │ ╰───────────── conditional directive used here +───╯ + diff --git a/crates/apollo-compiler/test_data/ok/0117_subscription_conditions_not_at_root.graphql b/crates/apollo-compiler/test_data/ok/0117_subscription_conditions_not_at_root.graphql new file mode 100644 index 000000000..3a1a1ca12 --- /dev/null +++ b/crates/apollo-compiler/test_data/ok/0117_subscription_conditions_not_at_root.graphql @@ -0,0 +1,21 @@ +subscription ConditionalSub($includeContent: Boolean = true, $small: Boolean = true) { + messages { + username + text @include(if: $includeContent) + avatar @skip(if: $small) + } +} + +type Query { + hello: String +} + +type Message { + username: String + text: String + avatar: String +} + +type Subscription { + messages: Message +} diff --git a/crates/apollo-compiler/test_data/ok/0117_subscription_conditions_not_at_root.txt b/crates/apollo-compiler/test_data/ok/0117_subscription_conditions_not_at_root.txt new file mode 100644 index 000000000..5824191ac --- /dev/null +++ b/crates/apollo-compiler/test_data/ok/0117_subscription_conditions_not_at_root.txt @@ -0,0 +1,302 @@ +Schema { + sources: { + 1: SourceFile { + path: "built_in.graphql", + source_text: include_str!("built_in.graphql"), + }, + 46: SourceFile { + path: "0117_subscription_conditions_not_at_root.graphql", + source_text: "subscription ConditionalSub($includeContent: Boolean = true, $small: Boolean = true) {\n messages {\n username\n text @include(if: $includeContent)\n avatar @skip(if: $small)\n }\n}\n\ntype Query {\n hello: String\n}\n\ntype Message {\n username: String\n text: String\n avatar: String\n}\n\ntype Subscription {\n messages: Message\n}\n", + }, + }, + schema_definition: SchemaDefinition { + description: None, + directives: [], + query: Some( + ComponentName { + origin: Definition, + name: "Query", + }, + ), + mutation: None, + subscription: Some( + ComponentName { + origin: Definition, + name: "Subscription", + }, + ), + }, + directive_definitions: { + "skip": built_in_directive!("skip"), + "include": built_in_directive!("include"), + "deprecated": built_in_directive!("deprecated"), + "specifiedBy": built_in_directive!("specifiedBy"), + }, + types: { + "__Schema": built_in_type!("__Schema"), + "__Type": built_in_type!("__Type"), + "__TypeKind": built_in_type!("__TypeKind"), + "__Field": built_in_type!("__Field"), + "__InputValue": built_in_type!("__InputValue"), + "__EnumValue": built_in_type!("__EnumValue"), + "__Directive": built_in_type!("__Directive"), + "__DirectiveLocation": built_in_type!("__DirectiveLocation"), + "String": built_in_type!("String"), + "Boolean": built_in_type!("Boolean"), + "Query": Object( + 204..236 @46 ObjectType { + description: None, + name: "Query", + implements_interfaces: {}, + directives: [], + fields: { + "hello": Component { + origin: Definition, + node: 221..234 @46 FieldDefinition { + description: None, + name: "hello", + arguments: [], + ty: Named( + "String", + ), + directives: [], + }, + }, + }, + }, + ), + "Message": Object( + 238..311 @46 ObjectType { + description: None, + name: "Message", + implements_interfaces: {}, + directives: [], + fields: { + "username": Component { + origin: Definition, + node: 257..273 @46 FieldDefinition { + description: None, + name: "username", + arguments: [], + ty: Named( + "String", + ), + directives: [], + }, + }, + "text": Component { + origin: Definition, + node: 278..290 @46 FieldDefinition { + description: None, + name: "text", + arguments: [], + ty: Named( + "String", + ), + directives: [], + }, + }, + "avatar": Component { + origin: Definition, + node: 295..309 @46 FieldDefinition { + description: None, + name: "avatar", + arguments: [], + ty: Named( + "String", + ), + directives: [], + }, + }, + }, + }, + ), + "Subscription": Object( + 313..356 @46 ObjectType { + description: None, + name: "Subscription", + implements_interfaces: {}, + directives: [], + fields: { + "messages": Component { + origin: Definition, + node: 337..354 @46 FieldDefinition { + description: None, + name: "messages", + arguments: [], + ty: Named( + "Message", + ), + directives: [], + }, + }, + }, + }, + ), + }, +} +ExecutableDocument { + sources: { + 1: SourceFile { + path: "built_in.graphql", + source_text: include_str!("built_in.graphql"), + }, + 46: SourceFile { + path: "0117_subscription_conditions_not_at_root.graphql", + source_text: "subscription ConditionalSub($includeContent: Boolean = true, $small: Boolean = true) {\n messages {\n username\n text @include(if: $includeContent)\n avatar @skip(if: $small)\n }\n}\n\ntype Query {\n hello: String\n}\n\ntype Message {\n username: String\n text: String\n avatar: String\n}\n\ntype Subscription {\n messages: Message\n}\n", + }, + }, + operations: OperationMap { + anonymous: None, + named: { + "ConditionalSub": 0..202 @46 Operation { + operation_type: Subscription, + name: Some( + "ConditionalSub", + ), + variables: [ + 28..59 @46 VariableDefinition { + name: "includeContent", + ty: 45..52 @46 Named( + "Boolean", + ), + default_value: Some( + 55..59 @46 Boolean( + true, + ), + ), + directives: [], + }, + 61..83 @46 VariableDefinition { + name: "small", + ty: 69..76 @46 Named( + "Boolean", + ), + default_value: Some( + 79..83 @46 Boolean( + true, + ), + ), + directives: [], + }, + ], + directives: [], + selection_set: SelectionSet { + ty: "Subscription", + selections: [ + Field( + 91..200 @46 Field { + definition: 337..354 @46 FieldDefinition { + description: None, + name: "messages", + arguments: [], + ty: Named( + "Message", + ), + directives: [], + }, + alias: None, + name: "messages", + arguments: [], + directives: [], + selection_set: SelectionSet { + ty: "Message", + selections: [ + Field( + 110..118 @46 Field { + definition: 257..273 @46 FieldDefinition { + description: None, + name: "username", + arguments: [], + ty: Named( + "String", + ), + directives: [], + }, + alias: None, + name: "username", + arguments: [], + directives: [], + selection_set: SelectionSet { + ty: "String", + selections: [], + }, + }, + ), + Field( + 127..161 @46 Field { + definition: 278..290 @46 FieldDefinition { + description: None, + name: "text", + arguments: [], + ty: Named( + "String", + ), + directives: [], + }, + alias: None, + name: "text", + arguments: [], + directives: [ + 132..161 @46 Directive { + name: "include", + arguments: [ + 141..160 @46 Argument { + name: "if", + value: 145..160 @46 Variable( + "includeContent", + ), + }, + ], + }, + ], + selection_set: SelectionSet { + ty: "String", + selections: [], + }, + }, + ), + Field( + 170..194 @46 Field { + definition: 295..309 @46 FieldDefinition { + description: None, + name: "avatar", + arguments: [], + ty: Named( + "String", + ), + directives: [], + }, + alias: None, + name: "avatar", + arguments: [], + directives: [ + 177..194 @46 Directive { + name: "skip", + arguments: [ + 183..193 @46 Argument { + name: "if", + value: 187..193 @46 Variable( + "small", + ), + }, + ], + }, + ], + selection_set: SelectionSet { + ty: "String", + selections: [], + }, + }, + ), + ], + }, + }, + ), + ], + }, + }, + }, + }, + fragments: {}, +} diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0120_conditional_subscriptions.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0120_conditional_subscriptions.graphql new file mode 100644 index 000000000..182e45da2 --- /dev/null +++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0120_conditional_subscriptions.graphql @@ -0,0 +1,11 @@ +subscription ConditionalSub($condition: Boolean = true) { + ticker @include(if: $condition) +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0121_conditional_subscriptions_with_inline_fragment.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0121_conditional_subscriptions_with_inline_fragment.graphql new file mode 100644 index 000000000..c0c9b8025 --- /dev/null +++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0121_conditional_subscriptions_with_inline_fragment.graphql @@ -0,0 +1,13 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ... @include(if: $condition) { + ticker + } +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0121_conditional_subscriptions_with_inline_fragments.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0121_conditional_subscriptions_with_inline_fragments.graphql new file mode 100644 index 000000000..c0c9b8025 --- /dev/null +++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0121_conditional_subscriptions_with_inline_fragments.graphql @@ -0,0 +1,13 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ... @include(if: $condition) { + ticker + } +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0122_conditional_subscriptions_with_named_fragment.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0122_conditional_subscriptions_with_named_fragment.graphql new file mode 100644 index 000000000..a23b43ceb --- /dev/null +++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0122_conditional_subscriptions_with_named_fragment.graphql @@ -0,0 +1,15 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ...mySubscription @include(if: $condition) +} + +fragment mySubscription on Subscription { + ticker +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.graphql new file mode 100644 index 000000000..5a3f1c1f2 --- /dev/null +++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0123_conditional_subscriptions_inside_inline_fragment.graphql @@ -0,0 +1,13 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ... { + ticker @include(if: $condition) + } +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0124_conditional_subscriptions_inside_named_fragment.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0124_conditional_subscriptions_inside_named_fragment.graphql new file mode 100644 index 000000000..55fe965a1 --- /dev/null +++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0124_conditional_subscriptions_inside_named_fragment.graphql @@ -0,0 +1,15 @@ +subscription ConditionalInlineSub($condition: Boolean = true) { + ...mySubscription +} + +fragment mySubscription on Subscription { + ticker @include(if: $condition) +} + +type Query { + hello: String +} + +type Subscription { + ticker: String +} diff --git a/crates/apollo-compiler/test_data/serializer/ok/0117_subscription_conditions_not_at_root.graphql b/crates/apollo-compiler/test_data/serializer/ok/0117_subscription_conditions_not_at_root.graphql new file mode 100644 index 000000000..a5dc9ad44 --- /dev/null +++ b/crates/apollo-compiler/test_data/serializer/ok/0117_subscription_conditions_not_at_root.graphql @@ -0,0 +1,21 @@ +subscription ConditionalSub($includeContent: Boolean = true, $small: Boolean = true) { + messages { + username + text @include(if: $includeContent) + avatar @skip(if: $small) + } +} + +type Query { + hello: String +} + +type Message { + username: String + text: String + avatar: String +} + +type Subscription { + messages: Message +}