Skip to content

Commit 6d297c5

Browse files
stefanprobstfreiksenet
authored andcommitted
fix(schema): Fix proxying invalid field names (#14108)
* Fix proxying invalid field names * Change proxyFrom extension to proxy * Update index.md
1 parent 69445ce commit 6d297c5

File tree

6 files changed

+89
-11
lines changed

6 files changed

+89
-11
lines changed

docs/blog/2019-05-17-improvements-to-schema-customization/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ Add resolver and resolver options (such as arguments) to the given field. There
103103
- `@fileByRelativePath` - connect to a File node. Same arguments. The
104104
difference from link is that this normalizes the relative path to be
105105
relative from the path where source node is found.
106+
- `proxy` - in case the underlying node data contains field names with
107+
characters that are invalid in GraphQL, `proxy` allows to explicitly
108+
proxy those properties to fields with valid field names. Takes a `from` arg.
106109

107110
```graphql:title=gatsby-node.js
108111
exports.sourceNodes = function sourceNodes({ actions }) {

packages/gatsby/src/redux/actions.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,9 @@ import type GatsbyGraphQLType from "../schema/types/type-builders"
13081308
* * `@fileByRelativePath` - connect to a File node. Same arguments. The
13091309
* difference from link is that this normalizes the relative path to be
13101310
* relative from the path where source node is found.
1311-
*
1311+
* * `proxy` - in case the underlying node data contains field names with
1312+
* characters that are invalid in GraphQL, `proxy` allows to explicitly
1313+
* proxy those properties to fields with valid field names. Takes a `from` arg.
13121314
*
13131315
*
13141316
* @example

packages/gatsby/src/schema/extensions/index.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,14 @@ const fieldExtensions = {
8080
},
8181
},
8282

83-
proxyFrom: {
83+
proxy: {
8484
description: `Proxy resolver from another field.`,
85-
process(from, fieldConfig) {
85+
args: {
86+
from: {
87+
type: GraphQLString,
88+
},
89+
},
90+
process({ from }, fieldConfig) {
8691
const resolver = fieldConfig.resolve || defaultFieldResolver
8792
return {
8893
resolve(source, args, context, info) {
@@ -125,7 +130,7 @@ const processFieldExtensions = ({
125130
const extensions = typeComposer.getFieldExtensions(fieldName)
126131
Object.keys(extensions)
127132
.filter(name => !internalExtensionNames.includes(name))
128-
.sort(a => a === `proxyFrom`) // Ensure `proxyFrom` is run last
133+
.sort(a => a === `proxy`) // Ensure `proxy` is run last
129134
.forEach(name => {
130135
const { process } = fieldExtensions[name] || {}
131136
if (process) {

packages/gatsby/src/schema/infer/__tests__/infer.js

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const { store } = require(`../../../redux`)
99
const createPageDependency = require(`../../../redux/actions/add-page-dependency`)
1010
const { buildSchema } = require(`../../schema`)
1111
const { createSchemaComposer } = require(`../../schema-composer`)
12+
const { buildObjectType } = require(`../../types/type-builders`)
1213
const { TypeConflictReporter } = require(`../type-conflict-reporter`)
1314
require(`../../../db/__tests__/fixtures/ensure-loki`)()
1415

@@ -79,7 +80,7 @@ const makeNodes = () => [
7980
describe(`GraphQL type inference`, () => {
8081
const typeConflictReporter = new TypeConflictReporter()
8182

82-
const buildTestSchema = async (nodes, buildSchemaArgs) => {
83+
const buildTestSchema = async (nodes, buildSchemaArgs, typeDefs) => {
8384
store.dispatch({ type: `DELETE_CACHE` })
8485
nodes.forEach(node =>
8586
store.dispatch({ type: `CREATE_NODE`, payload: node })
@@ -89,7 +90,7 @@ describe(`GraphQL type inference`, () => {
8990
const schema = await buildSchema({
9091
schemaComposer,
9192
nodeStore,
92-
types: [],
93+
types: typeDefs || [],
9394
thirdPartySchemas: [],
9495
typeMapping: [],
9596
typeConflictReporter,
@@ -98,8 +99,14 @@ describe(`GraphQL type inference`, () => {
9899
return schema
99100
}
100101

101-
const getQueryResult = async (nodes, fragment, buildSchemaArgs) => {
102-
const schema = await buildTestSchema(nodes, buildSchemaArgs)
102+
const getQueryResult = async (
103+
nodes,
104+
fragment,
105+
buildSchemaArgs,
106+
extraquery = ``,
107+
typeDefs
108+
) => {
109+
const schema = await buildTestSchema(nodes, buildSchemaArgs, typeDefs)
103110
store.dispatch({ type: `SET_SCHEMA`, payload: schema })
104111
return graphql(
105112
schema,
@@ -111,6 +118,7 @@ describe(`GraphQL type inference`, () => {
111118
}
112119
}
113120
}
121+
${extraquery}
114122
}
115123
`,
116124
undefined,
@@ -342,6 +350,60 @@ describe(`GraphQL type inference`, () => {
342350
expect(result.data.allTest.edges[0].node._456).toEqual(nodes[0][`456`])
343351
})
344352

353+
it(`handles invalid graphql field names on explicitly defined fields`, async () => {
354+
const nodes = [
355+
{ id: `test`, internal: { type: `Test` } },
356+
{
357+
id: `foo`,
358+
[`field_that_needs_to_be_sanitized?`]: `foo`,
359+
[`(another)_field_that_needs_to_be_sanitized`]: `bar`,
360+
[`!third_field_that_needs_to_be_sanitized`]: `baz`,
361+
internal: {
362+
type: `Repro`,
363+
contentDigest: `foo`,
364+
},
365+
},
366+
]
367+
const typeDefs = [
368+
{
369+
typeOrTypeDef: buildObjectType({
370+
name: `Repro`,
371+
interfaces: [`Node`],
372+
fields: {
373+
field_that_needs_to_be_sanitized_: `String`,
374+
_another__field_that_needs_to_be_sanitized: {
375+
type: `String`,
376+
resolve: source =>
377+
source[`(another)_field_that_needs_to_be_sanitized`],
378+
},
379+
},
380+
}),
381+
},
382+
]
383+
384+
const result = await getQueryResult(
385+
nodes,
386+
`id`,
387+
undefined,
388+
`
389+
repro {
390+
field_that_needs_to_be_sanitized_
391+
_another__field_that_needs_to_be_sanitized
392+
_third_field_that_needs_to_be_sanitized
393+
}
394+
`,
395+
typeDefs
396+
)
397+
expect(result.errors).not.toBeDefined()
398+
expect(result.data.repro[`field_that_needs_to_be_sanitized_`]).toBe(`foo`)
399+
expect(
400+
result.data.repro[`_another__field_that_needs_to_be_sanitized`]
401+
).toBe(`bar`)
402+
expect(result.data.repro[`_third_field_that_needs_to_be_sanitized`]).toBe(
403+
`baz`
404+
)
405+
})
406+
345407
it(`Handles priority for conflicting fields`, async () => {
346408
const nodes = [
347409
{

packages/gatsby/src/schema/infer/__tests__/merge-types.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ describe(`merges explicit and inferred type definitions`, () => {
529529
const typeDefs = `
530530
type Test implements Node @infer {
531531
explicitDate: Date @dateformat
532+
proxied: Date @proxy(from: "explicitDate")
532533
}
533534
534535
type LinkTest implements Node @infer {
@@ -546,9 +547,12 @@ describe(`merges explicit and inferred type definitions`, () => {
546547
expect(link.resolve).toBeDefined()
547548
expect(links.resolve).toBeDefined()
548549

549-
const { explicitDate, inferDate } = schema.getType(`Test`).getFields()
550+
const { explicitDate, inferDate, proxied } = schema
551+
.getType(`Test`)
552+
.getFields()
550553
expect(explicitDate.resolve).toBeDefined()
551554
expect(inferDate.resolve).toBeDefined()
555+
expect(proxied.resolve).toBeDefined()
552556
})
553557

554558
it(`adds explicit resolvers through extensions`, async () => {

packages/gatsby/src/schema/infer/add-inferred-fields.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ const addInferredFieldsImpl = ({
116116
.filter(name =>
117117
// It is okay to list allowed extensions explicitly here,
118118
// since this is deprecated anyway and won't change.
119-
[`dateformat`, `fileByRelativePath`, `link`].includes(name)
119+
[`dateformat`, `fileByRelativePath`, `link`, `proxy`].includes(
120+
name
121+
)
120122
)
121123
.forEach(name => {
122124
if (!typeComposer.hasFieldExtension(key, name)) {
@@ -192,7 +194,7 @@ const getFieldConfig = ({
192194
...fieldConfig,
193195
extensions: {
194196
...(fieldConfig.extensions || {}),
195-
proxyFrom: unsanitizedKey,
197+
proxy: { from: unsanitizedKey },
196198
},
197199
}
198200
}

0 commit comments

Comments
 (0)