Skip to content

Add mergeExtensions and toFullSchemaGQLDocument #5162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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