Skip to content

Commit 9b67d73

Browse files
authored
[validation] Normalize the order of arguments of checked definitions (#6650)
* Also normalize the order of arguments * Also sort the implemented interfaces * update test fixtures
1 parent 1e4a906 commit 9b67d73

File tree

4 files changed

+46
-10
lines changed

4 files changed

+46
-10
lines changed

libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Issue.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@ class UnknownDirective(
4545
) : GraphQLValidationIssue
4646

4747
/**
48-
* The definition is inconsistent with the expected one.
48+
* The provided schema contains a definition that doesn't match the GraphQL specification.
49+
*
50+
* This may happen in particular for directive that are in the process of being specified like `@defer` (as of Jul 2025).
51+
*
52+
* Because the Apollo compiler expects a certain shape of directive and arguments, anything else may trigger
53+
* a crash in the compiler. By validating explicitly, the error is more explicit.
4954
*/
5055
class IncompatibleDefinition(
5156
name: String,

libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/semanticEquals.kt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,25 @@ internal fun GQLNode.semanticEquals(other: GQLNode?): Boolean {
1111
return toSemanticSdl() == other.toSemanticSdl()
1212
}
1313

14+
/**
15+
* Normalizes the given [GQLNode].
16+
*
17+
* Two nodes with the same normalized representation behave the same during execution.
18+
*
19+
* Especially, descriptions are removed and field, inputFields, arguments, etc.. are sorted lexicographically.
20+
*/
1421
internal fun GQLNode.toSemanticSdl(): String {
1522
return transform2 { node ->
1623
when (node) {
1724
is GQLInputValueDefinition -> node.copy(description = null)
18-
is GQLObjectTypeDefinition -> node.copy(description = null)
25+
is GQLObjectTypeDefinition -> node.copy(description = null, fields = node.fields.sortedBy { it.name }, implementsInterfaces = node.implementsInterfaces.sortedBy { it })
1926
is GQLScalarTypeDefinition -> node.copy(description = null)
20-
is GQLInputObjectTypeDefinition -> node.copy(description = null)
21-
is GQLUnionTypeDefinition -> node.copy(description = null)
22-
is GQLEnumTypeDefinition -> node.copy(description = null)
23-
is GQLInterfaceTypeDefinition -> node.copy(description = null)
24-
is GQLFieldDefinition -> node.copy(description = null)
25-
is GQLDirectiveDefinition -> node.copy(description = null)
27+
is GQLInputObjectTypeDefinition -> node.copy(description = null, inputFields = node.inputFields.sortedBy { it.name })
28+
is GQLUnionTypeDefinition -> node.copy(description = null, memberTypes = node.memberTypes.sortedBy { it.name })
29+
is GQLEnumTypeDefinition -> node.copy(description = null, enumValues = node.enumValues.sortedBy { it.name })
30+
is GQLInterfaceTypeDefinition -> node.copy(description = null, fields = node.fields.sortedBy { it.name }, implementsInterfaces = node.implementsInterfaces.sortedBy { it })
31+
is GQLFieldDefinition -> node.copy(description = null, arguments = node.arguments.sortedBy { it.name })
32+
is GQLDirectiveDefinition -> node.copy(description = null, arguments = node.arguments.sortedBy { it.name })
2633
is GQLEnumValueDefinition -> node.copy(description = null)
2734
is GQLFragmentDefinition -> node.copy(description = null)
2835
is GQLOperationDefinition -> node.copy(description = null)

libraries/apollo-ast/src/commonTest/kotlin/com/apollographql/apollo/graphql/ast/test/SchemaTest.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.apollographql.apollo.graphql.ast.test
22

33
import com.apollographql.apollo.ast.ForeignSchema
44
import com.apollographql.apollo.ast.internal.SchemaValidationOptions
5+
import com.apollographql.apollo.ast.internal.toSemanticSdl
56
import com.apollographql.apollo.ast.parseAsGQLDocument
67
import com.apollographql.apollo.ast.toGQLDocument
78
import com.apollographql.apollo.ast.toSchema
@@ -150,4 +151,27 @@ class SchemaTest {
150151
assertEquals(1, result.issues.size)
151152
assertEquals("Apollo: unknown definition '@unknownDirective'", result.issues.first().message)
152153
}
154+
155+
@Test
156+
fun descriptionsAndArgumentOrderDoesNotMatterForSemanticComparison() {
157+
// language=graphql
158+
val document1 = """
159+
directive @defer(
160+
"Deferred behaviour is controlled by this argument"
161+
if: Boolean! = true,
162+
"A unique label that represents the fragment being deferred"
163+
label: String
164+
) on FRAGMENT_SPREAD|INLINE_FRAGMENT
165+
""".trimIndent()
166+
167+
// language=graphql
168+
val document2 = """
169+
directive @defer(
170+
label: String,
171+
if: Boolean! = true,
172+
) on FRAGMENT_SPREAD|INLINE_FRAGMENT
173+
""".trimIndent()
174+
175+
assertEquals(document1.toGQLDocument().toSemanticSdl(),document2.toGQLDocument().toSemanticSdl())
176+
}
153177
}

libraries/apollo-compiler/src/test/validation/schema/unexpected-definitions.expected

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)