diff --git a/libraries/apollo-ast/api/apollo-ast.api b/libraries/apollo-ast/api/apollo-ast.api index bf062c5b3c5..6cf9a0c396c 100644 --- a/libraries/apollo-ast/api/apollo-ast.api +++ b/libraries/apollo-ast/api/apollo-ast.api @@ -188,6 +188,20 @@ public final class com/apollographql/apollo/ast/GQLBooleanValue : com/apollograp public fun writeInternal (Lcom/apollographql/apollo/ast/SDLWriter;)V } +public final class com/apollographql/apollo/ast/GQLCapability : com/apollographql/apollo/ast/GQLNode { + public fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/apollo/ast/GQLCapability; + public static synthetic fun copy$default (Lcom/apollographql/apollo/ast/GQLCapability;Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/apollographql/apollo/ast/GQLCapability; + 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 fun getSourceLocation ()Lcom/apollographql/apollo/ast/SourceLocation; + public final fun getValue ()Ljava/lang/String; + public fun writeInternal (Lcom/apollographql/apollo/ast/SDLWriter;)V +} + public abstract interface class com/apollographql/apollo/ast/GQLDefinition : com/apollographql/apollo/ast/GQLNode { } @@ -803,6 +817,33 @@ public final class com/apollographql/apollo/ast/GQLSelectionSet : com/apollograp public fun writeInternal (Lcom/apollographql/apollo/ast/SDLWriter;)V } +public final class com/apollographql/apollo/ast/GQLServiceDefinition : com/apollographql/apollo/ast/GQLDefinition, com/apollographql/apollo/ast/GQLDescribed, com/apollographql/apollo/ast/GQLHasDirectives { + public fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Lcom/apollographql/apollo/ast/GQLServiceDefinition; + public static synthetic fun copy$default (Lcom/apollographql/apollo/ast/GQLServiceDefinition;Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/apollographql/apollo/ast/GQLServiceDefinition; + public fun copyWithNewChildrenInternal (Lcom/apollographql/apollo/ast/NodeContainer;)Lcom/apollographql/apollo/ast/GQLNode; + public final fun getCapabilities ()Ljava/util/List; + public fun getChildren ()Ljava/util/List; + public fun getDescription ()Ljava/lang/String; + public fun getDirectives ()Ljava/util/List; + public fun getSourceLocation ()Lcom/apollographql/apollo/ast/SourceLocation; + public fun writeInternal (Lcom/apollographql/apollo/ast/SDLWriter;)V +} + +public final class com/apollographql/apollo/ast/GQLServiceExtension : com/apollographql/apollo/ast/GQLDefinition, com/apollographql/apollo/ast/GQLHasDirectives, com/apollographql/apollo/ast/GQLTypeSystemExtension { + public fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/util/List;Ljava/util/List;)Lcom/apollographql/apollo/ast/GQLServiceExtension; + public static synthetic fun copy$default (Lcom/apollographql/apollo/ast/GQLServiceExtension;Lcom/apollographql/apollo/ast/SourceLocation;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/apollographql/apollo/ast/GQLServiceExtension; + public fun copyWithNewChildrenInternal (Lcom/apollographql/apollo/ast/NodeContainer;)Lcom/apollographql/apollo/ast/GQLNode; + public final fun getCapabilities ()Ljava/util/List; + public fun getChildren ()Ljava/util/List; + public fun getDirectives ()Ljava/util/List; + public fun getSourceLocation ()Lcom/apollographql/apollo/ast/SourceLocation; + public fun writeInternal (Lcom/apollographql/apollo/ast/SDLWriter;)V +} + public final class com/apollographql/apollo/ast/GQLStringValue : com/apollographql/apollo/ast/GQLValue { public fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;)V public synthetic fun (Lcom/apollographql/apollo/ast/SourceLocation;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -1092,9 +1133,10 @@ public final class com/apollographql/apollo/ast/OtherValidationIssue : com/apoll public final class com/apollographql/apollo/ast/ParserOptions { public static final field Companion Lcom/apollographql/apollo/ast/ParserOptions$Companion; - public synthetic fun (ZZZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZZZZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getAllowDirectivesOnDirectives ()Z public final fun getAllowEmptyDocuments ()Z + public final fun getAllowServiceCapabilities ()Z public final fun getWithSourceLocation ()Z } @@ -1102,12 +1144,15 @@ public final class com/apollographql/apollo/ast/ParserOptions$Builder { public fun ()V public final fun allowDirectivesOnDirectives (Z)Lcom/apollographql/apollo/ast/ParserOptions$Builder; public final fun allowEmptyDocuments (Z)Lcom/apollographql/apollo/ast/ParserOptions$Builder; + public final fun allowServiceCapabilities (Z)Lcom/apollographql/apollo/ast/ParserOptions$Builder; public final fun build ()Lcom/apollographql/apollo/ast/ParserOptions; public final fun getAllowDirectivesOnDirectives ()Z public final fun getAllowEmptyDocuments ()Z + public final fun getAllowServiceCapabilities ()Z public final fun getWithSourceLocation ()Z public final fun setAllowDirectivesOnDirectives (Z)V public final fun setAllowEmptyDocuments (Z)V + public final fun setAllowServiceCapabilities (Z)V public final fun setWithSourceLocation (Z)V public final fun withSourceLocation (Z)Lcom/apollographql/apollo/ast/ParserOptions$Builder; } @@ -1355,7 +1400,6 @@ public final class com/apollographql/apollo/ast/introspection/ApolloIntrospectio } public final class com/apollographql/apollo/ast/introspection/Introspection { - public static final fun normalize (Lcom/apollographql/apollo/ast/introspection/IntrospectionSchema;)Lcom/apollographql/apollo/ast/introspection/IntrospectionSchema; public static final fun toGQLDocument (Lcom/apollographql/apollo/ast/introspection/IntrospectionSchema;)Lcom/apollographql/apollo/ast/GQLDocument; public static final fun toIntrospectionSchema (Lcom/apollographql/apollo/ast/GQLDocument;)Lcom/apollographql/apollo/ast/introspection/ApolloIntrospectionSchema; public static final fun toIntrospectionSchema (Ljava/io/File;)Lcom/apollographql/apollo/ast/introspection/IntrospectionSchema; diff --git a/libraries/apollo-ast/api/apollo-ast.klib.api b/libraries/apollo-ast/api/apollo-ast.klib.api index d3771ee5903..6185d7a4c07 100644 --- a/libraries/apollo-ast/api/apollo-ast.klib.api +++ b/libraries/apollo-ast/api/apollo-ast.klib.api @@ -314,6 +314,25 @@ final class com.apollographql.apollo.ast/GQLBooleanValue : com.apollographql.apo final fun writeInternal(com.apollographql.apollo.ast/SDLWriter) // com.apollographql.apollo.ast/GQLBooleanValue.writeInternal|writeInternal(com.apollographql.apollo.ast.SDLWriter){}[0] } +final class com.apollographql.apollo.ast/GQLCapability : com.apollographql.apollo.ast/GQLNode { // com.apollographql.apollo.ast/GQLCapability|null[0] + constructor (com.apollographql.apollo.ast/SourceLocation? = ..., kotlin/String?, kotlin/String, kotlin/String?) // com.apollographql.apollo.ast/GQLCapability.|(com.apollographql.apollo.ast.SourceLocation?;kotlin.String?;kotlin.String;kotlin.String?){}[0] + + final val children // com.apollographql.apollo.ast/GQLCapability.children|{}children[0] + 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 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] + final fun (): kotlin/String? // com.apollographql.apollo.ast/GQLCapability.value.|(){}[0] + + final fun copy(com.apollographql.apollo.ast/SourceLocation? = ..., kotlin/String? = ..., kotlin/String = ..., kotlin/String? = ...): com.apollographql.apollo.ast/GQLCapability // com.apollographql.apollo.ast/GQLCapability.copy|copy(com.apollographql.apollo.ast.SourceLocation?;kotlin.String?;kotlin.String;kotlin.String?){}[0] + final fun copyWithNewChildrenInternal(com.apollographql.apollo.ast/NodeContainer): com.apollographql.apollo.ast/GQLNode // com.apollographql.apollo.ast/GQLCapability.copyWithNewChildrenInternal|copyWithNewChildrenInternal(com.apollographql.apollo.ast.NodeContainer){}[0] + final fun writeInternal(com.apollographql.apollo.ast/SDLWriter) // com.apollographql.apollo.ast/GQLCapability.writeInternal|writeInternal(com.apollographql.apollo.ast.SDLWriter){}[0] +} + final class com.apollographql.apollo.ast/GQLDirective : com.apollographql.apollo.ast/GQLNamed, com.apollographql.apollo.ast/GQLNode { // com.apollographql.apollo.ast/GQLDirective|null[0] constructor (com.apollographql.apollo.ast/SourceLocation? = ..., kotlin/String, kotlin.collections/List) // com.apollographql.apollo.ast/GQLDirective.|(com.apollographql.apollo.ast.SourceLocation?;kotlin.String;kotlin.collections.List){}[0] @@ -1050,6 +1069,42 @@ final class com.apollographql.apollo.ast/GQLSelectionSet : com.apollographql.apo final fun writeInternal(com.apollographql.apollo.ast/SDLWriter) // com.apollographql.apollo.ast/GQLSelectionSet.writeInternal|writeInternal(com.apollographql.apollo.ast.SDLWriter){}[0] } +final class com.apollographql.apollo.ast/GQLServiceDefinition : com.apollographql.apollo.ast/GQLDefinition, com.apollographql.apollo.ast/GQLDescribed, com.apollographql.apollo.ast/GQLHasDirectives { // com.apollographql.apollo.ast/GQLServiceDefinition|null[0] + constructor (com.apollographql.apollo.ast/SourceLocation? = ..., kotlin/String?, kotlin.collections/List, kotlin.collections/List) // com.apollographql.apollo.ast/GQLServiceDefinition.|(com.apollographql.apollo.ast.SourceLocation?;kotlin.String?;kotlin.collections.List;kotlin.collections.List){}[0] + + final val capabilities // com.apollographql.apollo.ast/GQLServiceDefinition.capabilities|{}capabilities[0] + final fun (): kotlin.collections/List // com.apollographql.apollo.ast/GQLServiceDefinition.capabilities.|(){}[0] + final val children // com.apollographql.apollo.ast/GQLServiceDefinition.children|{}children[0] + final fun (): kotlin.collections/List // com.apollographql.apollo.ast/GQLServiceDefinition.children.|(){}[0] + final val description // com.apollographql.apollo.ast/GQLServiceDefinition.description|{}description[0] + final fun (): kotlin/String? // com.apollographql.apollo.ast/GQLServiceDefinition.description.|(){}[0] + final val directives // com.apollographql.apollo.ast/GQLServiceDefinition.directives|{}directives[0] + final fun (): kotlin.collections/List // com.apollographql.apollo.ast/GQLServiceDefinition.directives.|(){}[0] + final val sourceLocation // com.apollographql.apollo.ast/GQLServiceDefinition.sourceLocation|{}sourceLocation[0] + final fun (): com.apollographql.apollo.ast/SourceLocation? // com.apollographql.apollo.ast/GQLServiceDefinition.sourceLocation.|(){}[0] + + final fun copy(com.apollographql.apollo.ast/SourceLocation? = ..., kotlin/String? = ..., kotlin.collections/List = ..., kotlin.collections/List = ...): com.apollographql.apollo.ast/GQLServiceDefinition // com.apollographql.apollo.ast/GQLServiceDefinition.copy|copy(com.apollographql.apollo.ast.SourceLocation?;kotlin.String?;kotlin.collections.List;kotlin.collections.List){}[0] + final fun copyWithNewChildrenInternal(com.apollographql.apollo.ast/NodeContainer): com.apollographql.apollo.ast/GQLNode // com.apollographql.apollo.ast/GQLServiceDefinition.copyWithNewChildrenInternal|copyWithNewChildrenInternal(com.apollographql.apollo.ast.NodeContainer){}[0] + final fun writeInternal(com.apollographql.apollo.ast/SDLWriter) // com.apollographql.apollo.ast/GQLServiceDefinition.writeInternal|writeInternal(com.apollographql.apollo.ast.SDLWriter){}[0] +} + +final class com.apollographql.apollo.ast/GQLServiceExtension : com.apollographql.apollo.ast/GQLDefinition, com.apollographql.apollo.ast/GQLHasDirectives, com.apollographql.apollo.ast/GQLTypeSystemExtension { // com.apollographql.apollo.ast/GQLServiceExtension|null[0] + constructor (com.apollographql.apollo.ast/SourceLocation? = ..., kotlin.collections/List, kotlin.collections/List) // com.apollographql.apollo.ast/GQLServiceExtension.|(com.apollographql.apollo.ast.SourceLocation?;kotlin.collections.List;kotlin.collections.List){}[0] + + final val capabilities // com.apollographql.apollo.ast/GQLServiceExtension.capabilities|{}capabilities[0] + final fun (): kotlin.collections/List // com.apollographql.apollo.ast/GQLServiceExtension.capabilities.|(){}[0] + final val children // com.apollographql.apollo.ast/GQLServiceExtension.children|{}children[0] + final fun (): kotlin.collections/List // com.apollographql.apollo.ast/GQLServiceExtension.children.|(){}[0] + final val directives // com.apollographql.apollo.ast/GQLServiceExtension.directives|{}directives[0] + final fun (): kotlin.collections/List // com.apollographql.apollo.ast/GQLServiceExtension.directives.|(){}[0] + final val sourceLocation // com.apollographql.apollo.ast/GQLServiceExtension.sourceLocation|{}sourceLocation[0] + final fun (): com.apollographql.apollo.ast/SourceLocation? // com.apollographql.apollo.ast/GQLServiceExtension.sourceLocation.|(){}[0] + + final fun copy(com.apollographql.apollo.ast/SourceLocation? = ..., kotlin.collections/List = ..., kotlin.collections/List = ...): com.apollographql.apollo.ast/GQLServiceExtension // com.apollographql.apollo.ast/GQLServiceExtension.copy|copy(com.apollographql.apollo.ast.SourceLocation?;kotlin.collections.List;kotlin.collections.List){}[0] + final fun copyWithNewChildrenInternal(com.apollographql.apollo.ast/NodeContainer): com.apollographql.apollo.ast/GQLNode // com.apollographql.apollo.ast/GQLServiceExtension.copyWithNewChildrenInternal|copyWithNewChildrenInternal(com.apollographql.apollo.ast.NodeContainer){}[0] + final fun writeInternal(com.apollographql.apollo.ast/SDLWriter) // com.apollographql.apollo.ast/GQLServiceExtension.writeInternal|writeInternal(com.apollographql.apollo.ast.SDLWriter){}[0] +} + final class com.apollographql.apollo.ast/GQLStringValue : com.apollographql.apollo.ast/GQLValue { // com.apollographql.apollo.ast/GQLStringValue|null[0] constructor (com.apollographql.apollo.ast/SourceLocation? = ..., kotlin/String) // com.apollographql.apollo.ast/GQLStringValue.|(com.apollographql.apollo.ast.SourceLocation?;kotlin.String){}[0] @@ -1261,6 +1316,8 @@ final class com.apollographql.apollo.ast/ParserOptions { // com.apollographql.ap final fun (): kotlin/Boolean // com.apollographql.apollo.ast/ParserOptions.allowDirectivesOnDirectives.|(){}[0] final val allowEmptyDocuments // com.apollographql.apollo.ast/ParserOptions.allowEmptyDocuments|{}allowEmptyDocuments[0] final fun (): kotlin/Boolean // com.apollographql.apollo.ast/ParserOptions.allowEmptyDocuments.|(){}[0] + final val allowServiceCapabilities // com.apollographql.apollo.ast/ParserOptions.allowServiceCapabilities|{}allowServiceCapabilities[0] + final fun (): kotlin/Boolean // com.apollographql.apollo.ast/ParserOptions.allowServiceCapabilities.|(){}[0] final val withSourceLocation // com.apollographql.apollo.ast/ParserOptions.withSourceLocation|{}withSourceLocation[0] final fun (): kotlin/Boolean // com.apollographql.apollo.ast/ParserOptions.withSourceLocation.|(){}[0] @@ -1273,12 +1330,16 @@ final class com.apollographql.apollo.ast/ParserOptions { // com.apollographql.ap final var allowEmptyDocuments // com.apollographql.apollo.ast/ParserOptions.Builder.allowEmptyDocuments|{}allowEmptyDocuments[0] final fun (): kotlin/Boolean // com.apollographql.apollo.ast/ParserOptions.Builder.allowEmptyDocuments.|(){}[0] final fun (kotlin/Boolean) // com.apollographql.apollo.ast/ParserOptions.Builder.allowEmptyDocuments.|(kotlin.Boolean){}[0] + final var allowServiceCapabilities // com.apollographql.apollo.ast/ParserOptions.Builder.allowServiceCapabilities|{}allowServiceCapabilities[0] + final fun (): kotlin/Boolean // com.apollographql.apollo.ast/ParserOptions.Builder.allowServiceCapabilities.|(){}[0] + final fun (kotlin/Boolean) // com.apollographql.apollo.ast/ParserOptions.Builder.allowServiceCapabilities.|(kotlin.Boolean){}[0] final var withSourceLocation // com.apollographql.apollo.ast/ParserOptions.Builder.withSourceLocation|{}withSourceLocation[0] final fun (): kotlin/Boolean // com.apollographql.apollo.ast/ParserOptions.Builder.withSourceLocation.|(){}[0] final fun (kotlin/Boolean) // com.apollographql.apollo.ast/ParserOptions.Builder.withSourceLocation.|(kotlin.Boolean){}[0] final fun allowDirectivesOnDirectives(kotlin/Boolean): com.apollographql.apollo.ast/ParserOptions.Builder // com.apollographql.apollo.ast/ParserOptions.Builder.allowDirectivesOnDirectives|allowDirectivesOnDirectives(kotlin.Boolean){}[0] final fun allowEmptyDocuments(kotlin/Boolean): com.apollographql.apollo.ast/ParserOptions.Builder // com.apollographql.apollo.ast/ParserOptions.Builder.allowEmptyDocuments|allowEmptyDocuments(kotlin.Boolean){}[0] + final fun allowServiceCapabilities(kotlin/Boolean): com.apollographql.apollo.ast/ParserOptions.Builder // com.apollographql.apollo.ast/ParserOptions.Builder.allowServiceCapabilities|allowServiceCapabilities(kotlin.Boolean){}[0] final fun build(): com.apollographql.apollo.ast/ParserOptions // com.apollographql.apollo.ast/ParserOptions.Builder.build|build(){}[0] final fun withSourceLocation(kotlin/Boolean): com.apollographql.apollo.ast/ParserOptions.Builder // com.apollographql.apollo.ast/ParserOptions.Builder.withSourceLocation|withSourceLocation(kotlin.Boolean){}[0] } @@ -1612,7 +1673,6 @@ final const val com.apollographql.apollo.ast/NULLABILITY_VERSION // com.apollogr final fun (): kotlin/String // com.apollographql.apollo.ast/NULLABILITY_VERSION.|(){}[0] final fun (com.apollographql.apollo.ast.introspection/ApolloIntrospectionSchema).com.apollographql.apollo.ast.introspection/toJson(): kotlin/String // com.apollographql.apollo.ast.introspection/toJson|toJson@com.apollographql.apollo.ast.introspection.ApolloIntrospectionSchema(){}[0] -final fun (com.apollographql.apollo.ast.introspection/IntrospectionSchema).com.apollographql.apollo.ast.introspection/normalize(): com.apollographql.apollo.ast.introspection/IntrospectionSchema // com.apollographql.apollo.ast.introspection/normalize|normalize@com.apollographql.apollo.ast.introspection.IntrospectionSchema(){}[0] final fun (com.apollographql.apollo.ast.introspection/IntrospectionSchema).com.apollographql.apollo.ast.introspection/toGQLDocument(): com.apollographql.apollo.ast/GQLDocument // com.apollographql.apollo.ast.introspection/toGQLDocument|toGQLDocument@com.apollographql.apollo.ast.introspection.IntrospectionSchema(){}[0] final fun (com.apollographql.apollo.ast/GQLDirective).com.apollographql.apollo.ast/getArgumentValueOrDefault(kotlin/String, com.apollographql.apollo.ast/Schema): com.apollographql.apollo.ast/GQLValue? // com.apollographql.apollo.ast/getArgumentValueOrDefault|getArgumentValueOrDefault@com.apollographql.apollo.ast.GQLDirective(kotlin.String;com.apollographql.apollo.ast.Schema){}[0] final fun (com.apollographql.apollo.ast/GQLDocument).com.apollographql.apollo.ast.introspection/toIntrospectionSchema(): com.apollographql.apollo.ast.introspection/ApolloIntrospectionSchema // com.apollographql.apollo.ast.introspection/toIntrospectionSchema|toIntrospectionSchema@com.apollographql.apollo.ast.GQLDocument(){}[0] diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Schema.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Schema.kt index 30984085e00..eedde28b481 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Schema.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Schema.kt @@ -44,6 +44,8 @@ class Schema internal constructor( originalDirectiveName(it.key) == CATCH } + internal val hasService: Boolean = typeDefinitions.get("__Service") != null + val queryTypeDefinition: GQLTypeDefinition = rootOperationTypeDefinition("query", definitions) ?: throw SchemaValidationException("No query root type found") diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/api.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/api.kt index 04fba443aaa..314497b25ad 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/api.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/api.kt @@ -76,6 +76,7 @@ class ParserOptions private constructor( val allowEmptyDocuments: Boolean, val withSourceLocation: Boolean, val allowDirectivesOnDirectives: Boolean, + val allowServiceCapabilities: Boolean ) { class Builder { var allowEmptyDocuments = true @@ -84,6 +85,9 @@ class ParserOptions private constructor( @ApolloExperimental var allowDirectivesOnDirectives = false + @ApolloExperimental + var allowServiceCapabilities = false + fun allowEmptyDocuments(allowEmptyDocuments: Boolean) = apply { this.allowEmptyDocuments = allowEmptyDocuments } @@ -92,6 +96,11 @@ class ParserOptions private constructor( this.withSourceLocation = withSourceLocation } + @ApolloExperimental + fun allowServiceCapabilities(allowServiceCapabilities: Boolean) = apply { + this.allowServiceCapabilities = allowServiceCapabilities + } + @ApolloExperimental fun allowDirectivesOnDirectives(allowDirectivesOnDirectives: Boolean) = apply { this.allowDirectivesOnDirectives = allowDirectivesOnDirectives @@ -102,6 +111,7 @@ class ParserOptions private constructor( allowEmptyDocuments = allowEmptyDocuments, withSourceLocation = withSourceLocation, allowDirectivesOnDirectives = allowDirectivesOnDirectives, + allowServiceCapabilities = allowServiceCapabilities ) } } 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 06d06f9a993..a195b1337ad 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 @@ -339,6 +339,142 @@ class GQLSchemaDefinition( } } +@ApolloExperimental +class GQLServiceDefinition( + override val sourceLocation: SourceLocation? = null, + override val description: String?, + override val directives: List, + val capabilities: List, +) : GQLDefinition, GQLDescribed, GQLHasDirectives { + + override val children = directives + capabilities + + override fun writeInternal(writer: SDLWriter) { + if (description == null && directives.isEmpty() && capabilities.isEmpty()) { + return + } + with(writer) { + writeDescription(description) + write("service ") + if (directives.isNotEmpty()) { + directives.join(writer) + write(" ") + } + write("{\n") + indent() + capabilities.join(writer, separator = "") + unindent() + write("}\n") + } + } + + fun copy( + sourceLocation: SourceLocation? = this.sourceLocation, + description: String? = this.description, + directives: List = this.directives, + capabilities: List = this.capabilities, + ): GQLServiceDefinition { + return GQLServiceDefinition( + sourceLocation = sourceLocation, + description = description, + directives = directives, + capabilities = capabilities, + ) + } + + override fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode { + return copy( + directives = container.take(), + capabilities = container.take(), + ) + } +} + +@ApolloExperimental +class GQLServiceExtension( + override val sourceLocation: SourceLocation? = null, + override val directives: List, + val capabilities: List, +) : GQLDefinition, GQLTypeSystemExtension, GQLHasDirectives { + + override val children = directives + capabilities + + override fun writeInternal(writer: SDLWriter) { + with(writer) { + write("extend service ") + if (directives.isNotEmpty()) { + directives.join(writer) + write(" ") + } + if (capabilities.isNotEmpty()) { + write("{\n") + indent() + capabilities.join(writer, separator = "") + unindent() + write("}\n") + } + } + } + + fun copy( + sourceLocation: SourceLocation? = this.sourceLocation, + directives: List = this.directives, + capabilities: List = this.capabilities, + ): GQLServiceExtension = GQLServiceExtension( + sourceLocation = sourceLocation, + directives = directives, + capabilities = capabilities, + ) + + override fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode { + return copy( + directives = container.take(), + capabilities = container.take() + ) + } +} + +@ApolloExperimental +class GQLCapability( + override val sourceLocation: SourceLocation? = null, + val description: String?, + val qualifiedName: String, + val value: String?, +) : GQLNode { + override val children: List + get() = emptyList() + + override fun writeInternal(writer: SDLWriter) { + with(writer) { + writeDescription(description) + write("capability ") + write(qualifiedName) + if (value != null) { + write("(\"$value\")") + } + write("\n") + } + } + + fun copy( + sourceLocation: SourceLocation? = this.sourceLocation, + description: String? = this.description, + qualifiedName: String = this.qualifiedName, + value: String? = this.value, + ): GQLCapability { + return GQLCapability( + sourceLocation = sourceLocation, + description = description, + qualifiedName = qualifiedName, + value = value + ) + } + + override fun copyWithNewChildrenInternal(container: NodeContainer): GQLNode { + return this + } +} + sealed class GQLTypeDefinition : GQLDefinition, GQLNamed, GQLDescribed, GQLHasDirectives { fun isBuiltIn(): Boolean = builtInTypes.contains(this.name) @@ -359,7 +495,9 @@ sealed class GQLTypeDefinition : GQLDefinition, GQLNamed, GQLDescribed, GQLHasDi "__EnumValue", "__TypeKind", "__Directive", - "__DirectiveLocation" + "__DirectiveLocation", + "__Service", + "__Capability" ) } } diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gqlfield.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gqlfield.kt index 907b96bfbbc..89bd5c0e5e0 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gqlfield.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gqlfield.kt @@ -17,6 +17,14 @@ private val schemaMetaFieldDefinition = GQLFieldDefinition( description = "" ) +private val serviceMetaFieldDefinition = GQLFieldDefinition( + name = "__service", + type = GQLNonNullType(type = GQLNamedType(name = "__Service")), + directives = emptyList(), + arguments = emptyList(), + description = "" +) + private val typeMetaFieldDefinition = GQLFieldDefinition( name = "__type", type = GQLNonNullType(type = GQLNamedType(name = "__Type")), @@ -40,10 +48,16 @@ fun GQLField.definitionFromScope(schema: Schema, rawTypename: String): GQLFieldD fun GQLTypeDefinition.fieldDefinitions(schema: Schema): List { return when (this) { is GQLObjectTypeDefinition -> { - if (name == schema.queryTypeDefinition.name) { - fields + typeMetaFieldDefinition + schemaMetaFieldDefinition + typenameMetaFieldDefinition - } else { - fields + typenameMetaFieldDefinition + buildList { + addAll(fields) + add(typenameMetaFieldDefinition) + if (name == schema.queryTypeDefinition.name) { + add(typeMetaFieldDefinition) + if (schema.hasService) { + add(schemaMetaFieldDefinition) + } + add(serviceMetaFieldDefinition) + } } } is GQLInterfaceTypeDefinition -> { 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 3e19d93cf9f..b04ef31e71a 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 @@ -25,6 +25,8 @@ import com.apollographql.apollo.ast.GQLScalarTypeDefinition import com.apollographql.apollo.ast.GQLScalarTypeExtension import com.apollographql.apollo.ast.GQLSchemaDefinition import com.apollographql.apollo.ast.GQLSchemaExtension +import com.apollographql.apollo.ast.GQLServiceDefinition +import com.apollographql.apollo.ast.GQLServiceExtension import com.apollographql.apollo.ast.GQLStringValue import com.apollographql.apollo.ast.GQLType import com.apollographql.apollo.ast.GQLTypeSystemExtension @@ -64,6 +66,7 @@ internal class ExtensionsMerger(private val definitions: List, in is GQLEnumTypeExtension -> mergeNamedDefinition(GQLEnumTypeDefinition::class, newDefinitions, definition, "enum") { mergeEnum(it, definition) } is GQLUnionTypeExtension -> mergeNamedDefinition(GQLUnionTypeDefinition::class, newDefinitions, definition, "union") { mergeUnion(it, definition) } is GQLDirectiveExtension -> mergeNamedDefinition(GQLDirectiveDefinition::class, newDefinitions, definition, "directive") { mergeDirective(it, definition) } + is GQLServiceExtension -> mergeTypedDefinition(GQLServiceDefinition::class, newDefinitions, definition, "service") { mergeService(it, definition) } } } @@ -274,6 +277,16 @@ private fun ExtensionsMerger.mergeSchema( ) } +private fun ExtensionsMerger.mergeService( + serviceDefinition: GQLServiceDefinition, + extension: GQLServiceExtension, +): GQLServiceDefinition = with(serviceDefinition) { + return copy( + directives = mergeDirectives(directives, extension.directives), + capabilities = mergeUniquesOrThrow(capabilities, extension.capabilities) { it.qualifiedName } + ) +} + private class MergedDirectives( val directives: List, val fieldDirectives: Map>, 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 d926fce6f78..b91989b5313 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 @@ -14,6 +14,7 @@ internal class Parser( private val withSourceLocation = options.withSourceLocation private val allowEmptyDocuments = options.allowEmptyDocuments private val allowDirectivesOnDirectives = options.allowDirectivesOnDirectives + private val allowServiceCapabilities = options.allowServiceCapabilities fun parseDocument(): GQLDocument { val start = token @@ -164,6 +165,18 @@ internal class Parser( return expectToken().value } + private fun parseQualifiedName(): String { + val components = mutableListOf() + components.add(parseName()) + expectToken() + components.add(parseName()) + while (peek()) { + advance() + components.add(parseName()) + } + return components.joinToString(".") + } + private inline fun peek(): Boolean { return token is T } @@ -339,6 +352,27 @@ internal class Parser( unexpected() } + private fun parseServiceCapability(): GQLCapability { + val start = token + val description = parseDescription() + + expectKeyword("capability") + + val qualifiedName = parseQualifiedName() + + var value: String? = null + if (expectOptionalToken() != null) { + value = expectToken().value + expectToken() + } + return GQLCapability( + sourceLocation = sourceLocation(start), + description = description, + qualifiedName = qualifiedName, + value = value + ) + } + private fun parseOperationTypeDefinition(): GQLOperationTypeDefinition { val start = token val operationType = parseOperationType() @@ -369,6 +403,23 @@ internal class Parser( ) } + private fun parseServiceDefinition(start: Token): GQLServiceDefinition { + val description = parseDescription() + + expectKeyword("service") + val directives = parseDirectives(const = true) + val capabilities = parseList { + parseServiceCapability() + } + + return GQLServiceDefinition( + sourceLocation = sourceLocation(start), + description = description, + directives = directives, + capabilities = capabilities + ) + } + private fun parseSchemaExtension(): GQLSchemaExtension { val start = token expectKeyword("extend") @@ -389,6 +440,22 @@ internal class Parser( ) } + private fun parseServiceExtension(): GQLServiceExtension { + val start = token + expectKeyword("extend") + expectKeyword("service") + val directives = parseDirectives(const = true) + val capabilities = parseNonEmptyListOrNull { + parseServiceCapability() + }.orEmpty() + + return GQLServiceExtension( + sourceLocation = sourceLocation(start), + directives = directives, + capabilities = capabilities + ) + } + private fun parseScalarTypeDefinition(start: Token): GQLScalarTypeDefinition { val description = parseDescription() expectKeyword("scalar") @@ -745,6 +812,12 @@ internal class Parser( } else { parseDirectiveExtension() } + "service" -> { + if (!allowServiceCapabilities) { + unexpected(t) + } + parseServiceExtension() + } else -> unexpected(t) } } @@ -757,9 +830,6 @@ internal class Parser( return when (t) { is Token.LeftBrace -> parseOperationDefinition(start) is Token.Name -> { - if (t.value == "extend" && hasDescription) { - throw ParserException("Type system extensions cannot have a description", t) - } when (t.value) { "schema" -> parseSchemaDefinition(start) "scalar" -> parseScalarTypeDefinition(start) @@ -771,7 +841,18 @@ internal class Parser( "directive" -> parseDirectiveDefinition(start) "query", "mutation", "subscription" -> parseOperationDefinition(start) "fragment" -> parseFragmentDefinition(start) - "extend" -> parseTypeSystemExtension() + "extend" -> { + if (hasDescription) { + throw ParserException("Type system extensions cannot have a description", t) + } + parseTypeSystemExtension() + } + "service" -> { + if (!allowServiceCapabilities) { + unexpected(t) + } + parseServiceDefinition(start) + } else -> unexpected(t) } } @@ -935,7 +1016,7 @@ internal class Parser( } } val name = parseName() - return GQLVariableValue( + GQLVariableValue( sourceLocation = sourceLocation(start), name = name ) 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 dc5475bd47c..5aee4d6d2dd 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 @@ -24,6 +24,7 @@ import com.apollographql.apollo.ast.GQLResult import com.apollographql.apollo.ast.GQLScalarTypeDefinition import com.apollographql.apollo.ast.GQLSchemaDefinition import com.apollographql.apollo.ast.GQLSchemaExtension +import com.apollographql.apollo.ast.GQLServiceDefinition import com.apollographql.apollo.ast.GQLStringValue import com.apollographql.apollo.ast.GQLTypeDefinition import com.apollographql.apollo.ast.GQLTypeDefinition.Companion.builtInTypes @@ -137,6 +138,11 @@ internal fun validateSchema(definitions: List, options: SchemaVal typeSystemExtensions.add(definition) } + is GQLServiceDefinition -> { + // TODO: any validation to do here? + // See https://github.com/graphql/graphql-spec/pull/1208/changes#r2732688632 + } + else -> { issues.add(OtherValidationIssue("Unsupported definition. Schemas should only contain type system definitions or extensions.", definition.sourceLocation)) } diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/definitions.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/definitions.kt index 5c7c822a6e1..7ab0816730f 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/definitions.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/definitions.kt @@ -473,6 +473,17 @@ internal val builtinsDefinitionsStr = """ ) on FRAGMENT_SPREAD | INLINE_FRAGMENT directive @specifiedBy(url: String!) on SCALAR + + type __Service { + description: String + capabilities: [__Capability!]! + } + + type __Capability { + identifier: String! + description: String + value: String + } """.trimIndent() internal val linkDefinitionsStr = """ 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 84a07cc6227..e480c7dab23 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 @@ -22,9 +22,12 @@ import kotlin.jvm.JvmName /** * Because there are different versions of GraphQL in the wild and because users may use - * different introspection queries it tries to be as lenient as possible. + * different introspection queries, this model is a superset of all possible introspection models. * - * Probable user error (incomplete introspection query): + * Especially: + * + * - a schema definition is always added + * - a service definition is always added * - missing description defaults to null * - missing directive argument definitions default to empty * - missing directives default to empty @@ -40,7 +43,6 @@ import kotlin.jvm.JvmName * - missing directive locations skip the directive definition * */ - interface IntrospectionSchema @ApolloExperimental @@ -53,11 +55,20 @@ fun BufferedSource.toIntrospectionSchema(filePath: String? = null): Introspectio val string = this.readUtf8() val envelope: REnvelope = json.decodeFromString(string) - val __schema = envelope.__schema ?: envelope.data?.__schema - if (__schema == null) { - throw ConversionException("Invalid introspection schema '${string.substring(0, 50)}': expected input should look like '{ \"__schema\": { \"types\": ...'") + val __schema: RSchema + val __service: Optional + if (envelope.__schema == null) { + if (envelope.data == null) { + throw ConversionException("Invalid introspection schema '${string.substring(0, 50)}': the input must have a __schema or a data property") + } + __schema = envelope.data.__schema + __service = envelope.data.__service + } else { + __schema = envelope.__schema + __service = envelope.__service } - return IntrospectionSchemaImpl(__schema, filePath) + + return IntrospectionSchemaImpl(__schema, __service, filePath) } fun String.toIntrospectionSchema(): IntrospectionSchema = Buffer().writeUtf8(this).toIntrospectionSchema() @@ -70,7 +81,7 @@ fun IntrospectionSchema.toGQLDocument(): GQLDocument { return GQLDocumentBuilder(this).toGQLDocument(filePath) } -private class IntrospectionSchemaImpl(val __schema: RSchema, val filePath: String?) : IntrospectionSchema +private class IntrospectionSchemaImpl(val __schema: RSchema, val __service: Optional, val filePath: String?) : IntrospectionSchema private val json = Json { // be robust to errors: [] keys @@ -82,12 +93,14 @@ private class REnvelope( val data: RData? = null, @Suppress("PropertyName") val __schema: RSchema? = null, + val __service: Optional = Optional.absent(), ) @Serializable private class RData( @Suppress("PropertyName") val __schema: RSchema, + val __service: Optional = Optional.absent(), ) @Serializable @@ -100,6 +113,19 @@ private class RSchema( val directives: Optional> = Optional.absent(), ) +@Serializable +private class RService( + val description: Optional = Optional.absent(), + val capabilities: List, +) + +@Serializable +private class RCapability( + val description: Optional = Optional.absent(), + val qualifiedName: String, + val value: Optional = Optional.absent(), +) + @Serializable private class RTypeRoot(val name: String) @@ -240,7 +266,8 @@ private class GQLDocumentBuilder(private val introspectionSchema: IntrospectionS } } + directives.mapNotNull { it.toGQLDirectiveDefinition() } - + schemaDefinition(), + + schemaDefinition() + + (introspectionSchema.__service.getOrNull()?.toGQLServiceDefinition() ?: defaultServiceDefinition()), sourceLocation = SourceLocation.forPath(filePath) ) } @@ -441,6 +468,23 @@ private class GQLDocumentBuilder(private val introspectionSchema: IntrospectionS ) } + private fun RService.toGQLServiceDefinition(): GQLServiceDefinition { + return GQLServiceDefinition( + description = description.unwrapDescription("service"), + directives = emptyList(), + capabilities = capabilities.map { it.toGQLCapability() } + ) + } + + private fun RCapability.toGQLCapability(): GQLCapability { + return GQLCapability( + description = description.unwrapDescription(qualifiedName), + qualifiedName = qualifiedName, + value = value.getOrThrow() + ) + } + + private fun RTypeFull.toGQLInputObjectTypeDefinition(): GQLInputObjectTypeDefinition { return GQLInputObjectTypeDefinition( description = description.unwrapDescription(name), @@ -511,23 +555,12 @@ private class GQLDocumentBuilder(private val introspectionSchema: IntrospectionS } } -@ApolloInternal -fun IntrospectionSchema.normalize(): IntrospectionSchema { - this as IntrospectionSchemaImpl - // This does not sort the fields/arguments for some reason - return with(__schema) { - IntrospectionSchemaImpl( - RSchema( - queryType = queryType, - mutationType = mutationType, - subscriptionType = subscriptionType, - types = types.sortedBy { it.name }, - directives = directives.mapValue { it.sortedBy { it.name } }, - description = description - ), - null - ) - } +private fun defaultServiceDefinition(): GQLServiceDefinition { + return GQLServiceDefinition( + description = null, + directives = emptyList(), + capabilities = emptyList(), + ) } diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_writer.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_writer.kt index 0e2ccf81519..41512ef52d1 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_writer.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/introspection/introspection_writer.kt @@ -4,6 +4,7 @@ package com.apollographql.apollo.ast.introspection +import com.apollographql.apollo.annotations.ApolloDeprecatedSince import com.apollographql.apollo.ast.ConversionException import com.apollographql.apollo.ast.GQLDirectiveDefinition import com.apollographql.apollo.ast.GQLDocument @@ -20,6 +21,7 @@ import com.apollographql.apollo.ast.GQLObjectTypeDefinition import com.apollographql.apollo.ast.GQLOperationTypeDefinition import com.apollographql.apollo.ast.GQLScalarTypeDefinition import com.apollographql.apollo.ast.GQLSchemaDefinition +import com.apollographql.apollo.ast.GQLServiceDefinition import com.apollographql.apollo.ast.GQLType import com.apollographql.apollo.ast.GQLTypeDefinition import com.apollographql.apollo.ast.GQLUnionTypeDefinition @@ -28,19 +30,21 @@ import com.apollographql.apollo.ast.findOneOf import com.apollographql.apollo.ast.findSpecifiedBy import com.apollographql.apollo.ast.toUtf8 import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** - * The Json Model matching the `__Schema` [GraphQL type](https://spec.graphql.org/draft/#sec-The-__Schema-Type) + * The JSON Model matching the `__Schema` [GraphQL type](https://spec.graphql.org/draft/#sec-The-__Schema-Type) * * It matches the draft version of the spec. Some fields did not exist in earlier versions * like `__InputValue.isDeprecated` for an example. * - * The models match with the spec types except for Type that is used recursively, and therefore - * we have [WTypeFull], [WTypeRef] and [WTypeRoot] + * TODO: This is potentially an issue because it means adding new features changes the output of this function. + * For an example, it doesn't support service capabilities as of now to avoid confusing other external tools. + * + * The models match with the spec types except for Type that has the full details [WTypeFull] for getting all + * the metadata and [WTypeRef] for getting only the type of a field. */ @Serializable internal class WSchema( @@ -152,6 +156,10 @@ internal class IntrospectionSchemaBuilder(document: GQLDocument) { schema = it } + is GQLServiceDefinition -> { + // Nothing to do here, since service definitions are experimental, we do not serialize them + } + else -> { throw ConversionException("Unsupported definition ${it::class.simpleName} apply type extensions before converting to introspection") } @@ -422,4 +430,8 @@ fun ApolloIntrospectionSchema.toJson(): String { return Json.encodeToString(WData(__schema)) } +/** + * TODO: should this be deprecated? See also the comment in [WSchema]. It's not clear what value there is + * in converting from SDL to JSON. + */ fun GQLDocument.toIntrospectionSchema(): ApolloIntrospectionSchema = IntrospectionSchemaBuilder(this).toIntrospectionSchema() diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/locations.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/locations.kt deleted file mode 100644 index 6e91e04118f..00000000000 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/locations.kt +++ /dev/null @@ -1,211 +0,0 @@ -package com.apollographql.apollo.ast - -internal fun GQLDocument.removeLocation(): GQLDocument = copy( - definitions = definitions.map { it.removeLocation() }, - sourceLocation = null -) - -internal fun GQLDefinition.removeLocation(): GQLDefinition = when(this) { - is GQLDirectiveDefinition -> this@removeLocation.removeLocation() - is GQLEnumTypeExtension -> this@removeLocation.removeLocation() - is GQLFragmentDefinition -> this@removeLocation.removeLocation() - is GQLOperationDefinition -> this@removeLocation.removeLocation() - is GQLInputObjectTypeExtension -> this@removeLocation.removeLocation() - is GQLInterfaceTypeExtension -> this@removeLocation.removeLocation() - is GQLObjectTypeExtension -> this@removeLocation.removeLocation() - is GQLScalarTypeExtension -> this@removeLocation.removeLocation() - is GQLSchemaDefinition -> this@removeLocation.removeLocation() - is GQLSchemaExtension -> this@removeLocation.removeLocation() - is GQLEnumTypeDefinition -> this@removeLocation.removeLocation() - is GQLInputObjectTypeDefinition -> this@removeLocation.removeLocation() - is GQLInterfaceTypeDefinition -> this@removeLocation.removeLocation() - is GQLObjectTypeDefinition -> this@removeLocation.removeLocation() - is GQLScalarTypeDefinition -> this@removeLocation.removeLocation() - is GQLUnionTypeDefinition -> this@removeLocation.removeLocation() - is GQLUnionTypeExtension -> this@removeLocation.removeLocation() - is GQLDirectiveExtension -> this@removeLocation.removeLocation() -} - -internal fun GQLEnumTypeDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - enumValues = enumValues.map { it.removeLocation() } -) -internal fun GQLInputObjectTypeDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - inputFields = inputFields.map { it.removeLocation() } -) -internal fun GQLInterfaceTypeDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - fields = fields.map { it.removeLocation() } -) -internal fun GQLObjectTypeDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - fields = fields.map { it.removeLocation() } -) -internal fun GQLScalarTypeDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, -) -internal fun GQLUnionTypeDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - memberTypes = memberTypes.map { it.removeLocation() } -) -internal fun GQLUnionTypeExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - memberTypes = memberTypes.map { it.removeLocation() } -) -internal fun GQLDirectiveExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, -) - -internal fun GQLFieldDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - arguments = arguments.map { it.removeLocation() }, - type = type.removeLocation() -) -internal fun GQLScalarTypeExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, -) - -internal fun GQLSchemaDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - rootOperationTypeDefinitions = rootOperationTypeDefinitions.map { it.removeLocation() }, -) - -internal fun GQLOperationTypeDefinition.removeLocation() = copy( - sourceLocation = null, -) -internal fun GQLSchemaExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - operationTypesDefinition =operationTypeDefinitions.map { it.removeLocation() }, -) - -internal fun GQLInterfaceTypeExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - fields = fields.map { it.removeLocation() }, -) - -internal fun GQLInputObjectTypeExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - inputFields = inputFields.map { it.removeLocation() }, -) - -internal fun GQLObjectTypeExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - fields = fields.map { it.removeLocation() }, -) - -internal fun GQLOperationDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - selections = selections.removeSelectionsLocation(), - variableDefinitions = variableDefinitions.map { it.removeLocation() } -) - -internal fun GQLVariableDefinition.removeLocation() = copy( - sourceLocation = null, - type = type.removeLocation(), - defaultValue = defaultValue?.removeLocation(), - directives = directives.map { it.removeLocation() } -) - -internal fun GQLFragmentDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - typeCondition = typeCondition.removeLocation(), - selections = selections.removeSelectionsLocation() -) - -private fun List.removeSelectionsLocation() = map { it.removeLocation() } - -private fun List.removeArgumentsLocation() = map { it.removeLocation() } - -internal fun GQLSelection.removeLocation(): GQLSelection = when(this) { - is GQLField -> copy( - sourceLocation = null, - arguments = arguments.removeArgumentsLocation(), - selections = selections.removeSelectionsLocation(), - directives = directives.map { it.removeLocation() } - ) - - is GQLFragmentSpread -> copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() } - ) - is GQLInlineFragment -> copy( - sourceLocation = null, - typeCondition = typeCondition?.removeLocation(), - directives = directives.map { it.removeLocation() }, - selections = selections.removeSelectionsLocation() - ) -} - -internal fun GQLEnumTypeExtension.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, - enumValues = enumValues.map { it.removeLocation() }, -) - -internal fun GQLEnumValueDefinition.removeLocation() = copy( - sourceLocation = null, - directives = directives.map { it.removeLocation() }, -) - -internal fun GQLEnumValue.removeLocation() = copy( - sourceLocation = null, -) - -internal fun GQLDirective.removeLocation() = copy( - sourceLocation = null, - arguments = arguments.removeArgumentsLocation() -) - -internal fun GQLArgument.removeLocation() = copy( - sourceLocation = null, - value = value.removeLocation() -) -internal fun GQLDirectiveDefinition.removeLocation(): GQLDirectiveDefinition = copy( - sourceLocation = null, - arguments = arguments.map { it.removeLocation() }, -) - - -private fun GQLInputValueDefinition.removeLocation() = copy( - sourceLocation = null, - type = type.removeLocation(), - defaultValue = defaultValue?.removeLocation(), -) - -internal fun GQLType.removeLocation() = when(this) { - is GQLNonNullType -> copy(sourceLocation = null) - is GQLListType -> copy(sourceLocation = null) - is GQLNamedType -> removeLocation() -} - -internal fun GQLNamedType.removeLocation() = copy(sourceLocation = null) - -internal fun GQLValue.removeLocation() = when (this) { - is GQLBooleanValue -> copy(sourceLocation = null) - is GQLEnumValue -> removeLocation() - is GQLFloatValue -> copy(sourceLocation = null) - is GQLIntValue -> copy(sourceLocation = null) - is GQLListValue -> copy(sourceLocation = null) - is GQLNullValue -> copy(sourceLocation = null) - is GQLObjectValue -> copy(sourceLocation = null) - is GQLStringValue -> copy(sourceLocation = null) - is GQLVariableValue -> copy(sourceLocation = null) -} diff --git a/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/MergerTest.kt b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/MergerTest.kt index ab0033dd7e5..f3d6b7fa031 100644 --- a/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/MergerTest.kt +++ b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/MergerTest.kt @@ -16,12 +16,10 @@ class MergerTest { @Test fun test(@TestParameter(valuesProvider = ParametersProvider::class) graphqlFile: File) { checkExpected(graphqlFile) { - val pragmas = it.readLines().takeWhile { it.startsWith("# PRAGMA") }.map { - it.substring("# PRAGMA".length).trim() - }.toSet() - it.parseAsGQLDocument() + val pragmas = it.pragmas() + it.parseAsGQLDocument(pragmas.toParserOptions()) .getOrThrow() - .mergeExtensions(MergeOptions("allowMergingFieldDefinitions" in pragmas)) + .mergeExtensions(pragmas.toMergerOptions()) .serialize() } } diff --git a/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/ParserTest.kt b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/ParserTest.kt index 8ab92467ada..3e37ec80773 100644 --- a/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/ParserTest.kt +++ b/libraries/apollo-ast/src/jvmTest/kotlin/com/apollographql/apollo/graphql/ast/test/ParserTest.kt @@ -1,6 +1,5 @@ package com.apollographql.apollo.graphql.ast.test -import com.apollographql.apollo.ast.ParserOptions import com.apollographql.apollo.ast.parseAsGQLDocument import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector @@ -14,18 +13,9 @@ class ParserTest { @Test fun test(@TestParameter(valuesProvider = ParametersProvider::class) graphqlFile: File) { checkExpected(graphqlFile) { - val pragmas = it.readLines().takeWhile { it.startsWith("# PRAGMA") }.map { - it.substring("# PRAGMA".length).trim() - }.toSet() - it.parseAsGQLDocument( - options = ParserOptions.Builder() - .apply { - if ("allowDirectivesOnDirectives" in pragmas) { - allowDirectivesOnDirectives(true) - } - } - .build() - ).serialize() + val pragmas = it.pragmas() + + it.parseAsGQLDocument(pragmas.toParserOptions()).serialize() } } 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 c5c0eaad483..b0125fbd0c1 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 @@ -3,6 +3,9 @@ package com.apollographql.apollo.graphql.ast.test import com.apollographql.apollo.ast.GQLDocument import com.apollographql.apollo.ast.GQLResult import com.apollographql.apollo.ast.Issue +import com.apollographql.apollo.ast.MergeOptions +import com.apollographql.apollo.ast.ParserOptions +import com.apollographql.apollo.ast.SchemaValidationOptions import com.apollographql.apollo.ast.toUtf8 import java.io.File import kotlin.test.assertEquals @@ -59,4 +62,30 @@ internal fun checkExpected(graphQLFile: File, block: (File) -> String) { } else { assertEquals(expected, actual) } +} + +enum class Pragma { + allowMergingFieldDefinitions, + allowDirectivesOnDirectives, + allowServiceCapabilities, +} + +fun File.pragmas(): List = + readText().lines().filter { it.startsWith("# PRAGMA ") }.map { it.removePrefix("# PRAGMA ").trim() }.map { Pragma.valueOf(it) } + +fun List.toParserOptions(): ParserOptions { + return ParserOptions.Builder() + .apply { + if (Pragma.allowServiceCapabilities in this@toParserOptions) { + allowServiceCapabilities(true) + } + if (Pragma.allowDirectivesOnDirectives in this@toParserOptions) { + allowDirectivesOnDirectives(true) + } + } + .build() +} + +fun List.toMergerOptions(): MergeOptions { + return MergeOptions(Pragma.allowMergingFieldDefinitions in this) } \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/merger/merge-service-capabilities.expected.graphql b/libraries/apollo-ast/test-fixtures/merger/merge-service-capabilities.expected.graphql new file mode 100644 index 00000000000..358de2bccee --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/merger/merge-service-capabilities.expected.graphql @@ -0,0 +1,11 @@ + +""" +my service +""" +service { + """ + my capability + """ + capability com.example + capability com.example2 +} diff --git a/libraries/apollo-ast/test-fixtures/merger/merge-service-capabilities.graphql b/libraries/apollo-ast/test-fixtures/merger/merge-service-capabilities.graphql new file mode 100644 index 00000000000..f9622195b19 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/merger/merge-service-capabilities.graphql @@ -0,0 +1,11 @@ +# PRAGMA allowServiceCapabilities + +"my service" +service { + "my capability" + capability com.example +} + +extend service { + capability com.example2 +} diff --git a/libraries/apollo-ast/test-fixtures/parser/service-capabilities.expected.graphql b/libraries/apollo-ast/test-fixtures/parser/service-capabilities.expected.graphql new file mode 100644 index 00000000000..3d7e2cda128 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/parser/service-capabilities.expected.graphql @@ -0,0 +1,12 @@ + +""" +my service +""" +service { + """ + my capability + """ + capability com.example + capability com.example.foo + capability com.example.bar("bar") +} diff --git a/libraries/apollo-ast/test-fixtures/parser/service-capabilities.graphql b/libraries/apollo-ast/test-fixtures/parser/service-capabilities.graphql new file mode 100644 index 00000000000..402fe73fce7 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/parser/service-capabilities.graphql @@ -0,0 +1,11 @@ +# PRAGMA allowServiceCapabilities + +"my service" +service { + "my capability" + capability com.example + + capability com.example.foo + + capability com.example.bar("bar") +} \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/parser/service-capability-error.expected.graphql b/libraries/apollo-ast/test-fixtures/parser/service-capability-error.expected.graphql new file mode 100644 index 00000000000..b64dc555a98 --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/parser/service-capability-error.expected.graphql @@ -0,0 +1,2 @@ +ParsingError (6:1) +Expected Dot, found '}'. \ No newline at end of file diff --git a/libraries/apollo-ast/test-fixtures/parser/service-capability-error.graphql b/libraries/apollo-ast/test-fixtures/parser/service-capability-error.graphql new file mode 100644 index 00000000000..2e6deb6525d --- /dev/null +++ b/libraries/apollo-ast/test-fixtures/parser/service-capability-error.graphql @@ -0,0 +1,6 @@ +# PRAGMA allowServiceCapabilities + +service { + # qualified names must have 2 components + capability example +} \ No newline at end of file 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 6c58054ce0a..67ce34c92ac 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 @@ -7,10 +7,11 @@ import com.apollographql.apollo.execution.StringCoercing private inline fun Any?.cast() = this as T private object SchemaObject +private object ServiceObject internal val introspectionCoercings = mapOf( - "__TypeKind" to StringCoercing, - "__DirectiveLocation" to StringCoercing, + "__TypeKind" to StringCoercing, + "__DirectiveLocation" to StringCoercing, ) internal fun introspectionResolver(schema: Schema): Resolver { @@ -23,11 +24,13 @@ internal fun introspectionResolver(schema: Schema): Resolver { val name = it.getRequiredArgument("name") return@Resolver IntrospectionType(GQLNamedType(name = name), schema) } + + "__service" -> return@Resolver ServiceObject } val coordinates = "${it.parentType}.${it.fieldName}" val resolver = resolvers.get(coordinates) if (resolver == null) { - error("Introspection: can't resolve '$coordinates'") + error("no resolver found for '$coordinates'") } resolver.resolve(it) } @@ -35,256 +38,267 @@ internal fun introspectionResolver(schema: Schema): Resolver { internal fun introspectionResolvers(schema: Schema): Map { return mapOf( - "__Schema" to mapOf( - "description" to Resolver { null }, - "types" to Resolver { - schema.typeDefinitions.keys.map { - IntrospectionType(GQLNamedType(name = it), schema) - } - }, - "queryType" to Resolver { IntrospectionType(GQLNamedType(name = schema.queryTypeDefinition.name), schema) }, - "mutationType" to Resolver { - schema.mutationTypeDefinition?.let { - IntrospectionType( - GQLNamedType(name = it.name), - schema - ) - } - }, - "subscriptionType" to Resolver { - schema.subscriptionTypeDefinition?.let { - IntrospectionType( - GQLNamedType(name = it.name), - schema - ) - } - }, - "directives" to Resolver { schema.directiveDefinitions.values.toList() }, - ), - "__Type" to mapOf( - "kind" to Resolver { - val type = it.parentObject.cast() - when (type.typeDefinition) { - is GQLEnumTypeDefinition -> __TypeKind.ENUM.name - is GQLInputObjectTypeDefinition -> __TypeKind.INPUT_OBJECT.name - is GQLInterfaceTypeDefinition -> __TypeKind.INTERFACE.name - is GQLObjectTypeDefinition -> __TypeKind.OBJECT.name - is GQLScalarTypeDefinition -> __TypeKind.SCALAR.name - is GQLUnionTypeDefinition -> __TypeKind.UNION.name - null -> { - when (type.type) { - is GQLNonNullType -> __TypeKind.NON_NULL.name - is GQLListType -> __TypeKind.LIST.name + "__Schema" to mapOf( + "description" to Resolver { null }, + "types" to Resolver { + schema.typeDefinitions.keys.map { + IntrospectionType(GQLNamedType(name = it), schema) + } + }, + "queryType" to Resolver { IntrospectionType(GQLNamedType(name = schema.queryTypeDefinition.name), schema) }, + "mutationType" to Resolver { + schema.mutationTypeDefinition?.let { + IntrospectionType( + GQLNamedType(name = it.name), + schema + ) + } + }, + "subscriptionType" to Resolver { + schema.subscriptionTypeDefinition?.let { + IntrospectionType( + GQLNamedType(name = it.name), + schema + ) + } + }, + "directives" to Resolver { schema.directiveDefinitions.values.toList() }, + ), + "__Type" to mapOf( + "kind" to Resolver { + val type = it.parentObject.cast() + when (type.typeDefinition) { + is GQLEnumTypeDefinition -> __TypeKind.ENUM.name + is GQLInputObjectTypeDefinition -> __TypeKind.INPUT_OBJECT.name + is GQLInterfaceTypeDefinition -> __TypeKind.INTERFACE.name + is GQLObjectTypeDefinition -> __TypeKind.OBJECT.name + is GQLScalarTypeDefinition -> __TypeKind.SCALAR.name + is GQLUnionTypeDefinition -> __TypeKind.UNION.name + null -> { + when (type.type) { + is GQLNonNullType -> __TypeKind.NON_NULL.name + is GQLListType -> __TypeKind.LIST.name + else -> null + } + } + } + }, + "name" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + typeDefinition?.name + }, + "description" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + typeDefinition?.description + }, + "specifiedByURL" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + typeDefinition?.directives?.findSpecifiedBy() + }, + "fields" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + val definitions = when (typeDefinition) { + is GQLObjectTypeDefinition -> typeDefinition.fields + is GQLInterfaceTypeDefinition -> typeDefinition.fields else -> null } - } - } - }, - "name" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - typeDefinition?.name - }, - "description" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - typeDefinition?.description - }, - "specifiedByURL" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - typeDefinition?.directives?.findSpecifiedBy() - }, - "fields" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - val definitions = when (typeDefinition) { - is GQLObjectTypeDefinition -> typeDefinition.fields - is GQLInterfaceTypeDefinition -> typeDefinition.fields - else -> null - } - if (definitions == null) { - return@Resolver null - } + if (definitions == null) { + return@Resolver null + } - val includeDeprecated = it.getRequiredArgument("includeDeprecated") - definitions.filter { - includeDeprecated || it.directives.findDeprecationReason() == null - } - }, - "interfaces" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - val interfaces = when (typeDefinition) { - is GQLObjectTypeDefinition -> typeDefinition.implementsInterfaces.map { - IntrospectionType( - GQLNamedType( - null, - it - ), schema.typeDefinition(it) - ) - } + val includeDeprecated = it.getRequiredArgument("includeDeprecated") + definitions.filter { + includeDeprecated || it.directives.findDeprecationReason() == null + } + }, + "interfaces" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + val interfaces = when (typeDefinition) { + is GQLObjectTypeDefinition -> typeDefinition.implementsInterfaces.map { + IntrospectionType( + GQLNamedType( + null, + it + ), schema.typeDefinition(it) + ) + } - is GQLInterfaceTypeDefinition -> typeDefinition.implementsInterfaces.map { - IntrospectionType( - GQLNamedType( - null, - it - ), schema.typeDefinition(it) - ) - } + is GQLInterfaceTypeDefinition -> typeDefinition.implementsInterfaces.map { + IntrospectionType( + GQLNamedType( + null, + it + ), schema.typeDefinition(it) + ) + } - else -> null - } + else -> null + } - if (interfaces == null) { - return@Resolver null - } + if (interfaces == null) { + return@Resolver null + } - interfaces - }, - "possibleTypes" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - val possibleTypes = when (typeDefinition) { - is GQLInterfaceTypeDefinition -> schema.possibleTypes(typeDefinition.name) - is GQLUnionTypeDefinition -> schema.possibleTypes(typeDefinition.name) - else -> null - } + interfaces + }, + "possibleTypes" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + val possibleTypes = when (typeDefinition) { + is GQLInterfaceTypeDefinition -> schema.possibleTypes(typeDefinition.name) + is GQLUnionTypeDefinition -> schema.possibleTypes(typeDefinition.name) + else -> null + } - if (possibleTypes == null) { - return@Resolver null - } + if (possibleTypes == null) { + return@Resolver null + } - possibleTypes.map { IntrospectionType(GQLNamedType(null, it), schema.typeDefinition(it)) } - }, - "enumValues" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - if (typeDefinition !is GQLEnumTypeDefinition) { - return@Resolver null - } + possibleTypes.map { IntrospectionType(GQLNamedType(null, it), schema.typeDefinition(it)) } + }, + "enumValues" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + if (typeDefinition !is GQLEnumTypeDefinition) { + return@Resolver null + } - val includeDeprecated = it.getRequiredArgument("includeDeprecated") + val includeDeprecated = it.getRequiredArgument("includeDeprecated") - typeDefinition.enumValues.filter { - includeDeprecated || it.directives.findDeprecationReason() == null - } - }, - "inputFields" to Resolver { - val typeDefinition = it.parentObject.cast().typeDefinition - if (typeDefinition !is GQLInputObjectTypeDefinition) { - return@Resolver null - } + typeDefinition.enumValues.filter { + includeDeprecated || it.directives.findDeprecationReason() == null + } + }, + "inputFields" to Resolver { + val typeDefinition = it.parentObject.cast().typeDefinition + if (typeDefinition !is GQLInputObjectTypeDefinition) { + return@Resolver null + } - val includeDeprecated = it.getRequiredArgument("includeDeprecated") + val includeDeprecated = it.getRequiredArgument("includeDeprecated") - typeDefinition.inputFields.filter { - includeDeprecated || it.directives.findDeprecationReason() == null - } - }, - "ofType" to Resolver { - val type = it.parentObject.cast() - when (type.type) { - is GQLNamedType -> null - is GQLListType -> IntrospectionType(type.type.type, schema) - is GQLNonNullType -> IntrospectionType(type.type.type, schema) - } - } - ), - "__Field" to mapOf( - "name" to Resolver { - val fieldDefinition = it.parentObject.cast() - fieldDefinition.name - }, - "description" to Resolver { - val fieldDefinition = it.parentObject.cast() - fieldDefinition.description - }, - "args" to Resolver { - val includeDeprecated = it.getRequiredArgument("includeDeprecated") + typeDefinition.inputFields.filter { + includeDeprecated || it.directives.findDeprecationReason() == null + } + }, + "ofType" to Resolver { + val type = it.parentObject.cast() + when (type.type) { + is GQLNamedType -> null + is GQLListType -> IntrospectionType(type.type.type, schema) + is GQLNonNullType -> IntrospectionType(type.type.type, schema) + } + } + ), + "__Field" to mapOf( + "name" to Resolver { + val fieldDefinition = it.parentObject.cast() + fieldDefinition.name + }, + "description" to Resolver { + val fieldDefinition = it.parentObject.cast() + fieldDefinition.description + }, + "args" to Resolver { + val includeDeprecated = it.getRequiredArgument("includeDeprecated") - val fieldDefinition = it.parentObject.cast() - fieldDefinition.arguments.filter { - includeDeprecated || it.directives.findDeprecationReason() == null - } - }, - "type" to Resolver { - val fieldDefinition = it.parentObject.cast() - IntrospectionType(fieldDefinition.type, schema) - }, - "isDeprecated" to Resolver { - val fieldDefinition = it.parentObject.cast() - fieldDefinition.directives.findDeprecationReason() != null - }, - "deprecationReason" to Resolver { - val fieldDefinition = it.parentObject.cast() - fieldDefinition.directives.findDeprecationReason() - } - ), - "__InputValue" to mapOf( - "name" to Resolver { - val inputValueDefinition = it.parentObject.cast() - inputValueDefinition.name - }, - "description" to Resolver { - val inputValueDefinition = it.parentObject.cast() - inputValueDefinition.description - }, - "type" to Resolver { - val inputValueDefinition = it.parentObject.cast() - IntrospectionType(inputValueDefinition.type, schema) - }, - "defaultValue" to Resolver { - val inputValueDefinition = it.parentObject.cast() - inputValueDefinition.defaultValue?.toUtf8() - }, - "isDeprecated" to Resolver { - val inputValueDefinition = it.parentObject.cast() - inputValueDefinition.directives.findDeprecationReason() != null - }, - "deprecationReason" to Resolver { - val inputValueDefinition = it.parentObject.cast() - inputValueDefinition.directives.findDeprecationReason() - }, - ), - "__EnumValue" to mapOf( - "name" to Resolver { - it.parentObject.cast().name - }, - "description" to Resolver { - it.parentObject.cast().description - }, - "isDeprecated" to Resolver { - it.parentObject.cast().directives.findDeprecationReason() != null - }, - "deprecationReason" to Resolver { - it.parentObject.cast().directives.findDeprecationReason() - }, - ), - "__Directive" to mapOf( - "name" to Resolver { - it.parentObject.cast().name - }, - "description" to Resolver { - it.parentObject.cast().description - }, - "isDeprecated" to Resolver { - it.parentObject.cast().directives.findDeprecationReason() != null - }, - "deprecationReason" to Resolver { - it.parentObject.cast().directives.findDeprecationReason() - }, - "isRepeatable" to Resolver { - it.parentObject.cast().repeatable - }, - "locations" to Resolver { - it.parentObject.cast().locations.map { - it.name - } - }, - "args" to Resolver { - val includeDeprecated = it.getRequiredArgument("includeDeprecated") + val fieldDefinition = it.parentObject.cast() + fieldDefinition.arguments.filter { + includeDeprecated || it.directives.findDeprecationReason() == null + } + }, + "type" to Resolver { + val fieldDefinition = it.parentObject.cast() + IntrospectionType(fieldDefinition.type, schema) + }, + "isDeprecated" to Resolver { + val fieldDefinition = it.parentObject.cast() + fieldDefinition.directives.findDeprecationReason() != null + }, + "deprecationReason" to Resolver { + val fieldDefinition = it.parentObject.cast() + fieldDefinition.directives.findDeprecationReason() + } + ), + "__InputValue" to mapOf( + "name" to Resolver { + val inputValueDefinition = it.parentObject.cast() + inputValueDefinition.name + }, + "description" to Resolver { + val inputValueDefinition = it.parentObject.cast() + inputValueDefinition.description + }, + "type" to Resolver { + val inputValueDefinition = it.parentObject.cast() + IntrospectionType(inputValueDefinition.type, schema) + }, + "defaultValue" to Resolver { + val inputValueDefinition = it.parentObject.cast() + inputValueDefinition.defaultValue?.toUtf8() + }, + "isDeprecated" to Resolver { + val inputValueDefinition = it.parentObject.cast() + inputValueDefinition.directives.findDeprecationReason() != null + }, + "deprecationReason" to Resolver { + val inputValueDefinition = it.parentObject.cast() + inputValueDefinition.directives.findDeprecationReason() + }, + ), + "__EnumValue" to mapOf( + "name" to Resolver { + it.parentObject.cast().name + }, + "description" to Resolver { + it.parentObject.cast().description + }, + "isDeprecated" to Resolver { + it.parentObject.cast().directives.findDeprecationReason() != null + }, + "deprecationReason" to Resolver { + it.parentObject.cast().directives.findDeprecationReason() + }, + ), + "__Directive" to mapOf( + "name" to Resolver { + it.parentObject.cast().name + }, + "description" to Resolver { + it.parentObject.cast().description + }, + "isDeprecated" to Resolver { + it.parentObject.cast().directives.findDeprecationReason() != null + }, + "deprecationReason" to Resolver { + it.parentObject.cast().directives.findDeprecationReason() + }, + "isRepeatable" to Resolver { + it.parentObject.cast().repeatable + }, + "locations" to Resolver { + it.parentObject.cast().locations.map { + it.name + } + }, + "args" to Resolver { + val includeDeprecated = it.getRequiredArgument("includeDeprecated") - it.parentObject.cast().arguments.filter { - includeDeprecated || it.directives.findDeprecationReason() == null - } - } - ) + it.parentObject.cast().arguments.filter { + includeDeprecated || it.directives.findDeprecationReason() == null + } + } + ), + "__Service" to mapOf( + "description" to Resolver { null }, + "capabilities" to Resolver { + emptyList() + } + ), + "__Capability" to mapOf( + "description" to Resolver { it.parentObject.cast().description }, + "identifier" to Resolver { it.parentObject.cast().identifier }, + "value" to Resolver { it.parentObject.cast().value }, + ) ).entries.flatMap { (type, fields) -> fields.entries.map { (field, resolver) -> "$type.$field" to resolver @@ -295,14 +309,20 @@ internal fun introspectionResolvers(schema: Schema): Map { private fun IntrospectionType(type: GQLType, schema: Schema): IntrospectionType { return IntrospectionType( - type, - (type as? GQLNamedType)?.name?.let { schema.typeDefinition(it) } + type, + (type as? GQLNamedType)?.name?.let { schema.typeDefinition(it) } ) } private class IntrospectionType( - val type: GQLType, - val typeDefinition: GQLTypeDefinition?, + val type: GQLType, + val typeDefinition: GQLTypeDefinition?, +) + +private class IntrospectionCapability( + val description: String?, + val identifier: String, + val value: String?, ) internal enum class __TypeKind { diff --git a/libraries/apollo-execution/testFixtures/introspection.graphql b/libraries/apollo-execution/testFixtures/introspection.graphql index 6285e4cbe7c..592e2389c14 100644 --- a/libraries/apollo-execution/testFixtures/introspection.graphql +++ b/libraries/apollo-execution/testFixtures/introspection.graphql @@ -16,6 +16,14 @@ query IntrospectionQuery { isRepeatable } } + __service { + description + capabilities { + description + identifier + value + } + } } fragment FullType on __Type { diff --git a/libraries/apollo-tooling/api/apollo-tooling.api b/libraries/apollo-tooling/api/apollo-tooling.api index 091f16fa035..dda4b9b6096 100644 --- a/libraries/apollo-tooling/api/apollo-tooling.api +++ b/libraries/apollo-tooling/api/apollo-tooling.api @@ -42,6 +42,7 @@ public final class com/apollographql/apollo/tooling/GraphQLFeature : java/lang/E public static final field OneOf Lcom/apollographql/apollo/tooling/GraphQLFeature; public static final field RepeatableDirectives Lcom/apollographql/apollo/tooling/GraphQLFeature; public static final field SchemaDescription Lcom/apollographql/apollo/tooling/GraphQLFeature; + public static final field ServiceCapabilities Lcom/apollographql/apollo/tooling/GraphQLFeature; public static final field SpecifiedBy Lcom/apollographql/apollo/tooling/GraphQLFeature; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/apollographql/apollo/tooling/GraphQLFeature; diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/GraphQLFeature.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/GraphQLFeature.kt index 6d8f9a9d7ba..61d92686692 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/GraphQLFeature.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/GraphQLFeature.kt @@ -59,6 +59,17 @@ enum class GraphQLFeature { * - `__Schema.directives`'s `includeDeprecated` argument */ DeprecatedDirectives, + + /** + * Service capabilities directives, experimental, see [Service capabilities](https://github.com/graphql/graphql-spec/pull/1208). + * + * Introspection: + * - `__Service` + * - `__Capability` + * - `Query.__service` + */ + @ApolloExperimental + ServiceCapabilities, } internal fun PreIntrospectionQuery.Data.getFeatures(): Set { @@ -108,5 +119,9 @@ internal fun PreIntrospectionQuery.Data.getFeatures(): Set { if (typeInputFieldsArgsIncludeDeprecated && directiveArgsIncludeDeprecated && fieldArgsIncludeDeprecated && inputValueIsDeprecated && inputValueDeprecatedReason) { add(DeprecatedInputValues) } + + if (__schema.types.any { it.typeFields.name == "__Service" }) { + add(GraphQLFeature.ServiceCapabilities) + } } } diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaDownloader.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaDownloader.kt index 49117fefdf6..0ab8d4b8fbe 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaDownloader.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaDownloader.kt @@ -17,6 +17,7 @@ import com.apollographql.apollo.network.okHttpClient import com.apollographql.apollo.tooling.SchemaHelper.reworkFullTypeFragment import com.apollographql.apollo.tooling.SchemaHelper.reworkInputValueFragment import com.apollographql.apollo.tooling.SchemaHelper.reworkIntrospectionQuery +import com.apollographql.apollo.tooling.SchemaHelper.reworkServiceCapabilities import com.apollographql.apollo.tooling.graphql.PreIntrospectionQuery import com.apollographql.apollo.tooling.platformapi.public.DownloadSchemaQuery import kotlinx.coroutines.runBlocking @@ -116,6 +117,8 @@ object SchemaDownloader { /* * As of Feb. 2025, the registry schema contains the introspection types but not * the scalars and we therefore need to call `toFullSchemaGQLDocument()` + * + * TODO: Should this be removed? It's not clear what value there is in converting from SDL to JSON. */ sdlSchema .toGQLDocument() @@ -152,6 +155,7 @@ object SchemaDownloader { .reworkIntrospectionQuery(features) .reworkFullTypeFragment(features) .reworkInputValueFragment(features) + .reworkServiceCapabilities(features) ) return introspectionGql.toUtf8() } diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaHelper.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaHelper.kt index 986fa0c5258..6d1820b7165 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaHelper.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaHelper.kt @@ -14,6 +14,7 @@ import com.apollographql.apollo.ast.GQLDefinition import com.apollographql.apollo.ast.GQLField import com.apollographql.apollo.ast.GQLFragmentDefinition import com.apollographql.apollo.ast.GQLOperationDefinition +import com.apollographql.apollo.ast.parseAsGQLDocument import com.apollographql.apollo.network.http.DefaultHttpEngine import com.apollographql.apollo.network.okHttpClient import com.apollographql.apollo.tooling.GraphQLFeature.* @@ -29,6 +30,7 @@ import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager +import kotlin.plus internal object SchemaHelper { internal val client = OkHttpClient() @@ -197,6 +199,35 @@ internal object SchemaHelper { ) } + private val __serviceQuery = """ + { + __service { + description + capabilities { + description + identifier + value + } + } + } + + """.trimIndent() + + internal fun List.reworkServiceCapabilities(features: Set): List { + return mapIf<_, GQLOperationDefinition>({ it.name == "IntrospectionQuery" }) { + val newSelections = buildList { + addAll(it.selections) + if (ServiceCapabilities in features) { + add((__serviceQuery.parseAsGQLDocument().getOrThrow().definitions.single() as GQLOperationDefinition).selections.single()) + } + } + it.copy( + selections = newSelections + ) + } + } + + private inline fun T.letIf(condition: Boolean, block: (T) -> T): T = if (condition) block(this) else this private inline fun List.mapIf(condition: Boolean, block: (T) -> T): List = if (condition) map(block) else this diff --git a/libraries/apollo-tooling/src/main/resources/base-introspection.graphql b/libraries/apollo-tooling/src/main/resources/base-introspection.graphql index 817f1520a3e..1d00decd157 100644 --- a/libraries/apollo-tooling/src/main/resources/base-introspection.graphql +++ b/libraries/apollo-tooling/src/main/resources/base-introspection.graphql @@ -24,6 +24,7 @@ query IntrospectionQuery { # deprecationReason - introduced in https://github.com/graphql/graphql-spec/pull/907 } } + # __service { } - experimental, see https://github.com/graphql/graphql-spec/pull/1208 } fragment FullType on __Type { diff --git a/libraries/apollo-tooling/src/test/fixtures/introspection-response-success-full.graphqls b/libraries/apollo-tooling/src/test/fixtures/introspection-response-success-full.graphqls index 2cd1a2cf15d..d310ba603cb 100644 --- a/libraries/apollo-tooling/src/test/fixtures/introspection-response-success-full.graphqls +++ b/libraries/apollo-tooling/src/test/fixtures/introspection-response-success-full.graphqls @@ -493,3 +493,4 @@ schema { mutation: Mutation subscription: Subscription } + diff --git a/tests/sample-server/graphql/schema.graphqls b/tests/sample-server/graphql/schema.graphqls index 1c7ec13d371..c11b1362142 100644 --- a/tests/sample-server/graphql/schema.graphqls +++ b/tests/sample-server/graphql/schema.graphqls @@ -199,3 +199,17 @@ directive @deprecated(reason: String! = "No longer supported") on FIELD_DEFINITI directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT directive @specifiedBy(url: String!) on SCALAR + +type __Service { + description: String + + capabilities: [__Capability!]! +} + +type __Capability { + identifier: String! + + description: String + + value: String +}