Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/deno-libs/gql
Browse files Browse the repository at this point in the history
  • Loading branch information
v1rtl committed Apr 26, 2022
2 parents 82f71f4 + fa80d00 commit 3a1ca5c
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 39 deletions.
4 changes: 3 additions & 1 deletion examples/vanilla.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const s = new Server({
})(req)
: new Response('Not Found', { status: 404 })
},
addr: ':3000'
port: 3000
})

s.listenAndServe()

console.log(`☁ Started on http://localhost:3000`)
86 changes: 57 additions & 29 deletions http.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { runHttpQuery, GQLOptions } from './common.ts'
import { runHttpQuery, GQLOptions, GraphQLParams } from './common.ts'
import type { GQLRequest } from './types.ts'

/**
Expand All @@ -18,8 +18,31 @@ export function GraphQLHTTP<Req extends GQLRequest = GQLRequest, Ctx extends { r
...options
}: GQLOptions<Ctx, Req>) {
return async (request: Req) => {
if (options.graphiql && request.method === 'GET') {
if (request.headers.get('Accept')?.includes('text/html')) {
const accept = request.headers.get('Accept') || ''

const typeList = [
'text/html',
'text/plain',
'application/json',
'*/*'
].map(contentType => ({ contentType, index: accept.indexOf(contentType) }))
.filter(({ index }) => index >= 0)
.sort((a, b) => a.index - b.index)
.map(({ contentType }) => contentType)

if (accept && !typeList.length) {
return new Response('Not Acceptable', { status: 406, headers: new Headers(headers) })
} else if (!['GET', 'PUT', 'POST', 'PATCH'].includes(request.method)) {
return new Response('Method Not Allowed', { status: 405, headers: new Headers(headers) })
}

let params: Promise<GraphQLParams>

if (request.method === 'GET') {
const urlQuery = request.url.substring(request.url.indexOf('?'))
const queryParams = new URLSearchParams(urlQuery)

if (options.graphiql && typeList[0] === 'text/html' && !queryParams.has('raw')) {
const { renderPlaygroundPage } = await import('./graphiql/render.ts')
const playground = renderPlaygroundPage({ ...playgroundOptions, endpoint: '/graphql' })

Expand All @@ -29,35 +52,40 @@ export function GraphQLHTTP<Req extends GQLRequest = GQLRequest, Ctx extends { r
...headers
})
})
} else if(typeList.length === 1 && typeList[0] === 'text/html') {
return new Response('Not Acceptable', { status: 406, headers: new Headers(headers) })
} else if (queryParams.has('query')) {
params = Promise.resolve({ query: queryParams.get('query') } as GraphQLParams)
} else {
return new Response('"Accept" header value must include text/html', {
status: 400,

headers: new Headers(headers)
})
params = Promise.reject(new Error('No query given!'))
}
} else if (typeList.length === 1 && typeList[0] === 'text/html') {
return new Response('Not Acceptable', { status: 406, headers: new Headers(headers) })
} else {
if (!['PUT', 'POST', 'PATCH'].includes(request.method)) {
return new Response('Method Not Allowed', { status: 405, headers: new Headers(headers) })
} else {
try {
const result = await runHttpQuery<Req, Ctx>(await request.json(), options, { request })

return new Response(JSON.stringify(result, null, 2), {
status: 200,
headers: new Headers({
'Content-Type': 'application/json',
...headers
})
})
} catch (e) {
console.error(e)
return new Response('Malformed request body', {
status: 400,
headers: new Headers(headers)
})
}
}
params = request.json()
}

