Skip to content

Commit

Permalink
Better error handling on URL Loader
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed May 27, 2020
1 parent 10252a1 commit aba98a6
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 36 deletions.
9 changes: 6 additions & 3 deletions packages/loaders/url/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
"supertest": "4.0.2"
},
"dependencies": {
"@graphql-tools/utils": "6.0.1",
"@graphql-tools/wrap": "6.0.1",
"@graphql-tools/utils": "6.0.1",
"@types/websocket": "1.0.0",
"cross-fetch": "3.0.4",
"tslib": "~2.0.0",
"valid-url": "1.0.9"
"valid-url": "1.0.9",
"subscriptions-transport-ws": "0.9.16",
"websocket": "1.0.31"
},
"publishConfig": {
"access": "public",
"directory": "dist"
}
}
}
66 changes: 52 additions & 14 deletions packages/loaders/url/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { print, IntrospectionOptions } from 'graphql';
import { print, IntrospectionOptions, DocumentNode } from 'graphql';
import {
SchemaPointerSingle,
Source,
DocumentLoader,
SingleFileOptions,
printSchemaWithDirectives,
observableToAsyncIterable,
ExecutionResult,
} from '@graphql-tools/utils';
import { isWebUri } from 'valid-url';
import { fetch as crossFetch } from 'cross-fetch';
import { makeRemoteExecutableSchema, introspectSchema } from '@graphql-tools/wrap';
import { AsyncExecutor } from '@graphql-tools/delegate';
import { introspectSchema, makeRemoteExecutableSchema, IMakeRemoteExecutableSchemaOptions } from '@graphql-tools/wrap';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { w3cwebsocket } from 'websocket';

export type FetchFn = typeof import('cross-fetch').fetch;

Expand All @@ -19,25 +21,28 @@ export interface LoadFromUrlOptions extends SingleFileOptions, Partial<Introspec
headers?: Headers;
customFetch?: FetchFn | string;
method?: 'GET' | 'POST';
enableSubscriptions?: boolean;
webSocketImpl?: typeof w3cwebsocket | string;
}

export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
loaderId(): string {
return 'url';
}

