Skip to content

Commit

Permalink
Update ApolloClient and ApolloRequest APIs from With-ers to Builders (#…
Browse files Browse the repository at this point in the history
…3404)

* Add Builders proposal to design-docs

* Make Builders mutable and simplify constructor, following review.
Also add precision about splitting ExecutionParameters and adding a toBuilder method.

* Make ApolloClient's API Builder based instead of With-er based

* Add ApolloClient.newBuilder extension

* Make ApolloRequest's API Builder based instead of With-er based

* Split ExecutionParameters in 2 interfaces: read-only and mutable.

* Rename interface ExecutionParameters to HasMutableExecutionContext

* Rename all "with" extensions for consistency

* Add a temporary "compatibility layer" keeping the previous With-ers API available, to ease the transition

* Small improvements following review

* Make a few fun extensions on HasExecutionContext to be vals instead, for clarity abnd consistency

* Update documentation. More doc updates will come later.

* Use apply in builders following review suggestion
  • Loading branch information
BoD authored Oct 15, 2021
1 parent fa1459f commit ee151f7
Show file tree
Hide file tree
Showing 61 changed files with 1,218 additions and 529 deletions.
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

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
)
)
)
}
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

0 comments on commit ee151f7

Please sign in to comment.