try {
const result = await runHttpQuery<Req, Ctx>(await params, options, { request })

let contentType = 'text/plain'

if(!typeList.length || typeList.includes('application/json') || typeList.includes('*/*'))
contentType = 'application/json'

return new Response(JSON.stringify(result, null, 2), {
status: 200,
headers: new Headers({
'Content-Type': contentType,
...headers
})
})
} catch (e) {
console.error(e)
return new Response('Malformed Request ' + (request.method === 'GET' ? 'Query' : 'Body'), {
status: 400,
headers: new Headers(headers)
})
}
}
}
83 changes: 74 additions & 9 deletions mod_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,75 @@ const rootValue = {
const app = GraphQLHTTP({ schema, rootValue })

describe('GraphQLHTTP({ schema, rootValue })', () => {
it('should send 405 on GET', async () => {
it('should send 400 on malformed request query', async () => {
const request = superdeno(app)

await request.get('/').expect(405)
await request.get('/').expect(400, 'Malformed Request Query')
})
it('should send 400 on malformed request body', async () => {
const request = superdeno(app)

await request.post('/').expect(400, 'Malformed request body')
await request.post('/').expect(400, 'Malformed Request Body')
})
it('should send resolved GraphQL query', async () => {
it('should send resolved POST GraphQL query', async () => {
const request = superdeno(app)

await request
.post('/')
.send('{ "query": "{ hello }" }')
.expect(200, '{\n "data": {\n "hello": "Hello World!"\n }\n}')
})
it('should send resolved GET GraphQL query', async () => {
const request = superdeno(app)

await request
.get('/?query={hello}')
.expect(200, '{\n "data": {\n "hello": "Hello World!"\n }\n}')
})
it('should send resolved GET GraphQL query when Accept is application/json', async () => {
const request = superdeno(app)

await request
.get('/?query={hello}')
.set('Accept', 'application/json')
.expect(200, '{\n "data": {\n "hello": "Hello World!"\n }\n}')
.expect('Content-Type', 'application/json')
})
it('should send resolved GET GraphQL query when Accept is */*', async() => {
const request = superdeno(app)

await request
.get('/?query={hello}')
.set('Accept', '*/*')
.expect(200, '{\n "data": {\n "hello": "Hello World!"\n }\n}')
.expect('Content-Type', 'application/json')
})

it('should send resolved GET GraphQL query when Accept is text/plain', async() => {
const request = superdeno(app)

await request
.get('/?query={hello}')
.set('Accept', 'text/plain')
.expect(200, '{\n "data": {\n "hello": "Hello World!"\n }\n}')
.expect('Content-Type', 'text/plain')
})
it('should send 406 not acceptable when Accept is other (text/html)', async () => {
const request = superdeno(app)

await request
.get('/?query={hello}')
.set('Accept', 'text/html')
.expect(406, 'Not Acceptable')
});
it('should send 406 not acceptable when Accept is other (text/css)', async () => {
const request = superdeno(app)

await request
.get('/?query={hello}')
.set('Accept', 'text/css')
.expect(406, 'Not Acceptable')
})
it('should pass req obj to server context', async () => {
type Context = { request: Request }

Expand All @@ -57,21 +108,35 @@ describe('GraphQLHTTP({ schema, rootValue })', () => {
})

describe('graphiql', () => {
it('should forbid GET requests when set to false', async () => {
it('should allow query GET requests when set to false', async () => {
const app = GraphQLHTTP({ graphiql: false, schema, rootValue })

const request = superdeno(app)

await request.get('/?query={hello}').expect(200, '{\n "data": {\n "hello": "Hello World!"\n }\n}')
})
it('should allow query GET requests when set to true', async () => {
const app = GraphQLHTTP({ graphiql: true, schema, rootValue })

const request = superdeno(app)

await request.get('/?query={hello}').expect(200, '{\n "data": {\n "hello": "Hello World!"\n }\n}')
})
it('should send 406 when Accept is only text/html when set to false', async () => {
const app = GraphQLHTTP({ graphiql: false, schema, rootValue })

const request = superdeno(app)

await request.get('/').expect(405)
await request.get('/').set('Accept', 'text/html').expect(406, 'Not Acceptable')
})
it('should send 400 when Accept does not include text/html when set to true', async () => {
it('should render a playground when Accept does include text/html when set to true', async () => {
const app = GraphQLHTTP({ graphiql: true, schema, rootValue })

const request = superdeno(app)

await request.get('/').expect(400, '"Accept" header value must include text/html')
await request.get('/?query={hello}').set('Accept', 'text/html;*/*').expect(200).expect('Content-Type', 'text/html')
})
it('should render a playground if graphql is set to true', async () => {
it('should render a playground if graphiql is set to true', async () => {
const app = GraphQLHTTP({ graphiql: true, schema, rootValue })

const request = superdeno(app)
Expand Down

0 comments on commit 3a1ca5c

Please sign in to comment.