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

Update ApolloClient and ApolloRequest APIs from With-ers to Builders #3404

Merged
merged 13 commits into from
Oct 15, 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
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
package com.apollographql.apollo3.api

import com.apollographql.apollo3.api.http.HttpHeader
import com.apollographql.apollo3.api.http.HttpMethod
import com.apollographql.apollo3.api.http.httpHeader
import com.apollographql.apollo3.api.http.httpHeaders
import com.apollographql.apollo3.api.http.httpMethod
import com.apollographql.apollo3.api.http.sendApqExtensions
import com.apollographql.apollo3.api.http.sendDocument
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuid4

/**
* A GraphQL request to execute. Execution can be customized with [executionContext]
*/
class ApolloRequest<D : Operation.Data>(
class ApolloRequest<D : Operation.Data> @Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.") constructor(
val operation: Operation<D>,
val requestUuid: Uuid = uuid4(),
override val executionContext: ExecutionContext = ExecutionContext.Empty,
): ExecutionParameters<ApolloRequest<D>> {
override fun withExecutionContext(executionContext: ExecutionContext): ApolloRequest<D> {
return copy(executionContext = this.executionContext + executionContext)
val requestUuid: Uuid,
override val executionContext: ExecutionContext,
) : HasExecutionContext {

fun newBuilder(): Builder<D> {
return Builder(operation).also {
it.requestUuid(requestUuid)
it.executionContext = executionContext
}
}

fun copy(
Expand All @@ -24,4 +35,45 @@ class ApolloRequest<D : Operation.Data>(
requestUuid,
executionContext
)

class Builder<D : Operation.Data>(
private var operation: Operation<D>,
) : HasMutableExecutionContext<Builder<D>> {
private var requestUuid: Uuid = uuid4()
override var executionContext: ExecutionContext = ExecutionContext.Empty
martinbonnin marked this conversation as resolved.
Show resolved Hide resolved

fun requestUuid(requestUuid: Uuid) = apply {
this.requestUuid = requestUuid
}

override fun addExecutionContext(executionContext: ExecutionContext) = apply {
this.executionContext = this.executionContext + executionContext
}

fun build(): ApolloRequest<D> {
return ApolloRequest(
operation = operation,
requestUuid = requestUuid,
executionContext = executionContext,
)
}
}
}

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpMethod(httpMethod: HttpMethod) = newBuilder().httpMethod(httpMethod).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpHeaders(httpHeaders: List<HttpHeader>) = newBuilder().httpHeaders(httpHeaders).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpHeader(httpHeader: HttpHeader) = newBuilder().httpHeader(httpHeader).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpHeader(name: String, value: String) = newBuilder().httpHeader(name, value).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withSendApqExtensions(sendApqExtensions: Boolean) = newBuilder().sendApqExtensions(sendApqExtensions).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withSendDocument(sendDocument: Boolean) = newBuilder().sendDocument(sendDocument).build()
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,17 @@ internal class CombinedExecutionContext(
}

/**
* Base class for [ApolloClient] and [ApolloRequest]
* Implemented by classes that have an [ExecutionContext]: [ApolloClient] and [ApolloRequest].
*
* This allows to set parameters on [ApolloClient] and override them per-request in [ApolloRequest] using the same API
* This allows to set parameters on [ApolloClient] and override them per-request in [ApolloRequest] using the same API.
*/
interface ExecutionParameters<T> where T : ExecutionParameters<T> {
interface HasExecutionContext {
val executionContext: ExecutionContext
fun withExecutionContext(executionContext: ExecutionContext): T
}

/**
* Implemented by classes whose [ExecutionContext] can be mutated: [ApolloClient.Builder] and [ApolloRequest.Builder].
*/
interface HasMutableExecutionContext<T> : HasExecutionContext where T : HasMutableExecutionContext<T> {
fun addExecutionContext(executionContext: ExecutionContext): T
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,9 @@ fun <D : Operation.Data> Operation<D>.composeJsonRequest(
val composer = DefaultHttpRequestComposer("unused")

val request = composer.compose(
ApolloRequest(operation = this)
.withExecutionContext(customScalarAdapters)
ApolloRequest.Builder(operation = this)
.addExecutionContext(customScalarAdapters)
.build()
)

request.body!!.writeTo(sink)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.apollographql.apollo3.api.http

import com.apollographql.apollo3.api.AnyAdapter
import com.apollographql.apollo3.api.ApolloRequest
import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.Operation
Expand Down Expand Up @@ -38,12 +37,12 @@ class DefaultHttpRequestComposer(
val requestHeaders = listOf(
HttpHeader(HEADER_APOLLO_OPERATION_ID, operation.id()),
HttpHeader(HEADER_APOLLO_OPERATION_NAME, operation.name())
) + apolloRequest.httpHeaders()
) + apolloRequest.httpHeaders

val sendApqExtensions = apolloRequest.sendApqExtensions()
val sendDocument = apolloRequest.sendDocument()
val sendApqExtensions = apolloRequest.sendApqExtensions
val sendDocument = apolloRequest.sendDocument

return when (apolloRequest.httpMethod()) {
return when (apolloRequest.httpMethod) {
HttpMethod.Get -> {
HttpRequest(
method = HttpMethod.Get,
Expand Down Expand Up @@ -279,8 +278,8 @@ class DefaultHttpRequestComposer(
apolloRequest: ApolloRequest<D>,
): Map<String, Any?> {
val operation = apolloRequest.operation
val sendApqExtensions = apolloRequest.sendApqExtensions()
val sendDocument = apolloRequest.sendDocument()
val sendApqExtensions = apolloRequest.sendApqExtensions
val sendDocument = apolloRequest.sendDocument
val customScalarAdapters = apolloRequest.executionContext[CustomScalarAdapters] ?: error("Cannot find a ResponseAdapterCache")

val query = if (sendDocument) operation.document() else null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
package com.apollographql.apollo3.api.http

import com.apollographql.apollo3.api.ExecutionContext
import com.apollographql.apollo3.api.ExecutionParameters
import com.apollographql.apollo3.api.HasExecutionContext
import com.apollographql.apollo3.api.HasMutableExecutionContext


fun <T> ExecutionParameters<T>.httpMethod() where T : ExecutionParameters<T> = executionContext[HttpMethodContext]?.value ?: HttpMethod.Post
fun <T> ExecutionParameters<T>.httpHeaders() where T : ExecutionParameters<T> = executionContext[HttpHeadersContext]?.value ?: emptyList()
fun <T> ExecutionParameters<T>.sendApqExtensions() where T : ExecutionParameters<T> = executionContext[SendApqExtensionsContext]?.value
?: false
val HasExecutionContext.httpMethod get() = executionContext[HttpMethodContext]?.value ?: HttpMethod.Post
val HasExecutionContext.httpHeaders get() = executionContext[HttpHeadersContext]?.value ?: emptyList()
val HasExecutionContext.sendApqExtensions get() = executionContext[SendApqExtensionsContext]?.value ?: false

fun <T> ExecutionParameters<T>.sendDocument() where T : ExecutionParameters<T> = executionContext[SendDocumentContext]?.value ?: true
val HasExecutionContext.sendDocument get() = executionContext[SendDocumentContext]?.value ?: true

/**
* Configures whether the request should use GET or POST
* Usually, POST request can transfer bigger GraphQL documents but are more difficult to cache
*
* Default: [HttpMethod.Post]
*/
fun <T> ExecutionParameters<T>.withHttpMethod(httpMethod: HttpMethod) where T : ExecutionParameters<T> = withExecutionContext(executionContext + HttpMethodContext(httpMethod))
fun <T> HasMutableExecutionContext<T>.httpMethod(httpMethod: HttpMethod) where T : HasMutableExecutionContext<T> = addExecutionContext(executionContext + HttpMethodContext(httpMethod))

/**
*
*/
fun <T> ExecutionParameters<T>.withHttpHeaders(httpHeaders: List<HttpHeader>) where T : ExecutionParameters<T> = withExecutionContext(
executionContext + HttpHeadersContext(httpHeaders() + httpHeaders)
fun <T> HasMutableExecutionContext<T>.httpHeaders(httpHeaders: List<HttpHeader>) where T : HasMutableExecutionContext<T> = addExecutionContext(
executionContext + HttpHeadersContext(this@httpHeaders.httpHeaders + httpHeaders)
)
fun <T> ExecutionParameters<T>.withHttpHeader(httpHeader: HttpHeader) where T : ExecutionParameters<T> = withExecutionContext(
executionContext + HttpHeadersContext(httpHeaders() + httpHeader)

fun <T> HasMutableExecutionContext<T>.httpHeader(httpHeader: HttpHeader) where T : HasMutableExecutionContext<T> = addExecutionContext(
executionContext + HttpHeadersContext(httpHeaders + httpHeader)
)
fun <T> ExecutionParameters<T>.withHttpHeader(name: String, value: String) where T : ExecutionParameters<T> = withHttpHeader(

fun <T> HasMutableExecutionContext<T>.httpHeader(name: String, value: String) where T : HasMutableExecutionContext<T> = httpHeader(
HttpHeader(name, value)
)

fun <T> ExecutionParameters<T>.withSendApqExtensions(sendApqExtensions: Boolean) where T : ExecutionParameters<T> = withExecutionContext(executionContext + SendApqExtensionsContext(sendApqExtensions))
fun <T> ExecutionParameters<T>.withSendDocument(sendDocument: Boolean) where T : ExecutionParameters<T> = withExecutionContext(executionContext + SendDocumentContext(sendDocument))
fun <T> HasMutableExecutionContext<T>.sendApqExtensions(sendApqExtensions: Boolean) where T : HasMutableExecutionContext<T> = addExecutionContext(executionContext + SendApqExtensionsContext(sendApqExtensions))
fun <T> HasMutableExecutionContext<T>.sendDocument(sendDocument: Boolean) where T : HasMutableExecutionContext<T> = addExecutionContext(executionContext + SendDocumentContext(sendDocument))


/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.apollographql.apollo3.api.internal

import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.ExecutionParameters
import com.apollographql.apollo3.api.HasExecutionContext

val <T> ExecutionParameters<T>.customScalarAdapters: CustomScalarAdapters where T: ExecutionParameters<T>
val HasExecutionContext.customScalarAdapters: CustomScalarAdapters
get() = executionContext[CustomScalarAdapters]!!
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.apollographql.apollo3.cache.http

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.ApolloRequest
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.ExecutionParameters
import com.apollographql.apollo3.api.HasMutableExecutionContext
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.http.withHttpHeader
import com.apollographql.apollo3.api.http.httpHeader
import com.apollographql.apollo3.network.http.HttpInfo
import com.apollographql.apollo3.network.http.HttpNetworkTransport
import java.io.File
Expand Down Expand Up @@ -34,21 +35,20 @@ enum class HttpFetchPolicy {
NetworkOnly,
}

fun ApolloClient.withHttpCache(
fun ApolloClient.Builder.httpCache(
directory: File,
maxSize: Long,
): ApolloClient {
): ApolloClient.Builder {
val networkTransport = networkTransport
check(networkTransport is HttpNetworkTransport) {
"withHttpCache requires a HttpNetworkTransport"
}
return copy(
networkTransport = networkTransport.copy(
engine = CachingHttpEngine(
directory = directory,
maxSize = maxSize,
delegate = networkTransport.engine
)
return networkTransport(networkTransport.copy(
engine = CachingHttpEngine(
directory = directory,
maxSize = maxSize,
delegate = networkTransport.engine
)
Comment on lines +46 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

Random thoughts: should we go Builders all the way down and convert HttpNetworkTransport to Builders as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not completely sure. Maybe we could come up with a rule of thumb like: if there are more than n possible parameters, let's have a builder, otherwise a regular constructor is enough?

Copy link
Contributor

Choose a reason for hiding this comment

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

There are 2 strong arguments for Builders everywhere though:

  1. consistency
  2. java interop

)
)
}
Expand All @@ -60,27 +60,57 @@ val <D : Operation.Data> ApolloResponse<D>.isFromHttpCache
} ?: false


fun <T> ExecutionParameters<T>.withHttpFetchPolicy(httpFetchPolicy: HttpFetchPolicy): T where T: ExecutionParameters<T> {
val policyStr = when(httpFetchPolicy) {
fun <T> HasMutableExecutionContext<T>.httpFetchPolicy(httpFetchPolicy: HttpFetchPolicy): T where T : HasMutableExecutionContext<T> {
val policyStr = when (httpFetchPolicy) {
HttpFetchPolicy.CacheFirst -> CachingHttpEngine.CACHE_FIRST
HttpFetchPolicy.CacheOnly -> CachingHttpEngine.CACHE_ONLY
HttpFetchPolicy.NetworkFirst -> CachingHttpEngine.NETWORK_FIRST
HttpFetchPolicy.NetworkOnly -> CachingHttpEngine.NETWORK_ONLY
}

return withHttpHeader(
return httpHeader(
CachingHttpEngine.CACHE_FETCH_POLICY_HEADER, policyStr
)
}

fun <T> ExecutionParameters<T>.withHttpExpireTimeout(millis: Long) where T: ExecutionParameters<T> = withHttpHeader(
fun <T> HasMutableExecutionContext<T>.httpExpireTimeout(millis: Long) where T : HasMutableExecutionContext<T> = httpHeader(
CachingHttpEngine.CACHE_EXPIRE_TIMEOUT_HEADER, millis.toString()
)

fun <T> ExecutionParameters<T>.withHttpExpireAfterRead(expireAfterRead: Boolean) where T: ExecutionParameters<T> = withHttpHeader(
fun <T> HasMutableExecutionContext<T>.httpExpireAfterRead(expireAfterRead: Boolean) where T : HasMutableExecutionContext<T> = httpHeader(
CachingHttpEngine.CACHE_EXPIRE_AFTER_READ_HEADER, expireAfterRead.toString()
)

fun <T> ExecutionParameters<T>.withHttpDoNotStore(doNotStore: Boolean) where T: ExecutionParameters<T> = withHttpHeader(
fun <T> HasMutableExecutionContext<T>.httpDoNotStore(doNotStore: Boolean) where T : HasMutableExecutionContext<T> = httpHeader(
CachingHttpEngine.CACHE_DO_NOT_STORE, doNotStore.toString()
)
)

@Deprecated("Please use ApolloClient.Builder methods instead. This will be removed in v3.0.0.")
fun ApolloClient.withHttpCache(
directory: File,
maxSize: Long,
): ApolloClient = newBuilder().httpCache(directory, maxSize).build()

@Deprecated("Please use ApolloClient.Builder methods instead. This will be removed in v3.0.0.")
fun ApolloClient.withHttpFetchPolicy(httpFetchPolicy: HttpFetchPolicy) = newBuilder().httpFetchPolicy(httpFetchPolicy).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpFetchPolicy(httpFetchPolicy: HttpFetchPolicy) = newBuilder().httpFetchPolicy(httpFetchPolicy).build()

@Deprecated("Please use ApolloClient.Builder methods instead. This will be removed in v3.0.0.")
fun ApolloClient.withHttpExpireTimeout(millis: Long) = newBuilder().httpExpireTimeout(millis).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpExpireTimeout(millis: Long) = newBuilder().httpExpireTimeout(millis).build()

@Deprecated("Please use ApolloClient.Builder methods instead. This will be removed in v3.0.0.")
fun ApolloClient.withHttpExpireAfterRead(expireAfterRead: Boolean) = newBuilder().httpExpireAfterRead(expireAfterRead).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpExpireAfterRead(expireAfterRead: Boolean) = newBuilder().httpExpireAfterRead(expireAfterRead).build()

@Deprecated("Please use ApolloClient.Builder methods instead. This will be removed in v3.0.0.")
fun ApolloClient.withHttpDoNotStore(doNotStore: Boolean) = newBuilder().httpDoNotStore(doNotStore).build()

@Deprecated("Please use ApolloRequest.Builder methods instead. This will be removed in v3.0.0.")
fun <D : Operation.Data> ApolloRequest<D>.withHttpDoNotStore(doNotStore: Boolean) = newBuilder().httpDoNotStore(doNotStore).build()
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ class ApolloIdlingResource(
}
}

fun ApolloClient.withIdlingResource(idlingResource: ApolloIdlingResource): ApolloClient {
return withFlowDecorator {
fun ApolloClient.Builder.idlingResource(idlingResource: ApolloIdlingResource): ApolloClient.Builder {
return addFlowDecorator {
it.onStart {
idlingResource.operationStart()
}.onCompletion {
idlingResource.operationEnd()
}
}
}
}

@Deprecated("Please use ApolloClient.Builder methods instead. This will be removed in v3.0.0.")
fun ApolloClient.withIdlingResource(idlingResource: ApolloIdlingResource) = newBuilder().idlingResource(idlingResource).build()
Loading