Merge branch 'master' into 1.2.0

This commit is contained in:
Tobe O 2019-08-14 10:50:21 -04:00
commit d23f899a6c
9 changed files with 144 additions and 124 deletions

View file

@ -1,3 +1,7 @@
# 1.0.1
* Fix a bug where `globalVariables` were not being properly passed
to field resolvers.
# 1.0.0 # 1.0.0
* Finish testing. * Finish testing.
* Add `package:pedantic` fixes. * Add `package:pedantic` fixes.

View file

@ -23,16 +23,16 @@ void main() {
'todos', 'todos',
listOf(todoType), listOf(todoType),
resolve: (_, __) => [ resolve: (_, __) => [
new Todo( Todo(
text: 'Clean your room!', text: 'Clean your room!',
completed: false, completed: false,
) )
], ],
), ),
]), ]),
); );
var graphql = new GraphQL(schema); var graphql = GraphQL(schema);
var result = await graphql.parseAndExecute('{ todos { text } }'); var result = await graphql.parseAndExecute('{ todos { text } }');
print(result); print(result);

View file

@ -45,15 +45,17 @@ class GraphQL {
} }
if (_schema.queryType != null) this.customTypes.add(_schema.queryType); if (_schema.queryType != null) this.customTypes.add(_schema.queryType);
if (_schema.mutationType != null) if (_schema.mutationType != null) {
this.customTypes.add(_schema.mutationType); this.customTypes.add(_schema.mutationType);
if (_schema.subscriptionType != null) }
if (_schema.subscriptionType != null) {
this.customTypes.add(_schema.subscriptionType); this.customTypes.add(_schema.subscriptionType);
}
} }
GraphQLType convertType(TypeContext ctx) { GraphQLType convertType(TypeContext ctx) {
if (ctx.listType != null) { if (ctx.listType != null) {
return new GraphQLListType(convertType(ctx.listType.type)); return GraphQLListType(convertType(ctx.listType.type));
} else if (ctx.typeName != null) { } else if (ctx.typeName != null) {
switch (ctx.typeName.name) { switch (ctx.typeName.name) {
case 'Int': case 'Int':
@ -71,11 +73,11 @@ class GraphQL {
return graphQLDate; return graphQLDate;
default: default:
return customTypes.firstWhere((t) => t.name == ctx.typeName.name, return customTypes.firstWhere((t) => t.name == ctx.typeName.name,
orElse: () => throw new ArgumentError( orElse: () => throw ArgumentError(
'Unknown GraphQL type: "${ctx.typeName.name}"')); 'Unknown GraphQL type: "${ctx.typeName.name}"'));
} }
} else { } else {
throw new ArgumentError('Invalid GraphQL type: "${ctx.span.text}"'); throw ArgumentError('Invalid GraphQL type: "${ctx.span.text}"');
} }
} }
@ -86,13 +88,13 @@ class GraphQL {
initialValue, initialValue,
Map<String, dynamic> globalVariables}) { Map<String, dynamic> globalVariables}) {
var tokens = scan(text, sourceUrl: sourceUrl); var tokens = scan(text, sourceUrl: sourceUrl);
var parser = new Parser(tokens); var parser = Parser(tokens);
var document = parser.parseDocument(); var document = parser.parseDocument();
if (parser.errors.isNotEmpty) { if (parser.errors.isNotEmpty) {
throw new GraphQLException(parser.errors throw GraphQLException(parser.errors
.map((e) => new GraphQLExceptionError(e.message, locations: [ .map((e) => GraphQLExceptionError(e.message, locations: [
new GraphExceptionErrorLocation.fromSourceLocation(e.span.start) GraphExceptionErrorLocation.fromSourceLocation(e.span.start)
])) ]))
.toList()); .toList());
} }
@ -115,10 +117,10 @@ class GraphQL {
var operation = getOperation(document, operationName); var operation = getOperation(document, operationName);
var coercedVariableValues = coerceVariableValues( var coercedVariableValues = coerceVariableValues(
schema, operation, variableValues ?? <String, dynamic>{}); schema, operation, variableValues ?? <String, dynamic>{});
if (operation.isQuery) if (operation.isQuery) {
return await executeQuery(document, operation, schema, return await executeQuery(document, operation, schema,
coercedVariableValues, initialValue, globalVariables); coercedVariableValues, initialValue, globalVariables);
else if (operation.isSubscription) { } else if (operation.isSubscription) {
return await subscribe(document, operation, schema, coercedVariableValues, return await subscribe(document, operation, schema, coercedVariableValues,
globalVariables, initialValue); globalVariables, initialValue);
} else { } else {
@ -129,20 +131,17 @@ class GraphQL {
OperationDefinitionContext getOperation( OperationDefinitionContext getOperation(
DocumentContext document, String operationName) { DocumentContext document, String operationName) {
var ops = var ops = document.definitions.whereType<OperationDefinitionContext>();
document.definitions.where((d) => d is OperationDefinitionContext);
if (operationName == null) { if (operationName == null) {
return ops.length == 1 return ops.length == 1
? ops.first as OperationDefinitionContext ? ops.first
: throw new GraphQLException.fromMessage( : throw GraphQLException.fromMessage(
'This document does not define any operations.'); 'This document does not define any operations.');
} else { } else {
return ops.firstWhere( return ops.firstWhere((d) => d.name == operationName,
(d) => (d as OperationDefinitionContext).name == operationName, orElse: () => throw GraphQLException.fromMessage(
orElse: () => throw new GraphQLException.fromMessage( 'Missing required operation "$operationName".'));
'Missing required operation "$operationName".'))
as OperationDefinitionContext;
} }
} }
@ -164,7 +163,7 @@ class GraphQL {
if (defaultValue != null) { if (defaultValue != null) {
coercedValues[variableName] = defaultValue.value.value; coercedValues[variableName] = defaultValue.value.value;
} else if (!variableType.isNullable) { } else if (!variableType.isNullable) {
throw new GraphQLException.fromSourceSpan( throw GraphQLException.fromSourceSpan(
'Missing required variable "$variableName".', 'Missing required variable "$variableName".',
variableDefinition.span); variableDefinition.span);
} }
@ -173,9 +172,9 @@ class GraphQL {
var validation = type.validate(variableName, value); var validation = type.validate(variableName, value);
if (!validation.successful) { if (!validation.successful) {
throw new GraphQLException(validation.errors throw GraphQLException(validation.errors
.map((e) => new GraphQLExceptionError(e, locations: [ .map((e) => GraphQLExceptionError(e, locations: [
new GraphExceptionErrorLocation.fromSourceLocation( GraphExceptionErrorLocation.fromSourceLocation(
variableDefinition.span.start) variableDefinition.span.start)
])) ]))
.toList()); .toList());
@ -211,7 +210,7 @@ class GraphQL {
var mutationType = schema.mutationType; var mutationType = schema.mutationType;
if (mutationType == null) { if (mutationType == null) {
throw new GraphQLException.fromMessage( throw GraphQLException.fromMessage(
'The schema does not define a mutation type.'); 'The schema does not define a mutation type.');
} }
@ -241,15 +240,17 @@ class GraphQL {
initialValue) { initialValue) {
var selectionSet = subscription.selectionSet; var selectionSet = subscription.selectionSet;
var subscriptionType = schema.subscriptionType; var subscriptionType = schema.subscriptionType;
if (subscriptionType == null) if (subscriptionType == null) {
throw GraphQLException.fromSourceSpan( throw GraphQLException.fromSourceSpan(
'The schema does not define a subscription type.', subscription.span); 'The schema does not define a subscription type.', subscription.span);
}
var groupedFieldSet = var groupedFieldSet =
collectFields(document, subscriptionType, selectionSet, variableValues); collectFields(document, subscriptionType, selectionSet, variableValues);
if (groupedFieldSet.length != 1) if (groupedFieldSet.length != 1) {
throw GraphQLException.fromSourceSpan( throw GraphQLException.fromSourceSpan(
'The grouped field set from this query must have exactly one entry.', 'The grouped field set from this query must have exactly one entry.',
selectionSet.span); selectionSet.span);
}
var fields = groupedFieldSet.entries.first.value; var fields = groupedFieldSet.entries.first.value;
var fieldName = fields.first.field.fieldName.alias?.name ?? var fieldName = fields.first.field.fieldName.alias?.name ??
fields.first.field.fieldName.name; fields.first.field.fieldName.name;
@ -284,10 +285,10 @@ class GraphQL {
Map<String, dynamic> globalVariables) async { Map<String, dynamic> globalVariables) async {
var selectionSet = subscription.selectionSet; var selectionSet = subscription.selectionSet;
var subscriptionType = schema.subscriptionType; var subscriptionType = schema.subscriptionType;
if (subscriptionType == null) if (subscriptionType == null) {
throw GraphQLException.fromSourceSpan( throw GraphQLException.fromSourceSpan(
'The schema does not define a subscription type.', subscription.span); 'The schema does not define a subscription type.', subscription.span);
}
try { try {
var data = await executeSelectionSet(document, selectionSet, var data = await executeSelectionSet(document, selectionSet,
subscriptionType, initialValue, variableValues, globalVariables); subscriptionType, initialValue, variableValues, globalVariables);
@ -309,10 +310,11 @@ class GraphQL {
}); });
var resolver = field.resolve; var resolver = field.resolve;
var result = await resolver(rootValue, argumentValues); var result = await resolver(rootValue, argumentValues);
if (result is Stream) if (result is Stream) {
return result; return result;
else } else {
return Stream.fromIterable([result]); return Stream.fromIterable([result]);
}
} }
Future<Map<String, dynamic>> executeSelectionSet( Future<Map<String, dynamic>> executeSelectionSet(
@ -348,8 +350,7 @@ class GraphQL {
objectValue, objectValue,
fields, fields,
fieldType, fieldType,
new Map<String, dynamic>.from( Map<String, dynamic>.from(globalVariables ?? <String, dynamic>{})
globalVariables ?? <String, dynamic>{})
..addAll(variableValues), ..addAll(variableValues),
globalVariables); globalVariables);
} }
@ -374,7 +375,11 @@ class GraphQL {
var argumentValues = var argumentValues =
coerceArgumentValues(objectType, field, variableValues); coerceArgumentValues(objectType, field, variableValues);
var resolvedValue = await resolveFieldValue( var resolvedValue = await resolveFieldValue(
objectType, objectValue, fieldName, argumentValues); objectType,
objectValue,
fieldName,
Map<String, dynamic>.from(globalVariables ?? {})
..addAll(argumentValues ?? {}));
return completeValue(document, fieldName, fieldType, fields, resolvedValue, return completeValue(document, fieldName, fieldType, fields, resolvedValue,
variableValues, globalVariables); variableValues, globalVariables);
} }
@ -407,7 +412,7 @@ class GraphQL {
} else if (defaultValue != null || argumentDefinition.defaultsToNull) { } else if (defaultValue != null || argumentDefinition.defaultsToNull) {
coercedValues[argumentName] = defaultValue; coercedValues[argumentName] = defaultValue;
} else if (argumentType is GraphQLNonNullableType) { } else if (argumentType is GraphQLNonNullableType) {
throw new GraphQLException.fromSourceSpan( throw GraphQLException.fromSourceSpan(
'Missing value for argument "$argumentName" of field "$fieldName".', 'Missing value for argument "$argumentName" of field "$fieldName".',
value.valueOrVariable.span); value.valueOrVariable.span);
} else { } else {
@ -417,7 +422,7 @@ class GraphQL {
if (defaultValue != null || argumentDefinition.defaultsToNull) { if (defaultValue != null || argumentDefinition.defaultsToNull) {
coercedValues[argumentName] = defaultValue; coercedValues[argumentName] = defaultValue;
} else if (argumentType is GraphQLNonNullableType) { } else if (argumentType is GraphQLNonNullableType) {
throw new GraphQLException.fromMessage( throw GraphQLException.fromMessage(
'Missing value for argument "$argumentName" of field "$fieldName".'); 'Missing value for argument "$argumentName" of field "$fieldName".');
} else { } else {
continue; continue;
@ -429,10 +434,10 @@ class GraphQL {
if (!validation.successful) { if (!validation.successful) {
var errors = <GraphQLExceptionError>[ var errors = <GraphQLExceptionError>[
new GraphQLExceptionError( GraphQLExceptionError(
'Type coercion error for value of argument "$argumentName" of field "$fieldName".', 'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
locations: [ locations: [
new GraphExceptionErrorLocation.fromSourceLocation( GraphExceptionErrorLocation.fromSourceLocation(
value.valueOrVariable.span.start) value.valueOrVariable.span.start)
], ],
) )
@ -440,34 +445,34 @@ class GraphQL {
for (var error in validation.errors) { for (var error in validation.errors) {
errors.add( errors.add(
new GraphQLExceptionError( GraphQLExceptionError(
error, error,
locations: [ locations: [
new GraphExceptionErrorLocation.fromSourceLocation( GraphExceptionErrorLocation.fromSourceLocation(
value.valueOrVariable.span.start) value.valueOrVariable.span.start)
], ],
), ),
); );
} }
throw new GraphQLException(errors); throw GraphQLException(errors);
} else { } else {
var coercedValue = validation.value; var coercedValue = validation.value;
coercedValues[argumentName] = coercedValue; coercedValues[argumentName] = coercedValue;
} }
} on TypeError catch (e) { } on TypeError catch (e) {
throw new GraphQLException(<GraphQLExceptionError>[ throw GraphQLException(<GraphQLExceptionError>[
new GraphQLExceptionError( GraphQLExceptionError(
'Type coercion error for value of argument "$argumentName" of field "$fieldName".', 'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
locations: [ locations: [
new GraphExceptionErrorLocation.fromSourceLocation( GraphExceptionErrorLocation.fromSourceLocation(
value.valueOrVariable.span.start) value.valueOrVariable.span.start)
], ],
), ),
new GraphQLExceptionError( GraphQLExceptionError(
e.message.toString(), e.message.toString(),
locations: [ locations: [
new GraphExceptionErrorLocation.fromSourceLocation( GraphExceptionErrorLocation.fromSourceLocation(
value.valueOrVariable.span.start) value.valueOrVariable.span.start)
], ],
), ),
@ -486,9 +491,10 @@ class GraphQL {
if (objectValue is Map) { if (objectValue is Map) {
return objectValue[fieldName] as T; return objectValue[fieldName] as T;
} else if (field.resolve == null) { } else if (field.resolve == null) {
if (defaultFieldResolver != null) if (defaultFieldResolver != null) {
return await defaultFieldResolver( return await defaultFieldResolver(
objectValue, fieldName, argumentValues); objectValue, fieldName, argumentValues);
}
return null; return null;
} else { } else {
@ -510,7 +516,7 @@ class GraphQL {
fields, result, variableValues, globalVariables); fields, result, variableValues, globalVariables);
if (completedResult == null) { if (completedResult == null) {
throw new GraphQLException.fromMessage( throw GraphQLException.fromMessage(
'Null value provided for non-nullable field "$fieldName".'); 'Null value provided for non-nullable field "$fieldName".');
} else { } else {
return completedResult; return completedResult;
@ -523,7 +529,7 @@ class GraphQL {
if (fieldType is GraphQLListType) { if (fieldType is GraphQLListType) {
if (result is! Iterable) { if (result is! Iterable) {
throw new GraphQLException.fromMessage( throw GraphQLException.fromMessage(
'Value of field "$fieldName" must be a list or iterable, got $result instead.'); 'Value of field "$fieldName" must be a list or iterable, got $result instead.');
} }
@ -548,7 +554,7 @@ class GraphQL {
return validation.value; return validation.value;
} }
} on TypeError { } on TypeError {
throw new GraphQLException.fromMessage( throw GraphQLException.fromMessage(
'Value of field "$fieldName" must be ${fieldType.valueType}, got $result instead.'); 'Value of field "$fieldName" must be ${fieldType.valueType}, got $result instead.');
} }
} }
@ -567,7 +573,7 @@ class GraphQL {
result, variableValues, globalVariables); result, variableValues, globalVariables);
} }
throw new UnsupportedError('Unsupported type: $fieldType'); throw UnsupportedError('Unsupported type: $fieldType');
} }
GraphQLObjectType resolveAbstractType( GraphQLObjectType resolveAbstractType(
@ -583,7 +589,7 @@ class GraphQL {
} else if (type is GraphQLUnionType) { } else if (type is GraphQLUnionType) {
possibleTypes = type.possibleTypes; possibleTypes = type.possibleTypes;
} else { } else {
throw new ArgumentError(); throw ArgumentError();
} }
var errors = <GraphQLExceptionError>[]; var errors = <GraphQLExceptionError>[];
@ -597,19 +603,16 @@ class GraphQL {
return t; return t;
} }
errors errors.addAll(validation.errors.map((m) => GraphQLExceptionError(m)));
.addAll(validation.errors.map((m) => new GraphQLExceptionError(m)));
} on GraphQLException catch (e) { } on GraphQLException catch (e) {
errors.addAll(e.errors); errors.addAll(e.errors);
} }
} }
errors.insert( errors.insert(0,
0, GraphQLExceptionError('Cannot convert value $result to type $type.'));
new GraphQLExceptionError(
'Cannot convert value $result to type $type.'));
throw new GraphQLException(errors); throw GraphQLException(errors);
} }
SelectionSetContext mergeSelectionSets(List<SelectionContext> fields) { SelectionSetContext mergeSelectionSets(List<SelectionContext> fields) {
@ -623,7 +626,7 @@ class GraphQL {
} }
} }
return new SelectionSetContext.merged(selections); return SelectionSetContext.merged(selections);
} }
Map<String, List<SelectionContext>> collectFields( Map<String, List<SelectionContext>> collectFields(
@ -636,10 +639,13 @@ class GraphQL {
visitedFragments ??= []; visitedFragments ??= [];
for (var selection in selectionSet.selections) { for (var selection in selectionSet.selections) {
if (getDirectiveValue('skip', 'if', selection, variableValues) == true) if (getDirectiveValue('skip', 'if', selection, variableValues) == true) {
continue; continue;
}
if (getDirectiveValue('include', 'if', selection, variableValues) == if (getDirectiveValue('include', 'if', selection, variableValues) ==
false) continue; false) {
continue;
}
if (selection.field != null) { if (selection.field != null) {
var responseKey = selection.field.fieldName.alias?.alias ?? var responseKey = selection.field.fieldName.alias?.alias ??
@ -652,11 +658,9 @@ class GraphQL {
if (visitedFragments.contains(fragmentSpreadName)) continue; if (visitedFragments.contains(fragmentSpreadName)) continue;
visitedFragments.add(fragmentSpreadName); visitedFragments.add(fragmentSpreadName);
var fragment = document.definitions var fragment = document.definitions
.where((d) => d is FragmentDefinitionContext) .whereType<FragmentDefinitionContext>()
.firstWhere( .firstWhere((f) => f.name == fragmentSpreadName,
(f) => orElse: () => null);
(f as FragmentDefinitionContext).name == fragmentSpreadName,
orElse: () => null) as FragmentDefinitionContext;
if (fragment == null) continue; if (fragment == null) continue;
var fragmentType = fragment.typeCondition; var fragmentType = fragment.typeCondition;
@ -708,19 +712,21 @@ class GraphQL {
if (vv.value != null) return vv.value.value; if (vv.value != null) return vv.value.value;
var vname = vv.variable.name; var vname = vv.variable.name;
if (!variableValues.containsKey(vname)) if (!variableValues.containsKey(vname)) {
throw new GraphQLException.fromSourceSpan( throw GraphQLException.fromSourceSpan(
'Unknown variable: "$vname"', vv.span); 'Unknown variable: "$vname"', vv.span);
}
return variableValues[vname]; return variableValues[vname];
} }
bool doesFragmentTypeApply( bool doesFragmentTypeApply(
GraphQLObjectType objectType, TypeConditionContext fragmentType) { GraphQLObjectType objectType, TypeConditionContext fragmentType) {
var type = convertType(new TypeContext(fragmentType.typeName, null)); var type = convertType(TypeContext(fragmentType.typeName, null));
if (type is GraphQLObjectType && !type.isInterface) { if (type is GraphQLObjectType && !type.isInterface) {
for (var field in type.fields) for (var field in type.fields) {
if (!objectType.fields.any((f) => f.name == field.name)) return false; if (!objectType.fields.any((f) => f.name == field.name)) return false;
}
return true; return true;
} else if (type is GraphQLObjectType && type.isInterface) { } else if (type is GraphQLObjectType && type.isInterface) {
return objectType.isImplementationOf(type); return objectType.isImplementationOf(type);

View file

@ -1,7 +1,7 @@
import 'package:graphql_parser/graphql_parser.dart'; import 'package:graphql_parser/graphql_parser.dart';
import 'package:graphql_schema/graphql_schema.dart'; import 'package:graphql_schema/graphql_schema.dart';
/// Performs introspection over a GraphQL [schema], and returns a new one, containing /// Performs introspection over a GraphQL [schema], and returns a one, containing
/// introspective information. /// introspective information.
/// ///
/// [allTypes] should contain all types, not directly defined in the schema, that you /// [allTypes] should contain all types, not directly defined in the schema, that you
@ -79,11 +79,11 @@ GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
field( field(
'__type', '__type',
typeType, typeType,
inputs: [new GraphQLFieldInput('name', graphQLString.nonNullable())], inputs: [GraphQLFieldInput('name', graphQLString.nonNullable())],
resolve: (_, args) { resolve: (_, args) {
var name = args['name'] as String; var name = args['name'] as String;
return allTypes.firstWhere((t) => t.name == name, return allTypes.firstWhere((t) => t.name == name,
orElse: () => throw new GraphQLException.fromMessage( orElse: () => throw GraphQLException.fromMessage(
'No type named "$name" exists.')); 'No type named "$name" exists.'));
}, },
), ),
@ -91,7 +91,7 @@ GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
fields.addAll(schema.queryType.fields); fields.addAll(schema.queryType.fields);
return new GraphQLSchema( return GraphQLSchema(
queryType: objectType(schema.queryType.name, fields: fields), queryType: objectType(schema.queryType.name, fields: fields),
mutationType: schema.mutationType, mutationType: schema.mutationType,
subscriptionType: schema.subscriptionType, subscriptionType: schema.subscriptionType,
@ -108,9 +108,9 @@ GraphQLObjectType _reflectSchemaTypes() {
'ofType', 'ofType',
_reflectSchemaTypes(), _reflectSchemaTypes(),
resolve: (type, _) { resolve: (type, _) {
if (type is GraphQLListType) if (type is GraphQLListType) {
return type.ofType; return type.ofType;
else if (type is GraphQLNonNullableType) return type.ofType; } else if (type is GraphQLNonNullableType) return type.ofType;
return null; return null;
}, },
), ),
@ -213,29 +213,30 @@ GraphQLObjectType _createTypeType() {
resolve: (type, _) { resolve: (type, _) {
var t = type as GraphQLType; var t = type as GraphQLType;
if (t is GraphQLEnumType) if (t is GraphQLEnumType) {
return 'ENUM'; return 'ENUM';
else if (t is GraphQLScalarType) } else if (t is GraphQLScalarType) {
return 'SCALAR'; return 'SCALAR';
else if (t is GraphQLInputObjectType) } else if (t is GraphQLInputObjectType) {
return 'INPUT_OBJECT'; return 'INPUT_OBJECT';
else if (t is GraphQLObjectType) } else if (t is GraphQLObjectType) {
return t.isInterface ? 'INTERFACE' : 'OBJECT'; return t.isInterface ? 'INTERFACE' : 'OBJECT';
else if (t is GraphQLListType) } else if (t is GraphQLListType) {
return 'LIST'; return 'LIST';
else if (t is GraphQLNonNullableType) } else if (t is GraphQLNonNullableType) {
return 'NON_NULL'; return 'NON_NULL';
else if (t is GraphQLUnionType) } else if (t is GraphQLUnionType) {
return 'UNION'; return 'UNION';
else } else {
throw new UnsupportedError('Cannot get the kind of $t.'); throw UnsupportedError('Cannot get the kind of $t.');
}
}, },
), ),
field( field(
'fields', 'fields',
listOf(fieldType), listOf(fieldType),
inputs: [ inputs: [
new GraphQLFieldInput( GraphQLFieldInput(
'includeDeprecated', 'includeDeprecated',
graphQLBoolean, graphQLBoolean,
defaultValue: false, defaultValue: false,
@ -252,7 +253,7 @@ GraphQLObjectType _createTypeType() {
'enumValues', 'enumValues',
listOf(enumValueType.nonNullable()), listOf(enumValueType.nonNullable()),
inputs: [ inputs: [
new GraphQLFieldInput( GraphQLFieldInput(
'includeDeprecated', 'includeDeprecated',
graphQLBoolean, graphQLBoolean,
defaultValue: false, defaultValue: false,

View file

@ -7,10 +7,11 @@ import 'package:recase/recase.dart';
/// Uses `dart:mirrors` to read field names from items. If they are Maps, performs a regular lookup. /// Uses `dart:mirrors` to read field names from items. If they are Maps, performs a regular lookup.
T mirrorsFieldResolver<T>(objectValue, String fieldName, T mirrorsFieldResolver<T>(objectValue, String fieldName,
[Map<String, dynamic> objectValues]) { [Map<String, dynamic> objectValues]) {
if (objectValue is Map) if (objectValue is Map) {
return objectValue[fieldName] as T; return objectValue[fieldName] as T;
else } else {
return reflect(objectValue).getField(Symbol(fieldName)).reflectee as T; return reflect(objectValue).getField(Symbol(fieldName)).reflectee as T;
}
} }
/// Reflects upon a given [type] and dynamically generates a [GraphQLType] that corresponds to it. /// Reflects upon a given [type] and dynamically generates a [GraphQLType] that corresponds to it.
@ -42,10 +43,10 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
} else if (type == double) { } else if (type == double) {
return graphQLFloat; return graphQLFloat;
} else if (type == num) { } else if (type == num) {
throw new UnsupportedError( throw UnsupportedError(
'Cannot convert `num` to a GraphQL type. Choose `int` or `float` instead.'); 'Cannot convert `num` to a GraphQL type. Choose `int` or `float` instead.');
} else if (type == Null) { } else if (type == Null) {
throw new UnsupportedError('Cannot convert `Null` to a GraphQL type.'); throw UnsupportedError('Cannot convert `Null` to a GraphQL type.');
} else if (type == String) { } else if (type == String) {
return graphQLString; return graphQLString;
} else if (type == DateTime) { } else if (type == DateTime) {
@ -56,7 +57,7 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
type, typeArguments?.isNotEmpty == true ? typeArguments : null); type, typeArguments?.isNotEmpty == true ? typeArguments : null);
if (mirror is! ClassMirror) { if (mirror is! ClassMirror) {
throw new StateError( throw StateError(
'$type is not a class, and therefore cannot be converted into a GraphQL object type.'); '$type is not a class, and therefore cannot be converted into a GraphQL object type.');
} }
@ -69,7 +70,7 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
return listOf(inner.nonNullable()); return listOf(inner.nonNullable());
} }
throw new ArgumentError( throw ArgumentError(
'Cannot convert ${clazz.reflectedType}, an iterable WITHOUT a type argument, into a GraphQL type.'); 'Cannot convert ${clazz.reflectedType}, an iterable WITHOUT a type argument, into a GraphQL type.');
} }
@ -213,7 +214,7 @@ GraphQLEnumType enumTypeFromClassMirror(ClassMirror mirror) {
if (name != #values) { if (name != #values) {
var methodMirror = mirror.staticMembers[name]; var methodMirror = mirror.staticMembers[name];
values.add( values.add(
new GraphQLEnumValue( GraphQLEnumValue(
MirrorSystem.getName(name), MirrorSystem.getName(name),
mirror.getField(name).reflectee, mirror.getField(name).reflectee,
description: _getDescription(methodMirror.metadata), description: _getDescription(methodMirror.metadata),
@ -223,7 +224,7 @@ GraphQLEnumType enumTypeFromClassMirror(ClassMirror mirror) {
} }
} }
return new GraphQLEnumType( return GraphQLEnumType(
MirrorSystem.getName(mirror.simpleName), MirrorSystem.getName(mirror.simpleName),
values, values,
description: _getDescription(mirror.metadata), description: _getDescription(mirror.metadata),
@ -294,7 +295,7 @@ String _getSerializedName(Symbol name, MethodMirror mirror, ClassMirror clazz) {
var ann = obj.reflectee as Serializable; var ann = obj.reflectee as Serializable;
if (ann.autoSnakeCaseNames != false) { if (ann.autoSnakeCaseNames != false) {
return new ReCase(MirrorSystem.getName(name)).snakeCase; return ReCase(MirrorSystem.getName(name)).snakeCase;
} }
} }
} }

View file

@ -18,11 +18,12 @@ abstract class Server {
if ((msg.type == OperationMessage.gqlConnectionInit) && !_init) { if ((msg.type == OperationMessage.gqlConnectionInit) && !_init) {
try { try {
Map connectionParams; Map connectionParams;
if (msg.payload is Map) if (msg.payload is Map) {
connectionParams = msg.payload as Map; connectionParams = msg.payload as Map;
else if (msg.payload != null) } else if (msg.payload != null) {
throw FormatException( throw FormatException(
'${msg.type} payload must be a map (object).'); '${msg.type} payload must be a map (object).');
}
var connect = await onConnect(client, connectionParams); var connect = await onConnect(client, connectionParams);
if (!connect) throw false; if (!connect) throw false;
@ -39,33 +40,39 @@ abstract class Server {
}); });
} }
} catch (e) { } catch (e) {
if (e == false) if (e == false) {
_reportError('The connection was rejected.'); _reportError('The connection was rejected.');
else } else {
_reportError(e.toString()); _reportError(e.toString());
}
} }
} else if (_init) { } else if (_init) {
if (msg.type == OperationMessage.gqlStart) { if (msg.type == OperationMessage.gqlStart) {
if (msg.id == null) if (msg.id == null) {
throw FormatException('${msg.type} id is required.'); throw FormatException('${msg.type} id is required.');
if (msg.payload == null) }
if (msg.payload == null) {
throw FormatException('${msg.type} payload is required.'); throw FormatException('${msg.type} payload is required.');
else if (msg.payload is! Map) } else if (msg.payload is! Map) {
throw FormatException( throw FormatException(
'${msg.type} payload must be a map (object).'); '${msg.type} payload must be a map (object).');
}
var payload = msg.payload as Map; var payload = msg.payload as Map;
var query = payload['query']; var query = payload['query'];
var variables = payload['variables']; var variables = payload['variables'];
var operationName = payload['operationName']; var operationName = payload['operationName'];
if (query == null || query is! String) if (query == null || query is! String) {
throw FormatException( throw FormatException(
'${msg.type} payload must contain a string named "query".'); '${msg.type} payload must contain a string named "query".');
if (variables != null && variables is! Map) }
if (variables != null && variables is! Map) {
throw FormatException( throw FormatException(
'${msg.type} payload\'s "variables" field must be a map (object).'); '${msg.type} payload\'s "variables" field must be a map (object).');
if (operationName != null && operationName is! String) }
if (operationName != null && operationName is! String) {
throw FormatException( throw FormatException(
'${msg.type} payload\'s "operationName" field must be a string.'); '${msg.type} payload\'s "operationName" field must be a string.');
}
var result = await onOperation( var result = await onOperation(
msg.id, msg.id,
query as String, query as String,

View file

@ -44,14 +44,15 @@ class OperationMessage {
var payload = map['payload']; var payload = map['payload'];
var id = map['id']; var id = map['id'];
if (type == null) if (type == null) {
throw ArgumentError.notNull('type'); throw ArgumentError.notNull('type');
else if (type is! String) } else if (type is! String) {
throw ArgumentError.value(type, 'type', 'must be a string'); throw ArgumentError.value(type, 'type', 'must be a string');
else if (id is num) } else if (id is num) {
id = id.toString(); id = id.toString();
else if (id != null && id is! String) } else if (id != null && id is! String) {
throw ArgumentError.value(id, 'id', 'must be a string or number'); throw ArgumentError.value(id, 'id', 'must be a string or number');
}
// TODO: This is technically a violation of the spec. // TODO: This is technically a violation of the spec.
// https://github.com/apollographql/subscriptions-transport-ws/issues/551 // https://github.com/apollographql/subscriptions-transport-ws/issues/551

View file

@ -1,5 +1,5 @@
name: graphql_server name: graphql_server
version: 1.0.0 version: 1.0.1
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
description: Base package for implementing GraphQL servers. You might prefer `package:angel_graphql`, the fastest way to implement GraphQL backends in Dart. description: Base package for implementing GraphQL servers. You might prefer `package:angel_graphql`, the fastest way to implement GraphQL backends in Dart.
homepage: https://github.com/angel-dart/graphql homepage: https://github.com/angel-dart/graphql

View file

@ -23,16 +23,16 @@ void main() {
'todos', 'todos',
listOf(todoType), listOf(todoType),
resolve: (_, __) => [ resolve: (_, __) => [
new Todo( Todo(
text: 'Clean your room!', text: 'Clean your room!',
completed: false, completed: false,
) )
], ],
), ),
]), ]),
); );
var graphql = new GraphQL(schema); var graphql = GraphQL(schema);
var result = await graphql.parseAndExecute('{ todos { text } }'); var result = await graphql.parseAndExecute('{ todos { text } }');
print(result); print(result);