diff --git a/compiler/crates/graphql-text-printer/src/print_to_text.rs b/compiler/crates/graphql-text-printer/src/print_to_text.rs index 6372e08628306..931a158b61b1f 100644 --- a/compiler/crates/graphql-text-printer/src/print_to_text.rs +++ b/compiler/crates/graphql-text-printer/src/print_to_text.rs @@ -351,7 +351,25 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { accum_conditions: Option>, ) -> FmtResult { let mut accum_conditions: Vec<&Condition> = accum_conditions.unwrap_or_default(); - accum_conditions.push(condition); + // GraphQL spec doesn't allow repeated include or skip. See + // https://spec.graphql.org/October2021/#sec-Directives-Are-Unique-Per-Location + // To work around it, we extract repeated conditions into an inline fragment. + let mut has_repeated_condition = false; + for cond in &accum_conditions { + if cond.passing_value == condition.passing_value { + has_repeated_condition = true; + } + } + if has_repeated_condition { + write!(self.writer, "...")?; + self.print_directives(&[], Some(accum_conditions), None)?; + write!(self.writer, " {{")?; + self.indentation += 1; + self.print_new_line(false)?; + accum_conditions = vec![condition]; + } else { + accum_conditions.push(condition); + } let mut is_first_selection = true; for selection in condition.selections.iter() { @@ -367,6 +385,11 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { )?; } } + if has_repeated_condition { + self.indentation -= 1; + self.print_new_line(false)?; + write!(self.writer, "}}")?; + } Ok(()) } diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.expected b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.expected index fa710451c3ea1..e2071939bb2e7 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.expected +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.expected @@ -1,9 +1,15 @@ ==================================== INPUT ==================================== -query nestedConditions2Query($conditional: Boolean!, $condition: Boolean!) { +query nestedConditions2Query($c1: Boolean!, $c2: Boolean!, $c3: Boolean!, $c4: Boolean!, $c5: Boolean!, $c6: Boolean!, $c7: Boolean!) { node { - ... @include(if: $conditional){ - ...nestedConditions2_NestedFragment @include(if: $condition) - ...nestedConditions2_NestedFragment2 @skip(if: $condition) + ... @include(if: $c1){ + ...nestedConditions2_NestedFragment @include(if: $c2) + ...nestedConditions2_NestedFragment2 @skip(if: $c2) + ...@skip(if: $c3) { + ...@skip(if: $c4) @include(if: $c5) { + ...nestedConditions2_NestedFragment @skip(if: $c6) + ...nestedConditions2_NestedFragment2 @skip(if: $c6) @include(if: $c7) + } + } } } } @@ -22,12 +28,37 @@ fragment nestedConditions2_NestedFragment2 on User { { "defaultValue": null, "kind": "LocalArgument", - "name": "condition" + "name": "c1" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c2" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c3" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c4" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c5" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c6" }, { "defaultValue": null, "kind": "LocalArgument", - "name": "conditional" + "name": "c7" } ], "kind": "Fragment", @@ -43,12 +74,12 @@ fragment nestedConditions2_NestedFragment2 on User { "plural": false, "selections": [ { - "condition": "conditional", + "condition": "c1", "kind": "Condition", "passingValue": true, "selections": [ { - "condition": "condition", + "condition": "c2", "kind": "Condition", "passingValue": true, "selections": [ @@ -60,7 +91,7 @@ fragment nestedConditions2_NestedFragment2 on User { ] }, { - "condition": "condition", + "condition": "c2", "kind": "Condition", "passingValue": false, "selections": [ @@ -70,6 +101,58 @@ fragment nestedConditions2_NestedFragment2 on User { "name": "nestedConditions2_NestedFragment2" } ] + }, + { + "condition": "c3", + "kind": "Condition", + "passingValue": false, + "selections": [ + { + "condition": "c5", + "kind": "Condition", + "passingValue": true, + "selections": [ + { + "condition": "c4", + "kind": "Condition", + "passingValue": false, + "selections": [ + { + "condition": "c6", + "kind": "Condition", + "passingValue": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "nestedConditions2_NestedFragment" + } + ] + }, + { + "condition": "c7", + "kind": "Condition", + "passingValue": true, + "selections": [ + { + "condition": "c6", + "kind": "Condition", + "passingValue": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "nestedConditions2_NestedFragment2" + } + ] + } + ] + } + ] + } + ] + } + ] } ] } @@ -86,12 +169,37 @@ fragment nestedConditions2_NestedFragment2 on User { { "defaultValue": null, "kind": "LocalArgument", - "name": "conditional" + "name": "c1" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c2" }, { "defaultValue": null, "kind": "LocalArgument", - "name": "condition" + "name": "c3" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c4" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c5" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c6" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "c7" } ], "kind": "Operation", @@ -113,7 +221,14 @@ fragment nestedConditions2_NestedFragment2 on User { "storageKey": null }, { - "condition": "conditional", + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "condition": "c1", "kind": "Condition", "passingValue": true, "selections": [ @@ -122,7 +237,7 @@ fragment nestedConditions2_NestedFragment2 on User { "abstractKey": "__isNode" }, { - "condition": "condition", + "condition": "c2", "kind": "Condition", "passingValue": true, "selections": [ @@ -143,7 +258,7 @@ fragment nestedConditions2_NestedFragment2 on User { ] }, { - "condition": "condition", + "condition": "c2", "kind": "Condition", "passingValue": false, "selections": [ @@ -162,15 +277,50 @@ fragment nestedConditions2_NestedFragment2 on User { "abstractKey": null } ] + }, + { + "condition": "c3", + "kind": "Condition", + "passingValue": false, + "selections": [ + { + "condition": "c5", + "kind": "Condition", + "passingValue": true, + "selections": [ + { + "condition": "c4", + "kind": "Condition", + "passingValue": false, + "selections": [ + { + "condition": "c6", + "kind": "Condition", + "passingValue": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + } + ] + } + ] + } + ] + } + ] } ] - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null } ], "storageKey": null @@ -190,14 +340,31 @@ fragment nestedConditions2_NestedFragment2 on User { QUERY: query nestedConditions2Query( - $conditional: Boolean! - $condition: Boolean! + $c1: Boolean! + $c2: Boolean! + $c3: Boolean! + $c4: Boolean! + $c5: Boolean! + $c6: Boolean! + $c7: Boolean! ) { node { __typename - __isNode: __typename @include(if: $conditional) - ...nestedConditions2_NestedFragment @include(if: $condition) @include(if: $conditional) - ...nestedConditions2_NestedFragment2 @skip(if: $condition) @include(if: $conditional) + __isNode: __typename @include(if: $c1) + ... @include(if: $c1) { + ...nestedConditions2_NestedFragment @include(if: $c2) + } + ...nestedConditions2_NestedFragment2 @skip(if: $c2) @include(if: $c1) + __isNode: __typename @skip(if: $c3) @include(if: $c1) + ... @skip(if: $c3) @include(if: $c1) { + __isNode: __typename @skip(if: $c4) @include(if: $c5) + ... @skip(if: $c4) @include(if: $c5) { + ...nestedConditions2_NestedFragment @skip(if: $c6) + } + ... @skip(if: $c4) @include(if: $c5) { + ...nestedConditions2_NestedFragment2 @skip(if: $c6) @include(if: $c7) + } + } id } } diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.graphql b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.graphql index 17b0eaafe0665..cc2e1fc5c3ce0 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.graphql +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/nested-conditions-2.graphql @@ -1,8 +1,14 @@ -query nestedConditions2Query($conditional: Boolean!, $condition: Boolean!) { +query nestedConditions2Query($c1: Boolean!, $c2: Boolean!, $c3: Boolean!, $c4: Boolean!, $c5: Boolean!, $c6: Boolean!, $c7: Boolean!) { node { - ... @include(if: $conditional){ - ...nestedConditions2_NestedFragment @include(if: $condition) - ...nestedConditions2_NestedFragment2 @skip(if: $condition) + ... @include(if: $c1){ + ...nestedConditions2_NestedFragment @include(if: $c2) + ...nestedConditions2_NestedFragment2 @skip(if: $c2) + ...@skip(if: $c3) { + ...@skip(if: $c4) @include(if: $c5) { + ...nestedConditions2_NestedFragment @skip(if: $c6) + ...nestedConditions2_NestedFragment2 @skip(if: $c6) @include(if: $c7) + } + } } } }