Skip to content

Commit c9128e3

Browse files
authored
Initial CCN support (#5118)
* CCN parsing * add validation rules * add codegen and integration tests * update apiDump, relocateJar * prepare for potential removal of CCN * code review
1 parent ff87f1c commit c9128e3

File tree

34 files changed

+611
-88
lines changed

34 files changed

+611
-88
lines changed

libraries/apollo-ast/api/apollo-ast.api

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -845,12 +845,28 @@ public abstract interface class com/apollographql/apollo3/ast/NodeTransformer {
845845

846846
public final class com/apollographql/apollo3/ast/ParserOptions {
847847
public static final field Companion Lcom/apollographql/apollo3/ast/ParserOptions$Companion;
848+
public synthetic fun <init> (ZZZZLkotlin/jvm/internal/DefaultConstructorMarker;)V
849+
public final fun getAllowClientControlledNullability ()Z
850+
public final fun getAllowEmptyDocuments ()Z
851+
public final fun getUseAntlr ()Z
852+
public final fun getWithSourceLocation ()Z
853+
}
854+
855+
public final class com/apollographql/apollo3/ast/ParserOptions$Builder {
848856
public fun <init> ()V
849-
public fun <init> (ZZZ)V
850-
public synthetic fun <init> (ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
857+
public final fun allowClientControlledNullability (Z)Lcom/apollographql/apollo3/ast/ParserOptions$Builder;
858+
public final fun allowEmptyDocuments (Z)Lcom/apollographql/apollo3/ast/ParserOptions$Builder;
859+
public final fun build ()Lcom/apollographql/apollo3/ast/ParserOptions;
860+
public final fun getAllowClientControlledNullability ()Z
851861
public final fun getAllowEmptyDocuments ()Z
852862
public final fun getUseAntlr ()Z
853863
public final fun getWithSourceLocation ()Z
864+
public final fun setAllowClientControlledNullability (Z)V
865+
public final fun setAllowEmptyDocuments (Z)V
866+
public final fun setUseAntlr (Z)V
867+
public final fun setWithSourceLocation (Z)V
868+
public final fun useAntlr (Z)Lcom/apollographql/apollo3/ast/ParserOptions$Builder;
869+
public final fun withSourceLocation (Z)Lcom/apollographql/apollo3/ast/ParserOptions$Builder;
854870
}
855871

856872
public final class com/apollographql/apollo3/ast/ParserOptions$Companion {

libraries/apollo-ast/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,24 @@ kotlin {
3737
implementation(libs.antlr.runtime)
3838
}
3939
}
40+
getByName("jvmTest") {
41+
dependencies {
42+
implementation(libs.google.testparameterinjector)
43+
}
44+
}
4045
}
4146
}
4247

4348
dependencies {
4449
antlr(libs.antlr)
4550
}
4651

52+
tasks.named("jvmTest") {
53+
inputs.dir("test-fixtures/parser")
54+
.withPropertyName("testFixtures")
55+
.withPathSensitivity(PathSensitivity.RELATIVE)
56+
}
57+
4758
// Only expose the antlr runtime dependency
4859
// See https://github.com/gradle/gradle/issues/820#issuecomment-288838412
4960
configurations["jvmMainApi"].apply {

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

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ fun BufferedSource.toExecutableDefinitions(
4444
.validateAsExecutable(schema, fieldsOnDisjointTypesMustMerge)
4545
.getOrThrow()
4646

47-
private fun <T : Any> BufferedSource.parseInternal(filePath: String?, withSourceLocation: Boolean, block: Parser.() -> T): GQLResult<T> {
48-
return this.use { readUtf8() }.parseInternal(filePath, withSourceLocation, block)
47+
private fun <T : Any> BufferedSource.parseInternal(filePath: String?, options: ParserOptions, block: Parser.() -> T): GQLResult<T> {
48+
return this.use { readUtf8() }.parseInternal(filePath, options, block)
4949
}
5050

51-
private fun <T : Any> String.parseInternal(filePath: String?, withSourceLocation: Boolean, block: Parser.() -> T): GQLResult<T> {
51+
private fun <T : Any> String.parseInternal(filePath: String?, options: ParserOptions, block: Parser.() -> T): GQLResult<T> {
5252
return try {
53-
GQLResult(Parser(this, withSourceLocation, filePath).block(), emptyList())
53+
GQLResult(Parser(this, options, filePath).block(), emptyList())
5454
} catch (e: ParserException) {
5555
GQLResult(
5656
null,
@@ -86,15 +86,48 @@ private fun <T : Any> String.parseInternal(filePath: String?, withSourceLocation
8686
}
8787
}
8888

89-
class ParserOptions(
89+
class ParserOptions private constructor(
9090
@Deprecated("This is used as a fallback the time to stabilize the new parser but will be removed")
9191
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_0_0)
92-
val useAntlr: Boolean = false,
93-
val allowEmptyDocuments: Boolean = true,
94-
val withSourceLocation: Boolean = true,
92+
val useAntlr: Boolean,
93+
val allowEmptyDocuments: Boolean,
94+
val allowClientControlledNullability: Boolean,
95+
val withSourceLocation: Boolean,
9596
) {
97+
class Builder {
98+
var useAntlr: Boolean = false
99+
var allowEmptyDocuments = true
100+
var allowClientControlledNullability = true
101+
var withSourceLocation = true
102+
103+
fun useAntlr(useAntlr: Boolean) = apply {
104+
this.useAntlr = useAntlr
105+
}
106+
107+
fun allowEmptyDocuments(allowEmptyDocuments: Boolean) = apply {
108+
this.allowEmptyDocuments = allowEmptyDocuments
109+
}
110+
111+
fun allowClientControlledNullability(allowClientControlledNullability: Boolean) = apply {
112+
this.allowClientControlledNullability = allowClientControlledNullability
113+
}
114+
115+
fun withSourceLocation(withSourceLocation: Boolean) = apply {
116+
this.withSourceLocation = withSourceLocation
117+
}
118+
119+
fun build(): ParserOptions {
120+
return ParserOptions(
121+
useAntlr = useAntlr,
122+
allowEmptyDocuments = allowEmptyDocuments,
123+
allowClientControlledNullability = allowClientControlledNullability,
124+
withSourceLocation = withSourceLocation
125+
)
126+
}
127+
}
128+
96129
companion object {
97-
val Default = ParserOptions()
130+
val Default = Builder().build()
98131
}
99132
}
100133

@@ -108,31 +141,34 @@ fun String.parseAsGQLDocument(options: ParserOptions = ParserOptions.Default): G
108141
return if (options.useAntlr) {
109142
Buffer().writeUtf8(this).parseAsGQLDocument(options = options)
110143
} else {
111-
parseInternal(null, options.withSourceLocation) { parseDocument(options.allowEmptyDocuments) }
144+
parseInternal(null, options) { parseDocument() }
112145
}
113146
}
147+
114148
fun String.parseAsGQLValue(options: ParserOptions = ParserOptions.Default): GQLResult<GQLValue> {
115149
@Suppress("DEPRECATION")
116150
return if (options.useAntlr) {
117151
Buffer().writeUtf8(this).parseAsGQLValue(options = options)
118152
} else {
119-
parseInternal(null, options.withSourceLocation) { parseValue() }
153+
parseInternal(null, options) { parseValue() }
120154
}
121155
}
156+
122157
fun String.parseAsGQLType(options: ParserOptions = ParserOptions.Default): GQLResult<GQLType> {
123158
@Suppress("DEPRECATION")
124159
return if (options.useAntlr) {
125160
Buffer().writeUtf8(this).parseAsGQLType(options = options)
126161
} else {
127-
parseInternal(null, options.withSourceLocation) { parseType() }
162+
parseInternal(null, options) { parseType() }
128163
}
129164
}
165+
130166
fun String.parseAsGQLSelections(options: ParserOptions = ParserOptions.Default): GQLResult<List<GQLSelection>> {
131167
@Suppress("DEPRECATION")
132168
return if (options.useAntlr) {
133169
Buffer().writeUtf8(this).parseAsGQLSelections(options = options)
134170
} else {
135-
parseInternal(null, options.withSourceLocation) { parseSelections() }
171+
parseInternal(null, options) { parseSelections() }
136172
}
137173
}
138174

@@ -156,7 +192,7 @@ fun BufferedSource.parseAsGQLDocument(filePath: String? = null, options: ParserO
156192
return if (options.useAntlr) {
157193
parseDocumentWithAntlr(this, filePath)
158194
} else {
159-
parseInternal(filePath, options.withSourceLocation) { parseDocument(options.allowEmptyDocuments) }
195+
parseInternal(filePath, options) { parseDocument() }
160196
}
161197
}
162198

@@ -171,7 +207,7 @@ fun BufferedSource.parseAsGQLValue(filePath: String? = null, options: ParserOpti
171207
return if (options.useAntlr) {
172208
parseValueWithAntlr(this, filePath)
173209
} else {
174-
parseInternal(filePath, options.withSourceLocation) { parseValue() }
210+
parseInternal(filePath, options) { parseValue() }
175211
}
176212
}
177213

@@ -186,7 +222,7 @@ fun BufferedSource.parseAsGQLType(filePath: String? = null, options: ParserOptio
186222
return if (options.useAntlr) {
187223
parseTypeWithAntlr(this, filePath)
188224
} else {
189-
parseInternal(filePath, options.withSourceLocation) { parseType() }
225+
parseInternal(filePath, options) { parseType() }
190226
}
191227
}
192228

@@ -204,7 +240,7 @@ fun BufferedSource.parseAsGQLSelections(
204240
return if (options.useAntlr) {
205241
parseSelectionsWithAntlr(this, filePath)
206242
} else {
207-
parseInternal(filePath, options.withSourceLocation) { parseSelections() }
243+
parseInternal(filePath, options) { parseSelections() }
208244
}
209245
}
210246

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

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.apollographql.apollo3.ast
22

33
import com.apollographql.apollo3.annotations.ApolloDeprecatedSince
4+
import com.apollographql.apollo3.annotations.ApolloExperimental
45

56
/**
67
* The GraphQL AST definition
@@ -131,7 +132,7 @@ class GQLDocument(
131132
val definitions: List<GQLDefinition>,
132133
override val sourceLocation: SourceLocation?,
133134
) : GQLNode {
134-
constructor(definitions: List<GQLDefinition>, filePath: String?): this(definitions, SourceLocation.forPath(filePath))
135+
constructor(definitions: List<GQLDefinition>, filePath: String?) : this(definitions, SourceLocation.forPath(filePath))
135136

136137
override val children = definitions
137138

@@ -1493,14 +1494,94 @@ private fun List<GQLArgument>.writeArguments(writer: SDLWriter) {
14931494
join(writer, prefix = "(", separator = ", ", postfix = ")")
14941495
}
14951496

1496-
class GQLField(
1497+
1498+
@ApolloExperimental
1499+
sealed interface GQLNullability : GQLNode
1500+
1501+
@ApolloExperimental
1502+
class GQLNonNullDesignator(override val sourceLocation: SourceLocation? = null) : GQLNullability {
1503+
override val children: List<GQLNode>
1504+
get() = emptyList()
1505+
1506+
override fun writeInternal(writer: SDLWriter) {
1507+
writer.write("!")
1508+
}
1509+
1510+
override fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode {
1511+
return this
1512+
}
1513+
}
1514+
1515+
@ApolloExperimental
1516+
class GQLNullDesignator(override val sourceLocation: SourceLocation? = null) : GQLNullability {
1517+
override val children: List<GQLNode>
1518+
get() = emptyList()
1519+
1520+
override fun writeInternal(writer: SDLWriter) {
1521+
writer.write("?")
1522+
}
1523+
1524+
override fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode {
1525+
return this
1526+
}
1527+
}
1528+
1529+
@ApolloExperimental
1530+
class GQLListNullability(
1531+
override val sourceLocation: SourceLocation? = null,
1532+
val itemNullability: GQLNullability,
1533+
val selfNullability: GQLNullability?,
1534+
) : GQLNullability {
1535+
override val children: List<GQLNode>
1536+
get() = listOf(itemNullability)
1537+
1538+
override fun writeInternal(writer: SDLWriter) {
1539+
writer.write("[")
1540+
writer.write(itemNullability)
1541+
writer.write("]")
1542+
if (selfNullability != null) {
1543+
writer.write(selfNullability)
1544+
}
1545+
}
1546+
1547+
override fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode {
1548+
return copy(
1549+
ofNullability = container.takeSingle()!!
1550+
)
1551+
}
1552+
1553+
fun copy(
1554+
sourceLocation: SourceLocation? = this.sourceLocation,
1555+
ofNullability: GQLNullability = this.itemNullability,
1556+
selfNullability: GQLNullability? = this.selfNullability,
1557+
): GQLListNullability {
1558+
return GQLListNullability(
1559+
sourceLocation,
1560+
ofNullability,
1561+
selfNullability
1562+
)
1563+
}
1564+
}
1565+
1566+
class GQLField @ApolloExperimental constructor(
14971567
override val sourceLocation: SourceLocation? = null,
14981568
val alias: String?,
14991569
val name: String,
15001570
val arguments: List<GQLArgument>,
15011571
val directives: List<GQLDirective>,
15021572
val selections: List<GQLSelection>,
1573+
@property:ApolloExperimental
1574+
val nullability: GQLNullability?,
15031575
) : GQLSelection() {
1576+
constructor(
1577+
sourceLocation: SourceLocation? = null,
1578+
alias: String?,
1579+
name: String,
1580+
arguments: List<GQLArgument>,
1581+
directives: List<GQLDirective>,
1582+
selections: List<GQLSelection>,
1583+
) : this(sourceLocation, alias, name, arguments, directives, selections, null)
1584+
15041585
@Suppress("DEPRECATION")
15051586
@Deprecated("Use selections directly")
15061587
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_0_0)
@@ -1527,6 +1608,9 @@ class GQLField(
15271608
write(" ")
15281609
directives.join(writer)
15291610
}
1611+
if (nullability != null) {
1612+
writer.write(nullability)
1613+
}
15301614
if (selections.isNotEmpty()) {
15311615
write(" ")
15321616
selections.writeSelections(writer)
@@ -1550,6 +1634,23 @@ class GQLField(
15501634
arguments = arguments,
15511635
directives = directives,
15521636
selections = selections,
1637+
nullability = this.nullability
1638+
)
1639+
1640+
/**
1641+
* This is in a separate method from the copy() above so that we can more easily remove it if we need to
1642+
*/
1643+
@ApolloExperimental
1644+
fun copy(
1645+
nullability: GQLNullability?,
1646+
) = GQLField(
1647+
sourceLocation = sourceLocation,
1648+
alias = alias,
1649+
name = name,
1650+
arguments = arguments,
1651+
directives = directives,
1652+
selections = selections,
1653+
nullability = nullability
15531654
)
15541655

15551656
override fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode {

0 commit comments

Comments
 (0)