From 10e740b2b1415fc889b80d1dd24e86db957dcfb1 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 2 Aug 2018 10:16:46 -0400 Subject: [PATCH] Finished querying (missing interface+union types) --- .../lib/src/language/ast/selection_set.dart | 14 ++++ graphql_schema/example/todo.dart | 18 ++-- graphql_schema/lib/src/gen.dart | 7 +- graphql_schema/lib/src/type.dart | 29 +++---- graphql_schema/test/common.dart | 10 +-- graphql_server/lib/graphql.dart | 83 ++++++++++++++++--- 6 files changed, 120 insertions(+), 41 deletions(-) diff --git a/graphql_parser/lib/src/language/ast/selection_set.dart b/graphql_parser/lib/src/language/ast/selection_set.dart index 3f83ffcf..36453c67 100644 --- a/graphql_parser/lib/src/language/ast/selection_set.dart +++ b/graphql_parser/lib/src/language/ast/selection_set.dart @@ -1,4 +1,5 @@ import 'package:source_span/source_span.dart'; + import '../token.dart'; import 'node.dart'; import 'selection.dart'; @@ -9,6 +10,9 @@ class SelectionSetContext extends Node { SelectionSetContext(this.LBRACE, this.RBRACE); + factory SelectionSetContext.merged(List selections) = + _MergedSelectionSetContext; + @override FileSpan get span { var out = @@ -16,3 +20,13 @@ class SelectionSetContext extends Node { return out.expand(RBRACE.span); } } + +class _MergedSelectionSetContext extends SelectionSetContext { + final List selections; + + _MergedSelectionSetContext(this.selections) : super(null, null); + + @override + FileSpan get span => + selections.map((s) => s.span).reduce((a, b) => a.expand(b)); +} diff --git a/graphql_schema/example/todo.dart b/graphql_schema/example/todo.dart index 8d54707f..5561c806 100644 --- a/graphql_schema/example/todo.dart +++ b/graphql_schema/example/todo.dart @@ -1,15 +1,21 @@ import 'package:graphql_schema/graphql_schema.dart'; final GraphQLSchema todoSchema = new GraphQLSchema( - query: objectType('Todo', [ - field('text', type: graphQLString.nonNullable()), - field('created_at', type: graphQLDate) -])); + query: objectType('Todo', [ + field( + 'text', + innerType: graphQLString.nonNullable(), + ), + field('created_at', innerType: graphQLDate), + ]), +); main() { // Validation - var validation = todoSchema.query - .validate('@root', {'foo': 'bar', 'text': null, 'created_at': 24}); + var validation = todoSchema.query.validate( + '@root', + {'foo': 'bar', 'text': null, 'created_at': 24,}, + ); if (validation.successful) { print('This is valid data!!!'); diff --git a/graphql_schema/lib/src/gen.dart b/graphql_schema/lib/src/gen.dart index f2740140..e34575ca 100644 --- a/graphql_schema/lib/src/gen.dart +++ b/graphql_schema/lib/src/gen.dart @@ -5,9 +5,10 @@ GraphQLObjectType objectType(String name, new GraphQLObjectType(name)..fields.addAll(fields ?? []); GraphQLField field(String name, - {GraphQLFieldArgument argument, + {Iterable> arguments: + const >[], GraphQLFieldResolver resolve, - GraphQLType type}) { + GraphQLType innerType}) { return new GraphQLField(name, - argument: argument, resolve: resolve, type: type); + arguments: arguments, resolve: resolve, type: innerType); } diff --git a/graphql_schema/lib/src/type.dart b/graphql_schema/lib/src/type.dart index b6ffa733..db4e092a 100644 --- a/graphql_schema/lib/src/type.dart +++ b/graphql_schema/lib/src/type.dart @@ -5,8 +5,6 @@ abstract class GraphQLType { Value deserialize(Serialized serialized); ValidationResult validate(String key, Serialized input); GraphQLType nonNullable(); - - bool get isNullable => false; } /// Shorthand to create a [GraphQLListType]. @@ -17,8 +15,8 @@ GraphQLListType listType( class GraphQLListType extends GraphQLType, List> with _NonNullableMixin, List> { - final GraphQLType type; - GraphQLListType(this.type); + final GraphQLType innerType; + GraphQLListType(this.innerType); @override ValidationResult> validate( @@ -32,7 +30,7 @@ class GraphQLListType for (int i = 0; i < input.length; i++) { var k = '"$key" at index $i'; var v = input[i]; - var result = type.validate(k, v); + var result = innerType.validate(k, v); if (!result.successful) errors.addAll(result.errors); else @@ -45,12 +43,12 @@ class GraphQLListType @override List deserialize(List serialized) { - return serialized.map(type.deserialize).toList(); + return serialized.map(innerType.deserialize).toList(); } @override List serialize(List value) { - return value.map(type.serialize).toList(); + return value.map(innerType.serialize).toList(); } } @@ -58,16 +56,13 @@ abstract class _NonNullableMixin implements GraphQLType { GraphQLType _nonNullableCache; GraphQLType nonNullable() => _nonNullableCache ??= - new _GraphQLNonNullableType._(this); + new GraphQLNonNullableType._(this); } -class _GraphQLNonNullableType +class GraphQLNonNullableType extends GraphQLType { - final GraphQLType type; - _GraphQLNonNullableType._(this.type); - - @override - bool get isNullable => true; + final GraphQLType innerType; + GraphQLNonNullableType._(this.innerType); @override GraphQLType nonNullable() { @@ -80,16 +75,16 @@ class _GraphQLNonNullableType if (input == null) return new ValidationResult._failure( ['Expected "$key" to be a non-null value.']); - return type.validate(key, input); + return innerType.validate(key, input); } @override Value deserialize(Serialized serialized) { - return type.deserialize(serialized); + return innerType.deserialize(serialized); } @override Serialized serialize(Value value) { - return type.serialize(value); + return innerType.serialize(value); } } diff --git a/graphql_schema/test/common.dart b/graphql_schema/test/common.dart index 5b5ae5d2..f0147bc2 100644 --- a/graphql_schema/test/common.dart +++ b/graphql_schema/test/common.dart @@ -1,14 +1,14 @@ import 'package:graphql_schema/graphql_schema.dart'; final GraphQLObjectType pokemonType = objectType('Pokemon', [ - field('species', type: graphQLString), - field('catch_date', type: graphQLDate) + field('species', innerType: graphQLString), + field('catch_date', innerType: graphQLDate) ]); final GraphQLObjectType trainerType = - objectType('Trainer', [field('name', type: graphQLString)]); + objectType('Trainer', [field('name', innerType: graphQLString)]); final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', [ - field('trainer', type: trainerType), - field('pokemon_species', type: listType(pokemonType)) + field('trainer', innerType: trainerType), + field('pokemon_species', innerType: listType(pokemonType)) ]); diff --git a/graphql_server/lib/graphql.dart b/graphql_server/lib/graphql.dart index 2beaa48f..dd5d5cfe 100644 --- a/graphql_server/lib/graphql.dart +++ b/graphql_server/lib/graphql.dart @@ -44,7 +44,7 @@ class GraphQL { } } - Future executeRequest( + Future> executeRequest( GraphQLSchema schema, DocumentContext document, String operationName, {Map variableValues: const {}, initialValue}) async { var operation = getOperation(document, operationName); @@ -53,9 +53,11 @@ class GraphQL { if (operation.isQuery) return await executeQuery( document, operation, schema, coercedVariableValues, initialValue); - else - return executeMutation( - document, operation, schema, coercedVariableValues, initialValue); + else { + throw new UnimplementedError('mutations'); +// return executeMutation( +// document, operation, schema, coercedVariableValues, initialValue); + } } OperationDefinitionContext getOperation( @@ -110,7 +112,7 @@ class GraphQL { return coercedValues; } - Future executeQuery( + Future> executeQuery( DocumentContext document, OperationDefinitionContext query, GraphQLSchema schema, @@ -140,8 +142,8 @@ class GraphQL { var fieldType = objectType.fields.firstWhere((f) => f.name == fieldName)?.type; if (fieldType == null) continue; - var responseValue = await executeField( - objectType, objectValue, fields, fieldType, variableValues); + var responseValue = await executeField(document, fieldName, objectType, + objectValue, fields, fieldType, variableValues); resultMap[responseKey] = responseValue; } } @@ -150,6 +152,8 @@ class GraphQL { } executeField( + DocumentContext document, + String fieldName, GraphQLObjectType objectType, objectValue, List fields, @@ -160,7 +164,8 @@ class GraphQL { coerceArgumentValues(objectType, field, variableValues); var resolvedValue = await resolveFieldValue( objectType, objectValue, field.field.fieldName.name, argumentValues); - return completeValue(fieldType, fields, resolvedValue, variableValues); + return completeValue( + document, fieldName, fieldType, fields, resolvedValue, variableValues); } Map coerceArgumentValues(GraphQLObjectType objectType, @@ -186,14 +191,14 @@ class GraphQL { coercedValues[argumentName] = variableValue; } else if (defaultValue != null || argumentDefinition.defaultsToNull) { coercedValues[argumentName] = defaultValue; - } else if (!argumentType.isNullable) { + } else if (argumentType is GraphQLNonNullableType) { throw new GraphQLException( 'Missing value for argument "$argumentName".'); } } else { if (defaultValue != null || argumentDefinition.defaultsToNull) { coercedValues[argumentName] = defaultValue; - } else if (!argumentType.isNullable) { + } else if (argumentType is GraphQLNonNullableType) { throw new GraphQLException( 'Missing value for argument "$argumentName".'); } @@ -209,6 +214,64 @@ class GraphQL { return await field.resolve(objectValue, argumentValues) as T; } + Future completeValue( + DocumentContext document, + String fieldName, + GraphQLType fieldType, + List fields, + result, + Map variableValues) async { + if (fieldType is GraphQLNonNullableType) { + var innerType = fieldType.innerType; + var completedResult = completeValue( + document, fieldName, innerType, fields, result, variableValues); + + if (completedResult == null) { + throw new GraphQLException( + 'Null value provided for non-nullable field "$fieldName".'); + } else { + return completedResult; + } + } + + if (result == null) { + return null; + } + + if (fieldType is GraphQLListType) { + if (result is! Iterable) { + throw new GraphQLException( + 'Value of field "$fieldName" must be a list or iterable.'); + } + + var innerType = fieldType.innerType; + return (result as Iterable) + .map((resultItem) => completeValue(document, '(item in "$fieldName")', + innerType, fields, resultItem, variableValues)) + .toList(); + } + + if (fieldType is GraphQLScalarType) { + var validation = fieldType.validate(fieldName, result); + + if (!validation.successful) { + return null; + } else { + return validation.value; + } + } + + if (fieldType is GraphQLObjectType) { + var objectType = fieldType; + var subSelectionSet = new SelectionSetContext.merged(fields); + return await executeSelectionSet( + document, subSelectionSet, objectType, result, variableValues); + } + + // TODO: Interface/union type + throw new UnsupportedError('Unsupported type: $fieldType'); + } + Map> collectFields( DocumentContext document, GraphQLObjectType objectType,