diff --git a/libraries/apollo-ast/api/apollo-ast.api b/libraries/apollo-ast/api/apollo-ast.api index 6cf9a0c396c..7ddf6328002 100644 --- a/libraries/apollo-ast/api/apollo-ast.api +++ b/libraries/apollo-ast/api/apollo-ast.api @@ -196,7 +196,7 @@ public final class com/apollographql/apollo/ast/GQLCapability : com/apollographq public fun copyWithNewChildrenInternal (Lcom/apollographql/apollo/ast/NodeContainer;)Lcom/apollographql/apollo/ast/GQLNode; public fun getChildren ()Ljava/util/List; public final fun getDescription ()Ljava/lang/String; - public final fun getQualifiedName ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; public fun getSourceLocation ()Lcom/apollographql/apollo/ast/SourceLocation; public final fun getValue ()Ljava/lang/String; public fun writeInternal (Lcom/apollographql/apollo/ast/SDLWriter;)V diff --git a/libraries/apollo-ast/api/apollo-ast.klib.api b/libraries/apollo-ast/api/apollo-ast.klib.api index 6185d7a4c07..14b2c2c596f 100644 --- a/libraries/apollo-ast/api/apollo-ast.klib.api +++ b/libraries/apollo-ast/api/apollo-ast.klib.api @@ -321,8 +321,8 @@ final class com.apollographql.apollo.ast/GQLCapability : com.apollographql.apoll final fun (): kotlin.collections/List // com.apollographql.apollo.ast/GQLCapability.children.|(){}[0] final val description // com.apollographql.apollo.ast/GQLCapability.description|{}description[0] final fun (): kotlin/String? // com.apollographql.apollo.ast/GQLCapability.description.|(){}[0] - final val qualifiedName // com.apollographql.apollo.ast/GQLCapability.qualifiedName|{}qualifiedName[0] - final fun (): kotlin/String // com.apollographql.apollo.ast/GQLCapability.qualifiedName.|(){}[0] + final val name // com.apollographql.apollo.ast/GQLCapability.name|{}name[0] + final fun (): kotlin/String // com.apollographql.apollo.ast/GQLCapability.name.|(){}[0] final val sourceLocation // com.apollographql.apollo.ast/GQLCapability.sourceLocation|{}sourceLocation[0] final fun (): com.apollographql.apollo.ast/SourceLocation? // com.apollographql.apollo.ast/GQLCapability.sourceLocation.|(){}[0] final val value // com.apollographql.apollo.ast/GQLCapability.value|{}value[0] diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gql.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gql.kt index a195b1337ad..737f9cf9ed1 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gql.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gql.kt @@ -438,7 +438,7 @@ class GQLServiceExtension( class GQLCapability( override val sourceLocation: SourceLocation? = null, val description: String?, - val qualifiedName: String, + val name: String, val value: String?, ) : GQLNode { override val children: List @@ -448,7 +448,7 @@ class GQLCapability( with(writer) { writeDescription(description) write("capability ") - write(qualifiedName) + write(name) if (value != null) { write("(\"$value\")") } @@ -459,13 +459,13 @@ class GQLCapability( fun copy( sourceLocation: SourceLocation? = this.sourceLocation, description: String? = this.description, - qualifiedName: String = this.qualifiedName, + qualifiedName: String = this.name, value: String? = this.value, ): GQLCapability { return GQLCapability( sourceLocation = sourceLocation, description = description, - qualifiedName = qualifiedName, + name = qualifiedName, value = value ) } diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ExtensionsMerger.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ExtensionsMerger.kt index b04ef31e71a..78f7123026e 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ExtensionsMerger.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ExtensionsMerger.kt @@ -283,7 +283,7 @@ private fun ExtensionsMerger.mergeService( ): GQLServiceDefinition = with(serviceDefinition) { return copy( directives = mergeDirectives(directives, extension.directives), - capabilities = mergeUniquesOrThrow(capabilities, extension.capabilities) { it.qualifiedName } + capabilities = mergeUniquesOrThrow(capabilities, extension.capabilities) { it.name } ) } diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/Parser.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/Parser.kt index b91989b5313..4fd8c0d57fc 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/Parser.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/Parser.kt @@ -368,7 +368,7 @@ internal class Parser( return GQLCapability( sourceLocation = sourceLocation(start), description = description, - qualifiedName = qualifiedName, + name = qualifiedName, value = value ) } 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 5aee4d6d2dd..74e6244ac4b 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 @@ -89,6 +89,7 @@ internal fun validateSchema(definitions: List, options: SchemaVal val typeSystemExtensions = mutableListOf() var schemaDefinition: GQLSchemaDefinition? = null val reportedDefinitions = mutableSetOf() + var serviceDefinition: GQLServiceDefinition? = null /* * Deduplicate the user input @@ -139,6 +140,7 @@ internal fun validateSchema(definitions: List, options: SchemaVal } is GQLServiceDefinition -> { + serviceDefinition = definition // TODO: any validation to do here? // See https://github.com/graphql/graphql-spec/pull/1208/changes#r2732688632 } @@ -303,7 +305,7 @@ internal fun validateSchema(definitions: List, options: SchemaVal * * Moving forward, extensions merging should probably be done first thing as a separate step, before any validation and/or linking of foreign schemas. */ - val dedupedDefinitions = listOfNotNull(schemaDefinition) + directiveDefinitions.values + typeDefinitions.values + val dedupedDefinitions = listOfNotNull(schemaDefinition) + directiveDefinitions.values + typeDefinitions.values + listOfNotNull(serviceDefinition) val mergedDefinitions = ExtensionsMerger(dedupedDefinitions + typeSystemExtensions, options.mergeOptions).merge().getOrThrow() val mergedScope = DefaultValidationScope( diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_reader.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_reader.kt index e480c7dab23..0fd468e5cef 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_reader.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_reader.kt @@ -6,7 +6,6 @@ package com.apollographql.apollo.ast.introspection import com.apollographql.apollo.annotations.ApolloExperimental -import com.apollographql.apollo.annotations.ApolloInternal import com.apollographql.apollo.ast.* import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -479,7 +478,7 @@ private class GQLDocumentBuilder(private val introspectionSchema: IntrospectionS private fun RCapability.toGQLCapability(): GQLCapability { return GQLCapability( description = description.unwrapDescription(qualifiedName), - qualifiedName = qualifiedName, + name = qualifiedName, value = value.getOrThrow() ) } diff --git a/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/fixtures.kt b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/fixtures.kt index b0125fbd0c1..84528eb84e2 100644 --- a/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/fixtures.kt +++ b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/fixtures.kt @@ -28,13 +28,7 @@ internal fun GQLResult.serialize(): String { } } -fun shouldUpdateTestFixtures(): Boolean { - if (System.getenv("updateTestFixtures") != null) { - return true - } - - return false -} +fun shouldUpdateTestFixtures() = System.getenv("updateTestFixtures") != null internal fun testFilterMatches(value: String): Boolean { val testFilter = System.getenv("testFilter") ?: return true diff --git a/libraries/apollo-execution/api/apollo-execution.api b/libraries/apollo-execution/api/apollo-execution.api index 12670f27ae5..824cd8ccf5a 100644 --- a/libraries/apollo-execution/api/apollo-execution.api +++ b/libraries/apollo-execution/api/apollo-execution.api @@ -20,7 +20,6 @@ public final class com/apollographql/apollo/execution/ErrorPersistedDocument : c } public final class com/apollographql/apollo/execution/ExecutableSchema { - public fun (Lcom/apollographql/apollo/ast/Schema;Ljava/util/Map;Lcom/apollographql/apollo/execution/RootResolver;Lcom/apollographql/apollo/execution/RootResolver;Lcom/apollographql/apollo/execution/RootResolver;Lcom/apollographql/apollo/execution/Resolver;Lcom/apollographql/apollo/execution/TypeResolver;Ljava/util/List;Lcom/apollographql/apollo/execution/PersistedDocumentCache;)V public final fun execute (Lcom/apollographql/apollo/execution/GraphQLRequest;Lcom/apollographql/apollo/api/ExecutionContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun execute$default (Lcom/apollographql/apollo/execution/ExecutableSchema;Lcom/apollographql/apollo/execution/GraphQLRequest;Lcom/apollographql/apollo/api/ExecutionContext;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun subscribe (Lcom/apollographql/apollo/execution/GraphQLRequest;Lcom/apollographql/apollo/api/ExecutionContext;)Lkotlinx/coroutines/flow/Flow; @@ -33,6 +32,7 @@ public final class com/apollographql/apollo/execution/ExecutableSchema$Builder { public final fun addInstrumentation (Lcom/apollographql/apollo/execution/Instrumentation;)Lcom/apollographql/apollo/execution/ExecutableSchema$Builder; public final fun build ()Lcom/apollographql/apollo/execution/ExecutableSchema; public final fun mutationRoot (Lcom/apollographql/apollo/execution/RootResolver;)Lcom/apollographql/apollo/execution/ExecutableSchema$Builder; + public final fun onError (Lcom/apollographql/apollo/execution/OnError;)Lcom/apollographql/apollo/execution/ExecutableSchema$Builder; public final fun persistedDocumentCache (Lcom/apollographql/apollo/execution/PersistedDocumentCache;)Lcom/apollographql/apollo/execution/ExecutableSchema$Builder; public final fun queryRoot (Lcom/apollographql/apollo/execution/RootResolver;)Lcom/apollographql/apollo/execution/ExecutableSchema$Builder; public final fun resolver (Lcom/apollographql/apollo/execution/Resolver;)Lcom/apollographql/apollo/execution/ExecutableSchema$Builder; @@ -59,6 +59,7 @@ public final class com/apollographql/apollo/execution/FloatCoercing : com/apollo public final class com/apollographql/apollo/execution/GraphQLRequest { public final fun getDocument ()Ljava/lang/String; public final fun getExtensions ()Ljava/util/Map; + public final fun getOnError ()Lcom/apollographql/apollo/execution/OnError; public final fun getOperationName ()Ljava/lang/String; public final fun getVariables ()Ljava/util/Map; } @@ -70,11 +71,14 @@ public final class com/apollographql/apollo/execution/GraphQLRequest$Builder { public final fun extensions (Ljava/util/Map;)Lcom/apollographql/apollo/execution/GraphQLRequest$Builder; public final fun getDocument ()Ljava/lang/String; public final fun getExtensions ()Ljava/util/Map; + public final fun getOnError ()Lcom/apollographql/apollo/execution/OnError; public final fun getOperationName ()Ljava/lang/String; public final fun getVariables ()Ljava/util/Map; + public final fun onError (Lcom/apollographql/apollo/execution/OnError;)Lcom/apollographql/apollo/execution/GraphQLRequest$Builder; public final fun operationName (Ljava/lang/String;)Lcom/apollographql/apollo/execution/GraphQLRequest$Builder; public final fun setDocument (Ljava/lang/String;)V public final fun setExtensions (Ljava/util/Map;)V + public final fun setOnError (Lcom/apollographql/apollo/execution/OnError;)V public final fun setOperationName (Ljava/lang/String;)V public final fun setVariables (Ljava/util/Map;)V public final fun variables (Ljava/util/Map;)Lcom/apollographql/apollo/execution/GraphQLRequest$Builder; @@ -85,7 +89,6 @@ public final class com/apollographql/apollo/execution/GraphQLRequestKt { public static final fun parseAsGraphQLRequest (Ljava/util/Map;)Ljava/lang/Object; public static final fun parseAsGraphQLRequest (Lokio/BufferedSource;)Ljava/lang/Object; public static final fun toExternalValueMap (Ljava/util/Map;)Ljava/lang/Object; - public static final fun toGraphQLRequest (Ljava/lang/String;)Lcom/apollographql/apollo/execution/GraphQLRequest; } public final class com/apollographql/apollo/execution/GraphQLResponse { @@ -140,6 +143,15 @@ public final class com/apollographql/apollo/execution/JsonCoercing : com/apollog public fun serialize (Ljava/lang/Object;)Ljava/lang/Object; } +public final class com/apollographql/apollo/execution/OnError : java/lang/Enum { + public static final field HALT Lcom/apollographql/apollo/execution/OnError; + public static final field NULL Lcom/apollographql/apollo/execution/OnError; + public static final field PROPAGATE Lcom/apollographql/apollo/execution/OnError; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/apollographql/apollo/execution/OnError; + public static fun values ()[Lcom/apollographql/apollo/execution/OnError; +} + public abstract interface class com/apollographql/apollo/execution/OperationCallback { public abstract fun onOperationCompleted (Lcom/apollographql/apollo/execution/GraphQLResponse;)Lcom/apollographql/apollo/execution/GraphQLResponse; } diff --git a/libraries/apollo-execution/api/apollo-execution.klib.api b/libraries/apollo-execution/api/apollo-execution.klib.api index fc45c07f7a5..9235b4a52db 100644 --- a/libraries/apollo-execution/api/apollo-execution.klib.api +++ b/libraries/apollo-execution/api/apollo-execution.klib.api @@ -6,6 +6,18 @@ // - Show declarations: true // Library unique name: +final enum class com.apollographql.apollo.execution/OnError : kotlin/Enum { // com.apollographql.apollo.execution/OnError|null[0] + enum entry HALT // com.apollographql.apollo.execution/OnError.HALT|null[0] + enum entry NULL // com.apollographql.apollo.execution/OnError.NULL|null[0] + enum entry PROPAGATE // com.apollographql.apollo.execution/OnError.PROPAGATE|null[0] + + final val entries // com.apollographql.apollo.execution/OnError.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // com.apollographql.apollo.execution/OnError.entries.|#static(){}[0] + + final fun valueOf(kotlin/String): com.apollographql.apollo.execution/OnError // com.apollographql.apollo.execution/OnError.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // com.apollographql.apollo.execution/OnError.values|values#static(){}[0] +} + abstract fun interface com.apollographql.apollo.execution/FieldCallback { // com.apollographql.apollo.execution/FieldCallback|null[0] abstract fun onFieldCompleted(kotlin/Any?) // com.apollographql.apollo.execution/FieldCallback.onFieldCompleted|onFieldCompleted(kotlin.Any?){}[0] } @@ -56,8 +68,6 @@ final class com.apollographql.apollo.execution/ErrorPersistedDocument : com.apol } final class com.apollographql.apollo.execution/ExecutableSchema { // com.apollographql.apollo.execution/ExecutableSchema|null[0] - constructor (com.apollographql.apollo.ast/Schema, kotlin.collections/Map>, com.apollographql.apollo.execution/RootResolver?, com.apollographql.apollo.execution/RootResolver?, com.apollographql.apollo.execution/RootResolver?, com.apollographql.apollo.execution/Resolver, com.apollographql.apollo.execution/TypeResolver, kotlin.collections/List, com.apollographql.apollo.execution/PersistedDocumentCache?) // com.apollographql.apollo.execution/ExecutableSchema.|(com.apollographql.apollo.ast.Schema;kotlin.collections.Map>;com.apollographql.apollo.execution.RootResolver?;com.apollographql.apollo.execution.RootResolver?;com.apollographql.apollo.execution.RootResolver?;com.apollographql.apollo.execution.Resolver;com.apollographql.apollo.execution.TypeResolver;kotlin.collections.List;com.apollographql.apollo.execution.PersistedDocumentCache?){}[0] - final fun subscribe(com.apollographql.apollo.execution/GraphQLRequest, com.apollographql.apollo.api/ExecutionContext = ...): kotlinx.coroutines.flow/Flow // com.apollographql.apollo.execution/ExecutableSchema.subscribe|subscribe(com.apollographql.apollo.execution.GraphQLRequest;com.apollographql.apollo.api.ExecutionContext){}[0] final suspend fun execute(com.apollographql.apollo.execution/GraphQLRequest, com.apollographql.apollo.api/ExecutionContext = ...): com.apollographql.apollo.execution/GraphQLResponse // com.apollographql.apollo.execution/ExecutableSchema.execute|execute(com.apollographql.apollo.execution.GraphQLRequest;com.apollographql.apollo.api.ExecutionContext){}[0] @@ -68,6 +78,7 @@ final class com.apollographql.apollo.execution/ExecutableSchema { // com.apollog final fun addInstrumentation(com.apollographql.apollo.execution/Instrumentation): com.apollographql.apollo.execution/ExecutableSchema.Builder // com.apollographql.apollo.execution/ExecutableSchema.Builder.addInstrumentation|addInstrumentation(com.apollographql.apollo.execution.Instrumentation){}[0] final fun build(): com.apollographql.apollo.execution/ExecutableSchema // com.apollographql.apollo.execution/ExecutableSchema.Builder.build|build(){}[0] final fun mutationRoot(com.apollographql.apollo.execution/RootResolver): com.apollographql.apollo.execution/ExecutableSchema.Builder // com.apollographql.apollo.execution/ExecutableSchema.Builder.mutationRoot|mutationRoot(com.apollographql.apollo.execution.RootResolver){}[0] + final fun onError(com.apollographql.apollo.execution/OnError): com.apollographql.apollo.execution/ExecutableSchema.Builder // com.apollographql.apollo.execution/ExecutableSchema.Builder.onError|onError(com.apollographql.apollo.execution.OnError){}[0] final fun persistedDocumentCache(com.apollographql.apollo.execution/PersistedDocumentCache?): com.apollographql.apollo.execution/ExecutableSchema.Builder // com.apollographql.apollo.execution/ExecutableSchema.Builder.persistedDocumentCache|persistedDocumentCache(com.apollographql.apollo.execution.PersistedDocumentCache?){}[0] final fun queryRoot(com.apollographql.apollo.execution/RootResolver): com.apollographql.apollo.execution/ExecutableSchema.Builder // com.apollographql.apollo.execution/ExecutableSchema.Builder.queryRoot|queryRoot(com.apollographql.apollo.execution.RootResolver){}[0] final fun resolver(com.apollographql.apollo.execution/Resolver): com.apollographql.apollo.execution/ExecutableSchema.Builder // com.apollographql.apollo.execution/ExecutableSchema.Builder.resolver|resolver(com.apollographql.apollo.execution.Resolver){}[0] @@ -83,6 +94,8 @@ final class com.apollographql.apollo.execution/GraphQLRequest { // com.apollogra final fun (): kotlin/String? // com.apollographql.apollo.execution/GraphQLRequest.document.|(){}[0] final val extensions // com.apollographql.apollo.execution/GraphQLRequest.extensions|{}extensions[0] final fun (): kotlin.collections/Map // com.apollographql.apollo.execution/GraphQLRequest.extensions.|(){}[0] + final val onError // com.apollographql.apollo.execution/GraphQLRequest.onError|{}onError[0] + final fun (): com.apollographql.apollo.execution/OnError? // com.apollographql.apollo.execution/GraphQLRequest.onError.|(){}[0] final val operationName // com.apollographql.apollo.execution/GraphQLRequest.operationName|{}operationName[0] final fun (): kotlin/String? // com.apollographql.apollo.execution/GraphQLRequest.operationName.|(){}[0] final val variables // com.apollographql.apollo.execution/GraphQLRequest.variables|{}variables[0] @@ -97,6 +110,9 @@ final class com.apollographql.apollo.execution/GraphQLRequest { // com.apollogra final var extensions // com.apollographql.apollo.execution/GraphQLRequest.Builder.extensions|{}extensions[0] final fun (): kotlin.collections/Map? // com.apollographql.apollo.execution/GraphQLRequest.Builder.extensions.|(){}[0] final fun (kotlin.collections/Map?) // com.apollographql.apollo.execution/GraphQLRequest.Builder.extensions.|(kotlin.collections.Map?){}[0] + final var onError // com.apollographql.apollo.execution/GraphQLRequest.Builder.onError|{}onError[0] + final fun (): com.apollographql.apollo.execution/OnError? // com.apollographql.apollo.execution/GraphQLRequest.Builder.onError.|(){}[0] + final fun (com.apollographql.apollo.execution/OnError?) // com.apollographql.apollo.execution/GraphQLRequest.Builder.onError.|(com.apollographql.apollo.execution.OnError?){}[0] final var operationName // com.apollographql.apollo.execution/GraphQLRequest.Builder.operationName|{}operationName[0] final fun (): kotlin/String? // com.apollographql.apollo.execution/GraphQLRequest.Builder.operationName.|(){}[0] final fun (kotlin/String?) // com.apollographql.apollo.execution/GraphQLRequest.Builder.operationName.|(kotlin.String?){}[0] @@ -107,6 +123,7 @@ final class com.apollographql.apollo.execution/GraphQLRequest { // com.apollogra final fun build(): com.apollographql.apollo.execution/GraphQLRequest // com.apollographql.apollo.execution/GraphQLRequest.Builder.build|build(){}[0] final fun document(kotlin/String?): com.apollographql.apollo.execution/GraphQLRequest.Builder // com.apollographql.apollo.execution/GraphQLRequest.Builder.document|document(kotlin.String?){}[0] final fun extensions(kotlin.collections/Map?): com.apollographql.apollo.execution/GraphQLRequest.Builder // com.apollographql.apollo.execution/GraphQLRequest.Builder.extensions|extensions(kotlin.collections.Map?){}[0] + final fun onError(com.apollographql.apollo.execution/OnError?): com.apollographql.apollo.execution/GraphQLRequest.Builder // com.apollographql.apollo.execution/GraphQLRequest.Builder.onError|onError(com.apollographql.apollo.execution.OnError?){}[0] final fun operationName(kotlin/String?): com.apollographql.apollo.execution/GraphQLRequest.Builder // com.apollographql.apollo.execution/GraphQLRequest.Builder.operationName|operationName(kotlin.String?){}[0] final fun variables(kotlin.collections/Map?): com.apollographql.apollo.execution/GraphQLRequest.Builder // com.apollographql.apollo.execution/GraphQLRequest.Builder.variables|variables(kotlin.collections.Map?){}[0] } @@ -251,5 +268,4 @@ final object com.apollographql.apollo.execution/StringCoercing : com.apollograph final fun (kotlin.collections/Map>).com.apollographql.apollo.execution/toExternalValueMap(): kotlin/Result> // com.apollographql.apollo.execution/toExternalValueMap|toExternalValueMap@kotlin.collections.Map>(){}[0] final fun (kotlin.collections/Map).com.apollographql.apollo.execution/parseAsGraphQLRequest(): kotlin/Result // com.apollographql.apollo.execution/parseAsGraphQLRequest|parseAsGraphQLRequest@kotlin.collections.Map(){}[0] final fun (kotlin/String).com.apollographql.apollo.execution/parseAsGraphQLRequest(): kotlin/Result // com.apollographql.apollo.execution/parseAsGraphQLRequest|parseAsGraphQLRequest@kotlin.String(){}[0] -final fun (kotlin/String).com.apollographql.apollo.execution/toGraphQLRequest(): com.apollographql.apollo.execution/GraphQLRequest // com.apollographql.apollo.execution/toGraphQLRequest|toGraphQLRequest@kotlin.String(){}[0] final fun (okio/BufferedSource).com.apollographql.apollo.execution/parseAsGraphQLRequest(): kotlin/Result // com.apollographql.apollo.execution/parseAsGraphQLRequest|parseAsGraphQLRequest@okio.BufferedSource(){}[0] diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/ExecutableSchema.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/ExecutableSchema.kt index 2ac163b6132..60acdb7914e 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/ExecutableSchema.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/ExecutableSchema.kt @@ -14,66 +14,68 @@ import kotlinx.coroutines.flow.flowOf * A GraphQL schema with execution information: * - root values * - coercings - * - resolver + * - resolvers * * [ExecutableSchema] also includes handling for persisted documents. This part is not technically part of the main GraphQL spec but is popular that we add first party support to it. */ -class ExecutableSchema( - private val schema: Schema, - private val coercings: Map>, - private val queryRoot: RootResolver?, - private val mutationRoot: RootResolver?, - private val subscriptionRoot: RootResolver?, - private val resolver: Resolver, - private val typeResolver: TypeResolver, - private val instrumentations: List, - private val persistedDocumentCache: PersistedDocumentCache?, +class ExecutableSchema internal constructor( + private val schema: Schema, + private val coercings: Map>, + private val queryRoot: RootResolver?, + private val mutationRoot: RootResolver?, + private val subscriptionRoot: RootResolver?, + private val resolver: Resolver, + private val typeResolver: TypeResolver, + private val instrumentations: List, + private val persistedDocumentCache: PersistedDocumentCache?, + private val onError: OnError, ) { private val introspectionResolver: Resolver = introspectionResolver(schema) suspend fun execute( - request: GraphQLRequest, - executionContext: ExecutionContext = ExecutionContext.Empty + request: GraphQLRequest, + executionContext: ExecutionContext = ExecutionContext.Empty, ): GraphQLResponse { return prepareRequest(schema, coercings, persistedDocumentCache, request).fold( - ifLeft = { - GraphQLResponse.Builder().errors(it).build() - }, - ifRight = { - operationContext(it, executionContext).execute() - } + ifLeft = { + GraphQLResponse.Builder().errors(it).build() + }, + ifRight = { + operationContext(it, executionContext).execute() + } ) } fun subscribe( - request: GraphQLRequest, - executionContext: ExecutionContext = ExecutionContext.Empty + request: GraphQLRequest, + executionContext: ExecutionContext = ExecutionContext.Empty, ): Flow { return prepareRequest(schema, coercings, persistedDocumentCache, request).fold( - ifLeft = { - flowOf(SubscriptionResponse(GraphQLResponse.Builder().errors(it).build())) - }, - ifRight = { - operationContext(it, executionContext).subscribe() - } + ifLeft = { + flowOf(SubscriptionResponse(GraphQLResponse.Builder().errors(it).build())) + }, + ifRight = { + operationContext(it, executionContext).subscribe() + } ) } private fun operationContext(preparedRequest: PreparedRequest, executionContext: ExecutionContext): OperationContext { return OperationContext( - schema, - coercings + introspectionCoercings, - introspectionResolver, - queryRoot, - mutationRoot, - subscriptionRoot, - resolver, - typeResolver, - instrumentations, - preparedRequest.operation, - preparedRequest.fragments, - preparedRequest.variables, - executionContext, + schema, + coercings + introspectionCoercings, + introspectionResolver, + queryRoot, + mutationRoot, + subscriptionRoot, + resolver, + typeResolver, + instrumentations, + preparedRequest.operation, + preparedRequest.fragments, + preparedRequest.variables, + executionContext, + preparedRequest.onError ?: onError ) } @@ -87,6 +89,7 @@ class ExecutableSchema( private var typeResolver: TypeResolver? = null private val instrumentations = mutableListOf() private var persistedDocumentCache: PersistedDocumentCache? = null + private var onError: OnError = OnError.PROPAGATE fun schema(schema: GQLDocument): Builder = apply { this.schema = schema @@ -128,25 +131,65 @@ class ExecutableSchema( this.persistedDocumentCache = persistedDocumentCache } + fun onError(onError: OnError) = apply { + check(onError != OnError.HALT) { + "OnError.HALT is not supported" + } + this.onError = onError + } + fun build(): ExecutableSchema { check(schema != null) { "A schema is required to build an ExecutableSchema" } - val definitions = builtinDefinitions().filter { it !is GQLScalarTypeDefinition } + schema!!.definitions - val schema = GQLDocument(definitions, null).toSchema() + /** + * TODO: scalar definitions are added back when calling `toSchema()` but I'm unclear why we have to filter them out here. + */ + val ourDefinitions = builtinDefinitions().filter { it !is GQLScalarTypeDefinition } + serviceDefinition(onError) + val reservedNames = ourDefinitions.mapNotNull { it.definitionName() }.toSet() + val sourceDefinitions = schema!!.definitions + sourceDefinitions.forEach { + val definitionName = it.definitionName() + if (definitionName in reservedNames) { + error("Source schema cannot contain definition '$definitionName'. It is provided by the implementation") + } + } + val schema = GQLDocument(ourDefinitions + schema!!.definitions, null).toSchema() return ExecutableSchema( - schema, - coercings, - queryRoot, - mutationRoot, - subscriptionRoot, - resolver ?: ThrowingResolver, - typeResolver ?: ThrowingTypeResolver, - instrumentations, - persistedDocumentCache + schema, + coercings, + queryRoot, + mutationRoot, + subscriptionRoot, + resolver ?: ThrowingResolver, + typeResolver ?: ThrowingTypeResolver, + instrumentations, + persistedDocumentCache, + onError ) } } } +private fun serviceDefinition(onError: OnError): GQLDefinition { + return GQLServiceDefinition( + sourceLocation = null, + description = null, + directives = emptyList(), + capabilities = listOf( + GQLCapability(description = null, name = "graphql.onError", value = null), + GQLCapability(description = null, name = "graphql.defaultErrorBehavior", value = onError.name) + ), + ) +} + +private fun GQLDefinition.definitionName(): String? { + return when (this) { + is GQLTypeDefinition -> name + is GQLDirectiveDefinition -> "@$name" + is GQLSchemaDefinition -> "schema" + is GQLServiceDefinition -> "service" + else -> null + } +} \ No newline at end of file diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/GraphQLRequest.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/GraphQLRequest.kt index 348a11271e5..0f2ebb52c58 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/GraphQLRequest.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/GraphQLRequest.kt @@ -11,17 +11,24 @@ import okio.Buffer import okio.BufferedSource import okio.use +enum class OnError { + NULL, + PROPAGATE, + HALT +} + /** - * @property document the document, may be null if persisted queries are used. + * @property document the document. Can be null if persisted queries are used. * @property operationName the name of the operation to execute (optional). Useful if [document] contains several operations. - * @property variables the variables, may be empty - * @property extensions the extensions, may be empty + * @property variables the variables. + * @property extensions the extensions. */ class GraphQLRequest internal constructor( - val document: String?, - val operationName: String?, - val variables: Map, - val extensions: Map, + val document: String?, + val operationName: String?, + val variables: Map, + val extensions: Map, + val onError: OnError?, ) { class Builder { var document: String? = null @@ -29,6 +36,8 @@ class GraphQLRequest internal constructor( var variables: Map? = null var extensions: Map? = null + var onError: OnError? = null + fun document(document: String?): Builder = apply { this.document = document } @@ -45,12 +54,17 @@ class GraphQLRequest internal constructor( this.extensions = extensions } + fun onError(onError: OnError?): Builder = apply { + this.onError = onError + } + fun build(): GraphQLRequest { return GraphQLRequest( - document, - operationName, - variables.orEmpty(), - extensions.orEmpty() + document = document, + operationName = operationName, + variables = variables.orEmpty(), + extensions = extensions.orEmpty(), + onError = onError ) } } @@ -84,14 +98,29 @@ fun Map.parseAsGraphQLRequest(): Result { if (operationName !is String?) { return Result.failure(Exception("Expected 'operationName' to be a string")) } - return GraphQLRequest.Builder() - .document(document) - .variables(variables as Map?) - .extensions(extensions as Map?) - .operationName(operationName) - .build().let { - Result.success(it) + val onError = map.get("onError") + var onErrorValue: OnError? = null + if (onError != null) { + if (onError !is String) { + return Result.failure(Exception("Expected 'onError' to be a string")) + } + onErrorValue = when (onError) { + "NULL" -> OnError.PROPAGATE + "PROPAGATE" -> OnError.PROPAGATE + "HALT" -> OnError.HALT + else -> return Result.failure(Exception("Unknown 'onError' value: $onError")) } + } + + return GraphQLRequest.Builder() + .document(document) + .variables(variables as Map?) + .extensions(extensions as Map?) + .operationName(operationName) + .onError(onErrorValue) + .build().let { + Result.success(it) + } } @OptIn(ApolloInternal::class) @@ -129,19 +158,13 @@ fun String.parseAsGraphQLRequest(): Result { it.get(0).urlDecode() to it.get(1).urlDecode() } }.groupBy { it.first } - .mapValues { - it.value.map { it.second } - } - .toExternalValueMap() - .flatMap { - it.parseAsGraphQLRequest() - } -} - -fun String.toGraphQLRequest(): GraphQLRequest { - return GraphQLRequest.Builder() - .document(this) - .build() + .mapValues { + it.value.map { it.second } + } + .toExternalValueMap() + .flatMap { + it.parseAsGraphQLRequest() + } } /** diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/OperationContext.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/OperationContext.kt index dc841049e0d..7bac0ec0054 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/OperationContext.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/OperationContext.kt @@ -10,6 +10,7 @@ import com.apollographql.apollo.execution.FieldCallback import com.apollographql.apollo.execution.GraphQLResponse import com.apollographql.apollo.execution.Instrumentation import com.apollographql.apollo.execution.InternalValue +import com.apollographql.apollo.execution.OnError import com.apollographql.apollo.execution.OperationCallback import com.apollographql.apollo.execution.OperationInfo import com.apollographql.apollo.execution.ResolveInfo @@ -40,24 +41,23 @@ import kotlinx.coroutines.flow.* * */ internal class OperationContext( - private val schema: Schema, - private val coercings: Map>, - private val introspectionResolver: Resolver, - private val queryRoot: RootResolver?, - private val mutationRoot: RootResolver?, - private val subscriptionRoot: RootResolver?, - private val resolver: Resolver, - private val typeResolver: TypeResolver, - private val instrumentations: List, - private val operation: GQLOperationDefinition, - private val fragments: Map, - private val variableValues: Map, - private val executionContext: ExecutionContext, + private val schema: Schema, + private val coercings: Map>, + private val introspectionResolver: Resolver, + private val queryRoot: RootResolver?, + private val mutationRoot: RootResolver?, + private val subscriptionRoot: RootResolver?, + private val resolver: Resolver, + private val typeResolver: TypeResolver, + private val instrumentations: List, + private val operation: GQLOperationDefinition, + private val fragments: Map, + private val variableValues: Map, + private val executionContext: ExecutionContext, + private val onError: OnError, ) { - private val bubbles: Boolean = operation.bubbles() - /** - * executes the given operation and awaits its result. + * Executes the given operation and awaits its result. * * Note: a future version may add an "executeAsync" function so we can start sending some data before the whole * map is computed. @@ -66,11 +66,16 @@ internal class OperationContext( var instrumentationException: Exception? = null val operationCallbacks = mutableListOf() val operationInfo = OperationInfo( - operation, - fragments, - schema, - executionContext + operation, + fragments, + schema, + executionContext ) + + if (onError == OnError.HALT) { + return graphqlErrorResponse("onError: HALT is not supported.") + } + instrumentations.forEach { val callback = try { it.onOperation(operationInfo) @@ -110,13 +115,13 @@ internal class OperationContext( return coroutineScope { async(start = CoroutineStart.UNDISPATCHED) { executeGroupedFieldSet( - this, - groupedFieldSet, - typeDefinition as GQLObjectTypeDefinition, - rootObject, - variableValues, - emptyList(), - operation.operationType == "mutation" + this, + groupedFieldSet, + typeDefinition as GQLObjectTypeDefinition, + rootObject, + variableValues, + emptyList(), + operation.operationType == "mutation" ) }.toGraphQLResponse(callbacks = operationCallbacks) } @@ -156,21 +161,21 @@ internal class OperationContext( @OptIn(ExperimentalCoroutinesApi::class) private fun resolveFieldEventStream( - subscriptionType: GQLObjectTypeDefinition, - rootValue: ResolverValue, - fields: List, - argumentValues: Map, - responseName: String + subscriptionType: GQLObjectTypeDefinition, + rootValue: ResolverValue, + fields: List, + argumentValues: Map, + responseName: String, ): Flow { return flow { val resolveInfo = ResolveInfo( - parentObject = rootValue, - executionContext = executionContext, - fields = fields, - schema = schema, - arguments = argumentValues, - parentType = subscriptionType.name, - path = emptyList() + parentObject = rootValue, + executionContext = executionContext, + fields = fields, + schema = schema, + arguments = argumentValues, + parentType = subscriptionType.name, + path = emptyList() ) emit(resolveFieldValue(resolveInfo)) @@ -180,10 +185,10 @@ internal class OperationContext( } else { it.map { objectValue -> FieldEventItem( - parentType = subscriptionType.name, - objectValue = objectValue, - fields = fields, - responseName = responseName + parentType = subscriptionType.name, + objectValue = objectValue, + fields = fields, + responseName = responseName ) } } @@ -192,19 +197,19 @@ internal class OperationContext( sealed interface FieldEvent private class FieldEventItem( - val parentType: String, - val objectValue: InternalValue, - val fields: List, - val responseName: String, + val parentType: String, + val objectValue: InternalValue, + val fields: List, + val responseName: String, ) : FieldEvent private class FieldEventError( - val message: String, + val message: String, ) : FieldEvent private fun createSourceEventStream( - subscription: GQLOperationDefinition, - rootValue: ResolverValue + subscription: GQLOperationDefinition, + rootValue: ResolverValue, ): Flow { val rootTypename = schema.rootTypeNameOrNullFor(subscription.operationType) if (rootTypename == null) { @@ -224,17 +229,17 @@ internal class OperationContext( val field = fields.first() val argumentValues = coerceArgumentValues(schema, typeDefinition.name, field, coercings, variableValues) return resolveFieldEventStream( - subscriptionType = typeDefinition, - rootValue = rootValue, - fields = fields, - argumentValues = argumentValues, - responseName = fields.first().responseName() + subscriptionType = typeDefinition, + rootValue = rootValue, + fields = fields, + argumentValues = argumentValues, + responseName = fields.first().responseName() ) } private fun mapSourceToResponseEvent( - sourceStream: Flow, - variableValues: Map + sourceStream: Flow, + variableValues: Map, ): Flow { return sourceStream.map { // TODO: allow implementers to terminate the stream with an exception @@ -243,18 +248,18 @@ internal class OperationContext( } private suspend fun executeSubscriptionEvent( - event: FieldEvent, + event: FieldEvent, ): GraphQLResponse { return when (event) { is FieldEventError -> GraphQLResponse.Builder().errors(listOf(Error.Builder(event.message).build())).build() is FieldEventItem -> { coroutineScope { val fieldData = completeValue( - scope = this, - fieldType = event.fields.first().definitionFromScope(schema, event.parentType)!!.type, - fields = event.fields, - result = event.objectValue, - path = listOf(event.responseName) + scope = this, + fieldType = event.fields.first().definitionFromScope(schema, event.parentType)!!.type, + fields = event.fields, + result = event.objectValue, + path = listOf(event.responseName) ) mapOf(event.responseName to fieldData).toGraphQLResponse(emptyList()) @@ -274,26 +279,26 @@ internal class OperationContext( * @param variableValues the coerced variable values. */ private fun executeField( - scope: CoroutineScope, - objectType: GQLObjectTypeDefinition, - objectValue: ResolverValue, - fieldType: GQLType, - fields: List, - variableValues: Map, - path: List, + scope: CoroutineScope, + objectType: GQLObjectTypeDefinition, + objectValue: ResolverValue, + fieldType: GQLType, + fields: List, + variableValues: Map, + path: List, ): Deferred { val field = fields.first() val argumentValues = coerceArgumentValues(schema, objectType.name, field, coercings, variableValues) return scope.async(start = CoroutineStart.UNDISPATCHED) { val resolveInfo = ResolveInfo( - parentObject = objectValue, - executionContext = executionContext, - fields = fields, - schema = schema, - arguments = argumentValues, - parentType = objectType.name, - path = path, + parentObject = objectValue, + executionContext = executionContext, + fields = fields, + schema = schema, + arguments = argumentValues, + parentType = objectType.name, + path = path, ) val fieldCallbacks = mutableListOf() @@ -309,19 +314,19 @@ internal class OperationContext( throw e } instrumentationError = Error.Builder("Cannot instrument '${path.lastOrNull()}': ${e.message}") - .path(path) - .build() + .path(path) + .build() } } val completedValue = if (instrumentationError == null) { val resolvedValue = resolveFieldValue(resolveInfo) completeValue( - scope = scope, - fieldType = fieldType, - fields = fields, - result = resolvedValue, - path = path + scope = scope, + fieldType = fieldType, + fields = fields, + result = resolvedValue, + path = path ) } else { instrumentationError @@ -334,19 +339,19 @@ internal class OperationContext( } private suspend fun completeValue( - scope: CoroutineScope, - fieldType: GQLType, - fields: List, - result: ResolverValue, - path: List + scope: CoroutineScope, + fieldType: GQLType, + fields: List, + result: ResolverValue, + path: List, ): ExternalValue { return runFieldOrError(path) { completeValueOrThrow( - scope, - fieldType, - fields, - result, - path + scope, + fieldType, + fields, + result, + path ) } } @@ -356,11 +361,11 @@ internal class OperationContext( * @throws Exception if [typeResolver] fails. */ private suspend fun completeValueOrThrow( - scope: CoroutineScope, - fieldType: GQLType, - fields: List, - result: ResolverValue, - path: List + scope: CoroutineScope, + fieldType: GQLType, + fields: List, + result: ResolverValue, + path: List, ): ExternalValue { if (result is Error) { // fast path if the resolver failed @@ -371,8 +376,8 @@ internal class OperationContext( val completedResult = completeValue(scope, fieldType.type, fields, result, path) if (completedResult == null) { return Error.Builder("A resolver returned null in a non-nullable position") - .path(path) - .build() + .path(path) + .build() } return completedResult } @@ -384,8 +389,8 @@ internal class OperationContext( if (fieldType is GQLListType) { if (result !is List<*>) { return Error.Builder("A resolver returned non-list in a list position") - .path(path) - .build() + .path(path) + .build() } val deferred = result.mapIndexed { index, item -> @@ -395,12 +400,14 @@ internal class OperationContext( } val list = deferred.map { val completed = it.await() - if (bubbles && completed is Error && fieldType.type is GQLNonNullType) { - /** - * We got an error in non-null position, return early - * TODO: cancel other deferred items - */ - return completed + if (completed is Error) { + if (onError == OnError.PROPAGATE && fieldType.type is GQLNonNullType) { + /** + * We got an error in non-null position, bubble the error out of the list + * TODO: cancel other deferred items + */ + return completed + } } completed } @@ -408,8 +415,7 @@ internal class OperationContext( } fieldType as GQLNamedType - val typeDefinition = schema.typeDefinition(fieldType.name) - return when (typeDefinition) { + return when (val typeDefinition = schema.typeDefinition(fieldType.name)) { is GQLEnumTypeDefinition, is GQLScalarTypeDefinition, -> { @@ -429,28 +435,28 @@ internal class OperationContext( val selections = fields.flatMap { it.selections } val groupedFieldSet = collectFields(typename, selections, variableValues) - return executeGroupedFieldSet( - scope = scope, - groupedFieldSet = groupedFieldSet, - typeDefinition = schema.typeDefinition(typename) as GQLObjectTypeDefinition, - objectValue = result, - variableValues = variableValues, - path = path, - serial = false, + executeGroupedFieldSet( + scope = scope, + groupedFieldSet = groupedFieldSet, + typeDefinition = schema.typeDefinition(typename) as GQLObjectTypeDefinition, + objectValue = result, + variableValues = variableValues, + path = path, + serial = false, ) } is GQLInputObjectTypeDefinition -> { - return Error.Builder("Input type used in output position") - .path(path) - .build() + Error.Builder("Input type used in output position") + .path(path) + .build() } } } private suspend fun runFieldOrError( - path: List, - block: suspend () -> Any? + path: List, + block: suspend () -> Any?, ): Any? { return try { block() @@ -459,13 +465,13 @@ internal class OperationContext( throw e } Error.Builder("Cannot resolve '${path.lastOrNull()}': ${e.message}") - .path(path) - .build() + .path(path) + .build() } } private suspend fun resolveFieldValue( - resolveInfo: ResolveInfo, + resolveInfo: ResolveInfo, ): ResolverValueOrError { return runFieldOrError(resolveInfo.path) { resolveFieldValueOrThrow(resolveInfo) @@ -478,7 +484,7 @@ internal class OperationContext( * @throws [Exception] when the resolver throws. */ private suspend fun resolveFieldValueOrThrow( - resolveInfo: ResolveInfo + resolveInfo: ResolveInfo, ): ResolverValue { val resolver = when { resolveInfo.fieldName.startsWith("__") -> introspectionResolver @@ -489,19 +495,19 @@ internal class OperationContext( } private class Entry( - val key: String, - val value: Deferred, - val nullable: Boolean + val key: String, + val value: Deferred, + val nullable: Boolean, ) private suspend fun executeGroupedFieldSet( - scope: CoroutineScope, - groupedFieldSet: Map>, - typeDefinition: GQLObjectTypeDefinition, - objectValue: ResolverValue, - variableValues: Map, - path: List, - serial: Boolean + scope: CoroutineScope, + groupedFieldSet: Map>, + typeDefinition: GQLObjectTypeDefinition, + objectValue: ResolverValue, + variableValues: Map, + path: List, + serial: Boolean, ): ExternalValue { val typename = typeDefinition.name val entries = groupedFieldSet.entries.map { entry -> @@ -520,8 +526,11 @@ internal class OperationContext( val result = mutableMapOf() entries.forEach { val value = it.value.await() - if (bubbles && value is Error && !it.nullable) { - return value + if (value is Error) { + if (onError == OnError.PROPAGATE && !it.nullable) { + // Bubble the error out of the map + return value + } } result.put(it.key, it.value) @@ -574,9 +583,9 @@ internal class OperationContext( } private fun collectFields( - objectType: String, - selections: List, - coercedVariables: Map, + objectType: String, + selections: List, + coercedVariables: Map, ): Map> { val groupedFields = mutableMapOf>() collectFields(objectType, selections, coercedVariables, mutableSetOf(), groupedFields) @@ -584,11 +593,11 @@ internal class OperationContext( } private fun collectFields( - objectType: String, - selections: List, - coercedVariables: Map, - visitedFragments: MutableSet, - groupedFields: MutableMap>, + objectType: String, + selections: List, + coercedVariables: Map, + visitedFragments: MutableSet, + groupedFields: MutableMap>, ) { selections.forEach { selection -> if (selection.directives.shouldSkip(coercedVariables)) { @@ -625,12 +634,6 @@ internal class OperationContext( } } -private fun List.orNullIfEmpty(): List? { - return this.ifEmpty { - null - } -} - private val GQLSelection.directives: List get() = when (this) { is GQLField -> directives @@ -638,6 +641,3 @@ private val GQLSelection.directives: List is GQLInlineFragment -> directives } -private fun GQLOperationDefinition.bubbles(): Boolean { - return !directives.any { it.name == "noBubblesPlz" } -} diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/introspection.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/introspection.kt index 67ce34c92ac..a135e622017 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/introspection.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/introspection.kt @@ -14,6 +14,9 @@ internal val introspectionCoercings = mapOf( "__DirectiveLocation" to StringCoercing, ) +/** + * Returns a resolver that can resolve all fields that start with `__` or whose parent starts with `__` + */ internal fun introspectionResolver(schema: Schema): Resolver { val resolvers = introspectionResolvers(schema) return Resolver { @@ -291,13 +294,17 @@ internal fun introspectionResolvers(schema: Schema): Map { "__Service" to mapOf( "description" to Resolver { null }, "capabilities" to Resolver { - emptyList() + val serviceDefinition = schema.toGQLDocument().definitions.filterIsInstance().firstOrNull() + if (serviceDefinition == null) { + return@Resolver emptyList() + } + serviceDefinition.capabilities } ), "__Capability" to mapOf( - "description" to Resolver { it.parentObject.cast().description }, - "identifier" to Resolver { it.parentObject.cast().identifier }, - "value" to Resolver { it.parentObject.cast().value }, + "description" to Resolver { it.parentObject.cast().description }, + "identifier" to Resolver { it.parentObject.cast().name }, + "value" to Resolver { it.parentObject.cast().value }, ) ).entries.flatMap { (type, fields) -> fields.entries.map { (field, resolver) -> @@ -319,12 +326,6 @@ private class IntrospectionType( val typeDefinition: GQLTypeDefinition?, ) -private class IntrospectionCapability( - val description: String?, - val identifier: String, - val value: String?, -) - internal enum class __TypeKind { SCALAR, OBJECT, diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/prepare.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/prepare.kt index 448e099a6b5..99c988ce8fc 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/prepare.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/prepare.kt @@ -11,14 +11,16 @@ import com.apollographql.apollo.execution.ErrorPersistedDocument import com.apollographql.apollo.execution.ExternalValue import com.apollographql.apollo.execution.GraphQLRequest import com.apollographql.apollo.execution.InternalValue +import com.apollographql.apollo.execution.OnError import com.apollographql.apollo.execution.PersistedDocument import com.apollographql.apollo.execution.PersistedDocumentCache import com.apollographql.apollo.execution.ValidPersistedDocument internal class PreparedRequest( - val operation: GQLOperationDefinition, - val fragments: Map, - val variables: Map + val operation: GQLOperationDefinition, + val fragments: Map, + val variables: Map, + val onError: OnError?, ) /** @@ -27,14 +29,14 @@ internal class PreparedRequest( */ internal fun validateDocument(schema: Schema, document: String): Either, GQLDocument> { val parseResult = document.parseAsGQLDocument() - var issues = parseResult.issues.filter { it is GraphQLIssue } + var issues = parseResult.issues.filterIsInstance() if (issues.isNotEmpty()) { return issues.left() } val gqlDocument = parseResult.getOrThrow() val validationResult = gqlDocument.validateAsExecutable(schema) - issues = validationResult.issues.filter { it is GraphQLIssue } + issues = validationResult.issues.filterIsInstance() if (issues.isNotEmpty()) { return issues.left() } @@ -52,11 +54,12 @@ internal fun validateDocument(schema: Schema, document: String): Either.prepareRequest( - schema: Schema, - coercings: Map>, - document: GQLDocument, - operationName: String?, - variables: Map + schema: Schema, + coercings: Map>, + document: GQLDocument, + operationName: String?, + variables: Map, + onError: OnError? ): PreparedRequest { val operations = document.definitions.filterIsInstance() val operation = when { @@ -86,13 +89,17 @@ internal fun Raise.prepareRequest( } catch (e: Exception) { raise("Cannot coerce variable values: '${e.message}'") } - return PreparedRequest(operation, fragments, variableValues) + return PreparedRequest(operation, fragments, variableValues, onError) } /** * Returns a [com.apollographql.apollo.execution.PersistedDocument]. If no cache is configured, a new [com.apollographql.apollo.execution.PersistedDocument] is computed for each request. */ -internal fun Raise.getPersistedDocument(schema: Schema, persistedDocumentCache: PersistedDocumentCache?, request: GraphQLRequest): PersistedDocument { +internal fun Raise.getPersistedDocument( + schema: Schema, + persistedDocumentCache: PersistedDocumentCache?, + request: GraphQLRequest, +): PersistedDocument { val persistedQuery = request.extensions.get("persistedQuery") var persistedDocument: PersistedDocument? if (persistedQuery != null) { @@ -137,19 +144,19 @@ internal fun Raise.getPersistedDocument(schema: Schema, persistedDocumen } private fun Either, GQLDocument>.toPersistedDocument() = fold( - ifLeft = { - ErrorPersistedDocument(it) - }, - ifRight = { - ValidPersistedDocument(it) - } + ifLeft = { + ErrorPersistedDocument(it) + }, + ifRight = { + ValidPersistedDocument(it) + } ) internal fun Raise>.prepareRequest( - schema: Schema, - coercings: Map>, - persistedDocumentCache: PersistedDocumentCache?, - request: GraphQLRequest + schema: Schema, + coercings: Map>, + persistedDocumentCache: PersistedDocumentCache?, + request: GraphQLRequest, ): PreparedRequest { val persistedDocument = withError({ singleGraphQLError(it) @@ -166,15 +173,15 @@ internal fun Raise>.prepareRequest( return withError({ singleGraphQLError(it) }) { - prepareRequest(schema, coercings, persistedDocument.document, request.operationName, request.variables) + prepareRequest(schema, coercings, persistedDocument.document, request.operationName, request.variables, request.onError) } } internal fun prepareRequest( - schema: Schema, - coercings: Map>, - persistedDocumentCache: PersistedDocumentCache?, - request: GraphQLRequest + schema: Schema, + coercings: Map>, + persistedDocumentCache: PersistedDocumentCache?, + request: GraphQLRequest, ): Either, PreparedRequest> = either { prepareRequest(schema, coercings, persistedDocumentCache, request) } \ No newline at end of file diff --git a/libraries/apollo-execution/src/concurrentTest/kotlin/test/ExecutionTest.kt b/libraries/apollo-execution/src/concurrentTest/kotlin/test/ExecutionTest.kt index b1a04a0c8fe..3829f901d91 100644 --- a/libraries/apollo-execution/src/concurrentTest/kotlin/test/ExecutionTest.kt +++ b/libraries/apollo-execution/src/concurrentTest/kotlin/test/ExecutionTest.kt @@ -4,8 +4,9 @@ package test import com.apollographql.apollo.api.ExecutionContext import com.apollographql.apollo.execution.ExecutableSchema +import com.apollographql.apollo.execution.GraphQLRequest +import com.apollographql.apollo.execution.OnError import com.apollographql.apollo.execution.SubscriptionResponse -import com.apollographql.apollo.execution.toGraphQLRequest import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -15,6 +16,12 @@ import kotlin.test.Test import kotlin.test.assertEquals +fun String.toGraphQLRequest(): GraphQLRequest { + return GraphQLRequest.Builder() + .document(this) + .build() +} + class ExecutionTest { @Test @@ -32,13 +39,13 @@ class ExecutionTest { """.trimIndent() val response = ExecutableSchema.Builder() - .schema(schema) - .resolver { resolveInfo -> - if (resolveInfo.parentType != "Query" || resolveInfo.fieldName != "foo") return@resolver null - return@resolver "42" - } - .build() - .execute(document.toGraphQLRequest(), ExecutionContext.Empty) + .schema(schema) + .resolver { resolveInfo -> + if (resolveInfo.parentType != "Query" || resolveInfo.fieldName != "foo") return@resolver null + return@resolver "42" + } + .build() + .execute(document.toGraphQLRequest(), ExecutionContext.Empty) assertEquals(mapOf("foo" to "42"), response.data) assertEquals(null, response.errors) } @@ -58,12 +65,12 @@ class ExecutionTest { """.trimIndent() val response = ExecutableSchema.Builder() - .schema(schema) - .resolver { resolveInfo -> - resolveInfo.getArgument("first").getOrNull()?.toString(16) - } - .build() - .execute(document.toGraphQLRequest(), ExecutionContext.Empty) + .schema(schema) + .resolver { resolveInfo -> + resolveInfo.getArgument("first").getOrNull()?.toString(16) + } + .build() + .execute(document.toGraphQLRequest(), ExecutionContext.Empty) assertEquals(mapOf("foo" to "2a"), response.data) assertEquals(null, response.errors) @@ -87,31 +94,31 @@ class ExecutionTest { """.trimIndent() val executableSchema = ExecutableSchema.Builder() - .schema(schema) - .resolver { resolveInfo -> - val ret: Flow = when (resolveInfo.parentType) { - "Subscription" -> flow { - repeat(5) { - emit(it) - delay(1) + .schema(schema) + .resolver { resolveInfo -> + val ret: Flow = when (resolveInfo.parentType) { + "Subscription" -> flow { + repeat(5) { + emit(it) + delay(1) + } } + + else -> error("never called") } - else -> error("never called") + ret } - - ret - } - .build() + .build() val response = runBlocking { executableSchema.subscribe(document.toGraphQLRequest()).toList() } assertEquals( - listOf(0, 1, 2, 3, 4), - response.filterIsInstance().map { it.response.data as Map } - .map { it.get("foo") } + listOf(0, 1, 2, 3, 4), + response.filterIsInstance().map { it.response.data as Map } + .map { it.get("foo") } ) } @@ -134,8 +141,8 @@ class ExecutionTest { """.trimIndent() val executableSchema = ExecutableSchema.Builder() - .schema(schema) - .build() + .schema(schema) + .build() val response = runBlocking { executableSchema.subscribe(document.toGraphQLRequest()).toList() @@ -163,45 +170,50 @@ class ExecutionTest { """.trimIndent() ExecutableSchema.Builder() - .schema(schema) - .resolver { - null - } - .build() - .execute(document.toGraphQLRequest(), ExecutionContext.Empty) - .apply { - assertEquals(null, data) - assertEquals("A resolver returned null in a non-nullable position", errors.orEmpty().single().message) - assertEquals(listOf("foo"), errors.orEmpty().single().path) - } + .schema(schema) + .resolver { + null + } + .build() + .execute(document.toGraphQLRequest(), ExecutionContext.Empty) + .apply { + assertEquals(null, data) + assertEquals("A resolver returned null in a non-nullable position", errors.orEmpty().single().message) + assertEquals(listOf("foo"), errors.orEmpty().single().path) + } } @Test - fun noBubblesPlz(): Unit = runBlocking { + fun onErrorNull(): Unit = runBlocking { val schema = """ type Query { foo: String! - } - directive @noBubblesPlz on QUERY | MUTATION | SUBSCRIPTION + } """.trimIndent() val document = """ - query GetFoo @noBubblesPlz { + query GetFoo { foo } """.trimIndent() ExecutableSchema.Builder() - .schema(schema) - .resolver { - null - } - .build() - .execute(document.toGraphQLRequest(), ExecutionContext.Empty) - .apply { - assertEquals(mapOf("foo" to null), data) - assertEquals("A resolver returned null in a non-nullable position", errors.orEmpty().single().message) - assertEquals(listOf("foo"), errors.orEmpty().single().path) - } + .schema(schema) + .resolver { + null + } + .build() + .execute( + GraphQLRequest.Builder() + .onError(OnError.NULL) + .document(document) + .build(), + ExecutionContext.Empty + ) + .apply { + assertEquals(mapOf("foo" to null), data) + assertEquals("A resolver returned null in a non-nullable position", errors.orEmpty().single().message) + assertEquals(listOf("foo"), errors.orEmpty().single().path) + } } } \ No newline at end of file diff --git a/libraries/apollo-execution/src/jvmTest/kotlin/test/IntrospectionTest.kt b/libraries/apollo-execution/src/jvmTest/kotlin/test/IntrospectionTest.kt index 9ea40e9f8d2..59ba2d7516d 100644 --- a/libraries/apollo-execution/src/jvmTest/kotlin/test/IntrospectionTest.kt +++ b/libraries/apollo-execution/src/jvmTest/kotlin/test/IntrospectionTest.kt @@ -1,18 +1,22 @@ package test import com.apollographql.apollo.api.ExecutionContext +import com.apollographql.apollo.api.json.buildJsonString +import com.apollographql.apollo.api.json.writeAny +import com.apollographql.apollo.api.json.writeObject import com.apollographql.apollo.execution.ExecutableSchema -import com.apollographql.apollo.execution.toGraphQLRequest import kotlinx.coroutines.runBlocking import okio.FileSystem import okio.Path.Companion.toPath import okio.buffer +import java.io.File import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertNull class IntrospectionTest { @Test - fun introspection() = runBlocking { + fun introspectionSucceeds() = runBlocking { val schema = """ type Query { foo: String! @@ -21,7 +25,7 @@ class IntrospectionTest { val document = - FileSystem.SYSTEM.openReadOnly("testFixtures/introspection.graphql".toPath()).source().buffer().readUtf8() + FileSystem.SYSTEM.openReadOnly("testFixtures/introspection-query.graphql".toPath()).source().buffer().readUtf8() val response = ExecutableSchema.Builder() .schema(schema) @@ -30,6 +34,29 @@ class IntrospectionTest { document.toGraphQLRequest(), ExecutionContext.Empty ) - assertNull(response.errors) + checkExpected(File("testFixtures/introspection-response.json"), buildJsonString(indent = " ") { + writeObject { + name("data") + writeAny(response.data) + name("errors") + writeAny(response.errors) + } + }) } } + +internal fun checkExpected(expectedFile: File, actual: String) { + val expected = try { + expectedFile.readText() + } catch (e: Exception) { + null + } + + if (shouldUpdateTestFixtures()) { + expectedFile.writeText(actual) + } else { + assertEquals(expected, actual) + } +} + +fun shouldUpdateTestFixtures() = System.getenv("updateTestFixtures") != null diff --git a/libraries/apollo-execution/src/jvmTest/kotlin/test/ParallelExecutionTest.kt b/libraries/apollo-execution/src/jvmTest/kotlin/test/ParallelExecutionTest.kt index b5fa9066a2a..8c8e4596a45 100644 --- a/libraries/apollo-execution/src/jvmTest/kotlin/test/ParallelExecutionTest.kt +++ b/libraries/apollo-execution/src/jvmTest/kotlin/test/ParallelExecutionTest.kt @@ -4,7 +4,6 @@ package test import com.apollographql.apollo.ast.toGQLDocument import com.apollographql.apollo.execution.ExecutableSchema -import com.apollographql.apollo.execution.toGraphQLRequest import kotlinx.coroutines.* import kotlin.coroutines.EmptyCoroutineContext import kotlin.test.Test diff --git a/libraries/apollo-execution/testFixtures/introspection.graphql b/libraries/apollo-execution/testFixtures/introspection-query.graphql similarity index 100% rename from libraries/apollo-execution/testFixtures/introspection.graphql rename to libraries/apollo-execution/testFixtures/introspection-query.graphql diff --git a/libraries/apollo-execution/testFixtures/introspection-response.json b/libraries/apollo-execution/testFixtures/introspection-response.json new file mode 100644 index 00000000000..57e0d603b59 --- /dev/null +++ b/libraries/apollo-execution/testFixtures/introspection-response.json @@ -0,0 +1,1257 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "__Schema", + "description": null, + "fields": [ + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "types", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": null, + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "specifiedByURL", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isRepeatable", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "__Service", + "description": null, + "fields": [ + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "capabilities", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Capability", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "__Capability", + "description": null, + "fields": [ + { + "name": "identifier", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "value", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "foo", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "SCALAR", + "name": "Float", + "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null, + "specifiedByURL": null + } + ], + "directives": [ + { + "name": "skip", + "description": null, + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ], + "isRepeatable": false + }, + { + "name": "include", + "description": null, + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ], + "isRepeatable": false + }, + { + "name": "deprecated", + "description": null, + "locations": [ + "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INPUT_FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": "\"No longer supported\"" + } + ], + "isRepeatable": false + }, + { + "name": "defer", + "description": null, + "locations": [ + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "label", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "if", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": "true" + } + ], + "isRepeatable": false + }, + { + "name": "specifiedBy", + "description": null, + "locations": [ + "SCALAR" + ], + "args": [ + { + "name": "url", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "isRepeatable": false + } + ] + }, + "__service": { + "description": null, + "capabilities": [ + { + "description": null, + "identifier": "graphql.onError", + "value": null + }, + { + "description": null, + "identifier": "graphql.defaultErrorBehavior", + "value": "PROPAGATE" + } + ] + } + }, + "errors": null +} \ No newline at end of file