Skip to content

Commit 833d172

Browse files
authored
fix: use json content-type header (#949)
1 parent 31e586e commit 833d172

File tree

4 files changed

+80
-36
lines changed

4 files changed

+80
-36
lines changed

src/layers/5_client/client.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { describe, expect, test } from 'vitest'
1+
import { describe, expect } from 'vitest'
2+
import { createResponse, test } from '../../../tests/_/helpers.js'
23
import { Graffle } from '../../entrypoints/alpha/main.js'
4+
import { CONTENT_TYPE_GQL, CONTENT_TYPE_JSON } from '../../lib/http.js'
35

46
describe(`without schemaIndex only raw is available`, () => {
57
const schema = new URL(`https://foo.io/api/graphql`)
@@ -21,3 +23,15 @@ describe(`without schemaIndex only raw is available`, () => {
2123
expect(graffle.rawOrThrow).toBeTypeOf(`function`)
2224
})
2325
})
26+
27+
describe(`interface`, () => {
28+
describe(`http`, () => {
29+
test(`sends well formed request`, async ({ fetch, graffle }) => {
30+
fetch.mockImplementationOnce(() => createResponse({ data: { greetings: `Hello World` } }))
31+
await graffle.raw({ document: `query { greetings }` })
32+
const request = fetch.mock.calls[0][0] as Request // eslint-disable-line
33+
expect(request.headers.get(`content-type`)).toEqual(CONTENT_TYPE_JSON)
34+
expect(request.headers.get(`accept`)).toEqual(CONTENT_TYPE_GQL)
35+
})
36+
})
37+
})

src/layers/5_core/core.ts

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { print } from 'graphql'
33
import { Anyware } from '../../lib/anyware/__.js'
44
import { type StandardScalarVariables } from '../../lib/graphql.js'
55
import { parseExecutionResult } from '../../lib/graphqlHTTP.js'
6-
import { CONTENT_TYPE_GQL } from '../../lib/http.js'
6+
import { CONTENT_TYPE_GQL, CONTENT_TYPE_JSON } from '../../lib/http.js'
77
import { casesExhausted } from '../../lib/prelude.js'
88
import { execute } from '../0_functions/execute.js'
99
import type { Schema } from '../1_Schema/__.js'
@@ -104,6 +104,9 @@ type RequestInput = {
104104
}
105105

106106
export type HookDefExchange = {
107+
slots: {
108+
fetch: typeof fetch
109+
}
107110
input:
108111
& InterfaceInput
109112
& TransportInput<
@@ -186,14 +189,16 @@ export const anyware = Anyware.create<HookSequence, HookMap, ExecutionResult>({
186189

187190
switch (input.transport) {
188191
case `http`: {
192+
const body = slots.body({
193+
query: document,
194+
variables,
195+
operationName: `todo`,
196+
})
197+
189198
return {
190199
...input,
191200
url: input.schema,
192-
body: slots.body({
193-
query: document,
194-
variables,
195-
operationName: `todo`,
196-
}),
201+
body,
197202
}
198203
}
199204
case `memory`: {
@@ -215,12 +220,19 @@ export const anyware = Anyware.create<HookSequence, HookMap, ExecutionResult>({
215220
}
216221
case `http`: {
217222
const headers = new Headers(input.headers)
218-
headers.append(`accept`, CONTENT_TYPE_GQL)
223+
// @see https://graphql.github.io/graphql-over-http/draft/#sec-Accept
224+
headers.set(`accept`, CONTENT_TYPE_GQL)
225+
// @see https://graphql.github.io/graphql-over-http/draft/#sec-POST
226+
// todo if body is something else, say upload extension turns it into a FormData, then fetch will automatically set the content-type header.
227+
// ... however we should not rely on that behavior, and instead error here if there is no content type header and we cannot infer it here?
228+
if (typeof input.body === `string`) {
229+
headers.set(`content-type`, CONTENT_TYPE_JSON)
230+
}
219231
return {
220232
...input,
221233
request: {
222234
url: input.url,
223-
body: input.body, // JSON.stringify({ query, variables, operationName }),
235+
body: input.body,
224236
method: `POST`,
225237
headers,
226238
},
@@ -230,36 +242,43 @@ export const anyware = Anyware.create<HookSequence, HookMap, ExecutionResult>({
230242
throw casesExhausted(input)
231243
}
232244
},
233-
exchange: async ({ input }) => {
234-
switch (input.transport) {
235-
case `http`: {
236-
const response = await fetch(
237-
new Request(input.request.url, {
238-
method: input.request.method,
239-
headers: input.request.headers,
240-
body: input.request.body,
241-
}),
242-
)
243-
return {
244-
...input,
245-
response,
245+
exchange: {
246+
slots: {
247+
fetch: (request) => {
248+
return fetch(request)
249+
},
250+
},
251+
run: async ({ input, slots }) => {
252+
switch (input.transport) {
253+
case `http`: {
254+
const response = await slots.fetch(
255+
new Request(input.request.url, {
256+
method: input.request.method,
257+
headers: input.request.headers,
258+
body: input.request.body,
259+
}),
260+
)
261+
return {
262+
...input,
263+
response,
264+
}
246265
}
247-
}
248-
case `memory`: {
249-
const result = await execute({
250-
schema: input.schema,
251-
document: input.query,
252-
variables: input.variables,
253-
operationName: input.operationName,
254-
})
255-
return {
256-
...input,
257-
result,
266+
case `memory`: {
267+
const result = await execute({
268+
schema: input.schema,
269+
document: input.query,
270+
variables: input.variables,
271+
operationName: input.operationName,
272+
})
273+
return {
274+
...input,
275+
result,
276+
}
258277
}
278+
default:
279+
throw casesExhausted(input)
259280
}
260-
default:
261-
throw casesExhausted(input)
262-
}
281+
},
263282
},
264283
unpack: async ({ input }) => {
265284
switch (input.transport) {

tests/_/helpers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ export const createResponse = (body: object) =>
77

88
interface Fixtures {
99
fetch: Mock
10+
graffle: Client<any, any>
1011
}
1112

13+
import { Graffle } from '../../src/entrypoints/alpha/main.js'
14+
import type { Client } from '../../src/layers/5_client/client.js'
15+
1216
export const test = testBase.extend<Fixtures>({
1317
// @ts-expect-error https://github.com/vitest-dev/vitest/discussions/5710
1418
// eslint-disable-next-line
@@ -20,4 +24,11 @@ export const test = testBase.extend<Fixtures>({
2024
await use(fetchMock)
2125
globalThis.fetch = fetch
2226
},
27+
graffle: async ({ fetch }, use) => {
28+
const graffle = Graffle.create({ schema: new URL(`https://foo.io/api/graphql`) })
29+
.use(async ({ exchange }) => {
30+
return exchange({ using: { fetch } })
31+
})
32+
await use(graffle)
33+
},
2334
})

0 commit comments

Comments
 (0)