async canLoad(pointer: SchemaPointerSingle, _options: LoadFromUrlOptions): Promise<boolean> {
return !!isWebUri(pointer);
async canLoad(pointer: SchemaPointerSingle, options: LoadFromUrlOptions): Promise<boolean> {
return this.canLoadSync(pointer, options);
}

canLoadSync(_pointer: SchemaPointerSingle, _options: LoadFromUrlOptions): boolean {
return false;
canLoadSync(pointer: SchemaPointerSingle, _options: LoadFromUrlOptions): boolean {
return !!isWebUri(pointer);
}

async load(pointer: SchemaPointerSingle, options: LoadFromUrlOptions): Promise<Source> {
let headers = {};
let fetch = crossFetch;
let method: 'GET' | 'POST' = 'POST';
let webSocketImpl = w3cwebsocket;

if (options) {
if (Array.isArray(options.headers)) {
Expand All @@ -53,6 +58,15 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
}
}

if (options.webSocketImpl) {
if (typeof options.webSocketImpl === 'string') {
const [moduleName, webSocketImplName] = options.webSocketImpl.split('#');
webSocketImpl = await import(moduleName).then(module =>
webSocketImplName ? module[webSocketImplName] : module
);
}
}

if (options.method) {
method = options.method;
}
Expand All @@ -64,8 +78,10 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
...headers,
};

const executor: AsyncExecutor = async ({ document, variables }) => {
const fetchResult = await fetch(pointer, {
const HTTP_URL = pointer.replace('ws:', 'http:').replace('wss:', 'https:');

const executor = async ({ document, variables }: { document: DocumentNode; variables: any }) => {
const fetchResult = await fetch(HTTP_URL, {
method,
...(method === 'POST'
? {
Expand All @@ -77,12 +93,34 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
return fetchResult.json();
};

const clientSchema = await introspectSchema(executor);
const schema = await introspectSchema(executor);

const remoteExecutableSchema = makeRemoteExecutableSchema({
schema: printSchemaWithDirectives(clientSchema, options), // Keep descriptions
const remoteExecutableSchemaOptions: IMakeRemoteExecutableSchemaOptions = {
schema,
executor,
});
};

if (options.enableSubscriptions) {
const WS_URL = pointer.replace('http:', 'ws:').replace('https:', 'wss:');
const subscriptionClient = new SubscriptionClient(WS_URL, {}, webSocketImpl);

remoteExecutableSchemaOptions.subscriber = async <TReturn, TArgs>({
document,
variables,
}: {
document: DocumentNode;
variables: TArgs;
}) => {
return observableToAsyncIterable(
subscriptionClient.request({
query: document,
variables,
})
) as AsyncIterator<ExecutionResult<TReturn>>;
};
}

const remoteExecutableSchema = makeRemoteExecutableSchema(remoteExecutableSchemaOptions);

return {
location: pointer,
Expand Down
4 changes: 1 addition & 3 deletions packages/loaders/url/tests/url-loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ describe('Schema URL Loader', () => {
const loader = new UrlLoader();

const testTypeDefs = /* GraphQL */`
schema {
query: CustomQuery
}
schema { query: CustomQuery }
"""Test type comment"""
type CustomQuery {
"""Test field comment"""
Expand Down
23 changes: 9 additions & 14 deletions packages/wrap/src/introspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,17 @@ import { ExecutionResult, CombinedError } from '@graphql-tools/utils';
import { AsyncExecutor, SyncExecutor } from '@graphql-tools/delegate';

function getSchemaFromIntrospection(introspectionResult: ExecutionResult<IntrospectionQuery>): GraphQLSchema {
if (
(Array.isArray(introspectionResult.errors) && introspectionResult.errors.length) ||
!introspectionResult.data.__schema
) {
if (Array.isArray(introspectionResult.errors)) {
if (introspectionResult.errors.length > 1) {
const combinedError = new CombinedError(introspectionResult.errors);
throw combinedError;
}
const error = introspectionResult.errors[0];
throw error.originalError || error;
} else {
throw new Error('Could not obtain introspection result, received: ' + JSON.stringify(introspectionResult));
if (introspectionResult?.data?.__schema) {
return buildClientSchema(introspectionResult.data);
} else if (introspectionResult?.errors?.length) {
if (introspectionResult.errors.length > 1) {
const combinedError = new CombinedError(introspectionResult.errors);
throw combinedError;
}
const error = introspectionResult.errors[0];
throw error.originalError || error;
} else {
return buildClientSchema(introspectionResult.data);
throw new Error('Could not obtain introspection result, received: ' + JSON.stringify(introspectionResult));
}
}

Expand Down
83 changes: 81 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2262,6 +2262,13 @@
"@types/webpack-sources" "*"
source-map "^0.6.0"

"@types/websocket@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.0.tgz#828c794b0a50949ad061aa311af1009934197e4b"
integrity sha512-MLr8hDM8y7vvdAdnoDEP5LotRoYJj7wgT6mWzCUQH/gHqzS4qcnOT/K4dhC0WimWIUiA3Arj9QAJGGKNRiRZKA==
dependencies:
"@types/node" "*"

"@types/ws@^7.0.0":
version "7.2.4"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.4.tgz#b3859f7b9c243b220efac9716ec42c716a72969d"
Expand Down Expand Up @@ -4595,6 +4602,14 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=

d@1, d@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
dependencies:
es5-ext "^0.10.50"
type "^1.0.1"

dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
Expand Down Expand Up @@ -5205,6 +5220,24 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"

es5-ext@^0.10.35, es5-ext@^0.10.50:
version "0.10.53"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
dependencies:
es6-iterator "~2.0.3"
es6-symbol "~3.1.3"
next-tick "~1.0.0"

es6-iterator@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
dependencies:
d "1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"

es6-promise@^4.0.3, es6-promise@^4.1.0:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
Expand All @@ -5217,6 +5250,14 @@ es6-promisify@^5.0.0:
dependencies:
es6-promise "^4.0.3"

es6-symbol@^3.1.1, es6-symbol@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
dependencies:
d "^1.0.1"
ext "^1.1.2"

escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
Expand Down Expand Up @@ -5630,6 +5671,13 @@ express@4.17.1, express@^4.16.3, express@^4.17.1:
utils-merge "1.0.1"
vary "~1.1.2"

ext@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==
dependencies:
type "^2.0.0"

extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
Expand Down Expand Up @@ -8950,7 +8998,7 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==

nan@^2.12.1:
nan@^2.12.1, nan@^2.14.0:
version "2.14.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
Expand Down Expand Up @@ -8992,6 +9040,11 @@ neo-async@^2.5.0, neo-async@^2.6.1:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==

next-tick@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=

nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
Expand Down Expand Up @@ -12257,7 +12310,7 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"

subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16:
subscriptions-transport-ws@0.9.16, subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16:
version "0.9.16"
resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz#90a422f0771d9c32069294c08608af2d47f596ec"
integrity sha512-pQdoU7nC+EpStXnCfh/+ho0zE0Z+ma+i7xvj7bkXKb1dvYHSZxgRPaU6spRP+Bjzow67c/rRDoix5RT0uU9omw==
Expand Down Expand Up @@ -12710,6 +12763,16 @@ type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"

type@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==

type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3"
integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==

typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
Expand Down Expand Up @@ -13344,6 +13407,17 @@ websocket-extensions@>=0.1.1:
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==

websocket@1.0.31:
version "1.0.31"
resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.31.tgz#e5d0f16c3340ed87670e489ecae6144c79358730"
integrity sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ==
dependencies:
debug "^2.2.0"
es5-ext "^0.10.50"
nan "^2.14.0"
typedarray-to-buffer "^3.1.5"
yaeti "^0.0.6"

whatwg-encoding@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
Expand Down Expand Up @@ -13548,6 +13622,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==

yaeti@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=

yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
Expand Down

0 comments on commit aba98a6

Please sign in to comment.