Skip to content

Commit

Permalink
Add support for @catch and @semanticNonNull (#5405)
Browse files Browse the repository at this point in the history
* add nullability foreign schema

* add FieldResult and various catchTo adapters

* Add IrType.result

* add catch test

* support for @nullOnlyOnError and @catch

* Update libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo3/ast/internal/definitions.kt

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

* Update libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo3/ast/internal/definitions.kt

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

* Update libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo3/ast/internal/definitions.kt

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

* findCatch -> findCatches

* use the presence of the `@catch` directive definition to enable codegen

* Add @ignoreErrors and ErrorAwareAdapter. Remove CatchToThrowAdapter

* update test fixtures

* fix test

* fix nullable + catchToNull

* nullOnlyOnError -> semanticNonNull

* add ApolloExperimental

* fix api dump

* allow setting a default on the schema for @catch

* add catch(to: THROW)

* enforce chosing a default CatchTo

* add validation for @catch

* add validation for @catch

* update api dump

* fix ParserTest

* fix tests

* FieldResult.error -> FieldResult.exception

* update message

* simpler

* Update libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/api/Adapters.kt

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

* Update libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo3/ast/gqldirective.kt

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

* Update libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo3/ast/internal/definitions.kt

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

* nooe -> semanticNonNull

* fix bad copy paste

* update apiDump

* hide some more symbol

* allow to catch non-ApolloGraphQLException

* comment + reorder code

* valueOrNull -> getOrNull for consistency

* API visibility

* simplify code and does not generate errorAware() adapter wrappers for non-null types

* add an quick test

* fix tests

---------

Co-authored-by: Benoit Lubek <[email protected]>
  • Loading branch information
martinbonnin and BoD authored Dec 1, 2023
1 parent 0fc496f commit d14bcb6
Show file tree
Hide file tree
Showing 454 changed files with 3,061 additions and 467 deletions.
2 changes: 1 addition & 1 deletion .idea/runConfigurations/CodegenTest.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions build-logic/src/main/kotlin/Testing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent

fun Project.configureTesting() {
tasks.withType(Test::class.java) {
systemProperty("updateTestFixtures", System.getProperty("updateTestFixtures"))
systemProperty("testFilter", System.getProperty("testFilter"))
systemProperty("codegenModels", System.getProperty("codegenModels"))

forwardEnv("updateTestFixtures")
forwardEnv("testFilter")
forwardEnv("codegenModels")
}

pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
Expand All @@ -35,6 +34,16 @@ fun Project.configureTesting() {
}
}

/**
* forwards an environment variable to a test task and mark it as input
*/
fun Test.forwardEnv(name: String) {
System.getenv(name)?.let { value ->
environment(name, value)
inputs.property(name, value)
}
}

// See https://github.com/gradle/gradle/issues/23456
fun Test.addRelativeInput(name: String, dirPath: Any) {
this.inputs.dir(dirPath).withPropertyName(name).withPathSensitivity(PathSensitivity.RELATIVE)
Expand Down
35 changes: 33 additions & 2 deletions libraries/apollo-api/api/apollo-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public final class com/apollographql/apollo3/api/Adapters {
public static final field NullableStringAdapter Lcom/apollographql/apollo3/api/NullableAdapter;
public static final field StringAdapter Lcom/apollographql/apollo3/api/Adapter;
public static final field UploadAdapter Lcom/apollographql/apollo3/api/Adapter;
public static final fun -catchToNull (Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/api/Adapter;
public static final fun -catchToResult (Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/api/Adapter;
public static final fun -errorAware (Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/api/Adapter;
public static final fun -list (Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/api/ListAdapter;
public static final fun -nullable (Lcom/apollographql/apollo3/api/Adapter;)Lcom/apollographql/apollo3/api/NullableAdapter;
public static final fun -obj (Lcom/apollographql/apollo3/api/Adapter;Z)Lcom/apollographql/apollo3/api/ObjectAdapter;
Expand Down Expand Up @@ -382,8 +385,9 @@ public final class com/apollographql/apollo3/api/CustomScalarAdapters : com/apol
public static final field Key Lcom/apollographql/apollo3/api/CustomScalarAdapters$Key;
public static final field PassThrough Lcom/apollographql/apollo3/api/CustomScalarAdapters;
public final field deferredFragmentIdentifiers Ljava/util/Set;
public final field errors Ljava/util/List;
public final field falseVariables Ljava/util/Set;
public synthetic fun <init> (Ljava/util/Map;Ljava/util/Set;Ljava/util/Set;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/util/Map;Ljava/util/Set;Ljava/util/Set;Ljava/util/List;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun adapterFor (Ljava/lang/String;)Lcom/apollographql/apollo3/api/Adapter;
public fun getKey ()Lcom/apollographql/apollo3/api/ExecutionContext$Key;
public final fun newBuilder ()Lcom/apollographql/apollo3/api/CustomScalarAdapters$Builder;
Expand All @@ -398,6 +402,7 @@ public final class com/apollographql/apollo3/api/CustomScalarAdapters$Builder {
public final fun build ()Lcom/apollographql/apollo3/api/CustomScalarAdapters;
public final fun clear ()V
public final fun deferredFragmentIdentifiers (Ljava/util/Set;)Lcom/apollographql/apollo3/api/CustomScalarAdapters$Builder;
public final fun errors (Ljava/util/List;)Lcom/apollographql/apollo3/api/CustomScalarAdapters$Builder;
public final fun falseVariables (Ljava/util/Set;)Lcom/apollographql/apollo3/api/CustomScalarAdapters$Builder;
}

Expand Down Expand Up @@ -494,6 +499,7 @@ public final class com/apollographql/apollo3/api/Error$Location {

public abstract interface class com/apollographql/apollo3/api/Executable {
public abstract fun adapter ()Lcom/apollographql/apollo3/api/Adapter;
public abstract fun getIgnoreErrors ()Z
public abstract fun rootField ()Lcom/apollographql/apollo3/api/CompiledField;
public abstract fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Z)V
}
Expand All @@ -512,7 +518,8 @@ public final class com/apollographql/apollo3/api/Executables {
public static final fun parseData (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lcom/apollographql/apollo3/api/Executable$Data;
public static final fun parseData (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;)Lcom/apollographql/apollo3/api/Executable$Data;
public static final fun parseData (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;Ljava/util/Set;)Lcom/apollographql/apollo3/api/Executable$Data;
public static synthetic fun parseData$default (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;Ljava/util/Set;ILjava/lang/Object;)Lcom/apollographql/apollo3/api/Executable$Data;
public static final fun parseData (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;Ljava/util/Set;Ljava/util/List;)Lcom/apollographql/apollo3/api/Executable$Data;
public static synthetic fun parseData$default (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;Ljava/util/Set;Ljava/util/List;ILjava/lang/Object;)Lcom/apollographql/apollo3/api/Executable$Data;
public static final fun variables (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lcom/apollographql/apollo3/api/Executable$Variables;
public static final fun variablesJson (Lcom/apollographql/apollo3/api/Executable;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/String;
}
Expand Down Expand Up @@ -574,6 +581,28 @@ public final class com/apollographql/apollo3/api/FakeResolverKt {
public static final fun buildData (Lcom/apollographql/apollo3/api/BuilderFactory;Lkotlin/jvm/functions/Function1;Lcom/apollographql/apollo3/api/Adapter;Ljava/util/List;Ljava/lang/String;Lcom/apollographql/apollo3/api/FakeResolver;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object;
}

public abstract interface class com/apollographql/apollo3/api/FieldResult {
}

public final class com/apollographql/apollo3/api/FieldResult$Failure : com/apollographql/apollo3/api/FieldResult {
public fun <init> (Lcom/apollographql/apollo3/exception/ApolloException;)V
public final fun getException ()Lcom/apollographql/apollo3/exception/ApolloException;
}

public final class com/apollographql/apollo3/api/FieldResult$Success : com/apollographql/apollo3/api/FieldResult {
public fun <init> (Ljava/lang/Object;)V
public final fun getValue ()Ljava/lang/Object;
}

public final class com/apollographql/apollo3/api/FieldResultKt {
public static final fun exceptionOrNull (Lcom/apollographql/apollo3/api/FieldResult;)Ljava/lang/Exception;
public static final fun getOrElse (Lcom/apollographql/apollo3/api/FieldResult;Ljava/lang/Object;)Ljava/lang/Object;
public static final fun getOrNull (Lcom/apollographql/apollo3/api/FieldResult;)Ljava/lang/Object;
public static final fun getOrThrow (Lcom/apollographql/apollo3/api/FieldResult;)Ljava/lang/Object;
public static final fun graphQLErrorOrNull (Lcom/apollographql/apollo3/api/FieldResult;)Lcom/apollographql/apollo3/api/Error;
public static final fun isSuccess (Lcom/apollographql/apollo3/api/FieldResult;)Z
}

public final class com/apollographql/apollo3/api/FileUpload {
public static final fun toUpload (Ljava/io/File;Ljava/lang/String;)Lcom/apollographql/apollo3/api/DefaultUpload;
}
Expand Down Expand Up @@ -1174,7 +1203,9 @@ public final class com/apollographql/apollo3/exception/ApolloExceptionHandlerKt
}

public final class com/apollographql/apollo3/exception/ApolloGraphQLException : com/apollographql/apollo3/exception/ApolloException {
public fun <init> (Lcom/apollographql/apollo3/api/Error;)V
public fun <init> (Ljava/util/List;)V
public final fun getError ()Lcom/apollographql/apollo3/api/Error;
public final fun getErrors ()Ljava/util/List;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import com.apollographql.apollo3.api.json.MapJsonWriter
import com.apollographql.apollo3.api.json.buildJsonString
import com.apollographql.apollo3.api.json.readAny
import com.apollographql.apollo3.api.json.writeAny
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.ApolloGraphQLException
import kotlin.jvm.JvmField
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
Expand Down Expand Up @@ -80,7 +82,7 @@ class OptionalAdapter<T>(private val wrappedAdapter: Adapter<T>) : Adapter<Optio
}

/**
* PresentAdapter can only express something that's present. Absent values are handled outside of the adapter.
* PresentAdapter can only express something that's present. Absent values are handled outside the adapter.
*
* This adapter is used to handle optional arguments in operations and optional fields in Input objects.
*/
Expand Down Expand Up @@ -118,9 +120,6 @@ class ApolloOptionalAdapter<T>(private val wrappedAdapter: Adapter<T>) : Adapter
}
}

@JvmName("-obj")
fun <T> Adapter<T>.obj(buffered: Boolean = false) = ObjectAdapter(this, buffered)

@JvmField
val StringAdapter = object : Adapter<String> {
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): String {
Expand Down Expand Up @@ -283,12 +282,6 @@ val ApolloOptionalBooleanAdapter = ApolloOptionalAdapter(BooleanAdapter)
@JvmField
val ApolloOptionalAnyAdapter = ApolloOptionalAdapter(AnyAdapter)

@JvmName("-nullable")
fun <T : Any> Adapter<T>.nullable() = NullableAdapter(this)

@JvmName("-list")
fun <T> Adapter<T>.list() = ListAdapter(this)

/**
* Note that Arrays require their type to be known at compile time, so we construct an anonymous object with reference to
* function with reified type parameters as a workaround.
Expand All @@ -297,7 +290,11 @@ fun <T> Adapter<T>.list() = ListAdapter(this)
@JvmName("-array")
inline fun <reified T> Adapter<T>.array() = object : Adapter<Array<T>> {

private inline fun <reified T> arrayFromJson(wrappedAdapter: Adapter<T>, reader: JsonReader, customScalarAdapters: CustomScalarAdapters): Array<T> {
private inline fun <reified T> arrayFromJson(
wrappedAdapter: Adapter<T>,
reader: JsonReader,
customScalarAdapters: CustomScalarAdapters,
): Array<T> {
reader.beginArray()
val list = mutableListOf<T>()
while (reader.hasNext()) {
Expand All @@ -311,7 +308,7 @@ inline fun <reified T> Adapter<T>.array() = object : Adapter<Array<T>> {
wrappedAdapter: Adapter<T>,
writer: JsonWriter,
customScalarAdapters: CustomScalarAdapters,
value: Array<T>
value: Array<T>,
) {
writer.beginArray()
value.forEach {
Expand Down Expand Up @@ -363,14 +360,14 @@ class ObjectAdapter<T>(
}
}

override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: T, ) {
override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: T) {
if (buffered && writer !is MapJsonWriter) {
/**
* Convert to a Map first
*/
val mapWriter = MapJsonWriter()
mapWriter.beginObject()
wrappedAdapter.toJson(mapWriter, customScalarAdapters, value, )
wrappedAdapter.toJson(mapWriter, customScalarAdapters, value)
mapWriter.endObject()

/**
Expand All @@ -379,8 +376,80 @@ class ObjectAdapter<T>(
writer.writeAny(mapWriter.root()!!)
} else {
writer.beginObject()
wrappedAdapter.toJson(writer, customScalarAdapters, value,)
wrappedAdapter.toJson(writer, customScalarAdapters, value)
writer.endObject()
}
}
}
}

private class ErrorAwareAdapter<T>(private val wrappedAdapter: Adapter<T>) : Adapter<T> {
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): T {
if (reader.peek() == JsonReader.Token.NULL) {
val error = customScalarAdapters.firstErrorStartingWith(reader.getPath())
if (error != null) {
reader.skipValue()
throw ApolloGraphQLException(error)
}
}

return wrappedAdapter.fromJson(reader, customScalarAdapters)
}

override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: T) {
wrappedAdapter.toJson(writer, customScalarAdapters, value)
}
}

private class CatchToResultAdapter<T>(private val wrappedAdapter: Adapter<T>) : Adapter<FieldResult<T>> {
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): FieldResult<T> {
return try {
FieldResult.Success(wrappedAdapter.fromJson(reader, customScalarAdapters))
} catch (e: ApolloException) {
FieldResult.Failure(e)
}
}

override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: FieldResult<T>) {
when (value) {
is FieldResult.Success -> wrappedAdapter.toJson(writer, customScalarAdapters, value.getOrThrow())
else -> Unit // ignore errors
}
}
}

private class CatchToNullAdapter<T>(private val wrappedAdapter: Adapter<T>) : Adapter<@JvmSuppressWildcards T?> {
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): T? {
return try {
wrappedAdapter.fromJson(reader, customScalarAdapters)
} catch (e: ApolloException) {
null
}
}

override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: T?) {
if (value == null) {
// XXX: this potentially writes null instead of an error
writer.nullValue()
} else {
wrappedAdapter.toJson(writer, customScalarAdapters, value)
}
}
}

@JvmName("-nullable")
fun <T : Any> Adapter<T>.nullable() = NullableAdapter(this)

@JvmName("-list")
fun <T> Adapter<T>.list() = ListAdapter(this)

@JvmName("-obj")
fun <T> Adapter<T>.obj(buffered: Boolean = false) = ObjectAdapter(this, buffered)

@JvmName("-catchToResult")
fun <T> Adapter<T>.catchToResult(): Adapter<FieldResult<T>> = CatchToResultAdapter(this)

@JvmName("-errorAware")
fun <T> Adapter<T>.errorAware(): Adapter<T> = ErrorAwareAdapter(this)

@JvmName("-catchToNull")
fun <T> Adapter<T>.catchToNull(): Adapter<T?> = CatchToNullAdapter(this)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import kotlin.jvm.JvmName
*/
fun checkFieldNotMissing(value: Any?, name: String) {
if (value == null) {
throw DefaultApolloException("Field '$name' is missing")
throw DefaultApolloException("Field '$name' is missing or null")
}
}

Expand All @@ -35,5 +35,5 @@ fun assertOneOf(vararg args: Optional<*>) {
* Helper function for the Kotlin codegen
*/
fun missingField(jsonReader: JsonReader, name: String): Nothing {
throw DefaultApolloException("Field '$name' is missing at path ${jsonReader.getPath()}")
throw DefaultApolloException("Field '$name' is missing or null at path ${jsonReader.getPath()}")
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class CustomScalarAdapters private constructor(
*/
@JvmField
val deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>?,
/**
* Errors to use with @catch
*/
@JvmField
val errors: List<Error>?,

private val unsafe: Boolean,
) : ExecutionContext.Element {

Expand Down Expand Up @@ -97,6 +103,26 @@ class CustomScalarAdapters private constructor(
val PassThrough = Builder().unsafe(true).build()
}

@ApolloExperimental
fun firstErrorStartingWith(path: List<Any>): Error? {
return errors?.firstOrNull {
it.path?.startsWith(path) == true
}
}

private fun List<Any>.startsWith(responsePath: List<Any>): Boolean {
// start at 1 to drop the `data.`
for (i in 1.until(responsePath.size)) {
if (i - 1 >= this.size) {
return false
}
if (responsePath[i] != this[i - 1]) {
return false
}
}
return true
}

fun newBuilder(): Builder {
return Builder().addAll(this)
.falseVariables(falseVariables)
Expand All @@ -108,6 +134,7 @@ class CustomScalarAdapters private constructor(
private var unsafe = false
private var falseVariables: Set<String>? = null
private var deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>? = null
private var errors: List<Error>? = null

fun falseVariables(falseVariables: Set<String>?) = apply {
this.falseVariables = falseVariables
Expand All @@ -117,6 +144,10 @@ class CustomScalarAdapters private constructor(
this.deferredFragmentIdentifiers = deferredFragmentIdentifiers
}

fun errors(errors: List<Error>?) = apply {
this.errors = errors
}

fun <T> add(
name: String,
adapter: Adapter<T>,
Expand Down Expand Up @@ -148,8 +179,9 @@ class CustomScalarAdapters private constructor(
fun build(): CustomScalarAdapters {
return CustomScalarAdapters(
adaptersMap,
falseVariables ,
falseVariables,
deferredFragmentIdentifiers,
errors,
unsafe,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ interface Executable<D: Executable.Data> {
*/
fun rootField(): CompiledField

/**
* A flag to disable error checking for the whole operation.
* Used for backward compatibility.
*/
val ignoreErrors: Boolean

/**
* Marker interface for generated models
*/
Expand Down
Loading

0 comments on commit d14bcb6

Please sign in to comment.