Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaffold out new transport mechanisms #3576

Closed
wants to merge 84 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
40bc81f
v3: Begin to introduce new transport.
abernix Dec 2, 2019
d90f2ec
Remove some original implementation details which are no longer relev…
abernix Dec 2, 2019
2f5e7ab
Rename tsdoc which was for a different (imported) symbol, rather than…
abernix Dec 2, 2019
c9abb68
Remove `TData` until I understand it better!
abernix Dec 2, 2019
47f52a3
Apply guard on `data` to tests which seems to make sense.
abernix Dec 2, 2019
8f4e486
Rename badly named test file.
abernix Dec 2, 2019
da84ad2
Additional progress and clarity for transports.
abernix Dec 2, 2019
3cce418
Apply suggestions from code review
abernix Dec 3, 2019
52bb1fc
Adjust incorrect TSDoc `@param` declaration which was a lie.
abernix Dec 16, 2019
9df5a54
Remove trailing space.
abernix Dec 16, 2019
e203baa
Switch `httpHandler` to use easier to read `function` call.
abernix Dec 16, 2019
04f7745
Use `isSchema` rather than `instanceof` check.
abernix Dec 16, 2019
2862d98
Address clarity-of-code comment w/r/t unclear `return` practice.
abernix Dec 16, 2019
c989b92
Add a `@throws` TSDoc annotation for possible result of `jsonBodyParse`.
abernix Dec 16, 2019
58a4675
Switch to using `@link` notation for arguments.
abernix Dec 16, 2019
353f1f1
Remove unnecessary ternary operators.
abernix Dec 16, 2019
627d8a5
Remove unused `experimental_` interfaces.
abernix Dec 16, 2019
ed6f256
Just `throw` when `req` or `res` are not set.
abernix Dec 16, 2019
eae9bb0
Re-introduce `ApolloServer.prototype.executeOperation`, for now.
abernix Dec 16, 2019
a1be448
Switch to v3 type for `VariableValues`.
abernix Dec 17, 2019
5e572d4
Remove unused (commented out) type definitions.
abernix Dec 17, 2019
1417482
Fix formatting inconsistencies between TSDoc `@remarks` instances.
abernix Dec 17, 2019
04c9f8a
Merge branch 'release-3.x' into abernix/release-3.x-transport
abernix Jan 10, 2020
c8c4fcb
Simplify tests which don't have expectations in callbacks.
abernix Jan 10, 2020
2fe7a99
Implement internal HTTP error code ability.
abernix Jan 10, 2020
c7603e8
Re-order tests to error on invalid input conditions to fail first/fast.
abernix Jan 10, 2020
9f40074
Introduce `UserContext` rather than using `Record<string,any>` everyw…
abernix Jan 10, 2020
832e6b8
Rename `processGraphqlRequest` to `processGraphqlRequestAgainstSchema`.
abernix Jan 11, 2020
c7f9a06
Rename `ProcessRequestInput` to `ProcessGraphqlRequestInput` to match…
abernix Jan 11, 2020
34b8fb1
Move TSDoc comments for `processGraphqlRequestAgainstSchema` to `inte…
abernix Jan 11, 2020
55e512d
Introduce separate types for processing functions w/ and w/o schema.
abernix Jan 11, 2020
152dbbb
Change `processHttpRequest` to accept a processor func not just `sche…
abernix Jan 11, 2020
f609f70
tsdoc: Add descriptions to the `GraphQLRequest` type.
abernix Jan 11, 2020
1c8c271
Adjust the existing `executeOperation` method to use new execution.
abernix Jan 11, 2020
38ed945
tests: Complete tests which assert receipt of req and res parameters.
abernix Jan 11, 2020
c7a1381
Protect internal methods exported for testing within a `__testing__` …
abernix Jan 13, 2020
c5ec99d
Correctly type the request listener as an `async` function.
abernix Jan 13, 2020
99a1c9e
Ensure `ServerResponse.prototype.end` is invoked in `internalServerEr…
abernix Jan 13, 2020
9008ce3
Default to status message for "500" from RFC 7231 § 6.6.1
abernix Jan 13, 2020
22666a0
Reject `jsonBodyParse` Promise on `ReadableStream`'s `error` event.
abernix Jan 13, 2020
2e7501b
Provide our own `SyntaxError` during `JSON.parse` failures on the body.
abernix Jan 13, 2020
b0d27da
Use `node-mocks-http` to mock `ServerResponse`.
abernix Jan 13, 2020
4e64b76
Use new `badRequest` in place of `internalServerError` for parse errors.
abernix Jan 13, 2020
b6e0f47
Add a note as to why something is `@ts-ignore`'d.
abernix Jan 13, 2020
06f91a8
Add some personal thoughts to a test that I'm not sure I believe in.
abernix Jan 13, 2020
06f34b2
Remove unnecessary `expect.assertions(n)` matcher.
abernix Jan 13, 2020
cd26078
Adjust formatting of error message.
abernix Jan 13, 2020
663e374
tests: Introduce tests for `jsonBodyParse`.
abernix Jan 13, 2020
4d6df45
tests: Add a test for a normal 200 status request.
abernix Jan 14, 2020
fd330b0
Merge branch 'release-3.x' into abernix/release-3.x-transport
abernix Jan 14, 2020
24e342a
Also add `node-mocks-https` to the root `package.json` + lock.
abernix Jan 15, 2020
f1c1f22
Ensure that `variables` and `extensions` are properly parsed and tested.
abernix Jan 17, 2020
cdf1b4c
Re-org: DRY up tests by using `beforeEach` calls.
abernix Jan 17, 2020
9425344
Correct imprecise explanations in TSDoc comments.
abernix Jan 20, 2020
dc0eb61
Decompose body-parsing bits in preparation for `GET` requests.
abernix Jan 20, 2020
c12b30f
tests: Change `buildRequestListenerPair` to force specification of `m…
abernix Jan 20, 2020
08624a0
Introduce support for `GET` requests.
abernix Jan 20, 2020
7ba8aa3
Remove unnecessary `req` usage, now replaced with `beforeEach`.
abernix Jan 20, 2020
4df15b2
Fix typooo in error message.
abernix Jan 22, 2020
f503a06
Avoid using `apollo-server-env` for now, opting instead for Node.js' …
abernix Jan 22, 2020
c694971
Explicitly call the `processor` a `mockProcessor`, since that's what …
abernix Jan 22, 2020
1712aab
tests: Remove testing parsing which is tested elsewhere.
abernix Jan 22, 2020
66cd30c
tests: Bring a mock test handler which behaves for both GET and POST …
abernix Jan 22, 2020
56ade2b
tests: Note what "validQuery" actually means in a comment.
abernix Jan 22, 2020
4a6e661
no-op: Comments for `mockProcessor`.
abernix Jan 22, 2020
a4a9d84
tests: Add a guard to `mockProcessor` to indicate intent.
abernix Jan 22, 2020
9d70bec
tests: Ensure that `extensions` are tested for JSON-parsing.
abernix Jan 23, 2020
858cc29
tests: Remove assertions which are scoped incorrectly.
abernix Jan 23, 2020
9d37ac7
Rename `jsonBodyParse` to `parseRequest` and adjust tests.
abernix Jan 23, 2020
28b26cd
no-op: Commentary.
abernix Jan 23, 2020
140768b
Merge branch 'release-3.x' into abernix/release-3.x-transport
abernix Jan 23, 2020
2f1ba48
Break out utility functions into `util.ts` and `util.test.ts`.
abernix Jan 23, 2020
df4ee6e
Move TODO tests away from comment which would seem to group them toge…
abernix Jan 23, 2020
ab80e3b
no-op: TSDoc spacing and typo.
abernix Jan 23, 2020
edd6c68
tests: Remove unnecessary `expect.assertions` calls.
abernix Jan 23, 2020
00e1d2a
Re-export `GraphQLError` directly from `types` module.
abernix Jan 23, 2020
caeab17
Move re-exported of `GraphQLSchemaModule` from `a-s-c` to `types` mod…
abernix Jan 23, 2020
f345380
tests: Using the correct `GraphQLSchemaModule` works (there are two!)
abernix Jan 23, 2020
b031d6c
Remove unused `Context` and `ContextFunction` types.
abernix Jan 23, 2020
185965c
Export the `httpHandler` as a top-level export.
abernix Jan 24, 2020
a00ec04
Typo-corrections from my own code review.
abernix Jan 24, 2020
7bd1949
Remove unused `export` of `GraphQLRequest` and `GraphQLResponse`.
abernix Feb 4, 2020
01be050
Feedback: Switch to using `rejects.toThrow` to assert even if resolved.
abernix Feb 4, 2020
b2ec085
tests: Feedback: Adjust types to properly indicate `Promise` of nothing.
abernix Feb 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions v3/src/__tests__/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion v3/src/execution/__tests__/execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ describe("processGraphqlRequest", () => {
context
});

