diff --git a/src/__tests__/fieldNotFoundMessageForType-test.ts b/src/__tests__/fieldNotFoundMessageForType-test.ts new file mode 100644 index 0000000..5110174 --- /dev/null +++ b/src/__tests__/fieldNotFoundMessageForType-test.ts @@ -0,0 +1,36 @@ +import { GraphQLObjectType, GraphQLString, GraphQLScalarType } from "graphql"; +import { fieldNotFoundMessageForType } from ".."; + +describe("fieldNotFoundMessageForType", () => { + it("returns a helpful message for null", () => { + expect(fieldNotFoundMessageForType(null)).toBe( + "The type should not be null." + ); + }); + + it("returns a helpful message for scalar types", () => { + expect( + fieldNotFoundMessageForType( + new GraphQLScalarType({ + name: "Odd", + serialize(value) { + return value % 2 === 1 ? value : null; + } + }) + ) + ).toBe("The field has a scalar type, which means it supports no nesting."); + }); + + it("returns all field names for object type", () => { + expect( + fieldNotFoundMessageForType( + new GraphQLObjectType({ + name: "Row", + fields: () => ({ + id: { type: GraphQLString } + }) + }) + ) + ).toBe("The only fields found in this Object are: id."); + }); +}); diff --git a/src/__tests__/graphqlObservable-test.ts b/src/__tests__/graphqlObservable-test.ts index edf52ca..7ea7e05 100644 --- a/src/__tests__/graphqlObservable-test.ts +++ b/src/__tests__/graphqlObservable-test.ts @@ -530,7 +530,7 @@ describe("graphqlObservable", function() { }); }); - itMarbles("nested resolvers pass down the context and parent", function(m) { + itMarbles("throwing an error results in an error observable", function(m) { const query = gql` query { throwingResolver @@ -546,6 +546,26 @@ describe("graphqlObservable", function() { const result = graphqlObservable(query, fieldResolverSchema, {}); m.expect(result.take(1)).toBeObservable(expected); }); + + itMarbles( + "accessing an unknown query field results in an error observable", + function(m) { + const query = gql` + query { + youDontKnowMe + } + `; + const expected = m.cold( + "#", + {}, + new Error( + "reactive-graphql: field 'youDontKnowMe' was not found on type 'Query'. The only fields found in this Object are: plain,item,nested,throwingResolver." + ) + ); + const result = graphqlObservable(query, fieldResolverSchema, {}); + m.expect(result.take(1)).toBeObservable(expected); + } + ); }); describe("Mutation", function() { diff --git a/src/index.ts b/src/index.ts index e44f08e..d3dfac7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,7 +22,10 @@ import { isTypeSystemDefinitionNode, isTypeSystemExtensionNode, Kind, - ArgumentNode + ArgumentNode, + isScalarType, + isEnumType, + isObjectType } from "graphql"; // WARNING: This is NOT a spec complete graphql implementation @@ -106,7 +109,11 @@ export default function graphqlObservable( // Something unexpcected was passed into getField if (field === null) { return throwObservable( - `field was not of the right type. Given type: ${type}` + `field '${ + definition.name.value + }' was not found on type '${type}'. ${fieldNotFoundMessageForType( + type + )}` ); } @@ -334,3 +341,25 @@ function resolveField( ); } } + +export function fieldNotFoundMessageForType(type: GraphQLType | null): string { + if (type === null) { + return "The type should not be null."; + } + + if (isScalarType(type)) { + return "The field has a scalar type, which means it supports no nesting."; + } + + if (isEnumType(type)) { + return "The field has an enum type, which means it supports no nesting."; + } + + if (isObjectType(type)) { + return `The only fields found in this Object are: ${Object.keys( + type.getFields() + )}.`; + } + + return ""; +}