Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add Mutiny support #3198

Merged
merged 2 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This library is designed primarily with Android in mind, but you can use it in a
* Normalized cache
* File uploads
* Custom scalar types
* Support for RxJava2, RxJava3, Coroutines and Reactor
* Reactive bindings for: RxJava2, RxJava3, Coroutines, Reactor and Mutiny

## Getting started

Expand Down Expand Up @@ -67,6 +67,8 @@ dependencies {
implementation("com.apollographql.apollo:apollo-normalized-cache-sqlite:x.y.z")
// optional: for coroutines support
implementation("com.apollographql.apollo:apollo-coroutines-support:x.y.z")
// optional: for Mutiny support
implementation("com.apollographql.apollo:apollo-mutiny-support:x.y.z")
// optional: for Reactor support
implementation("com.apollographql.apollo:apollo-reactor-support:x.y.z")
// optional: for RxJava3 support
Expand Down
17 changes: 17 additions & 0 deletions apollo-mutiny-support/api.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Signature format: 4.0
package com.apollographql.apollo.mutiny {

public final class KotlinExtensions {
}

public class MutinyApollo {
method public static <T> io.smallrye.mutiny.Uni<com.apollographql.apollo.api.Response<T!>!> from(com.apollographql.apollo.ApolloQueryWatcher<T!>);
method public static <T> io.smallrye.mutiny.Uni<com.apollographql.apollo.api.Response<T!>!> from(com.apollographql.apollo.ApolloCall<T!>);
method public static io.smallrye.mutiny.Uni<java.lang.Void!> from(com.apollographql.apollo.ApolloPrefetch);
method public static <T> io.smallrye.mutiny.Multi<com.apollographql.apollo.api.Response<T!>!> from(com.apollographql.apollo.ApolloSubscriptionCall<T!>);
method public static <T> io.smallrye.mutiny.Multi<com.apollographql.apollo.api.Response<T!>!> from(com.apollographql.apollo.ApolloSubscriptionCall<T!>, io.smallrye.mutiny.subscription.BackPressureStrategy);
method public static <T> io.smallrye.mutiny.Uni<T!> from(com.apollographql.apollo.cache.normalized.ApolloStoreOperation<T!>);
}

}

15 changes: 15 additions & 0 deletions apollo-mutiny-support/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
`java-library`
kotlin("jvm")
}

dependencies {
add("implementation", project(":apollo-api"))
add("api", groovy.util.Eval.x(project, "x.dep.mutiny"))
add("api", project(":apollo-runtime"))
}

tasks.withType<Javadoc> {
options.encoding = "UTF-8"
}

4 changes: 4 additions & 0 deletions apollo-mutiny-support/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POM_ARTIFACT_ID=apollo-mutiny-support
POM_NAME=Apollo GraphQL Mutiny Support
POM_DESCRIPTION=Apollo GraphQL Mutiny bindings
POM_PACKAGING=jar
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package com.apollographql.apollo.mutiny;

import com.apollographql.apollo.ApolloCall;
import com.apollographql.apollo.ApolloPrefetch;
import com.apollographql.apollo.ApolloQueryWatcher;
import com.apollographql.apollo.ApolloSubscriptionCall;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.cache.normalized.ApolloStoreOperation;
import com.apollographql.apollo.exception.ApolloException;
import com.apollographql.apollo.internal.subscription.ApolloSubscriptionTerminatedException;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.subscription.BackPressureStrategy;
import org.jetbrains.annotations.NotNull;

import static com.apollographql.apollo.api.internal.Utils.checkNotNull;