expect(data.modifiesContext).toBe("Context modified!");
expect(data && data.modifiesContext).toBe("Context modified!");
expect(context).toHaveProperty("modified");
});
});
27 changes: 10 additions & 17 deletions v3/src/execution/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
export {
processGraphQLRequest,
abernix marked this conversation as resolved.
Show resolved Hide resolved
GraphQLRequestContext,
GraphQLRequestPipelineConfig,
} from 'apollo-server-core/dist/requestPipeline';
import {
GraphQLRequest,
GraphQLResponse,
VariableValues,
} from 'apollo-server-types';
import { VariableValues } from 'apollo-server-types';
import {
DocumentNode,
parse,
GraphQLError,
GraphQLSchema,
validate,
execute,
ExecutionResult,
separateOperations,
} from 'graphql';
export { GraphQLRequest, GraphQLResponse };
import { GraphQLRequest, GraphQLResponse } from "../types/";

// TODO(AS3) I'm not sure if this is execution. Perhaps, a top-level export.
export { GraphQLSchemaModule } from 'apollo-graphql';

export { Context, ContextFunction } from 'apollo-server-core';

/** Options for {@link processGraphQLRequest} */
interface ProcessRequestInput<TContext extends Record<string, any>> {
/** Options for {@link processGraphqlRequest} */
interface ProcessRequestInput<TRequestContext extends Record<string, any>> {
request: GraphQLRequest;
schema: GraphQLSchema;
context?: TContext;
context?: TRequestContext;
}

/**
Expand All @@ -47,13 +41,12 @@ interface ProcessRequestInput<TContext extends Record<string, any>> {
* 3. Data without errors if execution was successful without errors
*/
export async function processGraphqlRequest<
TData = Record<string, any>,
TContext extends Record<string, any> = Record<string, any>
>({
request,
schema,
context,
}: ProcessRequestInput<TContext>): Promise<ExecutionResult<TData>> {
}: ProcessRequestInput<TContext>): Promise<GraphQLResponse> {
const { query, operationName, variables } = request;

if (!query) {
Expand All @@ -79,7 +72,7 @@ export async function processGraphqlRequest<
return { errors: documentValidationErrors };
}

return await executeGraphqlRequest<TData>({
return await executeGraphqlRequest({
schema,
document: parseResult.document,
operationName,
Expand Down Expand Up @@ -175,14 +168,14 @@ interface ExecutionInput<
*
* @see https://github.com/graphql/graphql-spec/blob/master/spec/Section%207%20--%20Response.md#response-format
*/
export async function executeGraphqlRequest<TData = Record<string, any>>({
export async function executeGraphqlRequest({
schema,
document,
operationName,
variables,
context,
}: ExecutionInput): Promise<ExecutionResult<TData>> {
return await execute<TData>({
}: ExecutionInput): Promise<GraphQLResponse> {
return await execute<Record<string, any>>({
schema,
document,
operationName,
Expand Down
79 changes: 0 additions & 79 deletions v3/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,12 @@ import { GraphQLSchema } from 'graphql/type/schema';
import { DocumentNode } from 'graphql/language/ast';
import { InMemoryLRUCache } from './caching';
import { approximateObjectSize } from './utilities';
import {
GraphQLRequest,
GraphQLResponse,
processGraphQLRequest,
GraphQLRequestContext,
GraphQLRequestPipelineConfig,
} from './execution';

// These should not be imported from here.
import { Config as BaseConfig } from 'apollo-server-core';
import { buildServiceDefinition } from '@apollographql/apollo-tools';

export { default as gql } from 'graphql-tag';
import {
Context,
ContextFunction,
} from './execution';

// A subset of the base configuration.
type Config = Pick<BaseConfig,
Expand Down Expand Up @@ -48,7 +37,6 @@ type SchemaDerivedData = {
export class ApolloServer {
// public requestOptions: Partial<GraphQLOptions<any>> = Object.create(null);

private userContext?: Context | ContextFunction;
// TODO(AS3) Reconsider these for Apollo Graph Manager
// private engineReportingAgent?: import('apollo-engine-reporting').EngineReportingAgent;
// private engineServiceId?: string;
Expand All @@ -64,9 +52,6 @@ export class ApolloServer {
constructor(private readonly config: Config) {
if (!config) throw new Error('ApolloServer requires options.');

// TODO(AS3) Rename content variables to be more clear, like `userContext`.
this.userContext = this.config.context;

// Plugins will be instantiated if they aren't already, and this.plugins
// is populated accordingly.
this.ensurePluginInstantiation(this.config.plugins);
Expand Down Expand Up @@ -269,68 +254,4 @@ export class ApolloServer {
sizeCalculator: approximateObjectSize,
});
}

public async executeOperation<
R extends GraphQLRequest,
S extends GraphQLResponse = GraphQLResponse
>(
request: R,
responseInit: S = Object.create(null),
) {

const { schema, documentStore } = await this.schemaDerivedData;

// TODO(AS3) The transport will provide context.
const integrationContextArgument = Object.create(null);

let context: Context;

try {
context =
typeof this.userContext === 'function'
? await this.userContext(integrationContextArgument || Object.create(null))
: this.userContext || Object.create(null);
} catch (error) {
// Defer context error resolution to inside of runQuery
context = () => {
throw error;
};
}

// TODO(AS3) This context argument is maybe quite wrong, check it.
const options: GraphQLRequestPipelineConfig<typeof context> = {
schema,
plugins: this.plugins,
documentStore,
// Allow overrides from options. Be explicit about a couple of them to
// avoid a bad side effect of the otherwise useful noUnusedLocals option
// (https://github.com/Microsoft/TypeScript/issues/21673).
// TODO(AS3)
// persistedQueries: this.requestOptions
// .persistedQueries as PersistedQueryOptions,
// TODO(AS3)
// fieldResolver: this.requestOptions.fieldResolver as GraphQLFieldResolver<
// any,
// any
// >,
// TODO(AS3)
// parseOptions: this.parseOptions,
// TODO(AS3): AGM
// reporting: !!this.engineReportingAgent,
// ...this.requestOptions,
};

// TODO(AS3) This is not where the global cache should be created.
// Especially since this is per request.
const cache = new InMemoryLRUCache();

const requestCtx: GraphQLRequestContext = {
request,
context,
cache,
response: responseInit,
};

return processGraphQLRequest(options, requestCtx);
}
}
131 changes: 131 additions & 0 deletions v3/src/transports/http/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { gql } from "apollo-server-core";
import { buildSchemaFromSDL } from "apollo-graphql";
import { httpHandler, responseAsInternalServerError } from "../handler";
import { ServerResponse, RequestListener } from "http";
import { PassThrough, Readable } from "stream";
const testModule = {
typeDefs: gql`
type Book {
title: String
author: String
}

type Query {
books: [Book]
}
`,
resolvers: {
Query: {
books: () => [
{
title: "Harry Potter and the Chamber of Secrets",
author: "J.K. Rowling",
},
{
title: "Jurassic Park",
author: "Michael Crichton",
},
],
},
},
};

type IMockedResponse = Pick<
ServerResponse,
| "setHeader"
| "writeHead"
| "statusCode"
| "statusMessage"
| "write"
| "end"
>

// type IMockedRequest = Pick<IncomingMessage, "method">;
// type IMockedRequestListener = (req: IMockedRequest) => void;

function mockedResponse(): IMockedResponse {
return {
writeHead: jest.fn(),
write: jest.fn(),
end: jest.fn(),
setHeader: jest.fn(),
statusCode: 0,
statusMessage: "DEFAULT_VALUE",
};
}

const validQuery= "query { books { author } }";
const schema = buildSchemaFromSDL([testModule]);

describe("httpHandler", () => {
describe("construction", () => {
it("returns a RequestListener when invoked with a schema", () => {
expect(httpHandler(schema)).toBeInstanceOf(Function);
});

it("throws when invoked without a schema", () => {
expect(() => {
// @ts-ignore
httpHandler();
}).toThrowErrorMatchingInlineSnapshot(`"Must pass a schema."`);
});
});

describe("RequestListener", () => {
let handler: RequestListener;
let res: IMockedResponse;

beforeEach(() => {
handler = httpHandler(schema);
res = mockedResponse();
});

// TODO(AS3) Skipped, but need to enable.
it.skip("throws when called with no request", () => {
abernix marked this conversation as resolved.
Show resolved Hide resolved
expect(() => {
// @ts-ignore
handler();
}).toThrowError();
});

// TODO(AS3) Move this to testing `jsonBodyParse` and finish it by
// proving that it's actually failing as it should be.
// (I'm pretty sure it's failing for the wrong reasons.)
it.skip("fails with an internal server error on corrupted body streams",
abernix marked this conversation as resolved.
Show resolved Hide resolved
async () => {
const pass = new PassThrough();
const readable = new Readable();
// readable._read = function () {};

const req = Object.assign({
method: 'POST',
headers: {},
}, readable);

pass.write(JSON.stringify({ query: validQuery }));

// @ts-ignore
await handler(req, res);

expect(res.writeHead).toBeCalledWith(500, "Error parsing body");
}
);

// Legacy
it.todo("returns a 500 if the body of the request is missing");
it.todo(
"returns a 400 if the 'query' is missing when the 'GET' method is used",
);
});
});

describe("responseAsInternalServerError", () => {
it("can call the writeHead message with the correct code and message", () => {
const res = mockedResponse();
// @ts-ignore
responseAsInternalServerError(res, "Catastrophic.");
expect(res.writeHead).toBeCalledTimes(1);
expect(res.writeHead).toBeCalledWith(500, "Catastrophic.");
expect(res.end).not.toBeCalled();
})
});
Loading