fix(graphql): handle invalid schemas gracefully in query editor (#7269)

Prevent app crashes when loading GraphQL schemas with validation errors
(e.g., object types with no fields). The fix:

- Validates schemas using validateSchema() and shows warnings for issues
- Still loads the schema so autocomplete continues to work
- Wraps the CodeMirror GraphQL linter with error handling to catch
  any validation errors during linting

Fixes #4529

Co-authored-by: Chirag Chandrashekhar <cchirag85@gmail.com>
This commit is contained in:
Chirag Chandrashekhar
2026-02-26 18:29:19 +05:30
committed by GitHub
parent b0d0e4aabc
commit da1d7e51d2
2 changed files with 57 additions and 14 deletions

View File

@@ -1,9 +1,28 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { buildClientSchema, buildSchema } from 'graphql';
import { buildClientSchema, buildSchema, validateSchema } from 'graphql';
import { fetchGqlSchema } from 'utils/network';
import { simpleHash, safeParseJSON } from 'utils/common';
const buildAndValidateSchema = (data) => {
let schema;
if (typeof data === 'object') {
schema = buildClientSchema(data);
} else {
schema = buildSchema(data);
}
// Validate the schema to catch issues like empty object types
// The GraphQL spec requires object types to have at least one field
const validationErrors = validateSchema(schema);
if (validationErrors.length > 0) {
const errorMessages = validationErrors.map((e) => e.message).join('; ');
console.warn('GraphQL schema has validation issues:', errorMessages);
}
return { schema, validationErrors };
};
const schemaHashPrefix = 'bruno.graphqlSchema';
const useGraphqlSchema = (endpoint, environment, request, collection) => {
@@ -19,13 +38,11 @@ const useGraphqlSchema = (endpoint, environment, request, collection) => {
return null;
}
let parsedData = safeParseJSON(saved);
if (typeof parsedData === 'object') {
return buildClientSchema(parsedData);
} else {
return buildSchema(parsedData);
}
} catch {
localStorage.setItem(localStorageKey, null);
const { schema } = buildAndValidateSchema(parsedData);
return schema;
} catch (err) {
localStorage.removeItem(localStorageKey);
console.warn('Failed to load cached GraphQL schema:', err.message);
return null;
}
});
@@ -72,13 +89,19 @@ const useGraphqlSchema = (endpoint, environment, request, collection) => {
data = await loadSchemaFromIntrospection();
}
if (data) {
if (typeof data === 'object') {
setSchema(buildClientSchema(data));
} else {
setSchema(buildSchema(data));
}
const { schema, validationErrors } = buildAndValidateSchema(data);
setSchema(schema);
localStorage.setItem(localStorageKey, JSON.stringify(data));
toast.success('GraphQL Schema loaded successfully');
if (validationErrors.length > 0) {
const errorMessages = validationErrors.map((e) => e.message).join('; ');
toast(`Schema validation issues: ${errorMessages}`, {
icon: '⚠️',
duration: 5000
});
} else {
toast.success('GraphQL Schema loaded successfully');
}
}
} catch (err) {
setError(err);

View File

@@ -24,6 +24,25 @@ const CodeMirror = require('codemirror');
const md = new MD();
const AUTO_COMPLETE_AFTER_KEY = /^[a-zA-Z0-9_@(]$/;
const createSafeGraphQLLinter = () => {
// Get the original GraphQL lint helper registered by codemirror-graphql
const originalLinter = CodeMirror.helpers?.lint?.graphql?.[0];
return (text, options) => {
try {
if (originalLinter) {
return originalLinter(text, options);
}
return [];
} catch (error) {
// Log the error but don't crash - return empty lint results
// This can happen if the schema has validation issues
console.warn('GraphQL lint error (schema may be invalid):', error.message);
return [];
}
};
};
export default class QueryEditor extends React.Component {
constructor(props) {
super(props);
@@ -57,6 +76,7 @@ export default class QueryEditor extends React.Component {
minFoldSize: 4
},
lint: {
getAnnotations: createSafeGraphQLLinter(),
schema: this.props.schema,
validationRules: this.props.validationRules ?? null,
// linting accepts string or FragmentDefinitionNode[]