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 '../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<SelectionContext> 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<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';
|
||||
|
||||
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!!!');
|
||||
|
|
|
@ -5,9 +5,10 @@ GraphQLObjectType objectType(String name,
|
|||
new GraphQLObjectType(name)..fields.addAll(fields ?? []);
|
||||
|
||||
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,
|
||||
GraphQLType<T, Serialized> type}) {
|
||||
GraphQLType<T, Serialized> innerType}) {
|
||||
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);
|
||||
ValidationResult<Serialized> validate(String key, Serialized input);
|
||||
GraphQLType<Value, Serialized> nonNullable();
|
||||
|
||||
bool get isNullable => false;
|
||||
}
|
||||
|
||||
/// Shorthand to create a [GraphQLListType].
|
||||
|
@ -17,8 +15,8 @@ GraphQLListType<Value, Serialized> listType<Value, Serialized>(
|
|||
class GraphQLListType<Value, Serialized>
|
||||
extends GraphQLType<List<Value>, List<Serialized>>
|
||||
with _NonNullableMixin<List<Value>, List<Serialized>> {
|
||||
final GraphQLType<Value, Serialized> type;
|
||||
GraphQLListType(this.type);
|
||||
final GraphQLType<Value, Serialized> innerType;
|
||||
GraphQLListType(this.innerType);
|
||||
|
||||
@override
|
||||
ValidationResult<List<Serialized>> validate(
|
||||
|
@ -32,7 +30,7 @@ class GraphQLListType<Value, Serialized>
|
|||
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<Value, Serialized>
|
|||
|
||||
@override
|
||||
List<Value> deserialize(List<Serialized> serialized) {
|
||||
return serialized.map<Value>(type.deserialize).toList();
|
||||
return serialized.map<Value>(innerType.deserialize).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
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> {
|
||||
GraphQLType<Value, Serialized> _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> {
|
||||
final GraphQLType<Value, Serialized> type;
|
||||
_GraphQLNonNullableType._(this.type);
|
||||
|
||||
@override
|
||||
bool get isNullable => true;
|
||||
final GraphQLType<Value, Serialized> innerType;
|
||||
GraphQLNonNullableType._(this.innerType);
|
||||
|
||||
@override
|
||||
GraphQLType<Value, Serialized> nonNullable() {
|
||||
|
@ -80,16 +75,16 @@ class _GraphQLNonNullableType<Value, Serialized>
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
]);
|
||||
|
|
|
@ -44,7 +44,7 @@ class GraphQL {
|
|||
}
|
||||
}
|
||||
|
||||
Future<GraphQLResult> executeRequest(
|
||||
Future<Map<String, dynamic>> executeRequest(
|
||||
GraphQLSchema schema, DocumentContext document, String operationName,
|
||||
{Map<String, dynamic> 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<GraphQLResult> executeQuery(
|
||||
Future<Map<String, dynamic>> 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<SelectionContext> 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<String, dynamic> 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<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(
|
||||
DocumentContext document,
|
||||
GraphQLObjectType objectType,
|
||||
|
|
Loading…
Reference in a new issue