Skip to content

Commit 382c0c4

Browse files
committed
Add cachePolicyResponseMapper
1 parent 7008f75 commit 382c0c4

8 files changed

Lines changed: 617 additions & 4 deletions

File tree

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,3 +753,27 @@ fun <T> MutableExecutionOptions<T>.clock(clock: () -> Long): T {
753753
@Suppress("UNCHECKED_CAST")
754754
return this as T
755755
}
756+
757+
internal class CachePolicyResponseMapperContext(val value: (ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*>) :
758+
ExecutionContext.Element {
759+
override val key: ExecutionContext.Key<*>
760+
get() = Key
761+
762+
companion object Key : ExecutionContext.Key<CachePolicyResponseMapperContext>
763+
}
764+
765+
private val defaultCachePolicyResponseMapper: (ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*> = { response, _ ->
766+
response.errorsAsException()
767+
}
768+
769+
internal val ExecutionOptions.cachePolicyResponseMapper: ((ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*>)
770+
get() = executionContext[CachePolicyResponseMapperContext]?.value ?: defaultCachePolicyResponseMapper
771+
772+
/**
773+
* Sets a response mapper that will be used inside the built-in fetch policies interceptors to transform the responses from the cache.
774+
*/
775+
fun <T> MutableExecutionOptions<T>.cachePolicyResponseMapper(mapper: (ApolloResponse<*>, FetchPolicy) -> ApolloResponse<*>): T {
776+
addExecutionContext(CachePolicyResponseMapperContext(mapper))
777+
@Suppress("UNCHECKED_CAST")
778+
return this as T
779+
}

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/FetchPolicyInterceptors.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ val CacheOnlyInterceptor = object : ApolloInterceptor {
3333
.newBuilder()
3434
.fetchFromCache(true)
3535
.build()
36-
).map { it.errorsAsException() }
36+
).map {
37+
@Suppress("UNCHECKED_CAST")
38+
request.cachePolicyResponseMapper(it, FetchPolicy.CacheOnly) as ApolloResponse<D>
39+
}
3740
}
3841
}
3942

@@ -57,7 +60,10 @@ val CacheFirstInterceptor = object : ApolloInterceptor {
5760
.newBuilder()
5861
.fetchFromCache(true)
5962
.build()
60-
).single().errorsAsException()
63+
).single().let {
64+
@Suppress("UNCHECKED_CAST")
65+
request.cachePolicyResponseMapper(it, FetchPolicy.CacheFirst) as ApolloResponse<D>
66+
}
6167
emit(cacheResponse.newBuilder().isLast(cacheResponse.exception == null).build())
6268
if (cacheResponse.exception == null) {
6369
return@flow
@@ -103,7 +109,10 @@ val NetworkFirstInterceptor = object : ApolloInterceptor {
103109
.newBuilder()
104110
.fetchFromCache(true)
105111
.build()
106-
).single().errorsAsException()
112+
).single().let {
113+
@Suppress("UNCHECKED_CAST")
114+
request.cachePolicyResponseMapper(it, FetchPolicy.NetworkFirst) as ApolloResponse<D>
115+
}
107116
emit(cacheResponse)
108117
}
109118
}
@@ -120,7 +129,10 @@ val CacheAndNetworkInterceptor = object : ApolloInterceptor {
120129
.newBuilder()
121130
.fetchFromCache(true)
122131
.build()
123-
).single().errorsAsException()
132+
).single().let {
133+
@Suppress("UNCHECKED_CAST")
134+
request.cachePolicyResponseMapper(it, FetchPolicy.CacheAndNetwork) as ApolloResponse<D>
135+
}
124136

