Skip to content

Commit

Permalink
Add mergeExtensions and toFullSchemaGQLDocument (#5162)
Browse files Browse the repository at this point in the history
* cleanup the introspection API

add GQLDocument.toFullSchemaGQLDocument and GQLDocument.mergeExtensions

* Make methods callable from Java

* remove old GQLDocument constructor

* Update libraries/apollo-gradle-plugin/src/test/kotlin/util/TestUtils.kt

Co-authored-by: Benoit Lubek <[email protected]>

* add comment

* rename file

---------

Co-authored-by: Benoit Lubek <[email protected]>
  • Loading branch information
martinbonnin and BoD authored Aug 17, 2023
1 parent 7f6f4ad commit 1b411f5
Show file tree
Hide file tree
Showing 59 changed files with 8,129 additions and 2,031 deletions.
4 changes: 3 additions & 1 deletion build-logic/src/main/kotlin/Common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest
fun Project.commonSetup() {
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
tasks.register("ft") {
if (this@commonSetup.name != "apollo-gradle-plugin") {
if (this@commonSetup.name !in setOf("apollo-gradle-plugin", "intellij-plugin")) {
dependsOn("test")
}
}
Expand All @@ -37,6 +37,8 @@ private fun Project.configureTestAggregation() {
attribute(USAGE_ATTRIBUTE, objects.named(Usage::class.java, "apolloTestAggregation"))
}
}
// Hide this from the 'assemble' task
configuration.setVisible(false)

tasks.withType(AbstractTestTask::class.java).configureEach {
configuration.getOutgoing().artifact(
Expand Down
7 changes: 2 additions & 5 deletions docs/source/advanced/apollo-ast.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ val graphQLText = """
}
""".trimIndent()

val parseResult = Buffer().writeUtf8(graphQLText).parseAsGQLDocument()
val parseResult = graphQLText.parseAsGQLDocument()
```

This method returns a `GQLResult<GQLDocument>`, which contains the document and/or parsing issues, each of which can have a severity of either `WARNING` or `ERROR`. Because there can be warnings, it is possible to have both a valid document and issues at the same time.
Expand Down Expand Up @@ -111,7 +111,7 @@ val schemaText = """
}
""".trimIndent()

val schemaGQLDocument = Buffer().writeUtf8(schemaText).parseAsGQLDocument().getOrThrow()
val schemaGQLDocument = schemaText.parseAsGQLDocument().getOrThrow()
val schemaResult = schemaGQLDocument.validateAsSchema()
println(schemaResult.issues.map { it.severity.name + ": " + it.message })
```
Expand Down Expand Up @@ -146,9 +146,6 @@ println(queryGqlDocument.toUtf8())

// Output to a File
queryGqlDocument.toUtf8(file)

// Output to an Okio BufferedSink
queryGqlDocument.toUtf8(sink)
```

## Transforming an AST
Expand Down
12 changes: 7 additions & 5 deletions docs/source/migration/4.0.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,17 @@ The normalized cache must be configured before the auto persisted queries, confi

## apollo-ast

The AST classes (`GQLNode` and subclasses) as well as `Introspection` classes are not data classes anymore (see https://github.com/apollographql/apollo-kotlin/pull/4704/).
The AST classes (`GQLNode` and subclasses) as well as `Introspection` classes are not data classes anymore (see https://github.com/apollographql/apollo-kotlin/pull/4704/). The class hierarchy has been tweaked so that `GQLNamed`, `GQLDescribed` and `GQLHasDirectives` are more consistently inherited from.

`GQLSelectionSet` and `GQLArguments` are deprecated and removed from `GQLField`, `GQLInlineFragment` and other constructors. Use `.selections` directly
`GQLSelectionSet` and `GQLArguments` are deprecated and removed from `GQLField` and `GQLInlineFragment`. Use `.selections` directly

`GQLInlineFragment.typeCondition` is now nullable to account for inline fragments who inherit their type condition
`GQLInlineFragment.typeCondition` is now nullable to account for inline fragments who inherit their type condition.

`SourceLocation.position` is renamed `SourceLocation.column` and is now 1-indexed. `GQLNode.sourceLocation` is now nullable to account for the cases where the nodes are constructed programmatically.

It is not possible to create a `Schema` from a File or String directly anymore. Instead, create a `GQLDocument` first and convert it to a schema with `toSchema()`.

`SourceLocation.position` is renamed `SourceLocation.column` and is now 1-indexed

`GQLNode.sourceLocation` is now nullable

## Gradle configuration

Expand Down
550 changes: 24 additions & 526 deletions libraries/apollo-ast/api/apollo-ast.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.apollographql.apollo3.ast

import com.apollographql.apollo3.annotations.ApolloInternal
import okio.BufferedSink
import okio.Closeable

/**
* A [SDLWriter] writes utf8 text to the given sink and supports [indent]/[unindent]
*/
@ApolloInternal
open class SDLWriter(
private val sink: BufferedSink,
private val indent: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class Schema internal constructor(

fun toGQLDocument(): GQLDocument = GQLDocument(
definitions = definitions,
filePath = null
sourceLocation = null
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SourceLocation(
get() = column - 1

override fun toString(): String {
return "($line:$column)"
return pretty()
}

fun pretty(): String = "$filePath: ($line, $column)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import com.apollographql.apollo3.ast.internal.LexerException
import com.apollographql.apollo3.ast.internal.Parser
import com.apollographql.apollo3.ast.internal.ParserException
import com.apollographql.apollo3.ast.internal.validateSchema
import com.apollographql.apollo3.ast.introspection.toGQLDocument
import com.apollographql.apollo3.ast.introspection.toIntrospectionSchema
import okio.Buffer
import okio.BufferedSource
import okio.Path
Expand All @@ -19,16 +21,6 @@ import okio.use
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

/**
* Parses the source to a [Schema], throwing on parsing or validation errors.
*
* See [parseAsGQLDocument] and [validateAsSchema] for more granular error reporting
*/
@ApolloExperimental
fun BufferedSource.toSchema(filePath: String? = null): Schema = parseAsGQLDocument(filePath).getOrThrow().validateAsSchema().getOrThrow()

fun String.toSchema(): Schema = parseAsGQLDocument().getOrThrow().validateAsSchema().getOrThrow()

/**
* Parses the source to a List<[GQLDefinition]>, throwing on parsing or validation errors.
*
Expand Down Expand Up @@ -145,6 +137,10 @@ fun String.parseAsGQLDocument(options: ParserOptions = ParserOptions.Default): G
}
}

fun String.toGQLDocument(options: ParserOptions = ParserOptions.Default): GQLDocument {
return parseAsGQLDocument(options).getOrThrow()
}

fun String.parseAsGQLValue(options: ParserOptions = ParserOptions.Default): GQLResult<GQLValue> {
@Suppress("DEPRECATION")
return if (options.useAntlr) {
Expand All @@ -154,6 +150,10 @@ fun String.parseAsGQLValue(options: ParserOptions = ParserOptions.Default): GQLR
}
}

fun String.toGQLValue(options: ParserOptions = ParserOptions.Default): GQLValue {
return parseAsGQLValue(options).getOrThrow()
}

fun String.parseAsGQLType(options: ParserOptions = ParserOptions.Default): GQLResult<GQLType> {
@Suppress("DEPRECATION")
return if (options.useAntlr) {
Expand All @@ -163,12 +163,20 @@ fun String.parseAsGQLType(options: ParserOptions = ParserOptions.Default): GQLRe
}
}

fun String.toGQLType(options: ParserOptions = ParserOptions.Default): GQLType {
return parseAsGQLType(options).getOrThrow()
}

internal fun String.parseAsGQLNullability(options: ParserOptions = ParserOptions.Default): GQLResult<GQLNullability> {
@Suppress("DEPRECATION")
check (!options.useAntlr)
check(!options.useAntlr)
return parseInternal(null, options) { parseNullability() ?: error("No nullability") }
}

fun String.toGQLNullability(options: ParserOptions = ParserOptions.Default): GQLNullability {
return parseAsGQLNullability(options).getOrThrow()
}

fun String.parseAsGQLSelections(options: ParserOptions = ParserOptions.Default): GQLResult<List<GQLSelection>> {
@Suppress("DEPRECATION")
return if (options.useAntlr) {
Expand All @@ -178,10 +186,22 @@ fun String.parseAsGQLSelections(options: ParserOptions = ParserOptions.Default):
}
}

fun String.toGQLSelections(options: ParserOptions = ParserOptions.Default): List<GQLSelection> {
return parseAsGQLSelections(options).getOrThrow()
}

fun Path.parseAsGQLDocument(options: ParserOptions = ParserOptions.Default): GQLResult<GQLDocument> {
return HOST_FILESYSTEM.source(this).buffer().parseAsGQLDocument(filePath = toString(), options = options)
}

fun Path.toGQLDocument(options: ParserOptions = ParserOptions.Default, allowJson: Boolean = false): GQLDocument {
return if (allowJson && name.endsWith(".json")) {
toIntrospectionSchema().toGQLDocument()
} else {
parseAsGQLDocument(options).getOrThrow()
}
}

/**
* Parses the source to a [GQLDocument], validating the syntax but not the contents of the document.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ sealed interface GQLNode {
fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode
}

@ApolloExperimental
sealed interface TransformResult {
object Delete : TransformResult
object Continue : TransformResult
Expand All @@ -57,10 +58,12 @@ sealed interface TransformResult {
class Replace(val newNode: GQLNode) : TransformResult
}

@ApolloExperimental
fun interface NodeTransformer {
fun transform(node: GQLNode): TransformResult
}

@ApolloExperimental
fun GQLNode.transform(transformer: NodeTransformer): GQLNode? {
return when (val result = transformer.transform(this)) {
is TransformResult.Delete -> null
Expand Down Expand Up @@ -117,7 +120,7 @@ interface GQLHasDirectives {

sealed interface GQLDefinition : GQLNode
sealed interface GQLExecutableDefinition : GQLDefinition
sealed interface GQLTypeSystemExtension : GQLNode
sealed interface GQLTypeSystemExtension : GQLDefinition
sealed interface GQLTypeExtension : GQLTypeSystemExtension, GQLNamed

sealed class GQLSelection : GQLNode
Expand All @@ -132,8 +135,6 @@ class GQLDocument(
val definitions: List<GQLDefinition>,
override val sourceLocation: SourceLocation?,
) : GQLNode {
constructor(definitions: List<GQLDefinition>, filePath: String?) : this(definitions, SourceLocation.forPath(filePath))

override val children = definitions

override fun writeInternal(writer: SDLWriter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ fun List<GQLDirective>.findDeprecationReason() = firstOrNull { it.name == "depre
?: "No longer supported"
}

fun List<GQLDirective>.findSpecifiedBy() = firstOrNull { it.name == "specifiedBy" }
?.let { directive ->
directive.arguments
.firstOrNull { it.name == "url" }
?.value
?.let { value ->
if (value !is GQLStringValue) {
throw ConversionException("url must be a string", directive.sourceLocation)
}
value.value
}
}

@ApolloInternal
fun List<GQLDirective>.findOptInFeature(schema: Schema): String? = filter { schema.originalDirectiveName(it.name) == Schema.REQUIRES_OPT_IN }
.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.apollographql.apollo3.ast

import com.apollographql.apollo3.annotations.ApolloDeprecatedSince
import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.ast.internal.ExtensionsMerger
import com.apollographql.apollo3.ast.internal.apollo_v0_1_definitionsStr
import com.apollographql.apollo3.ast.internal.apollo_v0_2_definitionsStr
import com.apollographql.apollo3.ast.internal.builtinsDefinitionsStr
import com.apollographql.apollo3.ast.internal.ensureSchemaDefinition
import com.apollographql.apollo3.ast.internal.linkDefinitionsStr
import okio.Buffer

Expand All @@ -22,7 +24,26 @@ fun GQLDocument.withBuiltinDefinitions(): GQLDocument {
return withDefinitions(builtinDefinitions())
}

@Deprecated("Use GQLDocument.toSDL() to write a GQLDocument without the scalar directives")
/**
* Returns a "full schema" document. Full schema documents are for use by clients and other tools that need
* to know what features are supported by a given server. They include builtin directives and merge all type
* extensions
*/
@ApolloExperimental
fun GQLDocument.toFullSchemaGQLDocument(): GQLDocument {
return ensureSchemaDefinition()
.withDefinitions(builtinDefinitions())
.mergeExtensions()
}

fun GQLDocument.toSchema(): Schema = validateAsSchema().getOrThrow()

@ApolloExperimental
fun GQLDocument.mergeExtensions(): GQLDocument {
return GQLDocument(ExtensionsMerger(definitions).merge().getOrThrow(), sourceLocation = null)
}

@Deprecated("Use GQLDocument.toSDL() to write a GQLDocument")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_0_0)
fun GQLDocument.withoutBuiltinDefinitions(): GQLDocument {
return withoutDefinitions(builtinDefinitions())
Expand Down Expand Up @@ -123,6 +144,7 @@ private fun GQLDocument.withDefinitions(definitions: List<GQLDefinition>): GQLDo
)
}


/**
* Outputs a schema document to SDL. For executable documents, use toUtf8()
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
@file:JvmName("GqlnodeKt")
package com.apollographql.apollo3.ast

import com.apollographql.apollo3.annotations.ApolloExperimental
import okio.Buffer
import okio.BufferedSink
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

@ApolloExperimental
fun GQLNode.toUtf8(sink: BufferedSink, indent: String = " ") {
val writer = SDLWriter(sink, indent)
writer.write(this)
Expand Down
Loading

0 comments on commit 1b411f5

Please sign in to comment.