diff --git a/angel_graphql/example/main.dart b/angel_graphql/example/main.dart index 2982df7e..8db71e26 100644 --- a/angel_graphql/example/main.dart +++ b/angel_graphql/example/main.dart @@ -19,7 +19,7 @@ main() async { var todoService = app.use('api/todos', new MapService()) as Service; - var api = objectType( + var queryType = objectType( 'Query', description: 'A simple API that manages your to-do list.', fields: [ @@ -39,7 +39,21 @@ main() async { ], ); - var schema = graphQLSchema(query: api); + var mutationType = objectType( + 'Mutation', + description: 'Modify the to-do list.', + fields: [ + field( + 'create', + type: graphQLString, + ), + ], + ); + + var schema = graphQLSchema( + query: queryType, + mutation: mutationType, + ); app.all('/graphql', graphQLHttp(new GraphQL(schema))); app.get('/graphiql', graphiql()); diff --git a/angel_graphql/lib/src/graphql_http.dart b/angel_graphql/lib/src/graphql_http.dart index 0f972b97..aaaf620e 100644 --- a/angel_graphql/lib/src/graphql_http.dart +++ b/angel_graphql/lib/src/graphql_http.dart @@ -1,5 +1,4 @@ import 'dart:io'; - import 'package:angel_framework/angel_framework.dart'; import 'package:angel_validate/server.dart'; import 'package:dart2_constant/convert.dart'; @@ -13,30 +12,47 @@ final ContentType graphQlContentType = final Validator graphQlPostBody = new Validator({ 'query*': isNonEmptyString, 'operation_name': isNonEmptyString, - 'variables': predicate((v) => v == null || v is Map), + 'variables': predicate((v) => v == null || v is String || v is Map), }); RequestHandler graphQLHttp(GraphQL graphQl) { return (req, res) async { + executeMap(Map map) async { + var text = req.body['query'] as String; + var operationName = req.body['operation_name'] as String; + var variables = req.body['variables']; + + if (variables is String) { + variables = json.decode(variables as String); + } + + return { + 'data': await graphQl.parseAndExecute( + text, + sourceUrl: 'input', + operationName: operationName, + variableValues: foldToStringDynamic(variables as Map), + ), + }; + } + try { - if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) { - var text = utf8.decode(await req.lazyOriginalBuffer()); - return { - 'data': await graphQl.parseAndExecute(text, sourceUrl: 'input') - }; - } else if (req.headers.contentType?.mimeType == 'application/json') { - if (await validate(graphQlPostBody)(req, res)) { - var text = req.body['query'] as String; - var operationName = req.body['operation_name'] as String; - var variables = req.body['variables'] as Map; + if (req.method == 'GET') { + if (await validateQuery(graphQlPostBody)(req, res)) { + return await executeMap(req.query); + } + } else if (req.method == 'POST') { + if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) { + var text = utf8.decode(await req.lazyOriginalBuffer()); return { - 'data': await graphQl.parseAndExecute( - text, - sourceUrl: 'input', - operationName: operationName, - variableValues: foldToStringDynamic(variables), - ), + 'data': await graphQl.parseAndExecute(text, sourceUrl: 'input') }; + } else if (req.headers.contentType?.mimeType == 'application/json') { + if (await validate(graphQlPostBody)(req, res)) { + return await executeMap(req.body); + } + } else { + throw new AngelHttpException.badRequest(); } } else { throw new AngelHttpException.badRequest(); diff --git a/graphql_parser/lib/src/language/parser.dart b/graphql_parser/lib/src/language/parser.dart index 45f4e780..1462aa30 100644 --- a/graphql_parser/lib/src/language/parser.dart +++ b/graphql_parser/lib/src/language/parser.dart @@ -56,23 +56,17 @@ class Parser { else { if (next(TokenType.MUTATION) || next(TokenType.QUERY)) { var TYPE = current; - if (next(TokenType.NAME)) { - var NAME = current; - var variables = parseVariableDefinitions(); - var dirs = parseDirectives(); - var selectionSet = parseSelectionSet(); - if (selectionSet != null) - return new OperationDefinitionContext( - TYPE, NAME, variables, selectionSet) - ..directives.addAll(dirs); - else - throw new SyntaxError( - 'Expected selection set in fragment definition.', - NAME.span); - } else + Token NAME = next(TokenType.NAME) ? current : null; + var variables = parseVariableDefinitions(); + var dirs = parseDirectives(); + var selectionSet = parseSelectionSet(); + if (selectionSet != null) + return new OperationDefinitionContext( + TYPE, NAME, variables, selectionSet) + ..directives.addAll(dirs); + else throw new SyntaxError( - 'Expected name after operation type "${TYPE.text}" in operation definition.', - TYPE.span); + 'Expected selection set in fragment definition.', NAME.span); } else return null; } @@ -142,11 +136,8 @@ class Parser { ELLIPSIS, ON, typeCondition, selectionSet) ..directives.addAll(directives); } else - throw new SyntaxError( - 'Expected selection set in inline fragment.', - directives.isEmpty - ? typeCondition.span - : directives.last.span); + throw new SyntaxError('Expected selection set in inline fragment.', + directives.isEmpty ? typeCondition.span : directives.last.span); } else throw new SyntaxError( 'Expected type condition after "on" in inline fragment.', @@ -174,8 +165,7 @@ class Parser { return new SelectionSetContext(LBRACE, current) ..selections.addAll(selections); else - throw new SyntaxError( - 'Expected "}" after selection set.', + throw new SyntaxError('Expected "}" after selection set.', selections.isEmpty ? LBRACE.span : selections.last.span); } else return null; @@ -287,11 +277,9 @@ class Parser { if (next(TokenType.RBRACKET)) { return new ListTypeContext(LBRACKET, type, current); } else - throw new SyntaxError( - 'Expected "]" in list type.', type.span); + throw new SyntaxError('Expected "]" in list type.', type.span); } else - throw new SyntaxError( - 'Expected type after "[".', LBRACKET.span); + throw new SyntaxError('Expected type after "[".', LBRACKET.span); } else return null; } @@ -331,8 +319,7 @@ class Parser { return new DirectiveContext( ARROBA, NAME, null, LPAREN, current, arg, null); } else - throw new SyntaxError( - 'Expected \')\'', arg.valueOrVariable.span); + throw new SyntaxError('Expected \')\'', arg.valueOrVariable.span); } else throw new SyntaxError( 'Expected argument in directive.', LPAREN.span); @@ -340,8 +327,7 @@ class Parser { return new DirectiveContext( ARROBA, NAME, null, null, null, null, null); } else - throw new SyntaxError( - 'Expected name for directive.', ARROBA.span); + throw new SyntaxError('Expected name for directive.', ARROBA.span); } else return null; } @@ -363,8 +349,7 @@ class Parser { if (next(TokenType.RPAREN)) return out; else - throw new SyntaxError( - 'Expected ")" in argument list.', LPAREN.span); + throw new SyntaxError('Expected ")" in argument list.', LPAREN.span); } else return []; } @@ -420,8 +405,7 @@ class Parser { if (value != null) { return new DefaultValueContext(EQUALS, value); } else - throw new SyntaxError( - 'Expected value after "=" sign.', EQUALS.span); + throw new SyntaxError('Expected value after "=" sign.', EQUALS.span); } else return null; } @@ -474,8 +458,7 @@ class Parser { if (next(TokenType.RBRACKET)) { return new ArrayValueContext(LBRACKET, current)..values.addAll(values); } else - throw new SyntaxError( - 'Unterminated array literal.', LBRACKET.span); + throw new SyntaxError('Unterminated array literal.', LBRACKET.span); } else return null; }