Basic query actually works
This commit is contained in:
parent
10e740b2b1
commit
39fd284c67
15 changed files with 177 additions and 95 deletions
7
.idea/runConfigurations/tests_in_query_test_dart.xml
Normal file
7
.idea/runConfigurations/tests_in_query_test_dart.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="tests in query_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/graphql_server/test/query_test.dart" />
|
||||||
|
<option name="testRunnerOptions" value="-j4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -1,7 +1,8 @@
|
||||||
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'definition.dart';
|
import 'definition.dart';
|
||||||
import 'directive.dart';
|
import 'directive.dart';
|
||||||
import 'package:source_span/source_span.dart';
|
|
||||||
import 'selection_set.dart';
|
import 'selection_set.dart';
|
||||||
import 'variable_definitions.dart';
|
import 'variable_definitions.dart';
|
||||||
|
|
||||||
|
@ -12,7 +13,8 @@ class OperationDefinitionContext extends DefinitionContext {
|
||||||
final SelectionSetContext selectionSet;
|
final SelectionSetContext selectionSet;
|
||||||
|
|
||||||
bool get isMutation => TYPE?.text == 'mutation';
|
bool get isMutation => TYPE?.text == 'mutation';
|
||||||
bool get isQuery => TYPE?.text == 'query';
|
|
||||||
|
bool get isQuery => TYPE?.text == 'query' || TYPE == null;
|
||||||
|
|
||||||
String get name => NAME?.text;
|
String get name => NAME?.text;
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,8 @@ class StringValueContext extends ValueContext {
|
||||||
buf.writeCharCode(next);
|
buf.writeCharCode(next);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Unexpected "\\" in string literal.', span.start);
|
'Unexpected "\\" in string literal.', span);
|
||||||
} else {
|
} else {
|
||||||
buf.writeCharCode(ch);
|
buf.writeCharCode(ch);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@ final Map<Pattern, TokenType> _patterns = {
|
||||||
_name: TokenType.NAME
|
_name: TokenType.NAME
|
||||||
};
|
};
|
||||||
|
|
||||||
List<Token> scan(String text) {
|
List<Token> scan(String text, {sourceUrl}) {
|
||||||
List<Token> out = [];
|
List<Token> out = [];
|
||||||
var scanner = new SpanScanner(text);
|
var scanner = new SpanScanner(text, sourceUrl: sourceUrl);
|
||||||
|
|
||||||
while (!scanner.isDone) {
|
while (!scanner.isDone) {
|
||||||
List<Token> potential = [];
|
List<Token> potential = [];
|
||||||
|
@ -54,7 +54,7 @@ List<Token> scan(String text) {
|
||||||
if (potential.isEmpty) {
|
if (potential.isEmpty) {
|
||||||
var ch = new String.fromCharCode(scanner.readChar());
|
var ch = new String.fromCharCode(scanner.readChar());
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
"Unexpected token '$ch'.", scanner.state.line, scanner.state.column);
|
"Unexpected token '$ch'.", scanner.emptySpan);
|
||||||
} else {
|
} else {
|
||||||
// Choose longest token
|
// Choose longest token
|
||||||
potential.sort((a, b) => b.text.length.compareTo(a.text.length));
|
potential.sort((a, b) => b.text.length.compareTo(a.text.length));
|
||||||
|
|
|
@ -66,13 +66,13 @@ class Parser {
|
||||||
TYPE, NAME, variables, selectionSet)
|
TYPE, NAME, variables, selectionSet)
|
||||||
..directives.addAll(dirs);
|
..directives.addAll(dirs);
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected selection set in fragment definition.',
|
'Expected selection set in fragment definition.',
|
||||||
NAME.span.end);
|
NAME.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected name after operation type "${TYPE.text}" in operation definition.',
|
'Expected name after operation type "${TYPE.text}" in operation definition.',
|
||||||
TYPE.span.end);
|
TYPE.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -94,21 +94,21 @@ class Parser {
|
||||||
FRAGMENT, NAME, ON, typeCondition, selectionSet)
|
FRAGMENT, NAME, ON, typeCondition, selectionSet)
|
||||||
..directives.addAll(dirs);
|
..directives.addAll(dirs);
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected selection set in fragment definition.',
|
'Expected selection set in fragment definition.',
|
||||||
typeCondition.span.end);
|
typeCondition.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected type condition after "on" in fragment definition.',
|
'Expected type condition after "on" in fragment definition.',
|
||||||
ON.span.end);
|
ON.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected "on" after name "${NAME.text}" in fragment definition.',
|
'Expected "on" after name "${NAME.text}" in fragment definition.',
|
||||||
NAME.span.end);
|
NAME.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected name after "fragment" in fragment definition.',
|
'Expected name after "fragment" in fragment definition.',
|
||||||
FRAGMENT.span.end);
|
FRAGMENT.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -142,18 +142,18 @@ class Parser {
|
||||||
ELLIPSIS, ON, typeCondition, selectionSet)
|
ELLIPSIS, ON, typeCondition, selectionSet)
|
||||||
..directives.addAll(directives);
|
..directives.addAll(directives);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected selection set in inline fragment.',
|
'Expected selection set in inline fragment.',
|
||||||
directives.isEmpty
|
directives.isEmpty
|
||||||
? typeCondition.span.end
|
? typeCondition.span
|
||||||
: directives.last.span.end);
|
: directives.last.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected type condition after "on" in inline fragment.',
|
'Expected type condition after "on" in inline fragment.',
|
||||||
ON.span.end);
|
ON.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected "on" after "..." in inline fragment.', ELLIPSIS.span.end);
|
'Expected "on" after "..." in inline fragment.', ELLIPSIS.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -174,9 +174,9 @@ class Parser {
|
||||||
return new SelectionSetContext(LBRACE, current)
|
return new SelectionSetContext(LBRACE, current)
|
||||||
..selections.addAll(selections);
|
..selections.addAll(selections);
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected "}" after selection set.',
|
'Expected "}" after selection set.',
|
||||||
selections.isEmpty ? LBRACE.span.end : selections.last.span.end);
|
selections.isEmpty ? LBRACE.span : selections.last.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -216,8 +216,8 @@ class Parser {
|
||||||
return new FieldNameContext(
|
return new FieldNameContext(
|
||||||
null, new AliasContext(NAME1, COLON, current));
|
null, new AliasContext(NAME1, COLON, current));
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected name after colon in alias.', COLON.span.end);
|
'Expected name after colon in alias.', COLON.span);
|
||||||
} else
|
} else
|
||||||
return new FieldNameContext(NAME1);
|
return new FieldNameContext(NAME1);
|
||||||
} else
|
} else
|
||||||
|
@ -240,8 +240,8 @@ class Parser {
|
||||||
return new VariableDefinitionsContext(LPAREN, current)
|
return new VariableDefinitionsContext(LPAREN, current)
|
||||||
..variableDefinitions.addAll(defs);
|
..variableDefinitions.addAll(defs);
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected ")" after variable definitions.', LPAREN.span.end);
|
'Expected ")" after variable definitions.', LPAREN.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -257,11 +257,11 @@ class Parser {
|
||||||
return new VariableDefinitionContext(
|
return new VariableDefinitionContext(
|
||||||
variable, COLON, type, defaultValue);
|
variable, COLON, type, defaultValue);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected type in variable definition.', COLON.span.end);
|
'Expected type in variable definition.', COLON.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected ":" in variable definition.', variable.span.end);
|
'Expected ":" in variable definition.', variable.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -287,11 +287,11 @@ class Parser {
|
||||||
if (next(TokenType.RBRACKET)) {
|
if (next(TokenType.RBRACKET)) {
|
||||||
return new ListTypeContext(LBRACKET, type, current);
|
return new ListTypeContext(LBRACKET, type, current);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected "]" in list type.', type.span.end);
|
'Expected "]" in list type.', type.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected type after "[".', LBRACKET.span.end);
|
'Expected type after "[".', LBRACKET.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -320,9 +320,9 @@ class Parser {
|
||||||
return new DirectiveContext(
|
return new DirectiveContext(
|
||||||
ARROBA, NAME, COLON, null, null, null, val);
|
ARROBA, NAME, COLON, null, null, null, val);
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected value or variable in directive after colon.',
|
'Expected value or variable in directive after colon.',
|
||||||
COLON.span.end);
|
COLON.span);
|
||||||
} else if (next(TokenType.LPAREN)) {
|
} else if (next(TokenType.LPAREN)) {
|
||||||
var LPAREN = current;
|
var LPAREN = current;
|
||||||
var arg = parseArgument();
|
var arg = parseArgument();
|
||||||
|
@ -331,17 +331,17 @@ class Parser {
|
||||||
return new DirectiveContext(
|
return new DirectiveContext(
|
||||||
ARROBA, NAME, null, LPAREN, current, arg, null);
|
ARROBA, NAME, null, LPAREN, current, arg, null);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected \')\'', arg.valueOrVariable.span.end);
|
'Expected \')\'', arg.valueOrVariable.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected argument in directive.', LPAREN.span.end);
|
'Expected argument in directive.', LPAREN.span);
|
||||||
} else
|
} else
|
||||||
return new DirectiveContext(
|
return new DirectiveContext(
|
||||||
ARROBA, NAME, null, null, null, null, null);
|
ARROBA, NAME, null, null, null, null, null);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected name for directive.', ARROBA.span.end);
|
'Expected name for directive.', ARROBA.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -363,8 +363,8 @@ class Parser {
|
||||||
if (next(TokenType.RPAREN))
|
if (next(TokenType.RPAREN))
|
||||||
return out;
|
return out;
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected ")" in argument list.', LPAREN.span.end);
|
'Expected ")" in argument list.', LPAREN.span);
|
||||||
} else
|
} else
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -378,11 +378,11 @@ class Parser {
|
||||||
if (val != null)
|
if (val != null)
|
||||||
return new ArgumentContext(NAME, COLON, val);
|
return new ArgumentContext(NAME, COLON, val);
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected value or variable in argument.', COLON.span.end);
|
'Expected value or variable in argument.', COLON.span);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected colon after name in argument.', NAME.span.end);
|
'Expected colon after name in argument.', NAME.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -406,9 +406,9 @@ class Parser {
|
||||||
if (next(TokenType.NAME))
|
if (next(TokenType.NAME))
|
||||||
return new VariableContext(DOLLAR, current);
|
return new VariableContext(DOLLAR, current);
|
||||||
else
|
else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected name for variable; found a lone "\$" instead.',
|
'Expected name for variable; found a lone "\$" instead.',
|
||||||
DOLLAR.span.end);
|
DOLLAR.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -420,8 +420,8 @@ class Parser {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return new DefaultValueContext(EQUALS, value);
|
return new DefaultValueContext(EQUALS, value);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Expected value after "=" sign.', EQUALS.span.end);
|
'Expected value after "=" sign.', EQUALS.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -474,8 +474,8 @@ class Parser {
|
||||||
if (next(TokenType.RBRACKET)) {
|
if (next(TokenType.RBRACKET)) {
|
||||||
return new ArrayValueContext(LBRACKET, current)..values.addAll(values);
|
return new ArrayValueContext(LBRACKET, current)..values.addAll(values);
|
||||||
} else
|
} else
|
||||||
throw new SyntaxError.fromSourceLocation(
|
throw new SyntaxError(
|
||||||
'Unterminated array literal.', LBRACKET.span.end);
|
'Unterminated array literal.', LBRACKET.span);
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import 'token.dart';
|
|
||||||
|
|
||||||
class SyntaxError implements Exception {
|
class SyntaxError implements Exception {
|
||||||
final String message;
|
final String message;
|
||||||
final int line, column;
|
final FileSpan span;
|
||||||
final Token offendingToken;
|
|
||||||
|
|
||||||
SyntaxError(this.message, this.line, this.column, [this.offendingToken]);
|
SyntaxError(this.message, this.span);
|
||||||
|
|
||||||
factory SyntaxError.fromSourceLocation(
|
|
||||||
String message, SourceLocation location,
|
|
||||||
[Token offendingToken]) =>
|
|
||||||
new SyntaxError(message, location.line, location.column, offendingToken);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'Syntax error at line $line, column $column: $message';
|
String toString() => 'Syntax error at ${span.start.toolString}: $message\n${span.highlight()}';
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,14 @@ final GraphQLSchema todoSchema = new GraphQLSchema(
|
||||||
query: objectType('Todo', [
|
query: objectType('Todo', [
|
||||||
field(
|
field(
|
||||||
'text',
|
'text',
|
||||||
innerType: graphQLString.nonNullable(),
|
type: graphQLString.nonNullable(),
|
||||||
|
resolve: resolveToNull,
|
||||||
|
),
|
||||||
|
field(
|
||||||
|
'created_at',
|
||||||
|
type: graphQLDate,
|
||||||
|
resolve: resolveToNull,
|
||||||
),
|
),
|
||||||
field('created_at', innerType: graphQLDate),
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -14,7 +19,11 @@ main() {
|
||||||
// Validation
|
// Validation
|
||||||
var validation = todoSchema.query.validate(
|
var validation = todoSchema.query.validate(
|
||||||
'@root',
|
'@root',
|
||||||
{'foo': 'bar', 'text': null, 'created_at': 24,},
|
{
|
||||||
|
'foo': 'bar',
|
||||||
|
'text': null,
|
||||||
|
'created_at': 24,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (validation.successful) {
|
if (validation.successful) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ class GraphQLField<Value, Serialized> {
|
||||||
|
|
||||||
GraphQLField(this.name,
|
GraphQLField(this.name,
|
||||||
{Iterable<GraphQLFieldArgument> arguments: const <GraphQLFieldArgument>[],
|
{Iterable<GraphQLFieldArgument> arguments: const <GraphQLFieldArgument>[],
|
||||||
this.resolve,
|
@required this.resolve,
|
||||||
this.type}) {
|
this.type}) {
|
||||||
this.arguments.addAll(arguments ?? <GraphQLFieldArgument>[]);
|
this.arguments.addAll(arguments ?? <GraphQLFieldArgument>[]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,9 @@ 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,
|
||||||
{Iterable<GraphQLFieldArgument<T, Serialized>> arguments:
|
{Iterable<GraphQLFieldArgument<T, Serialized>> arguments: const [],
|
||||||
const <GraphQLFieldArgument<T, Serialized>>[],
|
|
||||||
GraphQLFieldResolver<T, Serialized> resolve,
|
GraphQLFieldResolver<T, Serialized> resolve,
|
||||||
GraphQLType<T, Serialized> innerType}) {
|
GraphQLType<T, Serialized> type}) {
|
||||||
return new GraphQLField(name,
|
return new GraphQLField(name,
|
||||||
arguments: arguments, resolve: resolve, type: innerType);
|
arguments: arguments, resolve: resolve, type: type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,3 +20,6 @@ class GraphQLSchema {
|
||||||
GraphQLSchema graphQLSchema(
|
GraphQLSchema graphQLSchema(
|
||||||
{@required GraphQLObjectType query, GraphQLObjectType mutation}) =>
|
{@required GraphQLObjectType query, GraphQLObjectType mutation}) =>
|
||||||
new GraphQLSchema(query: query, mutation: mutation);
|
new GraphQLSchema(query: query, mutation: mutation);
|
||||||
|
|
||||||
|
/// A default resolver that always returns `null`.
|
||||||
|
resolveToNull(_, __) => null;
|
||||||
|
|
|
@ -5,5 +5,7 @@ author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/thosakwe/graphql_schema
|
homepage: https://github.com/thosakwe/graphql_schema
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=1.8.0 <3.0.0"
|
sdk: ">=1.8.0 <3.0.0"
|
||||||
|
dependencies:
|
||||||
|
meta: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^0.12.0
|
test: ^0.12.0
|
|
@ -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', innerType: graphQLString),
|
field('species', type: graphQLString),
|
||||||
field('catch_date', innerType: graphQLDate)
|
field('catch_date', type: graphQLDate)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final GraphQLObjectType trainerType =
|
final GraphQLObjectType trainerType =
|
||||||
objectType('Trainer', [field('name', innerType: graphQLString)]);
|
objectType('Trainer', [field('name', type: graphQLString)]);
|
||||||
|
|
||||||
final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', [
|
final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', [
|
||||||
field('trainer', innerType: trainerType),
|
field('trainer', type: trainerType),
|
||||||
field('pokemon_species', innerType: listType(pokemonType))
|
field('pokemon_species', type: listType(pokemonType))
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -44,9 +44,25 @@ class GraphQL {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> parseAndExecute(String text,
|
||||||
|
{String operationName,
|
||||||
|
sourceUrl,
|
||||||
|
Map<String, dynamic> variableValues: const {},
|
||||||
|
initialValue}) {
|
||||||
|
var tokens = scan(text, sourceUrl: sourceUrl);
|
||||||
|
var parser = new Parser(tokens);
|
||||||
|
var document = parser.parseDocument();
|
||||||
|
return executeRequest(schema, document,
|
||||||
|
operationName: operationName,
|
||||||
|
initialValue: initialValue,
|
||||||
|
variableValues: variableValues);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> executeRequest(
|
Future<Map<String, dynamic>> executeRequest(
|
||||||
GraphQLSchema schema, DocumentContext document, String operationName,
|
GraphQLSchema schema, DocumentContext document,
|
||||||
{Map<String, dynamic> variableValues: const {}, initialValue}) async {
|
{String operationName,
|
||||||
|
Map<String, dynamic> variableValues: const {},
|
||||||
|
initialValue}) async {
|
||||||
var operation = getOperation(document, operationName);
|
var operation = getOperation(document, operationName);
|
||||||
var coercedVariableValues =
|
var coercedVariableValues =
|
||||||
coerceVariableValues(schema, operation, variableValues ?? {});
|
coerceVariableValues(schema, operation, variableValues ?? {});
|
||||||
|
@ -62,17 +78,20 @@ class GraphQL {
|
||||||
|
|
||||||
OperationDefinitionContext getOperation(
|
OperationDefinitionContext getOperation(
|
||||||
DocumentContext document, String operationName) {
|
DocumentContext document, String operationName) {
|
||||||
var ops = document.definitions.whereType<OperationDefinitionContext>();
|
var ops =
|
||||||
|
document.definitions.where((d) => d is OperationDefinitionContext);
|
||||||
|
|
||||||
if (operationName == null) {
|
if (operationName == null) {
|
||||||
return ops.length == 1
|
return ops.length == 1
|
||||||
? ops.first
|
? ops.first as OperationDefinitionContext
|
||||||
: throw new GraphQLException(
|
: throw new GraphQLException(
|
||||||
'Missing required operation "$operationName".');
|
'This document does not define any operations.');
|
||||||
} else {
|
} else {
|
||||||
return ops.firstWhere((d) => d.name == operationName,
|
return ops.firstWhere(
|
||||||
orElse: () => throw new GraphQLException(
|
(d) => (d as OperationDefinitionContext).name == operationName,
|
||||||
'Missing required operation "$operationName".'));
|
orElse: () => throw new GraphQLException(
|
||||||
|
'Missing required operation "$operationName".'))
|
||||||
|
as OperationDefinitionContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,8 +158,9 @@ class GraphQL {
|
||||||
|
|
||||||
for (var field in fields) {
|
for (var field in fields) {
|
||||||
var fieldName = field.field.fieldName.name;
|
var fieldName = field.field.fieldName.name;
|
||||||
var fieldType =
|
var fieldType = objectType.fields
|
||||||
objectType.fields.firstWhere((f) => f.name == fieldName)?.type;
|
.firstWhere((f) => f.name == fieldName, orElse: () => null)
|
||||||
|
?.type;
|
||||||
if (fieldType == null) continue;
|
if (fieldType == null) continue;
|
||||||
var responseValue = await executeField(document, fieldName, objectType,
|
var responseValue = await executeField(document, fieldName, objectType,
|
||||||
objectValue, fields, fieldType, variableValues);
|
objectValue, fields, fieldType, variableValues);
|
||||||
|
@ -211,7 +231,12 @@ class GraphQL {
|
||||||
Future<T> resolveFieldValue<T>(GraphQLObjectType objectType, T objectValue,
|
Future<T> resolveFieldValue<T>(GraphQLObjectType objectType, T objectValue,
|
||||||
String fieldName, Map<String, dynamic> argumentValues) async {
|
String fieldName, Map<String, dynamic> argumentValues) async {
|
||||||
var field = objectType.fields.firstWhere((f) => f.name == fieldName);
|
var field = objectType.fields.firstWhere((f) => f.name == fieldName);
|
||||||
return await field.resolve(objectValue, argumentValues) as T;
|
|
||||||
|
if (field.resolve == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return await field.resolve(objectValue, argumentValues) as T;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future completeValue(
|
Future completeValue(
|
||||||
|
@ -375,4 +400,7 @@ class GraphQL {
|
||||||
|
|
||||||
class GraphQLException extends FormatException {
|
class GraphQLException extends FormatException {
|
||||||
GraphQLException(String message) : super(message);
|
GraphQLException(String message) : super(message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GraphQL exception: $message';
|
||||||
}
|
}
|
|
@ -4,4 +4,5 @@ dependencies:
|
||||||
path: ../graphql_schema
|
path: ../graphql_schema
|
||||||
graphql_parser:
|
graphql_parser:
|
||||||
path: ../graphql_parser
|
path: ../graphql_parser
|
||||||
symbol_table: ^1.0.0
|
dev_dependencies:
|
||||||
|
test: ^0.12.0
|
38
graphql_server/test/query_test.dart
Normal file
38
graphql_server/test/query_test.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:graphql_schema/graphql_schema.dart';
|
||||||
|
import 'package:graphql_server/graphql_server.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('todo', () async {
|
||||||
|
var schema = graphQLSchema(
|
||||||
|
query: objectType('todo', [
|
||||||
|
field(
|
||||||
|
'text',
|
||||||
|
type: graphQLString,
|
||||||
|
resolve: (obj, args) => obj['text'],
|
||||||
|
),
|
||||||
|
field(
|
||||||
|
'completed',
|
||||||
|
type: graphQLBoolean,
|
||||||
|
resolve: (obj, args) => obj['completed'],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
var graphql = new GraphQL(schema);
|
||||||
|
var result = await graphql.parseAndExecute('{ text }', initialValue: {
|
||||||
|
'text': 'Clean your room!',
|
||||||
|
'completed': false,
|
||||||
|
});
|
||||||
|
|
||||||
|
print(result);
|
||||||
|
expect(result, {'text': 'Clean your room!'});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Todo {
|
||||||
|
final String text;
|
||||||
|
final bool completed;
|
||||||
|
|
||||||
|
Todo({this.text, this.completed});
|
||||||
|
}
|
Loading…
Reference in a new issue