Skip to content

Commit

Permalink
Allow client to use post method with persisted query.
Browse files Browse the repository at this point in the history
  • Loading branch information
gumimin committed Jun 28, 2023
1 parent 6e0cc97 commit 5353d1d
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ internal val MESSAGE_DIGEST: MessageDigest = MessageDigest.getInstance("SHA-256"
fun GraphQLClientRequest<*>.getQueryId(): String =
String.format(
"%064x",
BigInteger(1, MESSAGE_DIGEST.digest(this.query.toByteArray(StandardCharsets.UTF_8)))
BigInteger(1, MESSAGE_DIGEST.digest(this.query?.toByteArray(StandardCharsets.UTF_8)))
).also {
MESSAGE_DIGEST.reset()
}

fun AutomaticPersistedQueriesExtension.toQueryParamString() = """{"persistedQuery":{"version":$version,"sha256Hash":"$sha256Hash"}}"""
fun AutomaticPersistedQueriesExtension.toExtentionsBodyMap() = mapOf(
"persistedQuery" to mapOf(
"version" to version,
"sha256Hash" to sha256Hash
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@
package com.expediagroup.graphql.client.types

data class AutomaticPersistedQueriesSettings(
val enabled: Boolean = false
val enabled: Boolean = false,
val httpMethod: HttpMethod = HttpMethod.GET
) {
companion object {
const val VERSION: Int = 1
}
sealed class HttpMethod {
object GET : HttpMethod()
object POST : HttpMethod()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import kotlin.reflect.KClass
* @see [GraphQL Over HTTP](https://graphql.org/learn/serving-over-http/#post-request) for additional details
*/
interface GraphQLClientRequest<T : Any> {
val query: String
val query: String?
val operationName: String?
get() = null
val variables: Any?
get() = null
val extensions: Any?
val extensions: Map<Any, Any>?
get() = null

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.expediagroup.graphql.client.ktor

import com.expediagroup.graphql.client.GraphQLClient
import com.expediagroup.graphql.client.extensions.getQueryId
import com.expediagroup.graphql.client.extensions.toExtentionsBodyMap
import com.expediagroup.graphql.client.extensions.toQueryParamString
import com.expediagroup.graphql.client.serializer.GraphQLClientSerializer
import com.expediagroup.graphql.client.serializer.defaultGraphQLSerializer
Expand Down Expand Up @@ -59,28 +60,65 @@ open class GraphQLKtorClient(
sha256Hash = queryId
)

val apqRawResultWithoutQuery: String = httpClient.get(url) {
expectSuccess = true
header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded)
accept(ContentType.Application.Json)
url {
parameters.append("extension", automaticPersistedQueriesExtension.toQueryParamString())
val apqRawResultWithoutQuery: String = when (automaticPersistedQueriesSettings.httpMethod) {
is AutomaticPersistedQueriesSettings.HttpMethod.GET -> {
httpClient
.get(url) {
expectSuccess = true
header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded)
accept(ContentType.Application.Json)
url {
parameters.append("extension", automaticPersistedQueriesExtension.toQueryParamString())
}
}.body()
}
}.body()

is AutomaticPersistedQueriesSettings.HttpMethod.POST -> {
val requestWithoutQuery = object : GraphQLClientRequest<T> by request {
override val query = null
override val extensions = request.extensions?.plus(automaticPersistedQueriesExtension.toExtentionsBodyMap())
}
httpClient
.post(url) {
expectSuccess = true
apply(requestCustomizer)
accept(ContentType.Application.Json)
setBody(TextContent(serializer.serialize(requestWithoutQuery), ContentType.Application.Json))
}.body()
}
}

serializer.deserialize(apqRawResultWithoutQuery, request.responseType()).let {
if (it.errors.isNullOrEmpty() && it.data != null) return it
}

val apqRawResultWithQuery: String = httpClient.get(url) {
expectSuccess = true
header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded)
accept(ContentType.Application.Json)
url {
parameters.append("query", serializer.serialize(request))
parameters.append("extension", automaticPersistedQueriesExtension.toQueryParamString())
val apqRawResultWithQuery: String = when (automaticPersistedQueriesSettings.httpMethod) {
is AutomaticPersistedQueriesSettings.HttpMethod.GET -> {
httpClient
.get(url) {
expectSuccess = true
header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded)
accept(ContentType.Application.Json)
url {
parameters.append("query", serializer.serialize(request))
parameters.append("extension", automaticPersistedQueriesExtension.toQueryParamString())
}
}.body()
}
}.body()

is AutomaticPersistedQueriesSettings.HttpMethod.POST -> {
val requestWithQuery = object : GraphQLClientRequest<T> by request {
override val extensions = request.extensions?.plus(automaticPersistedQueriesExtension.toExtentionsBodyMap())
}
httpClient
.post(url) {
expectSuccess = true
apply(requestCustomizer)
accept(ContentType.Application.Json)
setBody(TextContent(serializer.serialize(requestWithQuery), ContentType.Application.Json))
}.body()
}
}

serializer.deserialize(apqRawResultWithQuery, request.responseType())
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class GraphQLKtorClientTest {
}

@Test
fun `verifies spring web client can execute query using apq with cached query`() {
fun `verifies spring web client can execute query using GET with persisted query`() {
val expectedResponse = JacksonGraphQLResponse(data = HelloWorldResult("Hello World!"))
WireMock.stubFor(
WireMock
Expand Down Expand Up @@ -274,7 +274,39 @@ class GraphQLKtorClientTest {
}

@Test
fun `verifies spring web client can execute query using apq with non-cached query`() {
fun `verifies spring web client can execute query using POST with persisted query`() {
val expectedResponse = JacksonGraphQLResponse(data = HelloWorldResult("Hello World!"))
WireMock.stubFor(
WireMock
.post("/graphql")
.withHeader("Content-Type", EqualToPattern("application/json"))
.willReturn(
WireMock.aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsString(expectedResponse))
)
)

val client = GraphQLKtorClient(
url = URL("${wireMockServer.baseUrl()}/graphql"),
serializer = GraphQLClientJacksonSerializer(),
automaticPersistedQueriesSettings = AutomaticPersistedQueriesSettings(enabled = true, httpMethod = AutomaticPersistedQueriesSettings.HttpMethod.POST)
)

runBlocking {
val result: GraphQLClientResponse<HelloWorldResult> = client.execute(HelloWorldQuery())

assertNotNull(result)
assertNotNull(result.data)
assertEquals(expectedResponse.data?.helloWorld, result.data?.helloWorld)
assertNull(result.errors)
assertNull(result.extensions)
}
}

@Test
fun `verifies spring web client can execute query using GET with not persisted query`() {
val expectedErrorResponse = JacksonGraphQLResponse(
data = null,
errors = listOf(
Expand Down Expand Up @@ -331,6 +363,59 @@ class GraphQLKtorClientTest {
}
}

@Test
fun `verifies spring web client can execute query using POST with not persisted query`() {
val expectedErrorResponse = JacksonGraphQLResponse(
data = null,
errors = listOf(
JacksonGraphQLError(
message = "PersistedQueryNotFound"
)
),
extensions = mapOf("persistedQueryId" to "dd79d72356e3cfd09a542b572c3c73e4e8d90c1c7d5c27d74bcff4e7423178ae", "classification" to "PersistedQueryNotFound")
)
WireMock.stubFor(
WireMock
.post("/graphql")
.withHeader("Content-Type", EqualToPattern("application/json"))
.willReturn(
WireMock.aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsString(expectedErrorResponse))
)
)

val expectedResponse = JacksonGraphQLResponse(data = HelloWorldResult("Hello World!"))
WireMock.stubFor(
WireMock
.post("/graphql")
.withHeader("Content-Type", EqualToPattern("application/json"))
.willReturn(
WireMock.aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsString(expectedResponse))
)
)

val client = GraphQLKtorClient(
url = URL("${wireMockServer.baseUrl()}/graphql"),
serializer = GraphQLClientJacksonSerializer(),
automaticPersistedQueriesSettings = AutomaticPersistedQueriesSettings(enabled = true, httpMethod = AutomaticPersistedQueriesSettings.HttpMethod.POST)
)

runBlocking {
val result: GraphQLClientResponse<HelloWorldResult> = client.execute(HelloWorldQuery())

assertNotNull(result)
assertNotNull(result.data)
assertEquals(expectedResponse.data?.helloWorld, result.data?.helloWorld)
assertNull(result.errors)
assertNull(result.extensions)
}
}

@Test
fun `verifies Non-OK HTTP responses will throw error`() {
WireMock.stubFor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.expediagroup.graphql.client.spring

import com.expediagroup.graphql.client.GraphQLClient
import com.expediagroup.graphql.client.extensions.getQueryId
import com.expediagroup.graphql.client.extensions.toExtentionsBodyMap
import com.expediagroup.graphql.client.extensions.toQueryParamString
import com.expediagroup.graphql.client.serializer.GraphQLClientSerializer
import com.expediagroup.graphql.client.serializer.defaultGraphQLSerializer
Expand Down Expand Up @@ -62,32 +63,69 @@ open class GraphQLWebClient(
sha256Hash = queryId
)

val apqRawResultWithoutQuery = client
.get()
.uri {
it.queryParam("extension", "{extension}").build(automaticPersistedQueriesExtension.toQueryParamString())
val apqRawResultWithoutQuery: String = when (automaticPersistedQueriesSettings.httpMethod) {
is AutomaticPersistedQueriesSettings.HttpMethod.GET -> {
client
.get()
.uri {
it.queryParam("extension", "{extension}").build(automaticPersistedQueriesExtension.toQueryParamString())
}
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.awaitBody()
}
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.awaitBody<String>()

is AutomaticPersistedQueriesSettings.HttpMethod.POST -> {
val requestWithoutQuery = object : GraphQLClientRequest<T> by request {
override val query = null
override val extensions = request.extensions?.plus(automaticPersistedQueriesExtension.toExtentionsBodyMap())
}
client
.post()
.apply(requestCustomizer)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(serializer.serialize(requestWithoutQuery))
.retrieve()
.awaitBody()
}
}

serializer.deserialize(apqRawResultWithoutQuery, request.responseType()).let {
if (it.errors.isNullOrEmpty() && it.data != null) return it
}

val apqRawResultWithQuery = client
.get()
.uri {
it
.queryParam("query", "{query}")
.queryParam("extension", "{extension}")
.build(serializer.serialize(request), automaticPersistedQueriesExtension.toQueryParamString())
val apqRawResultWithQuery: String = when (automaticPersistedQueriesSettings.httpMethod) {
is AutomaticPersistedQueriesSettings.HttpMethod.GET -> {
client
.get()
.uri {
it
.queryParam("query", "{query}")
.queryParam("extension", "{extension}")
.build(serializer.serialize(request), automaticPersistedQueriesExtension.toQueryParamString())
}
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.awaitBody()
}
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.awaitBody<String>()

is AutomaticPersistedQueriesSettings.HttpMethod.POST -> {
val requestWithQuery = object : GraphQLClientRequest<T> by request {
override val extensions = request.extensions?.plus(automaticPersistedQueriesExtension.toExtentionsBodyMap())
}
client
.post()
.apply(requestCustomizer)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(serializer.serialize(requestWithQuery))
.retrieve()
.awaitBody()
}
}

serializer.deserialize(apqRawResultWithQuery, request.responseType())
} else {
Expand Down
Loading

0 comments on commit 5353d1d

Please sign in to comment.