-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(request): add
GraphQLRequest
object and types
- Loading branch information
1 parent
6d1965c
commit 8054e8e
Showing
4 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export { GraphQLRequest } from "./request.ts"; | ||
export { | ||
type GraphQLRequestOptions, | ||
type GraphQLRequestParams, | ||
type RequestOptions, | ||
type RequestParams, | ||
} from "./types.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { mergeInit } from "./utils.ts"; | ||
import { | ||
GraphQLRequestOptions, | ||
GraphQLRequestParams, | ||
RequestOptions, | ||
RequestParams, | ||
} from "./types.ts"; | ||
|
||
const ACCEPT = | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
CONTENT_TYPE = "application/json;charset=UTF-8", | ||
DEFAULT_METHOD = "POST"; | ||
|
||
/** `Request` object with GraphQL request parameters. | ||
* @throws {TypeError} When the input is Invalid URL or Header name is invalid. | ||
* | ||
* @example | ||
* ```ts | ||
* import { GraphQLRequest } from "https://deno.land/x/gql_request@$VERSION/mod.ts"; | ||
* | ||
* const query = `query { | ||
* person(personID: "1") { | ||
* name | ||
* } | ||
* }`; | ||
* const request = new GraphQLRequest("https://graphql.org/swapi-graphql", query); | ||
* fetch(request); | ||
* ``` | ||
*/ | ||
export class GraphQLRequest extends Request { | ||
constructor( | ||
input: string | URL, | ||
query: string, | ||
options?: RequestOptions, | ||
) { | ||
const { | ||
method = DEFAULT_METHOD, | ||
extensions, | ||
operationName, | ||
variables, | ||
...rest | ||
} = options ?? {}; | ||
const [url, init] = createRequestInit({ input, query, method }, { | ||
extensions, | ||
operationName, | ||
variables, | ||
}); | ||
|
||
const requestInit = mergeInit(init, rest); | ||
|
||
super(url, requestInit); | ||
} | ||
} | ||
|
||
/** | ||
* @throws {TypeError} | ||
*/ | ||
function createRequestInit( | ||
{ input, query, method }: RequestParams & { method: string }, | ||
{ variables, operationName, extensions }: GraphQLRequestOptions, | ||
): [url: string | URL, requestInit: RequestInit] { | ||
switch (method) { | ||
case "GET": { | ||
const url = addQueryString(input, { | ||
query, | ||
operationName, | ||
variables, | ||
extensions, | ||
}); | ||
|
||
const requestInit: RequestInit = { | ||
method, | ||
headers: { | ||
Accept: ACCEPT, | ||
}, | ||
}; | ||
|
||
return [url, requestInit]; | ||
} | ||
case "POST": { | ||
const body = JSON.stringify({ | ||
query, | ||
variables, | ||
operationName, | ||
extensions, | ||
}); | ||
const headers: HeadersInit = { | ||
"content-type": CONTENT_TYPE, | ||
accept: ACCEPT, | ||
}; | ||
const requestInit: RequestInit = { | ||
method, | ||
body, | ||
headers, | ||
}; | ||
|
||
return [input, requestInit]; | ||
} | ||
default: { | ||
return [input, {}]; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @throws {TypeError} | ||
*/ | ||
function addQueryString( | ||
url: string | URL, | ||
{ query, variables, operationName, extensions }: | ||
& GraphQLRequestParams | ||
& GraphQLRequestOptions, | ||
): URL { | ||
url = new URL(url); | ||
url.searchParams.set("query", query); | ||
|
||
if (variables) { | ||
const data = JSON.stringify(variables); | ||
url.searchParams.set("variables", data); | ||
} | ||
if (operationName) { | ||
url.searchParams.set("operationName", operationName); | ||
} | ||
if (extensions) { | ||
const data = JSON.stringify(extensions); | ||
url.searchParams.set("extensions", data); | ||
} | ||
|
||
return url; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import { GraphQLRequest } from "./request.ts"; | ||
import { | ||
assertEquals, | ||
assertEqualsRequest, | ||
assertThrows, | ||
describe, | ||
it, | ||
} from "./dev_deps.ts"; | ||
|
||
const input = "http://localhost:8000"; | ||
const document = `query{test}`; | ||
|
||
describe("GraphQLRequest", () => { | ||
describe("HTTP GET", () => { | ||
it("should contain query string and accept header", async () => { | ||
const request = new GraphQLRequest(input, document, { method: "GET" }); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request(`http://localhost:8000/?query=query%7Btest%7D`, { | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
}, | ||
}), | ||
); | ||
}); | ||
|
||
it("should contain variables in query string", async () => { | ||
const request = new GraphQLRequest(input, document, { | ||
method: "GET", | ||
variables: { | ||
a: "1", | ||
}, | ||
}); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request( | ||
`http://localhost:8000/?query=query%7Btest%7D&variables=%7B%22a%22%3A%221%22%7D`, | ||
{ | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
}, | ||
}, | ||
), | ||
); | ||
}); | ||
|
||
it("should contain operationName in query string", async () => { | ||
const request = new GraphQLRequest(input, document, { | ||
method: "GET", | ||
operationName: "NameQuery", | ||
}); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request( | ||
`http://localhost:8000/?query=query%7Btest%7D&operationName=NameQuery`, | ||
{ | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
}, | ||
}, | ||
), | ||
); | ||
}); | ||
|
||
it("should contain extensions in query string", async () => { | ||
const request = new GraphQLRequest(input, document, { | ||
method: "GET", | ||
extensions: { a: "0" }, | ||
}); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request( | ||
`http://localhost:8000/?query=query%7Btest%7D&extensions=%7B%22a%22%3A%220%22%7D`, | ||
{ | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
}, | ||
}, | ||
), | ||
); | ||
}); | ||
|
||
it("should override header", async () => { | ||
const request = new GraphQLRequest(input, document, { | ||
method: "GET", | ||
headers: { "x-custom": "test" }, | ||
}); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request(`http://localhost:8000/?query=query%7Btest%7D`, { | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
"x-custom": "test", | ||
}, | ||
}), | ||
); | ||
}); | ||
}); | ||
|
||
describe("HTTP POST", () => { | ||
it("should contain query in body and equal header", async () => { | ||
const request = new GraphQLRequest(input, document); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request(input, { | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
"content-type": "application/json;charset=UTF-8", | ||
}, | ||
body: `{"query":"query{test}"}`, | ||
method: "POST", | ||
}), | ||
); | ||
}); | ||
|
||
it("should contain query and variables in body", async () => { | ||
const request = new GraphQLRequest(input, document, { | ||
variables: { a: "0" }, | ||
}); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request(input, { | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
"content-type": "application/json;charset=UTF-8", | ||
}, | ||
body: `{"query":"query{test}","variables":{"a":"0"}}`, | ||
method: "POST", | ||
}), | ||
); | ||
}); | ||
|
||
it("should contain query and operationName in body", async () => { | ||
const request = new GraphQLRequest(input, document, { | ||
operationName: "Query", | ||
}); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request(input, { | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
"content-type": "application/json;charset=UTF-8", | ||
}, | ||
body: `{"query":"query{test}","operationName":"Query"}`, | ||
method: "POST", | ||
}), | ||
); | ||
}); | ||
|
||
it("should contain query and operationName in body", async () => { | ||
const request = new GraphQLRequest(input, document, { | ||
extensions: { a: "a", b: undefined }, | ||
}); | ||
|
||
await assertEqualsRequest( | ||
request, | ||
new Request(input, { | ||
headers: { | ||
accept: | ||
"application/graphql-response+json;charset=UTF-8,application/json;charset=UTF-8", | ||
"content-type": "application/json;charset=UTF-8", | ||
}, | ||
body: `{"query":"query{test}","extensions":{"a":"a"}}`, | ||
method: "POST", | ||
}), | ||
); | ||
}); | ||
}); | ||
|
||
it("should throw error when the input is invalid url", () => { | ||
assertThrows(() => new GraphQLRequest("", ""), TypeError, "Invalid URL"); | ||
}); | ||
|
||
it("should throw error when the header name is invalid", () => { | ||
assertThrows( | ||
() => new GraphQLRequest(input, "", { headers: { "?": "" } }), | ||
TypeError, | ||
"Header name is not valid.", | ||
); | ||
}); | ||
|
||
it("should pass example", () => { | ||
const query = `query { | ||
person(personID: "1") { | ||
name | ||
} | ||
}`; | ||
const request = new GraphQLRequest( | ||
"https://graphql.org/swapi-graphql", | ||
query, | ||
{ | ||
method: "GET", | ||
}, | ||
); | ||
assertEquals( | ||
request.url, | ||
"https://graphql.org/swapi-graphql?query=query+%7B%0A++++++person%28personID%3A+%221%22%29+%7B%0A++++++++name%0A++++++%7D%0A++++%7D", | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** GraphQL request options. */ | ||
export interface GraphQLRequestOptions { | ||
/** The name of the Operation in the Document to execute. */ | ||
readonly operationName?: string; | ||
|
||
/** Values for any Variables defined by the Operation. */ | ||
readonly variables?: Record<string, unknown>; | ||
|
||
/** This entry is reserved for implementors to extend the protocol however they see fit. */ | ||
readonly extensions?: Record<string, unknown>; | ||
} | ||
|
||
/** GraphQL request parameters. */ | ||
export interface GraphQLRequestParams { | ||
/** A Document containing GraphQL Operations and Fragments to execute. */ | ||
readonly query: string; | ||
} | ||
|
||
/** GraphQL-over-HTTP request parameters. */ | ||
export interface RequestParams extends GraphQLRequestParams { | ||
/** retrieve resource. */ | ||
readonly input: string | URL; | ||
} | ||
|
||
/** GraphQL-over-HTTP request options. */ | ||
export interface RequestOptions extends GraphQLRequestOptions, RequestInit {} |