Skip to content

Commit e751f65

Browse files
authored
feat(graphiql-react): expose customScalarSchemas for the variable editor (#4448)
1 parent 3979ad8 commit e751f65

9 files changed

Lines changed: 78 additions & 4 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphiql/react': minor
3+
'graphiql': minor
4+
---
5+
6+
Add `customScalarSchemas` prop to `GraphiQLProvider`/`GraphiQL`, forwarded to `monaco-graphql`'s per-schema `customScalarSchemas` config. Without it, the variable editor's live JSON Schema linter assumes every custom scalar only accepts primitives (string, number, boolean, integer) and reports a spurious "Incorrect type" error for scalars that legitimately accept objects or arrays (e.g. a `JSON` or `GeoJSON` scalar). `graphql-language-service` and `monaco-graphql` already supported this per-scalar override; `@graphiql/react` just never exposed it.

packages/graphiql-react/src/components/operation-editor.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const OperationEditor: FC<OperationEditorProps> = ({
5959
const {
6060
initialQuery,
6161
schema,
62+
customScalarSchemas,
6263
referencePlugin,
6364
operations,
6465
operationName,
@@ -70,6 +71,7 @@ export const OperationEditor: FC<OperationEditorProps> = ({
7071
pick(
7172
'initialQuery',
7273
'schema',
74+
'customScalarSchemas',
7375
'referencePlugin',
7476
'operations',
7577
'operationName',
@@ -273,7 +275,11 @@ export const OperationEditor: FC<OperationEditorProps> = ({
273275
return;
274276
}
275277
monacoGraphQL.setSchemaConfig([
276-
{ uri: `${uriInstanceId}${URI_NAME.schema}`, schema },
278+
{
279+
uri: `${uriInstanceId}${URI_NAME.schema}`,
280+
schema,
281+
customScalarSchemas,
282+
},
277283
]);
278284
monacoGraphQL.setExternalFragmentDefinitions([
279285
...externalFragments.values(),
@@ -338,6 +344,7 @@ export const OperationEditor: FC<OperationEditorProps> = ({
338344
return cleanupDisposables(disposables);
339345
}, [
340346
schema,
347+
customScalarSchemas,
341348
referencePlugin,
342349
setSchemaReference,
343350
setVisiblePlugin,

packages/graphiql-react/src/components/provider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ const InnerGraphiQLProvider: FC<GraphiQLProviderProps> = ({
151151
onCopyQuery,
152152
onPrettifyQuery = DEFAULT_PRETTIFY_QUERY,
153153

154+
customScalarSchemas,
154155
dangerouslyAssumeSchemaIsValid = false,
155156
fetcher,
156157
inputValueDeprecation = false,
@@ -269,6 +270,7 @@ const InnerGraphiQLProvider: FC<GraphiQLProviderProps> = ({
269270
referencePlugin,
270271
})(...args);
271272
const schemaSlice = createSchemaSlice({
273+
customScalarSchemas,
272274
inputValueDeprecation,
273275
introspectionQueryName,
274276
onSchemaChange,

packages/graphiql-react/src/stores/schema.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
GraphQLSchema,
1313
IntrospectionQuery,
1414
} from 'graphql';
15+
import type { JSONSchema6 } from 'graphql-language-service';
1516
import type { Dispatch } from 'react';
1617
import type { StateCreator } from 'zustand';
1718
import type { SlicesWithActions, SchemaReference } from '../types';
@@ -22,6 +23,7 @@ type MaybeGraphQLSchema = GraphQLSchema | null | undefined;
2223
type CreateSchemaSlice = (
2324
initial: Pick<
2425
SchemaSlice,
26+
| 'customScalarSchemas'
2527
| 'inputValueDeprecation'
2628
| 'introspectionQueryName'
2729
| 'onSchemaChange'
@@ -167,6 +169,7 @@ export const createSchemaSlice: CreateSchemaSlice = initial => (set, get) => ({
167169

168170
export interface SchemaSlice extends Pick<
169171
SchemaProps,
172+
| 'customScalarSchemas'
170173
| 'inputValueDeprecation'
171174
| 'introspectionQueryName'
172175
| 'schemaDescription'
@@ -289,4 +292,26 @@ export interface SchemaProps {
289292
* @see {@link https://github.com/graphql/graphql-js/blob/main/src/utilities/getIntrospectionQuery.ts|Utility for creating the introspection query}
290293
*/
291294
schemaDescription?: boolean;
295+
296+
/**
297+
* JSON Schema fragments used to validate custom scalars in the variable
298+
* editor. Without this, the variable editor's live JSON Schema linter
299+
* assumes every custom scalar only accepts primitives (string, number,
300+
* boolean, integer), and will report an "Incorrect type" error
301+
* for any custom scalar that legitimately accepts an object or array
302+
* (e.g. a `JSON` or `GeoJSON` scalar).
303+
*
304+
* Pass an empty object (`{}`) for a scalar to accept any JSON value, or
305+
* a more specific schema to constrain it further.
306+
* @example
307+
* ```ts
308+
* {
309+
* customScalarSchemas: {
310+
* GeoJSON: {},
311+
* DateTime: { type: 'string', format: 'date-time' },
312+
* }
313+
* }
314+
* ```
315+
*/
316+
customScalarSchemas?: Record<string, JSONSchema6>;
292317
}

packages/graphiql/cypress/e2e/lint.cy.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,22 @@ describe('Linting', () => {
138138
);
139139
});
140140

141+
it('Does not mark object variables for a custom scalar with a configured customScalarSchemas as error', () => {
142+
cy.visitWithOp({
143+
query: /* GraphQL */ `
144+
query WithVariables($jsonArg: JSON) {
145+
hasArgs(json: $jsonArg)
146+
}
147+
`,
148+
variables: {
149+
jsonArg: { foo: 'bar' },
150+
},
151+
})
152+
.contains('foo')
153+
.should('not.have.class', 'CodeMirror-lint-mark')
154+
.and('not.have.class', 'CodeMirror-lint-mark-error');
155+
});
156+
141157
it('Marks GraphQL syntax errors as error', () => {
142158
cy.visitWithOp({
143159
query: /* GraphQL */ `

packages/graphiql/src/e2e.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ const props: ComponentProps<typeof GraphiQL> = {
125125
onTabChange,
126126
forcedTheme: parameters.forcedTheme,
127127
defaultTheme: parameters.defaultTheme,
128+
customScalarSchemas: {
129+
JSON: {},
130+
},
128131
};
129132

130133
function App() {

packages/graphiql/test/schema.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,21 @@ export function createSchema({
2222
GraphQLString,
2323
GraphQLID,
2424
GraphQLList,
25+
GraphQLScalarType,
2526
GraphQLDeferDirective,
2627
GraphQLStreamDirective,
2728
specifiedDirectives,
2829
version,
2930
}) {
31+
const GraphQLJSON = new GraphQLScalarType({
32+
name: 'JSON',
33+
description: 'A scalar that accepts arbitrary JSON values.',
34+
serialize: value => value,
35+
parseValue: value => value,
36+
parseLiteral() {
37+
throw new TypeError('JSON literals are not supported, use variables.');
38+
},
39+
});
3040
const directives =
3141
parseInt(version, 10) > 16
3242
? [...specifiedDirectives, GraphQLDeferDirective, GraphQLStreamDirective]
@@ -337,6 +347,10 @@ And external image:
337347
id: { type: GraphQLID },
338348
enum: { type: TestEnum },
339349
object: { type: TestInputObject },
350+
json: {
351+
type: GraphQLJSON,
352+
description: 'A custom scalar that accepts any JSON value',
353+
},
340354
defaultValue: {
341355
type: GraphQLString,
342356
defaultValue: 'test default value',

packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ describe('MessageProcessor with config', () => {
436436
character: 0,
437437
},
438438
end: {
439-
line: 106 + offset,
439+
line: 109 + offset,
440440
character: 1,
441441
},
442442
});
@@ -450,11 +450,11 @@ describe('MessageProcessor with config', () => {
450450
// this might break, please adjust if you see a failure here
451451
expect(serializeRange(schemaDefs[0].range)).toEqual({
452452
start: {
453-
line: 108 + offset,
453+
line: 111 + offset,
454454
character: 0,
455455
},
456456
end: {
457-
line: 116 + offset,
457+
line: 119 + offset,
458458
character: 1,
459459
},
460460
});

resources/custom-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ proto
182182
qlapi
183183
qlid
184184
qlide
185+
qljson
185186
quasis
186187
ractive
187188
randomthing

0 commit comments

Comments
 (0)