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[]