Skip to content

Commit 67c4ace

Browse files
committed
I think I have it working with failing tests
1 parent b275d9f commit 67c4ace

File tree

6 files changed

+202
-121
lines changed

6 files changed

+202
-121
lines changed

packages/api/src/__tests__/normalizeRequest.test.ts

+67-30
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { APIGatewayProxyEvent } from 'aws-lambda'
33

44
import { normalizeRequest } from '../transforms'
55

6-
export const createMockedEvent = (
6+
export const createMockedLambdaEvent = (
77
httpMethod = 'POST',
88
body: any = undefined,
99
isBase64Encoded = false
@@ -53,41 +53,78 @@ export const createMockedEvent = (
5353
}
5454
}
5555

56-
test('Normalizes an aws event with base64', () => {
57-
const corsEventB64 = createMockedEvent(
58-
'POST',
59-
Buffer.from(JSON.stringify({ bazinga: 'hello_world' }), 'utf8').toString(
60-
'base64'
61-
),
62-
true
63-
)
56+
describe('Lambda Request', () => {
57+
it('Normalizes an aws event with base64', async () => {
58+
const corsEventB64 = createMockedLambdaEvent(
59+
'POST',
60+
Buffer.from(JSON.stringify({ bazinga: 'hello_world' }), 'utf8').toString(
61+
'base64'
62+
),
63+
true
64+
)
6465

65-
expect(normalizeRequest(corsEventB64)).toEqual({
66-
headers: new Headers(corsEventB64.headers),
67-
method: 'POST',
68-
query: null,
69-
body: {
70-
bazinga: 'hello_world',
71-
},
66+
expect(await normalizeRequest(corsEventB64)).toEqual({
67+
headers: new Headers(corsEventB64.headers as Record<string, string>),
68+
method: 'POST',
69+
query: null,
70+
jsonBody: {
71+
bazinga: 'hello_world',
72+
},
73+
})
7274
})
73-
})
7475

75-
test('Handles CORS requests with and without b64 encoded', () => {
76-
const corsEventB64 = createMockedEvent('OPTIONS', undefined, true)
76+
it('Handles CORS requests with and without b64 encoded', async () => {
77+
const corsEventB64 = createMockedLambdaEvent('OPTIONS', undefined, true)
78+
79+
expect(await normalizeRequest(corsEventB64)).toEqual({
80+
headers: new Headers(corsEventB64.headers as Record<string, string>), // headers returned as symbol
81+
method: 'OPTIONS',
82+
query: null,
83+
jsonBody: undefined,
84+
})
85+
86+
const corsEventWithoutB64 = createMockedLambdaEvent(
87+
'OPTIONS',
88+
undefined,
89+
false
90+
)
7791

78-
expect(normalizeRequest(corsEventB64)).toEqual({
79-
headers: new Headers(corsEventB64.headers), // headers returned as symbol
80-
method: 'OPTIONS',
81-
query: null,
82-
body: undefined,
92+
expect(await normalizeRequest(corsEventWithoutB64)).toEqual({
93+
headers: new Headers(corsEventB64.headers as Record<string, string>), // headers returned as symbol
94+
method: 'OPTIONS',
95+
query: null,
96+
jsonBody: undefined,
97+
})
8398
})
99+
})
100+
101+
describe('Fetch API Request', () => {
102+
it('Normalizes a fetch event', async () => {
103+
const fetchEvent = new Request(
104+
'http://localhost:9210/graphql?whatsup=doc&its=bugs',
105+
{
106+
method: 'POST',
107+
headers: {
108+
'content-type': 'application/json',
109+
},
110+
body: JSON.stringify({ bazinga: 'kittens_purr_purr' }),
111+
}
112+
)
113+
114+
const partial = await normalizeRequest(fetchEvent)
84115

85-
const corsEventWithoutB64 = createMockedEvent('OPTIONS', undefined, false)
116+
expect(partial).toMatchObject({
117+
// headers: fetchEvent.headers,
118+
method: 'POST',
119+
query: {
120+
whatsup: 'doc',
121+
its: 'bugs',
122+
},
123+
jsonBody: {
124+
bazinga: 'kittens_purr_purr',
125+
},
126+
})
86127

87-
expect(normalizeRequest(corsEventWithoutB64)).toEqual({
88-
headers: new Headers(corsEventB64.headers), // headers returned as symbol
89-
method: 'OPTIONS',
90-
query: null,
91-
body: undefined,
128+
expect(partial.headers.get('content-type')).toEqual('application/json')
92129
})
93130
})

packages/api/src/cors.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Headers } from '@whatwg-node/fetch'
22

3-
import type { Request } from './transforms'
3+
import type { PartialRequest } from './transforms'
44

55
export type CorsConfig = {
66
origin?: boolean | string | string[]
@@ -59,10 +59,10 @@ export function createCorsContext(cors: CorsConfig | undefined) {
5959
}
6060

6161
return {
62-
shouldHandleCors(request: Request) {
62+
shouldHandleCors(request: PartialRequest) {
6363
return request.method === 'OPTIONS'
6464
},
65-
getRequestHeaders(request: Request): CorsHeaders {
65+
getRequestHeaders(request: PartialRequest): CorsHeaders {
6666
const eventHeaders = new Headers(request.headers as HeadersInit)
6767
const requestCorsHeaders = new Headers(corsHeaders)
6868

packages/api/src/transforms.ts

+43-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Headers } from '@whatwg-node/fetch'
1+
import { Headers, Request as PonyFillRequest } from '@whatwg-node/fetch'
22
import type { APIGatewayProxyEvent } from 'aws-lambda'
33

4-
// This is the same interface used by GraphQL Yoga
5-
// But not importing here to avoid adding a dependency
6-
export interface Request {
7-
body?: any
4+
// This is part of the request, dreived either from a LambdaEvent or FetchAPI Request
5+
// We do this to keep the API consistent between the two
6+
// When we support only the FetchAPI request, we should remove this
7+
export interface PartialRequest<TBody = Record<string, any>> {
8+
jsonBody?: TBody
89
headers: Headers
910
method: string
1011
query: any
@@ -13,7 +14,7 @@ export interface Request {
1314
/**
1415
* Extracts and parses body payload from event with base64 encoding check
1516
*/
16-
export const parseEventBody = (event: APIGatewayProxyEvent) => {
17+
export const parseLambdaEventBody = (event: APIGatewayProxyEvent) => {
1718
if (!event.body) {
1819
return
1920
}
@@ -25,14 +26,47 @@ export const parseEventBody = (event: APIGatewayProxyEvent) => {
2526
}
2627
}
2728

28-
export function normalizeRequest(event: APIGatewayProxyEvent): Request {
29-
const body = parseEventBody(event)
29+
export const isFetchApiRequest = (event: any): event is Request => {
30+
return event instanceof Request || event instanceof PonyFillRequest
31+
}
32+
33+
function getQueryStringParams(reqUrl: string) {
34+
const url = new URL(reqUrl)
35+
const params = new URLSearchParams(url.search)
36+
37+
const paramObject: Record<string, string> = {}
38+
for (const entry of params.entries()) {
39+
paramObject[entry[0]] = entry[1] // each 'entry' is a [key, value] tuple
40+
}
41+
return paramObject
42+
}
43+
44+
/**
45+
*
46+
* This function returns a an object that lets you access _some_ of the request properties in a consistent way
47+
* You can give it either a LambdaEvent or a Fetch API Request
48+
*
49+
* NOTE: It does NOT return a full Request object!
50+
*/
51+
export async function normalizeRequest(
52+
event: APIGatewayProxyEvent | Request
53+
): Promise<PartialRequest> {
54+
if (isFetchApiRequest(event)) {
55+
return {
56+
headers: event.headers,
57+
method: event.method,
58+
query: getQueryStringParams(event.url),
59+
jsonBody: await event.json(),
60+
}
61+
}
62+
63+
const jsonBody = parseLambdaEventBody(event)
3064

3165
return {
3266
headers: new Headers(event.headers as Record<string, string>),
3367
method: event.httpMethod,
3468
query: event.queryStringParameters,
35-
body,
69+
jsonBody,
3670
}
3771
}
3872

0 commit comments

Comments
 (0)