From c3be2a2e982ac285489138495d785569e61a264c Mon Sep 17 00:00:00 2001 From: Brian Beck Date: Sat, 18 Nov 2017 00:35:28 -0800 Subject: [PATCH] More resolveType fixes, this time for Relay's nodeDefinitions (#51) * More resolveType fixes, this time for Relay's nodeDefinitions * Add a loadExtension helper * Use createContext in test helpers --- src/context.js | 3 +- src/extensions/index.js | 18 ++++++ src/index.js | 29 +++------ src/schema.js | 5 +- src/types/entity.js | 5 +- src/types/node.js | 20 ++++-- test/{schema.js => _schema.js} | 5 +- test/base-schema.js | 2 + test/extended-schema.js | 2 + test/helpers/context.js | 7 +-- test/helpers/schema.js | 7 +++ test/snapshots/base-schema.js.md | 81 +++++++++++++++++++++++++ test/snapshots/base-schema.js.snap | Bin 0 -> 541 bytes test/snapshots/extended-schema.js.md | 81 +++++++++++++++++++++++++ test/snapshots/extended-schema.js.snap | Bin 0 -> 542 bytes test/snapshots/schema.js.md | 43 ------------- test/snapshots/schema.js.snap | Bin 491 -> 0 bytes test/types/node.js | 10 ++- 18 files changed, 236 insertions(+), 82 deletions(-) create mode 100644 src/extensions/index.js rename test/{schema.js => _schema.js} (99%) create mode 100644 test/base-schema.js create mode 100644 test/extended-schema.js create mode 100644 test/helpers/schema.js create mode 100644 test/snapshots/base-schema.js.md create mode 100644 test/snapshots/base-schema.js.snap create mode 100644 test/snapshots/extended-schema.js.md create mode 100644 test/snapshots/extended-schema.js.snap delete mode 100644 test/snapshots/schema.js.md delete mode 100644 test/snapshots/schema.js.snap diff --git a/src/context.js b/src/context.js index 506fda6..4464e1a 100644 --- a/src/context.js +++ b/src/context.js @@ -1,4 +1,5 @@ import createLoaders from './loaders' +import { loadExtension } from './extensions' const debug = require('debug')('graphbrainz:context') @@ -27,6 +28,6 @@ export function createContext(options = {}) { const context = { client, loaders } const { extensions = [] } = options return extensions.reduce((context, extension) => { - return extendContext(extension, context, options) + return extendContext(loadExtension(extension), context, options) }, context) } diff --git a/src/extensions/index.js b/src/extensions/index.js new file mode 100644 index 0000000..a2b3189 --- /dev/null +++ b/src/extensions/index.js @@ -0,0 +1,18 @@ +export function loadExtension(extensionModule) { + let extension + if (typeof extensionModule === 'string') { + extension = require(extensionModule) + } else { + extension = extensionModule + } + if (extension == null || typeof extension !== 'object') { + throw new Error( + `Expected ${extensionModule} to export an extension but instead ` + + `got: ${extension}` + ) + } else if (extension.default) { + // ECMAScript module interop. + extension = extension.default + } + return extension +} diff --git a/src/index.js b/src/index.js index f879176..28f292d 100644 --- a/src/index.js +++ b/src/index.js @@ -6,40 +6,29 @@ import MusicBrainz from './api' import schema, { createSchema } from './schema' import { createContext } from './context' -const debug = require('debug')('graphbrainz') - const formatError = err => ({ message: err.message, locations: err.locations, stack: err.stack }) +export const defaultExtensions = [ + require.resolve('./extensions/cover-art-archive'), + require.resolve('./extensions/fanart-tv'), + require.resolve('./extensions/mediawiki'), + require.resolve('./extensions/the-audio-db') +] + const middleware = ( { client = new MusicBrainz(), extensions = process.env.GRAPHBRAINZ_EXTENSIONS ? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS) - : [ - './extensions/cover-art-archive', - './extensions/fanart-tv', - './extensions/mediawiki', - './extensions/the-audio-db' - ], + : defaultExtensions, ...middlewareOptions } = {} ) => { - debug(`Loading ${extensions.length} extension(s).`) - const options = { - client, - extensions: extensions.map(extensionModule => { - if (typeof extensionModule === 'object') { - return extensionModule - } - const extension = require(extensionModule) - return extension.default ? extension.default : extension - }), - ...middlewareOptions - } + const options = { client, extensions, ...middlewareOptions } const DEV = process.env.NODE_ENV !== 'production' const graphiql = DEV || process.env.GRAPHBRAINZ_GRAPHIQL === 'true' return graphqlHTTP({ diff --git a/src/schema.js b/src/schema.js index 5925bc2..d5da489 100644 --- a/src/schema.js +++ b/src/schema.js @@ -2,6 +2,7 @@ import { GraphQLSchema, GraphQLObjectType, extendSchema, parse } from 'graphql' import { addResolveFunctionsToSchema } from 'graphql-tools' import { lookup, browse, search } from './queries' import { nodeField } from './types/node' +import { loadExtension } from './extensions' const debug = require('debug')('graphbrainz:schema') @@ -45,9 +46,9 @@ export function applyExtension(extension, schema, options = {}) { } export function createSchema(schema, options = {}) { - const extensions = options.extensions || [] + const { extensions = [] } = options return extensions.reduce((updatedSchema, extension) => { - return applyExtension(extension, updatedSchema, options) + return applyExtension(loadExtension(extension), updatedSchema, options) }, schema) } diff --git a/src/types/entity.js b/src/types/entity.js index 7e1dabd..81ca130 100644 --- a/src/types/entity.js +++ b/src/types/entity.js @@ -1,21 +1,24 @@ import { GraphQLInterfaceType } from 'graphql' import { mbid, connectionWithExtras } from './helpers' +const debug = require('debug')('graphbrainz:types/entity') + const Entity = new GraphQLInterfaceType({ name: 'Entity', description: 'An entity in the MusicBrainz schema.', resolveType(value, context, info) { if (value._type) { - const typeMap = info.schema.getTypeMap() let originalType try { originalType = require(`./${value._type}`).default } catch (err) { + debug(`Failed to load type: ${value._type}`) return } // Don't use `originalType`! The schema may have been extended in which // case the types have all been replaced. Instead, find the current type // of the same name. + const typeMap = info.schema.getTypeMap() return typeMap[originalType.name] } }, diff --git a/src/types/node.js b/src/types/node.js index 8adab4a..210acb1 100644 --- a/src/types/node.js +++ b/src/types/node.js @@ -13,13 +13,21 @@ const { nodeInterface, nodeField } = nodeDefinitions( const entityType = toDashed(type) return loaders.lookup.load([entityType, id]) }, - obj => { + (obj, context, info) => { const type = TYPE_MODULES[obj._type] || obj._type - try { - return require(`./${type}`).default - } catch (err) { - debug(`Failed to load type: ${type}`) - return null + if (type) { + let originalType + try { + originalType = require(`./${type}`).default + } catch (err) { + debug(`Failed to load type: ${type}`) + return + } + // Don't use `originalType`! The schema may have been extended in which + // case the types have all been replaced. Instead, find the current type + // of the same name. + const typeMap = info.schema.getTypeMap() + return typeMap[originalType.name] } } ) diff --git a/test/schema.js b/test/_schema.js similarity index 99% rename from test/schema.js rename to test/_schema.js index 5ae0d43..4defba0 100644 --- a/test/schema.js +++ b/test/_schema.js @@ -1,8 +1,11 @@ import test from 'ava' import { graphql } from 'graphql' -import schema from '../src/schema' +import schemas from './helpers/schema' import context from './helpers/context' +const TEST_SCHEMA = process.env.TEST_SCHEMA || 'baseSchema' +const schema = schemas[TEST_SCHEMA] + function testData(t, query, handler) { return graphql(schema, query, null, context).then(result => { if (result.errors !== undefined) { diff --git a/test/base-schema.js b/test/base-schema.js new file mode 100644 index 0000000..21a1322 --- /dev/null +++ b/test/base-schema.js @@ -0,0 +1,2 @@ +process.env.TEST_SCHEMA = 'baseSchema' +require('./_schema') diff --git a/test/extended-schema.js b/test/extended-schema.js new file mode 100644 index 0000000..5092790 --- /dev/null +++ b/test/extended-schema.js @@ -0,0 +1,2 @@ +process.env.TEST_SCHEMA = 'extendedSchema' +require('./_schema') diff --git a/test/helpers/context.js b/test/helpers/context.js index 0dd5688..6e32613 100644 --- a/test/helpers/context.js +++ b/test/helpers/context.js @@ -1,7 +1,4 @@ -import createLoaders from '../../src/loaders' +import { createContext } from '../../src/context' import client from './client/musicbrainz' -export default { - client, - loaders: createLoaders(client) -} +export default createContext({ client }) diff --git a/test/helpers/schema.js b/test/helpers/schema.js new file mode 100644 index 0000000..a632ef1 --- /dev/null +++ b/test/helpers/schema.js @@ -0,0 +1,7 @@ +import schema, { createSchema } from '../../src/schema' +import { defaultExtensions } from '../../src' + +export default { + baseSchema: schema, + extendedSchema: createSchema(schema, { extensions: defaultExtensions }) +} diff --git a/test/snapshots/base-schema.js.md b/test/snapshots/base-schema.js.md new file mode 100644 index 0000000..40e668c --- /dev/null +++ b/test/snapshots/base-schema.js.md @@ -0,0 +1,81 @@ +# Snapshot report for `test/base-schema.js` + +The actual snapshot is saved in `base-schema.js.snap`. + +Generated by [AVA](https://ava.li). + +## areas have a type and typeID + +> Snapshot 1 + + { + search: { + areas: { + nodes: [ + { + name: 'Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'East Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'Brakel', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + ], + }, + }, + + +## baseSchema: areas have a type and typeID + +> Snapshot 1 + + { + search: { + areas: { + nodes: [ + { + name: 'Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'East Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'Brakel', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + ], + }, + }, + } \ No newline at end of file diff --git a/test/snapshots/base-schema.js.snap b/test/snapshots/base-schema.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..ae26ae626c3c6d161601229ada1c4d99b7a0df3c GIT binary patch literal 541 zcmV+&0^ZwG?!eieK@4bC91K&6RFsOWgR-6C$@@wPX zCAP7>w1JR5qZYuY(QnVM)V}PuRt|#qo3B2gTCHSKm!nkZARD81iEsg(^Jve84`q}f z{-(KHj{R5$90EkZvxu{#9YzDtBu!KDIC`LR+O)o`=dj~*R*&RZJeItvV&2m7J3YoY zKIZe$n0iA<6=M(6Fy$D@4^wI=I>LyW=|9bBqH}~6VU1~9u>RTtId(|3M;J5!t{a-e?&gR|wMckD*wP(N zH-~8gWC(e}eN%Is>E>7^)jHt~VOMtybh`>VigP4mor^H*d_i_ToI5gJ;PtkyN!pM9 zXr>yidJ^jzJJsC1)%m>Y`9gSH7Tt1GD!K(P4BfC;5N>coL{d}(e@w`e3k}`o&E#^g zwHlm}PyVHpIW?c0)X1-jay1`tw?M_*LcSWgLD>_o- f{)p_4$o`1ztyaA^kb1AtDUQgWXPB)~fC>Nr Snapshot 1 + + { + search: { + areas: { + nodes: [ + { + name: 'Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'East Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'Brakel', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + ], + }, + }, + + +## extendedSchema: areas have a type and typeID + +> Snapshot 1 + + { + search: { + areas: { + nodes: [ + { + name: 'Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'East Germany', + type: 'Country', + typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'New Germany', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + { + name: 'Brakel', + type: 'City', + typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', + }, + ], + }, + }, + } \ No newline at end of file diff --git a/test/snapshots/extended-schema.js.snap b/test/snapshots/extended-schema.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..6b1846944ba7cc95e04dcf1cc696a3328d317390 GIT binary patch literal 542 zcmV+(0^$8ZRzVIU)+T|ZN)ys5h>ewng_i2KgQsD<5UX&)Ej;$^@ZQ@uGw|I700x!sFBj&Hw!hRL zkFuSEl^ulixo!dMuWvM;4t=|Ia=5jB{CV^%s?|y+bva6f4ze+Nmk2}XTtRy#oR?98 z_?zZ(Ird`}a1amyFCos5b{GvnlQa#<(sp2N=0**ukF@mTVPirJ;*Lp{bg zJLdiAm|9&(6=M(6F^mvy5 zlTxPWr<59sUS&kh^q=NKqO*i1VT);7u>RTtIo^@#BVoV*xNT?-yOSd()iU8VVNZ8B zogAhKkRjv=PfX3x*U7O-s#k=!gb%u-ztdIFQCub&YhQ#}<4dyr;oO(;60fy%P11V) zM>AP(){ Snapshot 1 - - { - search: { - areas: { - nodes: [ - { - name: 'Germany', - type: 'Country', - typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', - }, - { - name: 'East Germany', - type: 'Country', - typeID: '06dd0ae4-8c74-30bb-b43d-95dcedf961de', - }, - { - name: 'New Germany', - type: 'City', - typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', - }, - { - name: 'New Germany', - type: 'City', - typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', - }, - { - name: 'Brakel', - type: 'City', - typeID: '6fd8f29a-3d0a-32fc-980d-ea697b69da78', - }, - ], - }, - }, - } diff --git a/test/snapshots/schema.js.snap b/test/snapshots/schema.js.snap deleted file mode 100644 index 3d34741dc331043a5ea4e1cc8598afe46c3bf757..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 491 zcmVLYRp6?6WVl>g+7B&a3Sa` zxUMejs*j*hy6M^%P`|k}t=vZJO63>?MLK{iAG3Sk_br)by0O&O(#ziFXRVAnmsQ9uNIf>;*%*E}PxFw{Du_tKQNy1BlM|e$G#$U14T$1pv zawwM1=T+!1panRBxUQ}$V>`sMlZYN?lqvc%qlTi-7*R7j(_AIGPUsLmGi?OxuMx;` zPO2Y-oB{C4&>Z$4M?$KPgag8@S=ZnKXIa)ir5%`;TU!)rr$Y*Vvin&0>G~!t;gjxGcNXs8V)IUKqMz zxg^|RRzy*jnY* { - t.is(Node.resolveType({ _type: 'release-group' }), ReleaseGroup) + t.is( + Node.resolveType({ _type: 'release-group' }, {}, { schema }), + ReleaseGroup + ) }) -test('returns null for unknown types', t => { - t.is(Node.resolveType({ _type: 'foo' }), null) +test('returns undefined for unknown types', t => { + t.is(Node.resolveType({ _type: 'foo' }, {}, { schema }), undefined) })