125137
emit(cacheResponse.newBuilder().isLast(false).build())
126138

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import com.apollographql.apollo.annotations.ApolloExperimental
2+
3+
plugins {
4+
alias(libs.plugins.kotlin.multiplatform)
5+
id("com.apollographql.apollo")
6+
}
7+
8+
kotlin {
9+
configureKmp(
10+
withJs = emptySet(),
11+
withWasm = emptySet(),
12+
withAndroid = false,
13+
withApple = AppleTargets.Host,
14+
)
15+
16+
sourceSets {
17+
getByName("commonMain") {
18+
dependencies {
19+
implementation(libs.apollo.runtime)
20+
implementation("com.apollographql.cache:normalized-cache-sqlite")
21+
}
22+
}
23+
24+
getByName("commonTest") {
25+
dependencies {
26+
implementation("com.apollographql.cache:test-utils")
27+
implementation(libs.apollo.mockserver)
28+
implementation(libs.kotlin.test)
29+
}
30+
}
31+
32+
getByName("jvmTest") {
33+
dependencies {
34+
implementation(libs.slf4j.nop)
35+
}
36+
}
37+
}
38+
}
39+
40+
apollo {
41+
service("service") {
42+
packageName.set("test")
43+
44+
@OptIn(ApolloExperimental::class)
45+
plugin("com.apollographql.cache:normalized-cache-apollo-compiler-plugin") {
46+
argument("packageName", packageName.get())
47+
}
48+
49+
mapScalar("Category", "test.Category", "test.CategoryAdapter")
50+
}
51+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
extend schema
2+
@link(
3+
url: "https://specs.apollo.dev/kotlin_labs/v0.5",
4+
import: ["@typePolicy", "@fieldPolicy"]
5+
)
6+
7+
extend type User @typePolicy(keyFields: "id")
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
query MeWithNickNameQuery {
2+
me {
3+
id
4+
firstName
5+
lastName
6+
nickName
7+
}
8+
}
9+
10+
query MeWithNickNameAndProjectQuery {
11+
me {
12+
id
13+
firstName
14+
lastName
15+
nickName
16+
bestFriend {
17+
id
18+
firstName
19+
lastName
20+
}
21+
}
22+
}
23+
24+
query UsersQuery($ids: [ID!]!) {
25+
users(ids: $ids) {
26+
id
27+
firstName
28+
lastName
29+
email
30+
}
31+
}
32+
33+
query MeWithBestFriendQuery {
34+
me {
35+
id
36+
firstName
37+
lastName
38+
bestFriend {
39+
id
40+
firstName
41+
lastName
42+
}
43+
projects {
44+
lead {
45+
id
46+
firstName
47+
lastName
48+
}
49+
users {
50+
id
51+
firstName
52+
lastName
53+
}
54+
}
55+
}
56+
}
57+
58+
query DefaultProjectQuery($id: ID! = "42") {
59+
project(id: $id) {
60+
id
61+
name
62+
description
63+
}
64+
project2: project(id: "44") {
65+
id
66+
name
67+
description
68+
}
69+
}
70+
71+
query WithFragmentsQuery {
72+
me {
73+
id
74+
firstName0: firstName
75+
... on User {
76+
lastName
77+
... on User {
78+
nickName0: nickName
79+
}
80+
}
81+
... UserFields
82+
}
83+
84+
me {
85+
firstName0: firstName
86+
mainProject {
87+
id
88+
lead0: lead {
89+
id
90+
}
91+
}
92+
mainProject {
93+
lead0: lead {
94+
id
95+
firstName
96+
}
97+
}
98+
}
99+
}
100+
101+
fragment UserFields on User {
102+
email0: email
103+
category
104+
bestFriend0:bestFriend {
105+
id
106+
firstName
107+
lastName
108+
}
109+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
type Query {
2+
me: User!
3+
users(ids: [ID!]!): [User]!
4+
project(id: ID! = "1"): Project
5+
user(category: Category!): User!
6+
someInt: Int
7+
someInt2: Int
8+
}
9+
10+
type User {
11+
id: ID!
12+
firstName: String!
13+
lastName: String!
14+
nickName: String
15+
email: String!
16+
bestFriend: User
17+
projects: [Project!]!
18+
mainProject: Project!
19+
category: Category!
20+
moreInfo: Json!
21+
employeeInfo: EmployeeInfo
22+
}
23+
24+
type Project {
25+
id: ID!
26+
name: String!
27+
description: String
28+
lead: User
29+
users: [User!]!
30+
}
31+
32+
type EmployeeInfo {
33+
id: ID!
34+
salary: Int
35+
department: String
36+
}
37+
38+
scalar Category
39+
scalar Json
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package test
2+
3+
import com.apollographql.apollo.api.Adapter
4+
import com.apollographql.apollo.api.CustomScalarAdapters
5+
import com.apollographql.apollo.api.json.JsonReader
6+
import com.apollographql.apollo.api.json.JsonWriter
7+
8+
data class Category(
9+
val code: Int,
10+
val name: String,
11+
)
12+
13+
val CategoryAdapter = object : Adapter<Category> {
14+
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): Category {
15+
reader.beginObject()
16+
var code: Int? = null
17+
var name: String? = null
18+
while (true) {
19+
when (reader.selectName(listOf("code", "name"))) {
20+
0 -> code = reader.nextInt()
21+
1 -> name = reader.nextString()
22+
else -> break
23+
}
24+
}
25+
reader.endObject()
26+
return Category(code = code!!, name = name!!)
27+
}
28+
29+
override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: Category) {
30+
writer.beginObject()
31+
writer.name("code").value(value.code)
32+
writer.name("name").value(value.name)
33+
writer.endObject()
34+
}
35+
}

0 commit comments

Comments
 (0)