diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/SchemaValidationScope.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/SchemaValidationScope.kt index 8b1662d336a..38261d7fc5c 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/SchemaValidationScope.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/SchemaValidationScope.kt @@ -827,16 +827,209 @@ private fun ValidationScope.validateScalars() { } } +internal class FieldAndNode(val field: GQLInputValueDefinition, val node: Node) + +internal class Node(val typeDefinition: GQLInputObjectTypeDefinition) { + val isOneOf = typeDefinition.directives.findOneOf() + + /** + * Whether that node is valid (can reach a leaf node) + * This will be updated as we traverse the graph + */ + var isValid = false + + /** + * Whether that node is visited + */ + var visited = false + var edgeCount = 0 + val predecessors = mutableSetOf() + val sucessors = mutableSetOf() + + /** + * tarjan + */ + var index: Int? = null + var lowLink: Int? = null + var onStack = false + + override fun toString() = typeDefinition.name +} + +private fun ValidationScope.reverseGraph(inputObjectTypeDefinitions: List): MutableCollection { + val nodes = mutableMapOf() + inputObjectTypeDefinitions.forEach { + nodes.put(it.name, Node(it)) + } + + nodes.values.forEach { node -> + /** + * Track the leaf fields. + * - `@oneOf` are not valid by default but may become if they have one escape hatch. + * - other types are valid by default but may become invalid if they have one non-null reference + */ + node.isValid = !node.isOneOf + node.typeDefinition.inputFields.forEach { field -> + val fieldType = field.type + if (node.isOneOf) { + if (fieldType is GQLNamedType) { + val fieldTypeDefinition = typeDefinitions.get(fieldType.name) + if (fieldTypeDefinition is GQLInputObjectTypeDefinition) { + val successor = nodes.get(fieldTypeDefinition.name)!! + successor.predecessors.add(node) + node.sucessors.add(FieldAndNode(field, successor)) + } else { + // scalar or enum + node.isValid = true + } + } else { + // Maybe a list + // Should not be non-null. If it is, other validation rules will catch it. + node.isValid = true + } + } else { + if (fieldType is GQLNonNullType) { + val innerType = fieldType.type + if (innerType is GQLNamedType) { + val fieldTypeDefinition = typeDefinitions.get(innerType.name) + if (fieldTypeDefinition is GQLInputObjectTypeDefinition) { + // Not a leaf field + node.isValid = false + node.edgeCount++ + val successor = nodes.get(fieldTypeDefinition.name)!! + successor.predecessors.add(node) + node.sucessors.add(FieldAndNode(field, successor)) + } + } else { + // List type => escape + } + } else { + // Nullable type => escape + } + } + } + } + return nodes.values +} + +/** + * walks the reverse graph, starting with the leaf nodes to find all the valid nodes + */ +private fun findValid(nodes: Collection) { + val stack = ArrayDeque() + // Start with the leaf, non-oneOf types + stack.addAll(nodes.filter { it.isValid }) + + while (stack.isNotEmpty()) { + val node = stack.removeFirst() + if (node.visited) continue + node.visited = true + node.predecessors.forEach { predecessor -> + if (predecessor.isOneOf) { + predecessor.isValid = true + stack.addAll(predecessor.predecessors) + } else { + predecessor.edgeCount-- + if (predecessor.edgeCount == 0) { + predecessor.isValid = true + stack.addAll(predecessor.predecessors) + } + } + } + } +} + +private fun removeValid(nodes: MutableCollection) { + nodes.removeAll { it.isValid } + // At this point, there shouldn't be any edge pointing to a valid node or that would be an escape input field +// nodes.forEach { +// check(it.sucessors.none { it.node.isValid }) +// } +} + +internal class PathElement(val typename: String, val inputField: GQLInputValueDefinition) +internal typealias Scc = Collection + +/** + * For error reporting purposes, find the longest cycle inside the SCC + */ +private fun findWitnessCycle(scc: Collection): List { + val start = scc.first() + + val path = mutableListOf() + val visited = mutableSetOf() + + fun dfs(current: Node): Boolean { + visited.add(current) + for (fieldAndNode in current.sucessors) { + val next = fieldAndNode.node + path.add(PathElement(current.typeDefinition.name, fieldAndNode.field)) + if (next == start) return true + if (next !in visited && dfs(next)) return true + path.removeAt(path.lastIndex) + } + visited.remove(current) + return false + } + + dfs(start) + return path +} + +internal fun tarjanScc(nodes: Collection): Collection { + var index = 0 + val stack = ArrayDeque() + val result = mutableListOf() + + fun strongConnect(v: Node) { + v.index = index + v.lowLink = index + index++ + stack.addLast(v) + v.onStack = true + + v.sucessors.forEach { + val w = it.node + if (w.index == null) { + strongConnect(w) + v.lowLink = minOf(v.lowLink!!, w.lowLink!!) + } else if (w.onStack) { + v.lowLink = minOf(v.lowLink!!, w.index!!) + } + } + + if (v.lowLink == v.index) { + val scc = mutableListOf() + while (true) { + val w = stack.removeLast() + w.onStack = false + scc.add(w) + if (w == v) break + } + result.add(scc) + } + } + + nodes.forEach { + if (it.index == null) { + strongConnect(it) + } + } + + return result +} + private fun ValidationScope.validateInputObjects() { - val traversalState = TraversalState() + val inputObjects = typeDefinitions.values.filterIsInstance() + validateInputObjectsCycles(inputObjects) + val defaultValueTraversalState = DefaultValueTraversalState() - typeDefinitions.values.filterIsInstance().forEach { o -> + inputObjects.forEach { o -> if (o.inputFields.isEmpty()) { registerIssue("Input object must specify one or more input fields", o.sourceLocation) } validateDirectivesInConstContext(o.directives, o) - validateInputFieldCycles(o, traversalState) validateInputObjectDefaultValue(o, defaultValueTraversalState) val isOneOfInputObject = o.directives.findOneOf() @@ -853,65 +1046,46 @@ private fun ValidationScope.validateInputObjects() { } } -private class TraversalState { - val visitedTypes = mutableSetOf() - val fieldPath = mutableListOf>() - val fieldPathIndexByTypeName = mutableMapOf() -} - -private class DefaultValueTraversalState { - val visitedFields = mutableSetOf() - val fieldPath = mutableListOf>() - val fieldPathIndex = mutableMapOf() -} - - -private fun ValidationScope.validateInputFieldCycles(inputObjectTypeDefinition: GQLInputObjectTypeDefinition, state: TraversalState) { - if (state.visitedTypes.contains(inputObjectTypeDefinition.name)) { - return - } - state.visitedTypes.add(inputObjectTypeDefinition.name) - - state.fieldPathIndexByTypeName[inputObjectTypeDefinition.name] = state.fieldPath.size - - inputObjectTypeDefinition.inputFields.forEach { - val type = it.type - if (type is GQLNonNullType && type.type is GQLNamedType) { - val fieldType = typeDefinitions.get(type.type.name) - if (fieldType is GQLInputObjectTypeDefinition) { - val cycleIndex = state.fieldPathIndexByTypeName.get(fieldType.name) - - state.fieldPath.add("${fieldType.name}.${it.name}" to it.sourceLocation) - - if (cycleIndex == null) { - validateInputFieldCycles(fieldType, state) - } else { - val cyclePath = state.fieldPath.subList(cycleIndex, state.fieldPath.size) - - cyclePath.forEach { - issues.add( - OtherValidationIssue( - buildString { - append("Invalid circular reference. The Input Object '${fieldType.name}' references itself ") - if (cyclePath.size > 1) { - append("via the non-null fields: ") - } else { - append("in the non-null field ") - } - append(cyclePath.map { it.first }.joinToString(", ")) - }, - it.second - ) - ) +private fun ValidationScope.validateInputObjectsCycles(inputObjectTypeDefinitions: List) { + val nodes = reverseGraph(inputObjectTypeDefinitions) + findValid(nodes) + removeValid(nodes) + tarjanScc(nodes).forEach { scc -> + if (scc.size == 1) { + val firstNode = scc.first() + val fieldAndNode = firstNode.sucessors.firstOrNull() + if (fieldAndNode != null && fieldAndNode.node.typeDefinition.name == firstNode.typeDefinition.name) { + registerIssue("Input object `${firstNode.typeDefinition.name}` references itself through field `${firstNode.typeDefinition.name}.${fieldAndNode.field.name}` and cannot be constructed.", fieldAndNode.field.sourceLocation) + } else { + // Trivial SCC containing a single, non self-referncing node are not an issue. + } + } else { + val cycle = findWitnessCycle(scc) + cycle.indices.forEach { i -> + val start = cycle.get(i) + val cycleAsString = buildString { + var j = i + repeat(cycle.size) { + val cur = cycle.get(j) + append("${cur.typename}.${cur.inputField.name} --> ") + j++ + if (j == cycle.size) { + j = 0 + } } + append(start.typename) } - - state.fieldPath.removeLast() + registerIssue("Input object `${start.typename}` references itself through an unbreakable chain of input fields and cannot be constructed: $cycleAsString", start.inputField.sourceLocation) } } } +} - state.fieldPathIndexByTypeName.remove(inputObjectTypeDefinition.name) + +private class DefaultValueTraversalState { + val visitedFields = mutableSetOf() + val fieldPath = mutableListOf>() + val fieldPathIndex = mutableMapOf() } private fun ValidationScope.validateInputObjectDefaultValue( diff --git a/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/TarjanTest.kt b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/TarjanTest.kt new file mode 100644 index 00000000000..b71a9b02006 --- /dev/null +++ b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/TarjanTest.kt @@ -0,0 +1,62 @@ +package com.apollographql.apollo.graphql.ast.test + +import com.apollographql.apollo.ast.GQLField +import com.apollographql.apollo.ast.GQLInputObjectTypeDefinition +import com.apollographql.apollo.ast.GQLInputValueDefinition +import com.apollographql.apollo.ast.GQLNamedType +import com.apollographql.apollo.ast.internal.FieldAndNode +import com.apollographql.apollo.ast.internal.Node +import com.apollographql.apollo.ast.internal.tarjanScc +import kotlin.test.Test + +class TarjanTest { + fun typeDefinition(name: String) = GQLInputObjectTypeDefinition( + sourceLocation = null, + description = "", + name = name, + directives = emptyList(), + inputFields = emptyList() + ) + val field = GQLInputValueDefinition( + sourceLocation = null, + name = "", + directives = emptyList(), + description = "", + type = GQLNamedType(null, ""), + defaultValue = null, + ) + + internal fun node(name: String) = Node(typeDefinition(name)).apply { isValid = false } + + @Test + fun test1() { + val a = node("a") + val b = node("b") + val c = node("c") + + a.sucessors.add(FieldAndNode(field, b)) + b.sucessors.add(FieldAndNode(field, c)) + c.sucessors.add(FieldAndNode(field, b)) + + val sccs = tarjanScc(listOf(a, b, c)) + println(sccs) + } + + @Test + fun test2() { + val a = node("a") + val b = node("b") + val c = node("c") + val d = node("d") + + a.sucessors.add(FieldAndNode(field, b)) + a.sucessors.add(FieldAndNode(field, d)) + b.sucessors.add(FieldAndNode(field, c)) + c.sucessors.add(FieldAndNode(field, b)) + d.sucessors.add(FieldAndNode(field, a)) + + val sccs = tarjanScc(listOf(a, b, c)) + println(sccs) + } + +} diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.expected index 2ba8842557b..878c08e3e79 100644 --- a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.expected +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.expected @@ -1,2 +1,2 @@ OtherValidationIssue (7:5) -Invalid circular reference. The Input Object 'SomeInputObject' references itself in the non-null field SomeInputObject.nonNullSelf \ No newline at end of file +Input object `SomeInputObject` references itself through field `SomeInputObject.nonNullSelf` and cannot be constructed. \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.graphqls index 3f196ef273b..f3a284d5672 100644 --- a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.graphqls +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles0.graphqls @@ -2,7 +2,7 @@ type Query { field(arg: SomeInputObject): String } -# simple cycle +# Input object `SomeInputObject` references itself through field `SomeInputObject.nonNullSelf` and cannot be constructed. input SomeInputObject { nonNullSelf: SomeInputObject! } \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.expected index 3a540df41f5..ee1abda2243 100644 --- a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.expected +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.expected @@ -1,8 +1,8 @@ +OtherValidationIssue (15:5) +Input object `C` references itself through an unbreakable chain of input fields and cannot be constructed: C.a --> A.b --> B.c --> C +------------ OtherValidationIssue (7:5) -Invalid circular reference. The Input Object 'SomeInputObject' references itself via the non-null fields: AnotherInputObject.startLoop, YetAnotherInputObject.nextInLoop, SomeInputObject.closeLoop +Input object `A` references itself through an unbreakable chain of input fields and cannot be constructed: A.b --> B.c --> C.a --> A ------------ OtherValidationIssue (11:5) -Invalid circular reference. The Input Object 'SomeInputObject' references itself via the non-null fields: AnotherInputObject.startLoop, YetAnotherInputObject.nextInLoop, SomeInputObject.closeLoop ------------- -OtherValidationIssue (15:5) -Invalid circular reference. The Input Object 'SomeInputObject' references itself via the non-null fields: AnotherInputObject.startLoop, YetAnotherInputObject.nextInLoop, SomeInputObject.closeLoop \ No newline at end of file +Input object `B` references itself through an unbreakable chain of input fields and cannot be constructed: B.c --> C.a --> A.b --> B \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.graphqls index 9ee188a1fbc..a73349e5023 100644 --- a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.graphqls +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles1.graphqls @@ -2,15 +2,15 @@ type Query { field(arg: SomeInputObject): String } -# cycle involving other objects -input SomeInputObject { - startLoop: AnotherInputObject! +# Input object `A` references itself through an unbreakable chain of input fields and cannot be constructed: A.b --> B.c --> C.a --> A +input A { + b: B! } -input AnotherInputObject { - nextInLoop: YetAnotherInputObject! +input B { + c: C! } -input YetAnotherInputObject { - closeLoop: SomeInputObject! +input C { + a: A! } \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles10.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles10.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles10.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles10.graphqls new file mode 100644 index 00000000000..5751bf808da --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles10.graphqls @@ -0,0 +1,13 @@ +type Query { + test(arg: A): Int +} + +# valid +input A @oneOf { + b: B + escape: Int +} + +input B { + a: A! +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles11.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles11.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles11.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles11.graphqls new file mode 100644 index 00000000000..a27642b6b3a --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles11.graphqls @@ -0,0 +1,11 @@ +type Query { + test(arg: A): Int +} + +input A { + b: B! +} + +input B { + a: A +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles12.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles12.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles12.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles12.graphqls new file mode 100644 index 00000000000..09eebb1feef --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles12.graphqls @@ -0,0 +1,11 @@ +type Query { + test(arg: A): Int +} + +input A { + b: [B!]! +} + +input B { + a: A! +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles13.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles13.expected new file mode 100644 index 00000000000..b60df4f8a78 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles13.expected @@ -0,0 +1,8 @@ +OtherValidationIssue (14:3) +Input object `C` references itself through an unbreakable chain of input fields and cannot be constructed: C.a --> A.b --> B.c --> C +------------ +OtherValidationIssue (6:3) +Input object `A` references itself through an unbreakable chain of input fields and cannot be constructed: A.b --> B.c --> C.a --> A +------------ +OtherValidationIssue (10:3) +Input object `B` references itself through an unbreakable chain of input fields and cannot be constructed: B.c --> C.a --> A.b --> B \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles13.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles13.graphqls new file mode 100644 index 00000000000..809692280dd --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles13.graphqls @@ -0,0 +1,15 @@ +type Query { + test(arg: A): Int +} + +input A @oneOf { + b: B +} + +input B { + c: C! +} + +input C @oneOf { + a: A +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.expected index 30a8cddd456..76dce57bbea 100644 --- a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.expected +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.expected @@ -1,14 +1,5 @@ -OtherValidationIssue (7:5) -Invalid circular reference. The Input Object 'SomeInputObject' references itself via the non-null fields: AnotherInputObject.startLoop, SomeInputObject.closeLoop ------------- -OtherValidationIssue (11:5) -Invalid circular reference. The Input Object 'SomeInputObject' references itself via the non-null fields: AnotherInputObject.startLoop, SomeInputObject.closeLoop ------------- -OtherValidationIssue (12:5) -Invalid circular reference. The Input Object 'AnotherInputObject' references itself via the non-null fields: YetAnotherInputObject.startSecondLoop, AnotherInputObject.closeSecondLoop ------------- -OtherValidationIssue (16:5) -Invalid circular reference. The Input Object 'AnotherInputObject' references itself via the non-null fields: YetAnotherInputObject.startSecondLoop, AnotherInputObject.closeSecondLoop ------------- OtherValidationIssue (17:5) -Invalid circular reference. The Input Object 'YetAnotherInputObject' references itself in the non-null field YetAnotherInputObject.nonNullSelf \ No newline at end of file +Input object `C` references itself through an unbreakable chain of input fields and cannot be constructed: C.b --> B.c --> C +------------ +OtherValidationIssue (13:5) +Input object `B` references itself through an unbreakable chain of input fields and cannot be constructed: B.c --> C.b --> B \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.graphqls index 7d4900cce38..2c1c0278feb 100644 --- a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.graphqls +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles2.graphqls @@ -1,18 +1,19 @@ type Query { - field(arg: SomeInputObject): String + field(arg: A): String } -# multiple cycles involving other objects -input SomeInputObject { - startLoop: AnotherInputObject! +input A { + b: B! } -input AnotherInputObject { - closeLoop: SomeInputObject! - startSecondLoop: YetAnotherInputObject! +# This is an example of a SCC of size 3 containing 2 cycles (A <-> B) and (B <-> C). Only (B <-> C) is reported +# Input object `B` references itself through an unbreakable chain of input fields and cannot be constructed: B.c --> C.b --> B +input B { + a: A! + c: C! } -input YetAnotherInputObject { - closeSecondLoop: AnotherInputObject! - nonNullSelf: YetAnotherInputObject! +input C { + b: B! + c: C! } \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles3.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles3.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles3.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles3.graphqls new file mode 100644 index 00000000000..faa85500df1 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles3.graphqls @@ -0,0 +1,7 @@ +type Query { + test(arg: A): Int +} + +input A @oneOf { + a: Int +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles4.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles4.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles4.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles4.graphqls new file mode 100644 index 00000000000..5aa215e1fe4 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles4.graphqls @@ -0,0 +1,7 @@ +type Query { + test(arg: A): Int +} + +input A @oneOf { + a: [A!] +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles5.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles5.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles5.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles5.graphqls new file mode 100644 index 00000000000..dda25a42443 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles5.graphqls @@ -0,0 +1,11 @@ +type Query { + test(arg: A): Int +} + +input A @oneOf { + b: B +} + +input B { + x: Int +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles6.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles6.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles6.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles6.graphqls new file mode 100644 index 00000000000..9b004baa583 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles6.graphqls @@ -0,0 +1,13 @@ +type Query { + test(arg: A): Int +} + +# valid +input A @oneOf { + b: B + escape: Int +} + +input B @oneOf { + a: A +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles7.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles7.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles7.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles7.graphqls new file mode 100644 index 00000000000..023c7105007 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles7.graphqls @@ -0,0 +1,11 @@ +type Query { + test(arg: A): Int +} + +input A @oneOf { + b: B +} + +input B { + a: A +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles8.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles8.expected new file mode 100644 index 00000000000..af17fcd4393 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles8.expected @@ -0,0 +1,2 @@ +OtherValidationIssue (6:3) +Input object `A` references itself through field `A.self` and cannot be constructed. \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles8.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles8.graphqls new file mode 100644 index 00000000000..5defb291f79 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles8.graphqls @@ -0,0 +1,7 @@ +type Query { + test(arg: A): Int +} + +input A @oneOf { + self: A +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles9.expected b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles9.expected new file mode 100644 index 00000000000..22cde4658c3 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles9.expected @@ -0,0 +1,5 @@ +OtherValidationIssue (10:3) +Input object `B` references itself through an unbreakable chain of input fields and cannot be constructed: B.a --> A.b --> B +------------ +OtherValidationIssue (6:3) +Input object `A` references itself through an unbreakable chain of input fields and cannot be constructed: A.b --> B.a --> A \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles9.graphqls b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles9.graphqls new file mode 100644 index 00000000000..2b62ddda734 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/validation/schema/input-cycles9.graphqls @@ -0,0 +1,11 @@ +type Query { + test(arg: A): Int +} + +input A @oneOf { + b: B +} + +input B { + a: A! +} \ No newline at end of file