From 8e14023b2a6c09bc15a865ae2690c56ad9a13ab5 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Wed, 18 Sep 2024 14:02:26 +0200 Subject: [PATCH] Deprecate `@nonnull` (#6152) * Deprecate `@nonnull` * more correct * import the nullability directive * update test fixtures --- docs/source/advanced/nullability.mdx | 43 +++++++++++++++++-- docs/source/advanced/plugin-recipes.mdx | 4 +- .../operations.graphql | 2 +- .../schema.graphqls | 6 +++ .../com/apollographql/apollo/api/Operation.kt | 2 +- .../com/apollographql/apollo/ast/Issue.kt | 2 +- .../ast/internal/SchemaValidationScope.kt | 2 +- .../apollo/ast/internal/ValidationCommon.kt | 3 ++ .../apollo/ast/internal/definitions.kt | 4 +- .../apollo_directive.expected | 6 +++ .../default_namespace.expected | 3 ++ .../definition_renames.expected | 3 ++ .../fragment_variables.expected | 5 +++ samples/README.md | 3 +- 14 files changed, 76 insertions(+), 12 deletions(-) diff --git a/docs/source/advanced/nullability.mdx b/docs/source/advanced/nullability.mdx index 46177787a48..cfb325826ad 100644 --- a/docs/source/advanced/nullability.mdx +++ b/docs/source/advanced/nullability.mdx @@ -319,6 +319,43 @@ In order to change that default to "opt-in the errors you want to handle", you c 1. import the nullability directives. 1. Default to coercing to null: `extend schema @catchByDefault(to: NULL)`. This is a no-op to start exploring the directives. 1. Add `@catch` to individual fields, get more comfortable with how it works. -1. When ready to do the big switch, change to `extend schema catch(to: THROW)` and (at the same time) add `query GetFoo @catch(to: NULL) {}` on all operations/fragments (this is a no-op). -1. From this moment on, new queries written are `catch(to: THROW)` by default. -1. Remove `query GetFoo @catch(to: NULL) {}` progressively. +1. When ready to do the big switch, change to `extend schema catchByDefault(to: THROW)` and (at the same time) add `query GetFoo @catchByDefault(to: NULL) {}` on all operations/fragments (this is a no-op). +1. From this moment on, new queries written throw on errors by default. +1. Remove `query GetFoo @catchByDefault(to: NULL) {}` progressively. + +## Migrate from `@nonnull` + +If you were using `@nonnull` before, you can now use `@semanticNonNull`. + +`@semanticNonNull`, coupled with `@catch` is more flexible and also more in line with other frameworks. + +**For usages in executable documents**: +```graphql +# Replace +query GetFoo { + foo @nonnull +} + +# With +query GetFoo { + foo @catch(to: THROW) +} +``` + +**For usages in schema documents**: +```graphql +# Replace +extend type Foo @nonnull(fields: "bar") + +# With +extend type Foo @semanticNonNullField(name: "bar") +``` + +If your schema is configured with `@catchByDefault(to: NULL)`, you'll also need to update the usages in your executable documents: + +```graphql +# Add `@catch(to: THROW)` +query GetFoo { + foo @catch(to: THROW) +} +``` \ No newline at end of file diff --git a/docs/source/advanced/plugin-recipes.mdx b/docs/source/advanced/plugin-recipes.mdx index d68bdadedf7..d2502f11ef0 100644 --- a/docs/source/advanced/plugin-recipes.mdx +++ b/docs/source/advanced/plugin-recipes.mdx @@ -40,8 +40,8 @@ apollo { ## Combining multiple schema files -Apollo Kotlin supports a collection of client directives, including `@nonnull`, `@optional`, and `@typePolicy`. These -directives enable you to extend your server's base schema with client-specific types and fields. +Apollo Kotlin supports a collection of directives, such as `@semanticNonNull`, `@fieldPolicy`, `@typePolicy`. These +directives enable you to extend your server's base schema to work better with your client. If you expand your schema in a separate file (usually named `extra.graphqls`), you can instruct Apollo Kotlin to construct its schema from a combination of multiple files, like so: diff --git a/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/operations.graphql b/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/operations.graphql index 9eb7ee83808..600c73b65da 100644 --- a/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/operations.graphql +++ b/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/operations.graphql @@ -1,7 +1,7 @@ query MyQuery { person { identity { - firstName @nonnull + firstName @catch(to: THROW) lastName } } diff --git a/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/schema.graphqls b/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/schema.graphqls index a1ffb654b70..bf9ed88f59f 100644 --- a/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/schema.graphqls +++ b/intellij-plugin/src/test/testData/graphql/ApolloClientDirectiveStripper/schema.graphqls @@ -1,3 +1,9 @@ +extend schema @link( + url: "https://specs.apollo.dev/nullability/v0.4", + import: ["@semanticNonNull", "@semanticNonNullField", "@catch", "CatchTo", "@catchByDefault"] +) +extend schema @catchByDefault(to: NULL) + type Query { person: Person } diff --git a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/Operation.kt b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/Operation.kt index 7f423882857..dccc6a3cebb 100644 --- a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/Operation.kt +++ b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/Operation.kt @@ -9,7 +9,7 @@ interface Operation : Executable { /** * The GraphQL operation String to be sent to the server. This might differ from the input `*.graphql` file with: * - whitespaces removed - * - Apollo client directives like `@nonnull` removed + * - Apollo client directives like `@catch` removed * - `typename` fields added for polymorphic/fragment cases */ fun document(): String diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Issue.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Issue.kt index b125d288698..6c0417c92b3 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Issue.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Issue.kt @@ -90,7 +90,7 @@ class AnonymousOperation(override val message: String, override val sourceLocati class OtherValidationIssue(override val message: String, override val sourceLocation: SourceLocation?) : GraphQLValidationIssue /** - * A deprecated field/enum is used + * A deprecated field/inputField/enumValue/directive is used */ class DeprecatedUsage(override val message: String, override val sourceLocation: SourceLocation?) : ApolloIssue 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 ac6001360f8..9bd500bc155 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 @@ -275,7 +275,7 @@ private class ForeignSchema( /** * Parses the schema extensions * - * Example: extend schema @link(url: "https://specs.apollo.dev/kotlin_labs/v0.3", import: ["@nonnull"]) + * Example: extend schema @link(url: "https://specs.apollo.dev/nullability/v0.4/", import: ["@catchByDefault", "CatchTo"]) */ private fun List.getForeignSchemas( issues: MutableList, diff --git a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ValidationCommon.kt b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ValidationCommon.kt index d96ea06fd82..adbbd1911e6 100644 --- a/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ValidationCommon.kt +++ b/libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/ValidationCommon.kt @@ -206,6 +206,9 @@ internal fun ValidationScope.validateDirectives( * Extra Apollo-specific validation for @nonnull */ internal fun ValidationScope.extraValidateNonNullDirective(directive: GQLDirective, directiveContext: GQLNode) { + issues.add( + DeprecatedUsage(message = "Using `@nonnull` is deprecated. Use `@semanticNonNull` and/or `@catch` instead. See https://go.apollo.dev/ak-nullability.", directive.sourceLocation) + ) if (directiveContext is GQLField && directive.arguments.isNotEmpty()) { registerIssue( message = "'${directive.name}' cannot have arguments when applied on a field", 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 abada9d1049..e167662109d 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 @@ -355,8 +355,8 @@ directive @catch(to: CatchTo! = RESULT, levels: [Int!]! = [0]) on FIELD ""${'"'} Indicates how clients should handle errors on a given position by default. -The semantics are the same as `@catch` but `@catchByDefault` only applies to positions that -can contain JSON `null`. Non-null positions are unchanged. +Compared to `@catch`, `@catchByDefault` does not have a `level` argument and applies to all +nullable positions. When multiple values of `catchTo` are set for a given position: * the `@catch` value is used if set. diff --git a/libraries/apollo-compiler/src/test/validation/operation/apollo_directive/apollo_directive.expected b/libraries/apollo-compiler/src/test/validation/operation/apollo_directive/apollo_directive.expected index 9081df403a9..52c24c9d258 100644 --- a/libraries/apollo-compiler/src/test/validation/operation/apollo_directive/apollo_directive.expected +++ b/libraries/apollo-compiler/src/test/validation/operation/apollo_directive/apollo_directive.expected @@ -1,5 +1,11 @@ +DeprecatedUsage (3:11) +Using `@nonnull` is deprecated. Use `@semanticNonNull` and/or `@catch` instead. See https://go.apollo.dev/ak-nullability. +------------ OtherValidationIssue (5:27) Unknown argument `toto` on directive 'nonnull' ------------ +DeprecatedUsage (5:18) +Using `@nonnull` is deprecated. Use `@semanticNonNull` and/or `@catch` instead. See https://go.apollo.dev/ak-nullability. +------------ OtherValidationIssue (5:18) 'nonnull' cannot have arguments when applied on a field \ No newline at end of file diff --git a/libraries/apollo-compiler/src/test/validation/operation/default_namespace/default_namespace.expected b/libraries/apollo-compiler/src/test/validation/operation/default_namespace/default_namespace.expected index 3c5d06fe88c..ee343050cb8 100644 --- a/libraries/apollo-compiler/src/test/validation/operation/default_namespace/default_namespace.expected +++ b/libraries/apollo-compiler/src/test/validation/operation/default_namespace/default_namespace.expected @@ -1,3 +1,6 @@ +DeprecatedUsage (3:11) +Using `@nonnull` is deprecated. Use `@semanticNonNull` and/or `@catch` instead. See https://go.apollo.dev/ak-nullability. +------------ OtherValidationIssue (6:11) Directive 'kotlin_labs__typePolicy' cannot be applied on 'FIELD' ------------ diff --git a/libraries/apollo-compiler/src/test/validation/operation/definition_renames/definition_renames.expected b/libraries/apollo-compiler/src/test/validation/operation/definition_renames/definition_renames.expected index 737d82046a1..91033bf3ea7 100644 --- a/libraries/apollo-compiler/src/test/validation/operation/definition_renames/definition_renames.expected +++ b/libraries/apollo-compiler/src/test/validation/operation/definition_renames/definition_renames.expected @@ -1,2 +1,5 @@ +DeprecatedUsage (3:19) +Using `@nonnull` is deprecated. Use `@semanticNonNull` and/or `@catch` instead. See https://go.apollo.dev/ak-nullability. +------------ OtherValidationIssue (5:19) Directive 'apollo__typePolicy' cannot be applied on 'FIELD' \ No newline at end of file diff --git a/libraries/apollo-compiler/src/test/validation/operation/fragment_variables/fragment_variables.expected b/libraries/apollo-compiler/src/test/validation/operation/fragment_variables/fragment_variables.expected index e69de29bb2d..5bae29ca63a 100644 --- a/libraries/apollo-compiler/src/test/validation/operation/fragment_variables/fragment_variables.expected +++ b/libraries/apollo-compiler/src/test/validation/operation/fragment_variables/fragment_variables.expected @@ -0,0 +1,5 @@ +DeprecatedUsage (13:37) +Using `@nonnull` is deprecated. Use `@semanticNonNull` and/or `@catch` instead. See https://go.apollo.dev/ak-nullability. +------------ +DeprecatedUsage (12:42) +Using `@nonnull` is deprecated. Use `@semanticNonNull` and/or `@catch` instead. See https://go.apollo.dev/ak-nullability. \ No newline at end of file diff --git a/samples/README.md b/samples/README.md index a5eb0cbd0d0..9b15b892e9c 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1,6 +1,7 @@ -If you're looking for a specific feature (APQs, Codegen options, `@nonnull`, ...) the [integration-tests](../tests) have a lot of very focused examples of how to use Apollo Kotlin. +If you're looking for a specific feature (APQs, Codegen options, `@catch`, ...) the [integration-tests](../tests) have a lot of very focused examples of how to use Apollo Kotlin. For more general examples demonstrating how to build an App end to end, check: * https://github.com/apollographql/apollo-kotlin-tutorial for an **Android App** example * https://github.com/joreilly/MortyComposeKMM for a **Multiplatform App** example +* https://github.com/joreilly/Confetti for a **full fledged Android App** example