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
This commit is contained in:
Brian Beck 2017-11-18 00:35:28 -08:00 committed by GitHub
parent 2de2e60079
commit c3be2a2e98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 236 additions and 82 deletions

View file

@ -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)
}

18
src/extensions/index.js Normal file
View file

@ -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
}

View file

@ -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({

View file

@ -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)
}

View file

@ -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]
}
},

View file

@ -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]
}
}
)

View file

@ -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) {

2
test/base-schema.js Normal file
View file

@ -0,0 +1,2 @@
process.env.TEST_SCHEMA = 'baseSchema'
require('./_schema')

2
test/extended-schema.js Normal file
View file

@ -0,0 +1,2 @@
process.env.TEST_SCHEMA = 'extendedSchema'
require('./_schema')

View file

@ -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 })

7
test/helpers/schema.js Normal file
View file

@ -0,0 +1,7 @@
import schema, { createSchema } from '../../src/schema'
import { defaultExtensions } from '../../src'
export default {
baseSchema: schema,
extendedSchema: createSchema(schema, { extensions: defaultExtensions })
}

View file

@ -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',
},
],
},
},
}

Binary file not shown.

View file

@ -0,0 +1,81 @@
# Snapshot report for `test/extended-schema.js`
The actual snapshot is saved in `extended-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',
},
],
},
},
## 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',
},
],
},
},
}

Binary file not shown.

View file

@ -1,43 +0,0 @@
# Snapshot report for `test/schema.js`
The actual snapshot is saved in `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',
},
],
},
},
}

Binary file not shown.

View file

@ -1,11 +1,15 @@
import test from 'ava'
import Node from '../../src/types/node'
import ReleaseGroup from '../../src/types/release-group'
import schema from '../../src/schema'
test('loads types from their module', t => {
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)
})