/**
* The MutinyApollo class provides methods for converting ApolloCall, ApolloPrefetch and ApolloWatcher types to Mutiny sources.
*/
public class MutinyApollo {

private MutinyApollo() {
throw new AssertionError("This class cannot be instantiated");
}

/**
* Converts an {@link ApolloQueryWatcher} to an asynchronous Uni.
*
* @param watcher the ApolloQueryWatcher to convert.
* @param <T> the value type
* @return the converted Uni
* @throws NullPointerException if watcher == null
*/
@NotNull
public static <T> Uni<Response<T>> from(@NotNull final ApolloQueryWatcher<T> watcher) {
checkNotNull(watcher, "watcher == null");
return Uni.createFrom().emitter(emitter -> {
ApolloQueryWatcher<T> clone = watcher.clone();
emitter.onTermination(clone::cancel);
clone.enqueueAndWatch(new ApolloCall.Callback<T>() {
@Override public void onResponse(@NotNull Response<T> response) {
emitter.complete(response);
}

@Override public void onFailure(@NotNull ApolloException e) {
emitter.fail(e);
}
});
});
}

/**
* Converts an {@link ApolloCall} to an {@link Uni}. The number of emissions this Uni will have is based on the {@link
* com.apollographql.apollo.fetcher.ResponseFetcher} used with the call.
*
* @param call the ApolloCall to convert
* @param <T> the value type.
* @return the converted Uni
* @throws NullPointerException if originalCall == null
*/
@NotNull
public static <T> Uni<Response<T>> from(@NotNull final ApolloCall<T> call) {
checkNotNull(call, "call == null");
return Uni.createFrom().emitter(emitter -> {
ApolloCall<T> clone = call.toBuilder().build();
emitter.onTermination(clone::cancel);
clone.enqueue(new ApolloCall.Callback<T>() {
@Override public void onResponse(@NotNull Response<T> response) {
emitter.complete(response);
}

@Override public void onFailure(@NotNull ApolloException e) {
emitter.fail(e);
}

@Override public void onStatusEvent(@NotNull ApolloCall.StatusEvent event) {
if (event == ApolloCall.StatusEvent.COMPLETED) {
emitter.complete(null);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the subscriber already got the response, this call will be dropped. I believe it's ok but better double-check.

Copy link
Contributor Author

@aoudiamoncef aoudiamoncef Jul 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In RxJava we have:

          if (event == ApolloCall.StatusEvent.COMPLETED && !emitter.isDisposed()) {
            emitter.onComplete();
          }

I don't know how to achieve the same check in Mutiny.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than that agreed with @cescoffier

}
}
}
);
}
);
}

/**
* Converts an {@link ApolloPrefetch} to a synchronous Uni<Void>
*
* @param prefetch the ApolloPrefetch to convert
* @return the converted Uni<Void>
* @throws NullPointerException if prefetch == null
*/
@NotNull
public static Uni<Void> from(@NotNull final ApolloPrefetch prefetch) {
checkNotNull(prefetch, "prefetch == null");
return Uni.createFrom().emitter(emitter -> {
ApolloPrefetch clone = prefetch.clone();
emitter.onTermination(clone::cancel);
clone.enqueue(new ApolloPrefetch.Callback() {
@Override public void onSuccess() {
emitter.complete(null);
}

@Override public void onFailure(@NotNull ApolloException e) {
emitter.fail(e);
}
}
);
});
}

@NotNull
public static <T> Multi<Response<T>> from(@NotNull ApolloSubscriptionCall<T> call) {
return from(call, BackPressureStrategy.LATEST);
}

@NotNull
public static <T> Multi<Response<T>> from(@NotNull final ApolloSubscriptionCall<T> call,
@NotNull BackPressureStrategy backpressureStrategy) {
checkNotNull(call, "originalCall == null");
checkNotNull(backpressureStrategy, "backpressureStrategy == null");
return Multi.createFrom().emitter(emitter -> {
ApolloSubscriptionCall<T> clone = call.clone();
emitter.onTermination(clone::cancel);
clone.execute(
new ApolloSubscriptionCall.Callback<T>() {
@Override public void onResponse(@NotNull Response<T> response) {
if (!emitter.isCancelled()) {
emitter.emit(response);
}
}

@Override public void onFailure(@NotNull ApolloException e) {
if (!emitter.isCancelled()) {
emitter.fail(e);
}
}

@Override public void onCompleted() {
if (!emitter.isCancelled()) {
emitter.complete();
}
}

@Override public void onTerminated() {
onFailure(new ApolloSubscriptionTerminatedException("Subscription server unexpectedly terminated connection"));
}

@Override public void onConnected() {
//Do nothing when GraphQL subscription server connection is opened
}
}
);
}, backpressureStrategy);
}

/**
* Converts an {@link ApolloStoreOperation} to a Uni.
*
* @param operation the ApolloStoreOperation to convert
* @param <T> the value type
* @return the converted Uni
*/
@NotNull
public static <T> Uni<T> from(@NotNull final ApolloStoreOperation<T> operation) {
checkNotNull(operation, "operation == null");
return Uni.createFrom().emitter(emitter -> operation.enqueue(new ApolloStoreOperation.Callback<T>() {
@Override
public void onSuccess(T result) {
emitter.complete(result);
}

@Override
public void onFailure(@NotNull Throwable t) {
emitter.fail(t);
}
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
@file:Suppress("NOTHING_TO_INLINE")
@file:JvmName("KotlinExtensions")

package com.apollographql.apollo.mutiny

import com.apollographql.apollo.ApolloCall
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.ApolloMutationCall
import com.apollographql.apollo.ApolloPrefetch
import com.apollographql.apollo.ApolloQueryCall
import com.apollographql.apollo.ApolloQueryWatcher
import com.apollographql.apollo.ApolloSubscriptionCall
import com.apollographql.apollo.api.Mutation
import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.api.Query
import com.apollographql.apollo.api.Response
import com.apollographql.apollo.api.Subscription
import com.apollographql.apollo.cache.normalized.ApolloStoreOperation

import io.smallrye.mutiny.Multi
import io.smallrye.mutiny.Uni
import io.smallrye.mutiny.subscription.BackPressureStrategy

@JvmSynthetic
inline fun ApolloPrefetch.mutiny(): Uni<Void> =
MutinyApollo.from(this)

@JvmSynthetic
inline fun <T> ApolloStoreOperation<T>.mutiny(): Uni<T> =
MutinyApollo.from(this)

@JvmSynthetic
inline fun <T> ApolloQueryWatcher<T>.mutiny(): Uni<Response<T>> =
MutinyApollo.from(this)

@JvmSynthetic
inline fun <T> ApolloCall<T>.mutiny(): Uni<Response<T>> =
MutinyApollo.from(this)

@JvmSynthetic
inline fun <T> ApolloSubscriptionCall<T>.mutiny(
backpressureStrategy: BackPressureStrategy = BackPressureStrategy.LATEST
): Multi<Response<T>> = MutinyApollo.from(this, backpressureStrategy)

/**
* Creates a new [ApolloQueryCall] call and then converts it to an [Uni].
*
* The number of emissions this Uni will have is based on the
* [com.apollographql.apollo.fetcher.ResponseFetcher] used with the call.
*/
@JvmSynthetic
inline fun <D : Operation.Data, T, V : Operation.Variables> ApolloClient.mutinyQuery(
query: Query<D, T, V>,
configure: ApolloQueryCall<T>.() -> ApolloQueryCall<T> = { this }
): Uni<Response<T>> = query(query).configure().mutiny()

/**
* Creates a new [ApolloMutationCall] call and then converts it to a [Uni].
*/
@JvmSynthetic
inline fun <D : Operation.Data, T, V : Operation.Variables> ApolloClient.mutinyMutate(
mutation: Mutation<D, T, V>,
configure: ApolloMutationCall<T>.() -> ApolloMutationCall<T> = { this }
): Uni<Response<T>> = mutate(mutation).configure().mutiny()

/**
* Creates a new [ApolloMutationCall] call and then converts it to a [Uni].
*
* Provided optimistic updates will be stored in [com.apollographql.apollo.cache.normalized.ApolloStore]
* immediately before mutation execution. Any [ApolloQueryWatcher] dependent on the changed cache records will
* be re-fetched.
*/
@JvmSynthetic
inline fun <D : Operation.Data, T, V : Operation.Variables> ApolloClient.mutinyMutate(
mutation: Mutation<D, T, V>,
withOptimisticUpdates: D,
configure: ApolloMutationCall<T>.() -> ApolloMutationCall<T> = { this }
): Uni<Response<T>> = mutate(mutation, withOptimisticUpdates).configure().mutiny()

/**
* Creates the [ApolloPrefetch] by wrapping the operation object inside and then converts it to a [Uni].
*/
@JvmSynthetic
inline fun <D : Operation.Data, T, V : Operation.Variables> ApolloClient.mutinyPrefetch(
operation: Operation<D, T, V>
): Uni<Void> = prefetch(operation).mutiny()

/**
* Creates a new [ApolloSubscriptionCall] call and then converts it to a [Multi].
*
* Back-pressure strategy can be provided via [backpressureStrategy] parameter. The default value is [BackPressureStrategy.LATEST]
*/
@JvmSynthetic
inline fun <D : Operation.Data, T, V : Operation.Variables> ApolloClient.mutinySubscribe(
subscription: Subscription<D, T, V>,
backpressureStrategy: BackPressureStrategy = BackPressureStrategy.LATEST
): Multi<Response<T>> = subscribe(subscription).mutiny(backpressureStrategy)
3 changes: 2 additions & 1 deletion docs/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ module.exports = {
'advanced/coroutines',
'advanced/rxjava2',
'advanced/rxjava3',
'advanced/reactor'
'advanced/reactor',
'advanced/mutiny'
],
Reference: [
'essentials/plugin-configuration',
Expand Down
Loading