From da1d7e51d26b0c321833f60389f6eca7b1632deb Mon Sep 17 00:00:00 2001 From: Chirag Chandrashekhar Date: Thu, 26 Feb 2026 18:29:19 +0530 Subject: [PATCH] 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 --- .../GraphQLSchemaActions/useGraphqlSchema.js | 51 ++++++++++++++----- .../RequestPane/QueryEditor/index.js | 20 ++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/useGraphqlSchema.js b/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/useGraphqlSchema.js index 5b1b6c277..c8a98916a 100644 --- a/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/useGraphqlSchema.js +++ b/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/useGraphqlSchema.js @@ -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); diff --git a/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js b/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js index 16746891a..4f599be66 100644 --- a/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js +++ b/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js @@ -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[]