Finished querying (missing interface+union types)
This commit is contained in:
parent
e267afe85b
commit
10e740b2b1
6 changed files with 120 additions and 41 deletions
|
@ -1,4 +1,5 @@
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'selection.dart';
|
import 'selection.dart';
|
||||||
|
@ -9,6 +10,9 @@ class SelectionSetContext extends Node {
|
||||||
|
|
||||||
SelectionSetContext(this.LBRACE, this.RBRACE);
|
SelectionSetContext(this.LBRACE, this.RBRACE);
|
||||||
|
|
||||||
|
factory SelectionSetContext.merged(List<SelectionContext> selections) =
|
||||||
|
_MergedSelectionSetContext;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out =
|
var out =
|
||||||
|
@ -16,3 +20,13 @@ class SelectionSetContext extends Node {
|
||||||
return out.expand(RBRACE.span);
|
return out.expand(RBRACE.span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _MergedSelectionSetContext extends SelectionSetContext {
|
||||||
|
final List<SelectionContext> selections;
|
||||||
|
|
||||||
|
_MergedSelectionSetContext(this.selections) : super(null, null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FileSpan get span =>
|
||||||
|
selections.map((s) => s.span).reduce((a, b) => a.expand(b));
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
import 'package:graphql_schema/graphql_schema.dart';
|
||||||
|
|
||||||
final GraphQLSchema todoSchema = new GraphQLSchema(
|
final GraphQLSchema todoSchema = new GraphQLSchema(
|
||||||
query: objectType('Todo', [
|
query: objectType('Todo', [
|
||||||
field('text', type: graphQLString.nonNullable()),
|
field(
|
||||||
field('created_at', type: graphQLDate)
|
'text',
|
||||||
]));
|
innerType: graphQLString.nonNullable(),
|
||||||
|
),
|
||||||
|
field('created_at', innerType: graphQLDate),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
// Validation
|
// Validation
|
||||||
var validation = todoSchema.query
|
var validation = todoSchema.query.validate(
|
||||||
.validate('@root', {'foo': 'bar', 'text': null, 'created_at': 24});
|
'@root',
|
||||||
|
{'foo': 'bar', 'text': null, 'created_at': 24,},
|
||||||
|
);
|
||||||
|
|
||||||
if (validation.successful) {
|
if (validation.successful) {
|
||||||
print('This is valid data!!!');
|
print('This is valid data!!!');
|
||||||
|
|
|
@ -5,9 +5,10 @@ GraphQLObjectType objectType(String name,
|
||||||
new GraphQLObjectType(name)..fields.addAll(fields ?? []);
|
new GraphQLObjectType(name)..fields.addAll(fields ?? []);
|
||||||
|
|
||||||
GraphQLField<T, Serialized> field<T, Serialized>(String name,
|
GraphQLField<T, Serialized> field<T, Serialized>(String name,
|
||||||
{GraphQLFieldArgument<T, Serialized> argument,
|
{Iterable<GraphQLFieldArgument<T, Serialized>> arguments:
|
||||||
|
const <GraphQLFieldArgument<T, Serialized>>[],
|
||||||
GraphQLFieldResolver<T, Serialized> resolve,
|
GraphQLFieldResolver<T, Serialized> resolve,
|
||||||
GraphQLType<T, Serialized> type}) {
|
GraphQLType<T, Serialized> innerType}) {
|
||||||
return new GraphQLField(name,
|
return new GraphQLField(name,
|
||||||
argument: argument, resolve: resolve, type: type);
|
arguments: arguments, resolve: resolve, type: innerType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ abstract class GraphQLType<Value, Serialized> {
|
||||||
Value deserialize(Serialized serialized);
|
Value deserialize(Serialized serialized);
|
||||||
ValidationResult<Serialized> validate(String key, Serialized input);
|
ValidationResult<Serialized> validate(String key, Serialized input);
|
||||||
GraphQLType<Value, Serialized> nonNullable();
|
GraphQLType<Value, Serialized> nonNullable();
|
||||||
|
|
||||||
bool get isNullable => false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shorthand to create a [GraphQLListType].
|
/// Shorthand to create a [GraphQLListType].
|
||||||
|
@ -17,8 +15,8 @@ GraphQLListType<Value, Serialized> listType<Value, Serialized>(
|
||||||
class GraphQLListType<Value, Serialized>
|
class GraphQLListType<Value, Serialized>
|
||||||
extends GraphQLType<List<Value>, List<Serialized>>
|
extends GraphQLType<List<Value>, List<Serialized>>
|
||||||
with _NonNullableMixin<List<Value>, List<Serialized>> {
|
with _NonNullableMixin<List<Value>, List<Serialized>> {
|
||||||
final GraphQLType<Value, Serialized> type;
|
final GraphQLType<Value, Serialized> innerType;
|
||||||
GraphQLListType(this.type);
|
GraphQLListType(this.innerType);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ValidationResult<List<Serialized>> validate(
|
ValidationResult<List<Serialized>> validate(
|
||||||
|
@ -32,7 +30,7 @@ class GraphQLListType<Value, Serialized>
|
||||||
for (int i = 0; i < input.length; i++) {
|
for (int i = 0; i < input.length; i++) {
|
||||||
var k = '"$key" at index $i';
|
var k = '"$key" at index $i';
|
||||||
var v = input[i];
|
var v = input[i];
|
||||||
var result = type.validate(k, v);
|
var result = innerType.validate(k, v);
|
||||||
if (!result.successful)
|
if (!result.successful)
|
||||||
errors.addAll(result.errors);
|
errors.addAll(result.errors);
|
||||||
else
|
else
|
||||||
|
@ -45,12 +43,12 @@ class GraphQLListType<Value, Serialized>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Value> deserialize(List<Serialized> serialized) {
|
List<Value> deserialize(List<Serialized> serialized) {
|
||||||
return serialized.map<Value>(type.deserialize).toList();
|
return serialized.map<Value>(innerType.deserialize).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Serialized> serialize(List<Value> value) {
|
List<Serialized> serialize(List<Value> value) {
|
||||||
return value.map<Serialized>(type.serialize).toList();
|
return value.map<Serialized>(innerType.serialize).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,16 +56,13 @@ abstract class _NonNullableMixin<Value, Serialized>
|
||||||
implements GraphQLType<Value, Serialized> {
|
implements GraphQLType<Value, Serialized> {
|
||||||
GraphQLType<Value, Serialized> _nonNullableCache;
|
GraphQLType<Value, Serialized> _nonNullableCache;
|
||||||
GraphQLType<Value, Serialized> nonNullable() => _nonNullableCache ??=
|
GraphQLType<Value, Serialized> nonNullable() => _nonNullableCache ??=
|
||||||
new _GraphQLNonNullableType<Value, Serialized>._(this);
|
new GraphQLNonNullableType<Value, Serialized>._(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GraphQLNonNullableType<Value, Serialized>
|
class GraphQLNonNullableType<Value, Serialized>
|
||||||
extends GraphQLType<Value, Serialized> {
|
extends GraphQLType<Value, Serialized> {
|
||||||
final GraphQLType<Value, Serialized> type;
|
final GraphQLType<Value, Serialized> innerType;
|
||||||
_GraphQLNonNullableType._(this.type);
|
GraphQLNonNullableType._(this.innerType);
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isNullable => true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GraphQLType<Value, Serialized> nonNullable() {
|
GraphQLType<Value, Serialized> nonNullable() {
|
||||||
|
@ -80,16 +75,16 @@ class _GraphQLNonNullableType<Value, Serialized>
|
||||||
if (input == null)
|
if (input == null)
|
||||||
return new ValidationResult._failure(
|
return new ValidationResult._failure(
|
||||||
['Expected "$key" to be a non-null value.']);
|
['Expected "$key" to be a non-null value.']);
|
||||||
return type.validate(key, input);
|
return innerType.validate(key, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Value deserialize(Serialized serialized) {
|
Value deserialize(Serialized serialized) {
|
||||||
return type.deserialize(serialized);
|
return innerType.deserialize(serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Serialized serialize(Value value) {
|
Serialized serialize(Value value) {
|
||||||
return type.serialize(value);
|
return innerType.serialize(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
import 'package:graphql_schema/graphql_schema.dart';
|
||||||
|
|
||||||
final GraphQLObjectType pokemonType = objectType('Pokemon', [
|
final GraphQLObjectType pokemonType = objectType('Pokemon', [
|
||||||
field('species', type: graphQLString),
|
field('species', innerType: graphQLString),
|
||||||
field('catch_date', type: graphQLDate)
|
field('catch_date', innerType: graphQLDate)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final GraphQLObjectType trainerType =
|
final GraphQLObjectType trainerType =
|
||||||
objectType('Trainer', [field('name', type: graphQLString)]);
|
objectType('Trainer', [field('name', innerType: graphQLString)]);
|
||||||
|
|
||||||
final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', [
|
final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', [
|
||||||
field('trainer', type: trainerType),
|
field('trainer', innerType: trainerType),
|
||||||
field('pokemon_species', type: listType(pokemonType))
|
field('pokemon_species', innerType: listType(pokemonType))
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -44,7 +44,7 @@ class GraphQL {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GraphQLResult> executeRequest(
|
Future<Map<String, dynamic>> executeRequest(
|
||||||
GraphQLSchema schema, DocumentContext document, String operationName,
|
GraphQLSchema schema, DocumentContext document, String operationName,
|
||||||
{Map<String, dynamic> variableValues: const {}, initialValue}) async {
|
{Map<String, dynamic> variableValues: const {}, initialValue}) async {
|
||||||
var operation = getOperation(document, operationName);
|
var operation = getOperation(document, operationName);
|
||||||
|
@ -53,9 +53,11 @@ class GraphQL {
|
||||||
if (operation.isQuery)
|
if (operation.isQuery)
|
||||||
return await executeQuery(
|
return await executeQuery(
|
||||||
document, operation, schema, coercedVariableValues, initialValue);
|
document, operation, schema, coercedVariableValues, initialValue);
|
||||||
else
|
else {
|
||||||
return executeMutation(
|
throw new UnimplementedError('mutations');
|
||||||
document, operation, schema, coercedVariableValues, initialValue);
|
// return executeMutation(
|
||||||
|
// document, operation, schema, coercedVariableValues, initialValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OperationDefinitionContext getOperation(
|
OperationDefinitionContext getOperation(
|
||||||
|
@ -110,7 +112,7 @@ class GraphQL {
|
||||||
return coercedValues;
|
return coercedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GraphQLResult> executeQuery(
|
Future<Map<String, dynamic>> executeQuery(
|
||||||
DocumentContext document,
|
DocumentContext document,
|
||||||
OperationDefinitionContext query,
|
OperationDefinitionContext query,
|
||||||
GraphQLSchema schema,
|
GraphQLSchema schema,
|
||||||
|
@ -140,8 +142,8 @@ class GraphQL {
|
||||||
var fieldType =
|
var fieldType =
|
||||||
objectType.fields.firstWhere((f) => f.name == fieldName)?.type;
|
objectType.fields.firstWhere((f) => f.name == fieldName)?.type;
|
||||||
if (fieldType == null) continue;
|
if (fieldType == null) continue;
|
||||||
var responseValue = await executeField(
|
var responseValue = await executeField(document, fieldName, objectType,
|
||||||
objectType, objectValue, fields, fieldType, variableValues);
|
objectValue, fields, fieldType, variableValues);
|
||||||
resultMap[responseKey] = responseValue;
|
resultMap[responseKey] = responseValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +152,8 @@ class GraphQL {
|
||||||
}
|
}
|
||||||
|
|
||||||
executeField(
|
executeField(
|
||||||
|
DocumentContext document,
|
||||||
|
String fieldName,
|
||||||
GraphQLObjectType objectType,
|
GraphQLObjectType objectType,
|
||||||
objectValue,
|
objectValue,
|
||||||
List<SelectionContext> fields,
|
List<SelectionContext> fields,
|
||||||
|
@ -160,7 +164,8 @@ class GraphQL {
|
||||||
coerceArgumentValues(objectType, field, variableValues);
|
coerceArgumentValues(objectType, field, variableValues);
|
||||||
var resolvedValue = await resolveFieldValue(
|
var resolvedValue = await resolveFieldValue(
|
||||||
objectType, objectValue, field.field.fieldName.name, argumentValues);
|
objectType, objectValue, field.field.fieldName.name, argumentValues);
|
||||||
return completeValue(fieldType, fields, resolvedValue, variableValues);
|
return completeValue(
|
||||||
|
document, fieldName, fieldType, fields, resolvedValue, variableValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> coerceArgumentValues(GraphQLObjectType objectType,
|
Map<String, dynamic> coerceArgumentValues(GraphQLObjectType objectType,
|
||||||
|
@ -186,14 +191,14 @@ class GraphQL {
|
||||||
coercedValues[argumentName] = variableValue;
|
coercedValues[argumentName] = variableValue;
|
||||||
} else if (defaultValue != null || argumentDefinition.defaultsToNull) {
|
} else if (defaultValue != null || argumentDefinition.defaultsToNull) {
|
||||||
coercedValues[argumentName] = defaultValue;
|
coercedValues[argumentName] = defaultValue;
|
||||||
} else if (!argumentType.isNullable) {
|
} else if (argumentType is GraphQLNonNullableType) {
|
||||||
throw new GraphQLException(
|
throw new GraphQLException(
|
||||||
'Missing value for argument "$argumentName".');
|
'Missing value for argument "$argumentName".');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (defaultValue != null || argumentDefinition.defaultsToNull) {
|
if (defaultValue != null || argumentDefinition.defaultsToNull) {
|
||||||
coercedValues[argumentName] = defaultValue;
|
coercedValues[argumentName] = defaultValue;
|
||||||
} else if (!argumentType.isNullable) {
|
} else if (argumentType is GraphQLNonNullableType) {
|
||||||
throw new GraphQLException(
|
throw new GraphQLException(
|
||||||
'Missing value for argument "$argumentName".');
|
'Missing value for argument "$argumentName".');
|
||||||
}
|
}
|
||||||
|
@ -209,6 +214,64 @@ class GraphQL {
|
||||||
return await field.resolve(objectValue, argumentValues) as T;
|
return await field.resolve(objectValue, argumentValues) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future completeValue(
|
||||||
|
DocumentContext document,
|
||||||
|
String fieldName,
|
||||||
|
GraphQLType fieldType,
|
||||||
|
List<SelectionContext> fields,
|
||||||
|
result,
|
||||||
|
Map<String, dynamic> 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<String, List<SelectionContext>> collectFields(
|
Map<String, List<SelectionContext>> collectFields(
|
||||||
DocumentContext document,
|
DocumentContext document,
|
||||||
GraphQLObjectType objectType,
|
GraphQLObjectType objectType,
|
||||||
|
|
Loading…
Reference in a new issue