GraphQL input objects
This commit is contained in:
parent
7e0eaa387d
commit
2256b2afdb
49 changed files with 1028 additions and 353 deletions
|
@ -1,5 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="server.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
|
||||
<option name="VMOptions" value="--observe" />
|
||||
<option name="filePath" value="$PROJECT_DIR$/example_star_wars/bin/server.dart" />
|
||||
<option name="workingDirectory" value="$PROJECT_DIR$/example_star_wars" />
|
||||
<method />
|
||||
|
|
|
@ -25,15 +25,15 @@ main() async {
|
|||
fields: [
|
||||
field(
|
||||
'todos',
|
||||
type: listType(convertDartType(Todo).nonNullable()),
|
||||
listType(convertDartType(Todo).nonNullable()),
|
||||
resolve: resolveViaServiceIndex(todoService),
|
||||
),
|
||||
field(
|
||||
'todo',
|
||||
type: convertDartType(Todo),
|
||||
convertDartType(Todo),
|
||||
resolve: resolveViaServiceRead(todoService),
|
||||
arguments: [
|
||||
new GraphQLFieldArgument('id', graphQLId.nonNullable()),
|
||||
inputs: [
|
||||
new GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -45,14 +45,14 @@ main() async {
|
|||
fields: [
|
||||
field(
|
||||
'create',
|
||||
type: graphQLString,
|
||||
graphQLString,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
var schema = graphQLSchema(
|
||||
query: queryType,
|
||||
mutation: mutationType,
|
||||
queryType: queryType,
|
||||
mutationType: mutationType,
|
||||
);
|
||||
|
||||
app.all('/graphql', graphQLHttp(new GraphQL(schema)));
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export 'src/graphiql.dart';
|
||||
export 'src/graphql_http.dart';
|
||||
export 'src/resolvers.dart';
|
||||
export 'src/resolvers.dart';
|
||||
|
|
|
@ -38,7 +38,7 @@ String renderGraphiql({String graphqlEndpoint: '/graphql'}) {
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_validate/server.dart';
|
||||
import 'package:dart2_constant/convert.dart';
|
||||
|
@ -81,7 +82,14 @@ RequestHandler graphQLHttp(GraphQL graphQL) {
|
|||
return new GraphQLException.fromSourceSpan(e.message, e.span);
|
||||
} on GraphQLException catch (e) {
|
||||
return e.toJson();
|
||||
} catch (e) {
|
||||
} catch (e, st) {
|
||||
if (req.app?.logger != null) {
|
||||
req.app.logger.severe(
|
||||
'An error occurred while processing GraphQL query at ${req.uri}.',
|
||||
e,
|
||||
st);
|
||||
}
|
||||
|
||||
return new GraphQLException.fromMessage(e.toString()).toJson();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,11 +3,10 @@ import 'package:graphql_schema/graphql_schema.dart';
|
|||
|
||||
/// A GraphQL resolver that `index`es an Angel service.
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to service, and the
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceIndex<Value, Serialized>(Service service,
|
||||
{String idField: 'id'}) {
|
||||
resolveViaServiceIndex<Value, Serialized>(Service service) {
|
||||
return (_, arguments) async {
|
||||
var params = {'query': arguments, 'provider': Providers.graphql};
|
||||
|
||||
|
@ -15,9 +14,24 @@ GraphQLFieldResolver<Value, Serialized>
|
|||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that calls `findOne` on an Angel service.
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceFindOne<Value, Serialized>(Service service) {
|
||||
return (_, arguments) async {
|
||||
var params = {'query': arguments, 'provider': Providers.graphql};
|
||||
return await service.findOne(params) as Value;
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `read`s a single value from an Angel service.
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to service, and the
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceRead<Value, Serialized>(Service service,
|
||||
|
@ -28,3 +42,59 @@ GraphQLFieldResolver<Value, Serialized>
|
|||
return await service.read(id, params) as Value;
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `modifies` a single value from an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `modify`
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceModify<Value, Serialized>(Service service,
|
||||
{String idField: 'id'}) {
|
||||
return (_, arguments) async {
|
||||
var params = {'provider': Providers.graphql};
|
||||
var id = arguments.remove(idField);
|
||||
return await service.modify(id, arguments['data'], params) as Value;
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `update`s a single value from an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `update`
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
///
|
||||
/// Keep in mind that `update` **overwrites** existing contents.
|
||||
/// To avoid this, use [resolveViaServiceModify] instead.
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceUpdate<Value, Serialized>(Service service,
|
||||
{String idField: 'id'}) {
|
||||
return (_, arguments) async {
|
||||
var params = {'provider': Providers.graphql};
|
||||
var id = arguments.remove(idField);
|
||||
return await service.update(id, arguments['data'], params) as Value;
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `remove`s a single value from an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceRemove<Value, Serialized>(Service service,
|
||||
{String idField: 'id'}) {
|
||||
return (_, arguments) async {
|
||||
var params = {'query': arguments, 'provider': Providers.graphql};
|
||||
var id = arguments.remove(idField);
|
||||
return await service.remove(id, params) as Value;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@ export 'character.dart';
|
|||
export 'droid.dart';
|
||||
export 'episode.dart';
|
||||
export 'human.dart';
|
||||
export 'starship.dart';
|
||||
export 'starship.dart';
|
||||
|
|
|
@ -32,4 +32,4 @@ AnsiCode chooseLogColor(Level level) {
|
|||
return cyan;
|
||||
else if (level == Level.FINER || level == Level.FINEST) return lightGray;
|
||||
return resetAll;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ Future configureServer(Angel app) async {
|
|||
var episodeType = convertDartType(Episode);
|
||||
var humanType = convertDartClass(Human);
|
||||
var starshipType = convertDartType(Starship);
|
||||
|
||||
// A Hero can be either a Droid or Human; create a union type that represents this.
|
||||
var heroType = new GraphQLUnionType('Hero', [droidType, humanType]);
|
||||
|
||||
// Create the query type.
|
||||
|
@ -35,48 +37,62 @@ Future configureServer(Angel app) async {
|
|||
fields: [
|
||||
field(
|
||||
'droids',
|
||||
type: listType(droidType.nonNullable()),
|
||||
listType(droidType.nonNullable()),
|
||||
description: 'All droids in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(droidService),
|
||||
),
|
||||
field(
|
||||
'humans',
|
||||
type: listType(humanType.nonNullable()),
|
||||
listType(humanType.nonNullable()),
|
||||
description: 'All humans in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(humansService),
|
||||
),
|
||||
field(
|
||||
'starships',
|
||||
type: listType(starshipType.nonNullable()),
|
||||
listType(starshipType.nonNullable()),
|
||||
description: 'All starships in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(starshipService),
|
||||
),
|
||||
field(
|
||||
'hero',
|
||||
type: heroType,
|
||||
arguments: [
|
||||
new GraphQLFieldArgument('ep', episodeType),
|
||||
heroType,
|
||||
description:
|
||||
'Finds a random hero within the known galaxy, whether a Droid or Human.',
|
||||
inputs: [
|
||||
new GraphQLFieldInput('ep', episodeType),
|
||||
],
|
||||
resolve: (_, args) async {
|
||||
var allHeroes = [];
|
||||
var allDroids = await droidService.index() as Iterable;
|
||||
var allHumans = await humansService.index() as Iterable;
|
||||
allHeroes..addAll(allDroids)..addAll(allHumans);
|
||||
resolve: randomHeroResolver(droidService, humansService, rnd),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Ignore the annoying cast here, hopefully Dart 2 fixes cases like this
|
||||
allHeroes = allHeroes
|
||||
.where((m) =>
|
||||
!args.containsKey('ep') ||
|
||||
(m['appears_in'].contains(args['ep']) as bool))
|
||||
.toList();
|
||||
// Convert our object types to input objects, so that they can be passed to
|
||||
// mutations.
|
||||
var humanChangesType = humanType.asInputObject('HumanChanges');
|
||||
|
||||
return allHeroes.isEmpty
|
||||
? null
|
||||
: allHeroes[rnd.nextInt(allHeroes.length)];
|
||||
},
|
||||
// Create the mutation type.
|
||||
var mutationType = objectType(
|
||||
'StarWarsMutation',
|
||||
fields: [
|
||||
// We'll use the `modify_human` mutation to modify a human in the database.
|
||||
field(
|
||||
'modify_human',
|
||||
humanType.nonNullable(),
|
||||
description: 'Modifies a human in the database.',
|
||||
inputs: [
|
||||
new GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||
new GraphQLFieldInput('data', humanChangesType.nonNullable()),
|
||||
],
|
||||
resolve: resolveViaServiceModify(humansService),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Finally, create the schema.
|
||||
var schema = graphQLSchema(query: queryType);
|
||||
var schema = graphQLSchema(
|
||||
queryType: queryType,
|
||||
mutationType: mutationType,
|
||||
);
|
||||
|
||||
// Next, create a GraphQL object, which will be passed to `graphQLHttp`, and
|
||||
// used to mount a spec-compliant GraphQL endpoint on the server.
|
||||
|
@ -110,7 +126,8 @@ Future configureServer(Angel app) async {
|
|||
'friends': [leia, lando],
|
||||
});
|
||||
|
||||
var luke = await humansService.create({
|
||||
// Luke, of course.
|
||||
await humansService.create({
|
||||
'name': 'Luke Skywalker',
|
||||
'appears_in': ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
'total_credits': 682,
|
||||
|
@ -118,5 +135,24 @@ Future configureServer(Angel app) async {
|
|||
});
|
||||
}
|
||||
|
||||
GraphQLFieldResolver randomHeroResolver(
|
||||
Service droidService, Service humansService, Random rnd) {
|
||||
return (_, args) async {
|
||||
var allHeroes = [];
|
||||
var allDroids = await droidService.index() as Iterable;
|
||||
var allHumans = await humansService.index() as Iterable;
|
||||
allHeroes..addAll(allDroids)..addAll(allHumans);
|
||||
|
||||
// Ignore the annoying cast here, hopefully Dart 2 fixes cases like this
|
||||
allHeroes = allHeroes
|
||||
.where((m) =>
|
||||
!args.containsKey('ep') ||
|
||||
(m['appears_in'].contains(args['ep']) as bool))
|
||||
.toList();
|
||||
|
||||
return allHeroes.isEmpty ? null : allHeroes[rnd.nextInt(allHeroes.length)];
|
||||
};
|
||||
}
|
||||
|
||||
Service mountService<T extends Model>(Angel app, String path) =>
|
||||
app.use(path, new TypedService(new MapService())) as Service;
|
||||
|
|
|
@ -47,4 +47,4 @@ class MapQuerier extends GraphQLVisitor {
|
|||
// Set output field...
|
||||
result[alias] = data[realName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ import 'package:source_span/source_span.dart';
|
|||
import '../token.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class ArrayValueContext extends ValueContext {
|
||||
class ListValueContext extends ValueContext {
|
||||
final Token LBRACKET, RBRACKET;
|
||||
final List<ValueContext> values = [];
|
||||
|
||||
ArrayValueContext(this.LBRACKET, this.RBRACKET);
|
||||
ListValueContext(this.LBRACKET, this.RBRACKET);
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
|
|
|
@ -14,6 +14,7 @@ export 'fragment_definition.dart';
|
|||
export 'fragment_spread.dart';
|
||||
export 'inline_fragment.dart';
|
||||
export 'list_type.dart';
|
||||
export 'misc_value.dart';
|
||||
export 'node.dart';
|
||||
export 'number_value.dart';
|
||||
export 'operation_definition.dart';
|
||||
|
|
|
@ -2,7 +2,7 @@ import '../token.dart';
|
|||
import 'package:source_span/source_span.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class BooleanValueContext extends ValueContext {
|
||||
class BooleanValueContext extends ValueContext<bool> {
|
||||
bool _valueCache;
|
||||
final Token BOOLEAN;
|
||||
|
||||
|
@ -13,7 +13,7 @@ class BooleanValueContext extends ValueContext {
|
|||
bool get booleanValue => _valueCache ??= BOOLEAN.text == 'true';
|
||||
|
||||
@override
|
||||
get value => booleanValue;
|
||||
bool get value => booleanValue;
|
||||
|
||||
@override
|
||||
FileSpan get span => BOOLEAN.span;
|
||||
|
|
71
graphql_parser/lib/src/language/ast/misc_value.dart
Normal file
71
graphql_parser/lib/src/language/ast/misc_value.dart
Normal file
|
@ -0,0 +1,71 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../token.dart';
|
||||
import 'node.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class NullValueContext extends ValueContext<Null> {
|
||||
final Token NULL;
|
||||
|
||||
NullValueContext(this.NULL);
|
||||
|
||||
@override
|
||||
FileSpan get span => NULL.span;
|
||||
|
||||
@override
|
||||
Null get value => null;
|
||||
}
|
||||
|
||||
class EnumValueContext extends ValueContext<String> {
|
||||
final Token NAME;
|
||||
|
||||
EnumValueContext(this.NAME);
|
||||
|
||||
@override
|
||||
FileSpan get span => NAME.span;
|
||||
|
||||
@override
|
||||
String get value => NAME.span.text;
|
||||
}
|
||||
|
||||
class ObjectValueContext extends ValueContext<Map<String, dynamic>> {
|
||||
final Token LBRACE;
|
||||
final List<ObjectFieldContext> fields;
|
||||
final Token RBRACE;
|
||||
|
||||
ObjectValueContext(this.LBRACE, this.fields, this.RBRACE);
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
var left = LBRACE.span;
|
||||
|
||||
for (var field in fields) {
|
||||
left = left.expand(field.span);
|
||||
}
|
||||
|
||||
return left.expand(RBRACE.span);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> get value {
|
||||
if (fields.isEmpty) {
|
||||
return <String, dynamic>{};
|
||||
} else {
|
||||
return fields.fold<Map<String, dynamic>>(<String, dynamic>{},
|
||||
(map, field) {
|
||||
return map..[field.NAME.text] = field.value.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectFieldContext extends Node {
|
||||
final Token NAME;
|
||||
final Token COLON;
|
||||
final ValueContext value;
|
||||
|
||||
ObjectFieldContext(this.NAME, this.COLON, this.value);
|
||||
|
||||
@override
|
||||
FileSpan get span => NAME.span.expand(COLON.span).expand(value.span);
|
||||
}
|
|
@ -3,7 +3,7 @@ import 'package:source_span/source_span.dart';
|
|||
import '../token.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class NumberValueContext extends ValueContext {
|
||||
class NumberValueContext extends ValueContext<num> {
|
||||
final Token NUMBER;
|
||||
|
||||
NumberValueContext(this.NUMBER);
|
||||
|
@ -21,7 +21,7 @@ class NumberValueContext extends ValueContext {
|
|||
}
|
||||
|
||||
@override
|
||||
get value => numberValue;
|
||||
num get value => numberValue;
|
||||
|
||||
@override
|
||||
FileSpan get span => NUMBER.span;
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
import 'package:charcode/charcode.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../syntax_error.dart';
|
||||
import '../token.dart';
|
||||
import 'value.dart';
|
||||
|
||||
class StringValueContext extends ValueContext {
|
||||
final Token STRING;
|
||||
final bool isBlockString;
|
||||
|
||||
StringValueContext(this.STRING);
|
||||
StringValueContext(this.STRING, {this.isBlockString: false});
|
||||
|
||||
@override
|
||||
FileSpan get span => STRING.span;
|
||||
|
||||
String get stringValue {
|
||||
var text = STRING.text.substring(1, STRING.text.length - 1);
|
||||
String text;
|
||||
|
||||
if (!isBlockString) {
|
||||
text = STRING.text.substring(1, STRING.text.length - 1);
|
||||
} else {
|
||||
text = STRING.text.substring(3, STRING.text.length - 3).trim();
|
||||
}
|
||||
|
||||
var codeUnits = text.codeUnits;
|
||||
var buf = new StringBuffer();
|
||||
|
||||
|
@ -55,8 +64,7 @@ class StringValueContext extends ValueContext {
|
|||
buf.writeCharCode(next);
|
||||
}
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
'Unexpected "\\" in string literal.', span);
|
||||
throw new SyntaxError('Unexpected "\\" in string literal.', span);
|
||||
} else {
|
||||
buf.writeCharCode(ch);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'node.dart';
|
||||
|
||||
abstract class ValueContext extends Node {
|
||||
get value;
|
||||
abstract class ValueContext<T> extends Node {
|
||||
T get value;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ final RegExp _boolean = new RegExp(r'true|false');
|
|||
final RegExp _number = new RegExp(r'-?[0-9]+(\.[0-9]+)?(E|e(\+|-)?[0-9]+)?');
|
||||
final RegExp _string = new RegExp(
|
||||
r'"((\\(["\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^"\\]))*"');
|
||||
final RegExp _blockString = new RegExp(r'"""(([^"])|(\\"""))*"""');
|
||||
final RegExp _name = new RegExp(r'[_A-Za-z][_0-9A-Za-z]*');
|
||||
|
||||
final Map<Pattern, TokenType> _patterns = {
|
||||
|
@ -29,9 +30,11 @@ final Map<Pattern, TokenType> _patterns = {
|
|||
'mutation': TokenType.MUTATION,
|
||||
'on': TokenType.ON,
|
||||
'query': TokenType.QUERY,
|
||||
'null': TokenType.NULL,
|
||||
_boolean: TokenType.BOOLEAN,
|
||||
_number: TokenType.NUMBER,
|
||||
_string: TokenType.STRING,
|
||||
_blockString: TokenType.BLOCK_STRING,
|
||||
_name: TokenType.NAME
|
||||
};
|
||||
|
||||
|
@ -53,8 +56,7 @@ List<Token> scan(String text, {sourceUrl}) {
|
|||
|
||||
if (potential.isEmpty) {
|
||||
var ch = new String.fromCharCode(scanner.readChar());
|
||||
throw new SyntaxError(
|
||||
"Unexpected token '$ch'.", scanner.emptySpan);
|
||||
throw new SyntaxError("Unexpected token '$ch'.", scanner.emptySpan);
|
||||
} else {
|
||||
// Choose longest token
|
||||
potential.sort((a, b) => b.text.length.compareTo(a.text.length));
|
||||
|
|
|
@ -10,6 +10,7 @@ class Parser {
|
|||
int _index = -1;
|
||||
|
||||
final List<Token> tokens;
|
||||
final List<SyntaxError> errors = <SyntaxError>[];
|
||||
|
||||
Parser(this.tokens);
|
||||
|
||||
|
@ -34,6 +35,12 @@ class Parser {
|
|||
|
||||
Token maybe(TokenType type) => next(type) ? current : null;
|
||||
|
||||
void eatCommas() {
|
||||
while (next(TokenType.COMMA)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DocumentContext parseDocument() {
|
||||
List<DefinitionContext> defs = [];
|
||||
DefinitionContext def = parseDefinition();
|
||||
|
@ -64,10 +71,12 @@ class Parser {
|
|||
return new OperationDefinitionContext(
|
||||
TYPE, NAME, variables, selectionSet)
|
||||
..directives.addAll(dirs);
|
||||
else
|
||||
throw new SyntaxError(
|
||||
'Expected selection set in fragment definition.',
|
||||
NAME?.span ?? TYPE.span);
|
||||
else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing selection set in fragment definition.',
|
||||
NAME?.span ?? TYPE.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -88,22 +97,30 @@ class Parser {
|
|||
return new FragmentDefinitionContext(
|
||||
FRAGMENT, NAME, ON, typeCondition, selectionSet)
|
||||
..directives.addAll(dirs);
|
||||
else
|
||||
throw new SyntaxError(
|
||||
else {
|
||||
errors.add(new SyntaxError(
|
||||
'Expected selection set in fragment definition.',
|
||||
typeCondition.span);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
typeCondition.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Expected type condition after "on" in fragment definition.',
|
||||
ON.span);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
ON.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Expected "on" after name "${NAME.text}" in fragment definition.',
|
||||
NAME.span);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
NAME.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Expected name after "fragment" in fragment definition.',
|
||||
FRAGMENT.span);
|
||||
FRAGMENT.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -136,16 +153,25 @@ class Parser {
|
|||
return new InlineFragmentContext(
|
||||
ELLIPSIS, ON, typeCondition, selectionSet)
|
||||
..directives.addAll(directives);
|
||||
} else
|
||||
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.',
|
||||
ON.span);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
'Expected "on" after "..." in inline fragment.', ELLIPSIS.span);
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing selection set in inline fragment.',
|
||||
directives.isEmpty
|
||||
? typeCondition.span
|
||||
: directives.last.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing type condition after "on" in inline fragment.',
|
||||
ON.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing "on" after "..." in inline fragment.', ELLIPSIS.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -158,16 +184,19 @@ class Parser {
|
|||
|
||||
while (selection != null) {
|
||||
selections.add(selection);
|
||||
next(TokenType.COMMA);
|
||||
eatCommas();
|
||||
selection = parseSelection();
|
||||
}
|
||||
|
||||
eatCommas();
|
||||
if (next(TokenType.RBRACE))
|
||||
return new SelectionSetContext(LBRACE, current)
|
||||
..selections.addAll(selections);
|
||||
else
|
||||
throw new SyntaxError('Expected "}" after selection set.',
|
||||
selections.isEmpty ? LBRACE.span : selections.last.span);
|
||||
else {
|
||||
errors.add(new SyntaxError('Missing "}" after selection set.',
|
||||
selections.isEmpty ? LBRACE.span : selections.last.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -192,7 +221,7 @@ class Parser {
|
|||
var directives = parseDirectives();
|
||||
var selectionSet = parseSelectionSet();
|
||||
return new FieldContext(fieldName, selectionSet)
|
||||
..arguments.addAll(args)
|
||||
..arguments.addAll(args ?? <ArgumentContext>[])
|
||||
..directives.addAll(directives);
|
||||
} else
|
||||
return null;
|
||||
|
@ -206,9 +235,11 @@ class Parser {
|
|||
if (next(TokenType.NAME))
|
||||
return new FieldNameContext(
|
||||
null, new AliasContext(NAME1, COLON, current));
|
||||
else
|
||||
throw new SyntaxError(
|
||||
'Expected name after colon in alias.', COLON.span);
|
||||
else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing name after colon in alias.', COLON.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return new FieldNameContext(NAME1);
|
||||
} else
|
||||
|
@ -223,16 +254,18 @@ class Parser {
|
|||
|
||||
while (def != null) {
|
||||
defs.add(def);
|
||||
maybe(TokenType.COMMA);
|
||||
eatCommas();
|
||||
def = parseVariableDefinition();
|
||||
}
|
||||
|
||||
if (next(TokenType.RPAREN))
|
||||
return new VariableDefinitionsContext(LPAREN, current)
|
||||
..variableDefinitions.addAll(defs);
|
||||
else
|
||||
throw new SyntaxError(
|
||||
'Expected ")" after variable definitions.', LPAREN.span);
|
||||
else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing ")" after variable definitions.', LPAREN.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -247,12 +280,16 @@ class Parser {
|
|||
var defaultValue = parseDefaultValue();
|
||||
return new VariableDefinitionContext(
|
||||
variable, COLON, type, defaultValue);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
'Expected type in variable definition.', COLON.span);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
'Expected ":" in variable definition.', variable.span);
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing type in variable definition.', COLON.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing ":" in variable definition.', variable.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -277,10 +314,14 @@ class Parser {
|
|||
if (type != null) {
|
||||
if (next(TokenType.RBRACKET)) {
|
||||
return new ListTypeContext(LBRACKET, type, current);
|
||||
} else
|
||||
throw new SyntaxError('Expected "]" in list type.', type.span);
|
||||
} else
|
||||
throw new SyntaxError('Expected type after "[".', LBRACKET.span);
|
||||
} else {
|
||||
errors.add(new SyntaxError('Missing "]" in list type.', type.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError('Missing type after "[".', LBRACKET.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -308,10 +349,12 @@ class Parser {
|
|||
if (val != null)
|
||||
return new DirectiveContext(
|
||||
ARROBA, NAME, COLON, null, null, null, val);
|
||||
else
|
||||
throw new SyntaxError(
|
||||
'Expected value or variable in directive after colon.',
|
||||
COLON.span);
|
||||
else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing value or variable in directive after colon.',
|
||||
COLON.span));
|
||||
return null;
|
||||
}
|
||||
} else if (next(TokenType.LPAREN)) {
|
||||
var LPAREN = current;
|
||||
var arg = parseArgument();
|
||||
|
@ -319,16 +362,23 @@ class Parser {
|
|||
if (next(TokenType.RPAREN)) {
|
||||
return new DirectiveContext(
|
||||
ARROBA, NAME, null, LPAREN, current, arg, null);
|
||||
} else
|
||||
throw new SyntaxError('Expected \')\'', arg.valueOrVariable.span);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
'Expected argument in directive.', LPAREN.span);
|
||||
} else {
|
||||
errors.add(
|
||||
new SyntaxError('Missing \')\'', arg.valueOrVariable.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(
|
||||
new SyntaxError('Missing argument in directive.', LPAREN.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return new DirectiveContext(
|
||||
ARROBA, NAME, null, null, null, null, null);
|
||||
} else
|
||||
throw new SyntaxError('Expected name for directive.', ARROBA.span);
|
||||
} else {
|
||||
errors.add(new SyntaxError('Missing name for directive.', ARROBA.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -341,16 +391,17 @@ class Parser {
|
|||
|
||||
while (arg != null) {
|
||||
out.add(arg);
|
||||
if (next(TokenType.COMMA))
|
||||
arg = parseArgument();
|
||||
else
|
||||
break;
|
||||
eatCommas();
|
||||
arg = parseArgument();
|
||||
}
|
||||
|
||||
if (next(TokenType.RPAREN))
|
||||
return out;
|
||||
else
|
||||
throw new SyntaxError('Expected ")" in argument list.', LPAREN.span);
|
||||
else {
|
||||
errors
|
||||
.add(new SyntaxError('Missing ")" in argument list.', LPAREN.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return [];
|
||||
}
|
||||
|
@ -363,12 +414,16 @@ class Parser {
|
|||
var val = parseValueOrVariable();
|
||||
if (val != null)
|
||||
return new ArgumentContext(NAME, COLON, val);
|
||||
else
|
||||
throw new SyntaxError(
|
||||
'Expected value or variable in argument.', COLON.span);
|
||||
} else
|
||||
throw new SyntaxError(
|
||||
'Expected colon after name in argument.', NAME.span);
|
||||
else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing value or variable in argument.', COLON.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing colon after name in argument.', NAME.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -391,10 +446,12 @@ class Parser {
|
|||
var DOLLAR = current;
|
||||
if (next(TokenType.NAME))
|
||||
return new VariableContext(DOLLAR, current);
|
||||
else
|
||||
throw new SyntaxError(
|
||||
'Expected name for variable; found a lone "\$" instead.',
|
||||
DOLLAR.span);
|
||||
else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing name for variable; found a lone "\$" instead.',
|
||||
DOLLAR.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -405,8 +462,11 @@ class Parser {
|
|||
var value = parseValue();
|
||||
if (value != null) {
|
||||
return new DefaultValueContext(EQUALS, value);
|
||||
} else
|
||||
throw new SyntaxError('Expected value after "=" sign.', EQUALS.span);
|
||||
} else {
|
||||
errors
|
||||
.add(new SyntaxError('Missing value after "=" sign.', EQUALS.span));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
@ -427,14 +487,20 @@ class Parser {
|
|||
}
|
||||
|
||||
ValueContext parseValue() {
|
||||
return parseStringValue() ??
|
||||
parseNumberValue() ??
|
||||
return (parseNumberValue() ??
|
||||
parseStringValue() ??
|
||||
parseBooleanValue() ??
|
||||
parseArrayValue();
|
||||
parseNullValue() ??
|
||||
parseEnumValue() ??
|
||||
parseListValue() ??
|
||||
parseObjectValue()) as ValueContext;
|
||||
}
|
||||
|
||||
StringValueContext parseStringValue() =>
|
||||
next(TokenType.STRING) ? new StringValueContext(current) : null;
|
||||
StringValueContext parseStringValue() => next(TokenType.STRING)
|
||||
? new StringValueContext(current)
|
||||
: (next(TokenType.BLOCK_STRING)
|
||||
? new StringValueContext(current, isBlockString: true)
|
||||
: null);
|
||||
|
||||
NumberValueContext parseNumberValue() =>
|
||||
next(TokenType.NUMBER) ? new NumberValueContext(current) : null;
|
||||
|
@ -442,25 +508,84 @@ class Parser {
|
|||
BooleanValueContext parseBooleanValue() =>
|
||||
next(TokenType.BOOLEAN) ? new BooleanValueContext(current) : null;
|
||||
|
||||
ArrayValueContext parseArrayValue() {
|
||||
EnumValueContext parseEnumValue() =>
|
||||
next(TokenType.NAME) ? new EnumValueContext(current) : null;
|
||||
|
||||
NullValueContext parseNullValue() =>
|
||||
next(TokenType.NULL) ? new NullValueContext(current) : null;
|
||||
|
||||
ListValueContext parseListValue() {
|
||||
if (next(TokenType.LBRACKET)) {
|
||||
var LBRACKET = current;
|
||||
var lastSpan = LBRACKET.span;
|
||||
List<ValueContext> values = [];
|
||||
ValueContext value = parseValue();
|
||||
|
||||
while (value != null) {
|
||||
lastSpan = value.span;
|
||||
values.add(value);
|
||||
if (next(TokenType.COMMA)) {
|
||||
value = parseValue();
|
||||
} else
|
||||
break;
|
||||
eatCommas();
|
||||
value = parseValue();
|
||||
}
|
||||
|
||||
eatCommas();
|
||||
if (next(TokenType.RBRACKET)) {
|
||||
return new ArrayValueContext(LBRACKET, current)..values.addAll(values);
|
||||
} else
|
||||
throw new SyntaxError('Unterminated array literal.', LBRACKET.span);
|
||||
return new ListValueContext(LBRACKET, current)..values.addAll(values);
|
||||
} else {
|
||||
errors.add(new SyntaxError('Unterminated list literal.', lastSpan));
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
ObjectValueContext parseObjectValue() {
|
||||
if (next(TokenType.LBRACE)) {
|
||||
var LBRACE = current;
|
||||
var lastSpan = LBRACE.span;
|
||||
var fields = <ObjectFieldContext>[];
|
||||
var field = parseObjectField();
|
||||
|
||||
while (field != null) {
|
||||
lastSpan = field.span;
|
||||
eatCommas();
|
||||
field = parseObjectField();
|
||||
}
|
||||
|
||||
eatCommas();
|
||||
|
||||
if (next(TokenType.RBRACE)) {
|
||||
return new ObjectValueContext(LBRACE, fields, current);
|
||||
} else {
|
||||
errors.add(new SyntaxError('Unterminated object literal.', lastSpan));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectFieldContext parseObjectField() {
|
||||
if (next(TokenType.NAME)) {
|
||||
var NAME = current;
|
||||
|
||||
if (next(TokenType.COLON)) {
|
||||
var COLON = current;
|
||||
var value = parseValue();
|
||||
|
||||
if (value != null) {
|
||||
return new ObjectFieldContext(NAME, COLON, value);
|
||||
} else {
|
||||
errors.add(new SyntaxError('Missing value after ":".', COLON.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
errors.add(new SyntaxError(
|
||||
'Missing ":" after name "${NAME.span.text}".', NAME.span));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,5 +7,6 @@ class SyntaxError implements Exception {
|
|||
SyntaxError(this.message, this.span);
|
||||
|
||||
@override
|
||||
String toString() => 'Syntax error at ${span.start.toolString}: $message\n${span.highlight()}';
|
||||
String toString() =>
|
||||
'Syntax error at ${span.start.toolString}: $message\n${span.highlight()}';
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ enum TokenType {
|
|||
BOOLEAN,
|
||||
NUMBER,
|
||||
STRING,
|
||||
BLOCK_STRING,
|
||||
|
||||
NAME
|
||||
NAME,
|
||||
NULL
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ class GraphQLVisitor {
|
|||
visitNumberValue(ctx);
|
||||
else if (ctx is BooleanValueContext)
|
||||
visitBooleanValue(ctx);
|
||||
else if (ctx is ArrayValueContext) visitArrayValue(ctx);
|
||||
else if (ctx is ListValueContext) visitArrayValue(ctx);
|
||||
}
|
||||
|
||||
visitStringValue(StringValueContext ctx) {}
|
||||
|
@ -104,7 +104,7 @@ class GraphQLVisitor {
|
|||
|
||||
visitNumberValue(NumberValueContext ctx) {}
|
||||
|
||||
visitArrayValue(ArrayValueContext ctx) {
|
||||
visitArrayValue(ListValueContext ctx) {
|
||||
ctx.values.forEach(visitValue);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@ main() {
|
|||
}
|
||||
|
||||
ArgumentContext parseArgument(String text) => parse(text).parseArgument();
|
||||
List<ArgumentContext> parseArgumentList(String text) => parse(text).parseArguments();
|
||||
List<ArgumentContext> parseArgumentList(String text) =>
|
||||
parse(text).parseArguments();
|
||||
|
||||
Matcher isArgument(String name, value) => new _IsArgument(name, value);
|
||||
|
||||
|
@ -60,8 +61,9 @@ class _IsArgumentList extends Matcher {
|
|||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
var args =
|
||||
item is List<ArgumentContext> ? item : parse(item.toString()).parseArguments();
|
||||
var args = item is List<ArgumentContext>
|
||||
? item
|
||||
: parse(item.toString()).parseArguments();
|
||||
|
||||
if (args.length != arguments.length) return false;
|
||||
|
||||
|
|
|
@ -58,7 +58,8 @@ class _IsDirective extends Matcher {
|
|||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
var directive = item is DirectiveContext ? item : parseDirective(item.toString());
|
||||
var directive =
|
||||
item is DirectiveContext ? item : parseDirective(item.toString());
|
||||
if (directive == null) return false;
|
||||
if (valueOrVariable != null) {
|
||||
if (directive.valueOrVariable == null)
|
||||
|
@ -90,8 +91,9 @@ class _IsDirectiveList extends Matcher {
|
|||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
var args =
|
||||
item is List<DirectiveContext> ? item : parse(item.toString()).parseDirectives();
|
||||
var args = item is List<DirectiveContext>
|
||||
? item
|
||||
: parse(item.toString()).parseDirectives();
|
||||
|
||||
if (args.length != directives.length) return false;
|
||||
|
||||
|
|
|
@ -116,15 +116,18 @@ class _IsFieldName extends Matcher {
|
|||
@override
|
||||
Description describe(Description description) {
|
||||
if (realName != null)
|
||||
return description.add('is field with name "$name" and alias "$realName"');
|
||||
return description
|
||||
.add('is field with name "$name" and alias "$realName"');
|
||||
return description.add('is field with name "$name"');
|
||||
}
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
var fieldName = item is FieldNameContext ? item : parseFieldName(item.toString());
|
||||
var fieldName =
|
||||
item is FieldNameContext ? item : parseFieldName(item.toString());
|
||||
if (realName != null)
|
||||
return fieldName.alias?.alias == name && fieldName.alias?.name == realName;
|
||||
return fieldName.alias?.alias == name &&
|
||||
fieldName.alias?.name == realName;
|
||||
else
|
||||
return fieldName.name == name;
|
||||
}
|
||||
|
|
|
@ -43,8 +43,9 @@ class _IsFragmentSpread extends Matcher {
|
|||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
var spread =
|
||||
item is FragmentSpreadContext ? item : parseFragmentSpread(item.toString());
|
||||
var spread = item is FragmentSpreadContext
|
||||
? item
|
||||
: parseFragmentSpread(item.toString());
|
||||
if (spread == null) return false;
|
||||
if (spread.name != name) return false;
|
||||
if (directives != null)
|
||||
|
|
|
@ -58,8 +58,9 @@ class _IsInlineFragment extends Matcher {
|
|||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
var fragment =
|
||||
item is InlineFragmentContext ? item : parseInlineFragment(item.toString());
|
||||
var fragment = item is InlineFragmentContext
|
||||
? item
|
||||
: parseInlineFragment(item.toString());
|
||||
if (fragment == null) return false;
|
||||
if (fragment.typeCondition.typeName.name != name) return false;
|
||||
if (directives != null &&
|
||||
|
|
|
@ -66,7 +66,8 @@ class _IsSelectionSet extends Matcher {
|
|||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
var set = item is SelectionSetContext ? item : parseSelectionSet(item.toString());
|
||||
var set =
|
||||
item is SelectionSetContext ? item : parseSelectionSet(item.toString());
|
||||
if (set == null) return false;
|
||||
if (set.selections.length != selections.length) return false;
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ It's easy to define a schema with the
|
|||
```dart
|
||||
final GraphQLSchema todoSchema = new GraphQLSchema(
|
||||
query: objectType('Todo', [
|
||||
field('text', type: graphQLString.nonNullable()),
|
||||
field('created_at', type: graphQLDate)
|
||||
field('text', graphQLString.nonNullable()),
|
||||
field('created_at', graphQLDate)
|
||||
]));
|
||||
```
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
|
||||
final GraphQLSchema todoSchema = new GraphQLSchema(
|
||||
query: objectType('Todo', fields: [
|
||||
queryType: objectType('Todo', fields: [
|
||||
field(
|
||||
'text',
|
||||
type: graphQLString.nonNullable(),
|
||||
graphQLString.nonNullable(),
|
||||
resolve: resolveToNull,
|
||||
),
|
||||
field(
|
||||
'created_at',
|
||||
type: graphQLDate,
|
||||
graphQLDate,
|
||||
resolve: resolveToNull,
|
||||
),
|
||||
]),
|
||||
|
@ -17,7 +17,7 @@ final GraphQLSchema todoSchema = new GraphQLSchema(
|
|||
|
||||
main() {
|
||||
// Validation
|
||||
var validation = todoSchema.query.validate(
|
||||
var validation = todoSchema.queryType.validate(
|
||||
'@root',
|
||||
{
|
||||
'foo': 'bar',
|
||||
|
@ -34,7 +34,7 @@ main() {
|
|||
}
|
||||
|
||||
// Serialization
|
||||
print(todoSchema.query.serialize({
|
||||
print(todoSchema.queryType.serialize({
|
||||
'text': 'Clean your room!',
|
||||
'created_at': new DateTime.now().subtract(new Duration(days: 10))
|
||||
}));
|
||||
|
|
|
@ -1 +1 @@
|
|||
export 'src/schema.dart';
|
||||
export 'src/schema.dart';
|
||||
|
|
|
@ -1,14 +1,42 @@
|
|||
part of graphql_schema.src.schema;
|
||||
|
||||
class GraphQLFieldArgument<Value, Serialized> {
|
||||
class GraphQLFieldInput<Value, Serialized> {
|
||||
final String name;
|
||||
final GraphQLType<Value, Serialized> type;
|
||||
final Value defaultValue;
|
||||
final String description;
|
||||
|
||||
/// If [defaultValue] is `null`, and `null` is a valid value for this argument, set this to `true`.
|
||||
/// If [defaultValue] is `null`, and `null` is a valid value for this parameter, set this to `true`.
|
||||
final bool defaultsToNull;
|
||||
|
||||
GraphQLFieldArgument(this.name, this.type,
|
||||
{this.defaultValue, this.defaultsToNull: false, this.description});
|
||||
static bool _isInputTypeOrScalar(GraphQLType type) {
|
||||
if (type is GraphQLInputObjectType) {
|
||||
return true;
|
||||
} else if (type is GraphQLUnionType) {
|
||||
return type.possibleTypes.every(_isInputTypeOrScalar);
|
||||
} else if (type is GraphQLObjectType) {
|
||||
return false;
|
||||
} else if (type is GraphQLNonNullableType) {
|
||||
return _isInputTypeOrScalar(type.ofType);
|
||||
} else if (type is GraphQLListType) {
|
||||
return _isInputTypeOrScalar(type.ofType);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GraphQLFieldInput(this.name, this.type,
|
||||
{this.defaultValue, this.defaultsToNull: false, this.description}) {
|
||||
assert(_isInputTypeOrScalar(type),
|
||||
'All inputs to a GraphQL field must either be scalar types, or explicitly marked as INPUT_OBJECT. Call `GraphQLObjectType.asInputObject()` on any object types you are passing as inputs to a field.');
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is GraphQLFieldInput &&
|
||||
other.name == name &&
|
||||
other.type == type &&
|
||||
other.defaultValue == other.defaultValue &&
|
||||
other.defaultsToNull == defaultsToNull &&
|
||||
other.description == description;
|
||||
}
|
||||
|
|
|
@ -36,12 +36,27 @@ class GraphQLEnumType<Value> extends GraphQLScalarType<Value, String>
|
|||
@override
|
||||
ValidationResult<String> validate(String key, String input) {
|
||||
if (!values.any((v) => v.name == input)) {
|
||||
if (input == null) {
|
||||
return new ValidationResult<String>._failure(
|
||||
['The enum "$name" does not accept null values.']);
|
||||
}
|
||||
|
||||
return new ValidationResult<String>._failure(
|
||||
['"$input" is not a valid value for the enum "$name".']);
|
||||
}
|
||||
|
||||
return new ValidationResult<String>._ok(input);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is GraphQLEnumType &&
|
||||
other.name == name &&
|
||||
other.description == description &&
|
||||
const ListEquality<GraphQLEnumValue>().equals(other.values, values);
|
||||
|
||||
@override
|
||||
GraphQLType<Value, String> coerceToInputObject() => this;
|
||||
}
|
||||
|
||||
class GraphQLEnumValue<Value> {
|
||||
|
@ -50,10 +65,16 @@ class GraphQLEnumValue<Value> {
|
|||
final String description;
|
||||
final String deprecationReason;
|
||||
|
||||
GraphQLEnumValue(this.name, this.value, {this.description, this.deprecationReason});
|
||||
GraphQLEnumValue(this.name, this.value,
|
||||
{this.description, this.deprecationReason});
|
||||
|
||||
bool get isDeprecated => deprecationReason != null;
|
||||
|
||||
@override
|
||||
bool operator ==(other) => other is GraphQLEnumValue && other.name == name;
|
||||
bool operator ==(other) =>
|
||||
other is GraphQLEnumValue &&
|
||||
other.name == name &&
|
||||
other.value == value &&
|
||||
other.description == description &&
|
||||
other.deprecationReason == deprecationReason;
|
||||
}
|
||||
|
|
|
@ -3,19 +3,23 @@ part of graphql_schema.src.schema;
|
|||
typedef FutureOr<Value> GraphQLFieldResolver<Value, Serialized>(
|
||||
Serialized serialized, Map<String, dynamic> argumentValues);
|
||||
|
||||
class GraphQLField<Value, Serialized> {
|
||||
final List<GraphQLFieldArgument> arguments = <GraphQLFieldArgument>[];
|
||||
class GraphQLObjectField<Value, Serialized> {
|
||||
final List<GraphQLFieldInput> inputs = <GraphQLFieldInput>[];
|
||||
final String name;
|
||||
final GraphQLFieldResolver<Value, Serialized> resolve;
|
||||
final GraphQLType<Value, Serialized> type;
|
||||
final String description;
|
||||
final String deprecationReason;
|
||||
|
||||
GraphQLField(this.name,
|
||||
{Iterable<GraphQLFieldArgument> arguments: const <GraphQLFieldArgument>[],
|
||||
GraphQLObjectField(this.name, this.type,
|
||||
{Iterable<GraphQLFieldInput> arguments: const <GraphQLFieldInput>[],
|
||||
@required this.resolve,
|
||||
this.type,
|
||||
this.deprecationReason}) {
|
||||
this.arguments.addAll(arguments ?? <GraphQLFieldArgument>[]);
|
||||
this.deprecationReason,
|
||||
this.description}) {
|
||||
assert(type != null, 'GraphQL fields must specify a `type`.');
|
||||
assert(
|
||||
resolve != null, 'GraphQL fields must specify a `resolve` callback.');
|
||||
this.inputs.addAll(arguments ?? <GraphQLFieldInput>[]);
|
||||
}
|
||||
|
||||
bool get isDeprecated => deprecationReason?.isNotEmpty == true;
|
||||
|
@ -29,4 +33,13 @@ class GraphQLField<Value, Serialized> {
|
|||
if (resolve != null) return resolve(serialized, argumentValues);
|
||||
return type.deserialize(serialized);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is GraphQLObjectField &&
|
||||
other.name == name &&
|
||||
other.deprecationReason == deprecationReason &&
|
||||
other.type == type &&
|
||||
other.resolve == resolve &&
|
||||
const ListEquality<GraphQLFieldInput>().equals(other.inputs, inputs);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ part of graphql_schema.src.schema;
|
|||
GraphQLObjectType objectType(String name,
|
||||
{String description,
|
||||
bool isInterface: false,
|
||||
Iterable<GraphQLField> fields = const [],
|
||||
Iterable<GraphQLObjectField> fields = const [],
|
||||
Iterable<GraphQLObjectType> interfaces = const []}) {
|
||||
var obj = new GraphQLObjectType(name, description, isInterface: isInterface)
|
||||
..fields.addAll(fields ?? []);
|
||||
|
@ -17,14 +17,28 @@ GraphQLObjectType objectType(String name,
|
|||
return obj;
|
||||
}
|
||||
|
||||
GraphQLField<T, Serialized> field<T, Serialized>(String name,
|
||||
{Iterable<GraphQLFieldArgument<T, Serialized>> arguments: const [],
|
||||
GraphQLObjectField<T, Serialized> field<T, Serialized>(
|
||||
String name, GraphQLType<T, Serialized> type,
|
||||
{Iterable<GraphQLFieldInput<T, Serialized>> inputs: const [],
|
||||
GraphQLFieldResolver<T, Serialized> resolve,
|
||||
GraphQLType<T, Serialized> type,
|
||||
String deprecationReason}) {
|
||||
return new GraphQLField(name,
|
||||
arguments: arguments,
|
||||
resolve: resolve,
|
||||
type: type,
|
||||
String deprecationReason, String description}) {
|
||||
return new GraphQLObjectField<T, Serialized>(name, type,
|
||||
arguments: inputs,
|
||||
resolve: resolve ?? (_, __) => null,
|
||||
description: description,
|
||||
deprecationReason: deprecationReason);
|
||||
}
|
||||
|
||||
GraphQLInputObjectType inputObjectType(String name,
|
||||
{String description,
|
||||
Iterable<GraphQLInputObjectField> inputFields: const []}) {
|
||||
return new GraphQLInputObjectType(name,
|
||||
description: description, inputFields: inputFields);
|
||||
}
|
||||
|
||||
GraphQLInputObjectField<T, Serialized> inputField<T, Serialized>(
|
||||
String name, GraphQLType<T, Serialized> type,
|
||||
{String description, T defaultValue}) {
|
||||
return new GraphQLInputObjectField(name, type,
|
||||
description: description, defaultValue: defaultValue);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ class GraphQLObjectType
|
|||
with _NonNullableMixin<Map<String, dynamic>, Map<String, dynamic>> {
|
||||
final String name;
|
||||
final String description;
|
||||
final List<GraphQLField> fields = [];
|
||||
final List<GraphQLObjectField> fields = [];
|
||||
final bool isInterface;
|
||||
|
||||
final List<GraphQLObjectType> _interfaces = [];
|
||||
|
@ -22,6 +22,19 @@ class GraphQLObjectType
|
|||
|
||||
GraphQLObjectType(this.name, this.description, {this.isInterface: false});
|
||||
|
||||
@override
|
||||
GraphQLType<Map<String, dynamic>, Map<String, dynamic>> coerceToInputObject() {
|
||||
return asInputObject('${name}Input', description: description);
|
||||
}
|
||||
|
||||
/// Converts [this] into a [GraphQLInputObjectType].
|
||||
GraphQLInputObjectType asInputObject(String name, {String description}) {
|
||||
return new GraphQLInputObjectType(name,
|
||||
description: description ?? this.description,
|
||||
inputFields:
|
||||
fields.map((f) => new GraphQLInputObjectField(f.name, f.type.coerceToInputObject())));
|
||||
}
|
||||
|
||||
void inheritFrom(GraphQLObjectType other) {
|
||||
if (!_interfaces.contains(other)) {
|
||||
_interfaces.add(other);
|
||||
|
@ -119,6 +132,19 @@ class GraphQLObjectType
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
return other is GraphQLObjectType &&
|
||||
other.name == name &&
|
||||
other.description == description &&
|
||||
other.isInterface == isInterface &&
|
||||
const ListEquality<GraphQLObjectField>().equals(other.fields, fields) &&
|
||||
// const ListEquality<GraphQLObjectType>() Removed, as it causes a stack overflow :(
|
||||
// .equals(other.interfaces, interfaces) &&
|
||||
const ListEquality<GraphQLObjectType>()
|
||||
.equals(other.possibleTypes, possibleTypes);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _foldToStringDynamic(Map map) {
|
||||
|
@ -127,3 +153,112 @@ Map<String, dynamic> _foldToStringDynamic(Map map) {
|
|||
: map.keys.fold<Map<String, dynamic>>(
|
||||
<String, dynamic>{}, (out, k) => out..[k.toString()] = map[k]);
|
||||
}
|
||||
|
||||
class GraphQLInputObjectType
|
||||
extends GraphQLType<Map<String, dynamic>, Map<String, dynamic>>
|
||||
with _NonNullableMixin<Map<String, dynamic>, Map<String, dynamic>> {
|
||||
final String name;
|
||||
final String description;
|
||||
final List<GraphQLInputObjectField> inputFields = [];
|
||||
|
||||
GraphQLInputObjectType(this.name,
|
||||
{this.description,
|
||||
Iterable<GraphQLInputObjectField> inputFields: const []}) {
|
||||
this.inputFields.addAll(inputFields ?? const <GraphQLInputObjectField>[]);
|
||||
}
|
||||
|
||||
@override
|
||||
ValidationResult<Map<String, dynamic>> validate(String key, Map input) {
|
||||
if (input is! Map)
|
||||
return new ValidationResult._failure(['Expected "$key" to be a Map.']);
|
||||
|
||||
var out = {};
|
||||
List<String> errors = [];
|
||||
|
||||
for (var field in inputFields) {
|
||||
if (field.type is GraphQLNonNullableType) {
|
||||
if (!input.containsKey(field.name) || input[field.name] == null) {
|
||||
errors.add(
|
||||
'Field "${field.name}, of type ${field.type} cannot be null."');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.keys.forEach((k) {
|
||||
var field =
|
||||
inputFields.firstWhere((f) => f.name == k, orElse: () => null);
|
||||
|
||||
if (field == null) {
|
||||
errors.add(
|
||||
'Unexpected field "$k" encountered in $key. Accepted values on type $name: ${inputFields.map((f) => f.name).toList()}');
|
||||
} else {
|
||||
var v = input[k];
|
||||
var result = field.type.validate(k.toString(), v);
|
||||
|
||||
if (!result.successful) {
|
||||
errors.addAll(result.errors.map((s) => '$key: $s'));
|
||||
} else {
|
||||
out[k] = v;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.isNotEmpty) {
|
||||
return new ValidationResult._failure(errors);
|
||||
} else
|
||||
return new ValidationResult._ok(_foldToStringDynamic(out));
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> serialize(Map value) {
|
||||
return value.keys.fold<Map<String, dynamic>>({}, (out, k) {
|
||||
var field =
|
||||
inputFields.firstWhere((f) => f.name == k, orElse: () => null);
|
||||
if (field == null)
|
||||
throw new UnsupportedError(
|
||||
'Cannot serialize field "$k", which was not defined in the schema.');
|
||||
return out..[k.toString()] = field.type.serialize(value[k]);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> deserialize(Map value) {
|
||||
return value.keys.fold<Map<String, dynamic>>({}, (out, k) {
|
||||
var field =
|
||||
inputFields.firstWhere((f) => f.name == k, orElse: () => null);
|
||||
if (field == null)
|
||||
throw new UnsupportedError('Unexpected field "$k" encountered in map.');
|
||||
return out..[k.toString()] = field.type.deserialize(value[k]);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
return other is GraphQLInputObjectType &&
|
||||
other.name == name &&
|
||||
other.description == description &&
|
||||
const ListEquality<GraphQLInputObjectField>()
|
||||
.equals(other.inputFields, inputFields);
|
||||
}
|
||||
|
||||
@override
|
||||
GraphQLType<Map<String, dynamic>, Map<String, dynamic>> coerceToInputObject() => this;
|
||||
}
|
||||
|
||||
class GraphQLInputObjectField<Value, Serialized> {
|
||||
final String name;
|
||||
final GraphQLType<Value, Serialized> type;
|
||||
final String description;
|
||||
final Value defaultValue;
|
||||
|
||||
GraphQLInputObjectField(this.name, this.type,
|
||||
{this.description, this.defaultValue});
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is GraphQLInputObjectField &&
|
||||
other.name == name &&
|
||||
other.type == type &&
|
||||
other.description == description &&
|
||||
other.defaultValue == defaultValue;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ final GraphQLScalarType<String, String> graphQLString =
|
|||
/// The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache.
|
||||
///
|
||||
/// The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.
|
||||
final GraphQLScalarType<String, String> graphQLId = new _GraphQLStringType._('ID');
|
||||
final GraphQLScalarType<String, String> graphQLId =
|
||||
new _GraphQLStringType._('ID');
|
||||
|
||||
/// A [DateTime].
|
||||
final GraphQLScalarType<DateTime, String> graphQLDate =
|
||||
|
@ -60,6 +61,9 @@ class _GraphQLBoolType extends GraphQLScalarType<bool, bool> {
|
|||
bool deserialize(bool serialized) {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@override
|
||||
GraphQLType<bool, bool> coerceToInputObject() => this;
|
||||
}
|
||||
|
||||
class _GraphQLNumType<T extends num> extends GraphQLScalarType<T, T> {
|
||||
|
@ -88,6 +92,9 @@ class _GraphQLNumType<T extends num> extends GraphQLScalarType<T, T> {
|
|||
T serialize(T value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
GraphQLType<T, T> coerceToInputObject() => this;
|
||||
}
|
||||
|
||||
class _GraphQLStringType extends GraphQLScalarType<String, String> {
|
||||
|
@ -109,6 +116,9 @@ class _GraphQLStringType extends GraphQLScalarType<String, String> {
|
|||
input == null || input is String
|
||||
? new ValidationResult<String>._ok(input)
|
||||
: new ValidationResult._failure(['Expected "$key" to be a string.']);
|
||||
|
||||
@override
|
||||
GraphQLType<String, String> coerceToInputObject() => this;
|
||||
}
|
||||
|
||||
class _GraphQLDateType extends GraphQLScalarType<DateTime, String>
|
||||
|
@ -142,4 +152,7 @@ class _GraphQLDateType extends GraphQLScalarType<DateTime, String>
|
|||
['$key must be an ISO 8601-formatted date string.']);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
GraphQLType<DateTime, String> coerceToInputObject() => this;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ library graphql_schema.src.schema;
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
|
@ -24,19 +25,21 @@ part 'union.dart';
|
|||
part 'validation_result.dart';
|
||||
|
||||
class GraphQLSchema {
|
||||
final GraphQLObjectType query;
|
||||
final GraphQLObjectType mutation;
|
||||
final GraphQLObjectType subscription;
|
||||
final GraphQLObjectType queryType;
|
||||
final GraphQLObjectType mutationType;
|
||||
final GraphQLObjectType subscriptionType;
|
||||
|
||||
GraphQLSchema({this.query, this.mutation, this.subscription});
|
||||
GraphQLSchema({this.queryType, this.mutationType, this.subscriptionType});
|
||||
}
|
||||
|
||||
GraphQLSchema graphQLSchema(
|
||||
{@required GraphQLObjectType query,
|
||||
GraphQLObjectType mutation,
|
||||
GraphQLObjectType subscription}) =>
|
||||
{@required GraphQLObjectType queryType,
|
||||
GraphQLObjectType mutationType,
|
||||
GraphQLObjectType subscriptionType}) =>
|
||||
new GraphQLSchema(
|
||||
query: query, mutation: mutation, subscription: subscription);
|
||||
queryType: queryType,
|
||||
mutationType: mutationType,
|
||||
subscriptionType: subscriptionType);
|
||||
|
||||
/// A default resolver that always returns `null`.
|
||||
resolveToNull(_, __) => null;
|
||||
|
|
|
@ -13,6 +13,8 @@ abstract class GraphQLType<Value, Serialized> {
|
|||
|
||||
GraphQLType<Value, Serialized> nonNullable();
|
||||
|
||||
GraphQLType<Value, Serialized> coerceToInputObject();
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
@ -25,16 +27,16 @@ 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> innerType;
|
||||
final GraphQLType<Value, Serialized> ofType;
|
||||
|
||||
GraphQLListType(this.innerType);
|
||||
GraphQLListType(this.ofType);
|
||||
|
||||
@override
|
||||
String get name => null;
|
||||
|
||||
@override
|
||||
String get description =>
|
||||
'A list of items of type ${innerType.name ?? '(${innerType.description}).'}';
|
||||
'A list of items of type ${ofType.name ?? '(${ofType.description}).'}';
|
||||
|
||||
@override
|
||||
ValidationResult<List<Serialized>> validate(
|
||||
|
@ -48,7 +50,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 = innerType.validate(k, v);
|
||||
var result = ofType.validate(k, v);
|
||||
if (!result.successful)
|
||||
errors.addAll(result.errors);
|
||||
else
|
||||
|
@ -61,16 +63,23 @@ class GraphQLListType<Value, Serialized>
|
|||
|
||||
@override
|
||||
List<Value> deserialize(List<Serialized> serialized) {
|
||||
return serialized.map<Value>(innerType.deserialize).toList();
|
||||
return serialized.map<Value>(ofType.deserialize).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Serialized> serialize(List<Value> value) {
|
||||
return value.map<Serialized>(innerType.serialize).toList();
|
||||
return value.map<Serialized>(ofType.serialize).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '[$innerType]';
|
||||
String toString() => '[$ofType]';
|
||||
|
||||
@override
|
||||
bool operator ==(other) => other is GraphQLListType && other.ofType == ofType;
|
||||
|
||||
@override
|
||||
GraphQLType<List<Value>, List<Serialized>> coerceToInputObject() =>
|
||||
new GraphQLListType<Value, Serialized>(ofType.coerceToInputObject());
|
||||
}
|
||||
|
||||
abstract class _NonNullableMixin<Value, Serialized>
|
||||
|
@ -83,16 +92,16 @@ abstract class _NonNullableMixin<Value, Serialized>
|
|||
|
||||
class GraphQLNonNullableType<Value, Serialized>
|
||||
extends GraphQLType<Value, Serialized> {
|
||||
final GraphQLType<Value, Serialized> innerType;
|
||||
final GraphQLType<Value, Serialized> ofType;
|
||||
|
||||
GraphQLNonNullableType._(this.innerType);
|
||||
GraphQLNonNullableType._(this.ofType);
|
||||
|
||||
@override
|
||||
String get name => innerType.name;
|
||||
String get name => null; //innerType.name;
|
||||
|
||||
@override
|
||||
String get description =>
|
||||
'A non-nullable binding to ${innerType.name ?? '(${innerType.description}).'}';
|
||||
'A non-nullable binding to ${ofType.name ?? '(${ofType.description}).'}';
|
||||
|
||||
@override
|
||||
GraphQLType<Value, Serialized> nonNullable() {
|
||||
|
@ -105,21 +114,30 @@ class GraphQLNonNullableType<Value, Serialized>
|
|||
if (input == null)
|
||||
return new ValidationResult._failure(
|
||||
['Expected "$key" to be a non-null value.']);
|
||||
return innerType.validate(key, input);
|
||||
return ofType.validate(key, input);
|
||||
}
|
||||
|
||||
@override
|
||||
Value deserialize(Serialized serialized) {
|
||||
return innerType.deserialize(serialized);
|
||||
return ofType.deserialize(serialized);
|
||||
}
|
||||
|
||||
@override
|
||||
Serialized serialize(Value value) {
|
||||
return innerType.serialize(value);
|
||||
return ofType.serialize(value);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$innerType!';
|
||||
return '$ofType!';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is GraphQLNonNullableType && other.ofType == ofType;
|
||||
|
||||
@override
|
||||
GraphQLType<Value, Serialized> coerceToInputObject() {
|
||||
return ofType.coerceToInputObject().nonNullable();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,17 +7,29 @@ class GraphQLUnionType
|
|||
final List<GraphQLObjectType> possibleTypes = [];
|
||||
|
||||
GraphQLUnionType(
|
||||
this.name,
|
||||
Iterable<GraphQLObjectType> possibleTypes,
|
||||
) {
|
||||
this.name,
|
||||
Iterable<GraphQLType<Map<String, dynamic>, Map<String, dynamic>>>
|
||||
possibleTypes) {
|
||||
assert(possibleTypes.every((t) => t is GraphQLObjectType),
|
||||
'The member types of a Union type must all be Object base types; Scalar, Interface and Union types must not be member types of a Union. Similarly, wrapping types must not be member types of a Union.');
|
||||
assert(possibleTypes.isNotEmpty,
|
||||
'A Union type must define one or more member types.');
|
||||
this.possibleTypes.addAll(possibleTypes.toSet());
|
||||
|
||||
for (var t in possibleTypes.toSet()) {
|
||||
this.possibleTypes.add(t as GraphQLObjectType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get description => possibleTypes.map((t) => t.name).join(' | ');
|
||||
|
||||
@override
|
||||
GraphQLType<Map<String, dynamic>, Map<String, dynamic>>
|
||||
coerceToInputObject() {
|
||||
return new GraphQLUnionType(
|
||||
'${name}Input', possibleTypes.map((t) => t.coerceToInputObject()));
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> serialize(Map<String, dynamic> value) {
|
||||
for (var type in possibleTypes) {
|
||||
|
@ -57,4 +69,12 @@ class GraphQLUnionType
|
|||
|
||||
return new ValidationResult<Map<String, dynamic>>._failure(errors);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is GraphQLUnionType &&
|
||||
other.name == name &&
|
||||
other.description == description &&
|
||||
const ListEquality<GraphQLObjectType>()
|
||||
.equals(other.possibleTypes, possibleTypes);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class ValidationResult<T> {
|
|||
: value = null,
|
||||
successful = false;
|
||||
|
||||
ValidationResult<T> _asFailure() {
|
||||
return new ValidationResult<T>._(false, value, errors);
|
||||
}
|
||||
}
|
||||
// ValidationResult<T> _asFailure() {
|
||||
// return new ValidationResult<T>._(false, value, errors);
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
|
||||
final GraphQLObjectType pokemonType = objectType('Pokemon', fields:[
|
||||
field('species', type: graphQLString),
|
||||
field('catch_date', type: graphQLDate)
|
||||
final GraphQLObjectType pokemonType = objectType('Pokemon', fields: [
|
||||
field('species', graphQLString),
|
||||
field('catch_date', graphQLDate)
|
||||
]);
|
||||
|
||||
final GraphQLObjectType trainerType =
|
||||
objectType('Trainer', fields:[field('name', type: graphQLString)]);
|
||||
objectType('Trainer', fields: [field('name', graphQLString)]);
|
||||
|
||||
final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', fields:[
|
||||
field('trainer', type: trainerType),
|
||||
field('pokemon_species', type: listType(pokemonType))
|
||||
]);
|
||||
final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion',
|
||||
fields: [
|
||||
field('trainer', trainerType),
|
||||
field('pokemon_species', listType(pokemonType))
|
||||
]);
|
||||
|
|
|
@ -51,9 +51,11 @@ main() {
|
|||
]
|
||||
});
|
||||
|
||||
expect(() => pokemonRegionType.serialize({
|
||||
'trainer': trainer,
|
||||
'DIGIMON_species': [pikachu, charizard]
|
||||
}), throwsUnsupportedError);
|
||||
expect(
|
||||
() => pokemonRegionType.serialize({
|
||||
'trainer': trainer,
|
||||
'DIGIMON_species': [pikachu, charizard]
|
||||
}),
|
||||
throwsUnsupportedError);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,11 +11,11 @@ void main() {
|
|||
var pokemonType = objectType('Pokémon', fields: [
|
||||
field(
|
||||
'name',
|
||||
type: graphQLString.nonNullable(),
|
||||
graphQLString.nonNullable(),
|
||||
),
|
||||
field(
|
||||
'type',
|
||||
type: typeType,
|
||||
typeType,
|
||||
),
|
||||
]);
|
||||
|
||||
|
|
|
@ -29,15 +29,16 @@ class GraphQL {
|
|||
allTypes.addAll(this.customTypes);
|
||||
_schema = reflectSchema(_schema, allTypes);
|
||||
|
||||
for (var type in allTypes) {
|
||||
for (var type in allTypes.toSet()) {
|
||||
if (!this.customTypes.contains(type)) {
|
||||
this.customTypes.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_schema.query != null) this.customTypes.add(_schema.query);
|
||||
if (_schema.mutation != null) this.customTypes.add(_schema.mutation);
|
||||
if (_schema.queryType != null) this.customTypes.add(_schema.queryType);
|
||||
if (_schema.mutationType != null)
|
||||
this.customTypes.add(_schema.mutationType);
|
||||
}
|
||||
|
||||
GraphQLType convertType(TypeContext ctx) {
|
||||
|
@ -76,6 +77,15 @@ class GraphQL {
|
|||
var tokens = scan(text, sourceUrl: sourceUrl);
|
||||
var parser = new Parser(tokens);
|
||||
var document = parser.parseDocument();
|
||||
|
||||
if (parser.errors.isNotEmpty) {
|
||||
throw new GraphQLException(parser.errors
|
||||
.map((e) => new GraphQLExceptionError(e.message, locations: [
|
||||
new GraphExceptionErrorLocation.fromSourceLocation(e.span.start)
|
||||
]))
|
||||
.toList());
|
||||
}
|
||||
|
||||
return executeRequest(_schema, document,
|
||||
operationName: operationName,
|
||||
initialValue: initialValue,
|
||||
|
@ -166,7 +176,7 @@ class GraphQL {
|
|||
GraphQLSchema schema,
|
||||
Map<String, dynamic> variableValues,
|
||||
initialValue) async {
|
||||
var queryType = schema.query;
|
||||
var queryType = schema.queryType;
|
||||
var selectionSet = query.selectionSet;
|
||||
return await executeSelectionSet(
|
||||
document, selectionSet, queryType, initialValue, variableValues);
|
||||
|
@ -178,7 +188,7 @@ class GraphQL {
|
|||
GraphQLSchema schema,
|
||||
Map<String, dynamic> variableValues,
|
||||
initialValue) async {
|
||||
var mutationType = schema.mutation;
|
||||
var mutationType = schema.mutationType;
|
||||
|
||||
if (mutationType == null) {
|
||||
throw new GraphQLException.fromMessage(
|
||||
|
@ -241,7 +251,7 @@ class GraphQL {
|
|||
var argumentValues = field.field.arguments;
|
||||
var fieldName = field.field.fieldName.name;
|
||||
var desiredField = objectType.fields.firstWhere((f) => f.name == fieldName);
|
||||
var argumentDefinitions = desiredField.arguments;
|
||||
var argumentDefinitions = desiredField.inputs;
|
||||
|
||||
for (var argumentDefinition in argumentDefinitions) {
|
||||
var argumentName = argumentDefinition.name;
|
||||
|
@ -260,7 +270,7 @@ class GraphQL {
|
|||
coercedValues[argumentName] = defaultValue;
|
||||
} else if (argumentType is GraphQLNonNullableType) {
|
||||
throw new GraphQLException.fromSourceSpan(
|
||||
'Missing value for argument "$argumentName".',
|
||||
'Missing value for argument "$argumentName" of field "$fieldName".',
|
||||
value.valueOrVariable.span);
|
||||
} else {
|
||||
continue;
|
||||
|
@ -270,21 +280,60 @@ class GraphQL {
|
|||
coercedValues[argumentName] = defaultValue;
|
||||
} else if (argumentType is GraphQLNonNullableType) {
|
||||
throw new GraphQLException.fromMessage(
|
||||
'Missing value for argument "$argumentName".');
|
||||
'Missing value for argument "$argumentName" of field "$fieldName".');
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
var validation =
|
||||
argumentType.validate(fieldName, value.valueOrVariable.value.value);
|
||||
try {
|
||||
var validation = argumentType.validate(
|
||||
fieldName, value.valueOrVariable.value.value);
|
||||
|
||||
if (!validation.successful) {
|
||||
throw new GraphQLException.fromSourceSpan(
|
||||
'Coercion error for value of argument "$argumentName".',
|
||||
value.valueOrVariable.span);
|
||||
} else {
|
||||
var coercedValue = validation.value;
|
||||
coercedValues[argumentName] = coercedValue;
|
||||
if (!validation.successful) {
|
||||
var errors = <GraphQLExceptionError>[
|
||||
new GraphQLExceptionError(
|
||||
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
|
||||
locations: [
|
||||
new GraphExceptionErrorLocation.fromSourceLocation(
|
||||
value.valueOrVariable.span.start)
|
||||
],
|
||||
)
|
||||
];
|
||||
|
||||
for (var error in validation.errors) {
|
||||
errors.add(
|
||||
new GraphQLExceptionError(
|
||||
error,
|
||||
locations: [
|
||||
new GraphExceptionErrorLocation.fromSourceLocation(
|
||||
value.valueOrVariable.span.start)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
throw new GraphQLException(errors);
|
||||
} else {
|
||||
var coercedValue = validation.value;
|
||||
coercedValues[argumentName] = coercedValue;
|
||||
}
|
||||
} on TypeError catch (e) {
|
||||
throw new GraphQLException(<GraphQLExceptionError>[
|
||||
new GraphQLExceptionError(
|
||||
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
|
||||
locations: [
|
||||
new GraphExceptionErrorLocation.fromSourceLocation(
|
||||
value.valueOrVariable.span.start)
|
||||
],
|
||||
),
|
||||
new GraphQLExceptionError(
|
||||
e.message.toString(),
|
||||
locations: [
|
||||
new GraphExceptionErrorLocation.fromSourceLocation(
|
||||
value.valueOrVariable.span.start)
|
||||
],
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -311,7 +360,7 @@ class GraphQL {
|
|||
result,
|
||||
Map<String, dynamic> variableValues) async {
|
||||
if (fieldType is GraphQLNonNullableType) {
|
||||
var innerType = fieldType.innerType;
|
||||
var innerType = fieldType.ofType;
|
||||
var completedResult = completeValue(
|
||||
document, fieldName, innerType, fields, result, variableValues);
|
||||
|
||||
|
@ -333,7 +382,7 @@ class GraphQL {
|
|||
'Value of field "$fieldName" must be a list or iterable, got $result instead.');
|
||||
}
|
||||
|
||||
var innerType = fieldType.innerType;
|
||||
var innerType = fieldType.ofType;
|
||||
var out = [];
|
||||
|
||||
for (var resultItem in (result as Iterable)) {
|
||||
|
@ -376,7 +425,8 @@ class GraphQL {
|
|||
throw new UnsupportedError('Unsupported type: $fieldType');
|
||||
}
|
||||
|
||||
GraphQLObjectType resolveAbstractType(String fieldName, GraphQLType type, result) {
|
||||
GraphQLObjectType resolveAbstractType(
|
||||
String fieldName, GraphQLType type, result) {
|
||||
List<GraphQLObjectType> possibleTypes;
|
||||
|
||||
if (type is GraphQLObjectType) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:graphql_parser/graphql_parser.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
|
||||
// TODO: How to handle custom types???
|
||||
GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
|
||||
for (var type in allTypes.toList()) {
|
||||
var custom = _fetchAllTypesFromType(type);
|
||||
|
@ -14,34 +13,36 @@ GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
|
|||
}
|
||||
|
||||
var objectTypes = fetchAllTypes(schema, allTypes);
|
||||
|
||||
var typeType = _reflectSchemaTypes();
|
||||
var directiveType = _reflectDirectiveType();
|
||||
allTypes.addAll(objectTypes);
|
||||
Set<GraphQLType> allTypeSet;
|
||||
|
||||
var schemaType = objectType('__Schema', fields: [
|
||||
field(
|
||||
'types',
|
||||
type: listType(typeType),
|
||||
resolve: (_, __) => allTypes,
|
||||
listType(typeType),
|
||||
resolve: (_, __) => allTypeSet ??= allTypes.toSet(),
|
||||
),
|
||||
field(
|
||||
'queryType',
|
||||
type: typeType,
|
||||
resolve: (_, __) => schema.query,
|
||||
typeType,
|
||||
resolve: (_, __) => schema.queryType,
|
||||
),
|
||||
field(
|
||||
'mutationType',
|
||||
type: typeType,
|
||||
resolve: (_, __) => schema.mutation,
|
||||
typeType,
|
||||
resolve: (_, __) => schema.mutationType,
|
||||
),
|
||||
field(
|
||||
'subscriptionType',
|
||||
type: typeType,
|
||||
resolve: (_, __) => schema.subscription,
|
||||
typeType,
|
||||
resolve: (_, __) => schema.subscriptionType,
|
||||
),
|
||||
field(
|
||||
'directives',
|
||||
type: listType(directiveType).nonNullable(),
|
||||
listType(directiveType).nonNullable(),
|
||||
resolve: (_, __) => [], // TODO: Actually fetch directives
|
||||
),
|
||||
]);
|
||||
|
@ -64,18 +65,16 @@ GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
|
|||
_reflectEnumValueType(),
|
||||
]);
|
||||
|
||||
var fields = <GraphQLField>[
|
||||
var fields = <GraphQLObjectField>[
|
||||
field(
|
||||
'__schema',
|
||||
type: schemaType,
|
||||
schemaType,
|
||||
resolve: (_, __) => schemaType,
|
||||
),
|
||||
field(
|
||||
'__type',
|
||||
type: typeType,
|
||||
arguments: [
|
||||
new GraphQLFieldArgument('name', graphQLString.nonNullable())
|
||||
],
|
||||
typeType,
|
||||
inputs: [new GraphQLFieldInput('name', graphQLString.nonNullable())],
|
||||
resolve: (_, args) {
|
||||
var name = args['name'] as String;
|
||||
return allTypes.firstWhere((t) => t.name == name,
|
||||
|
@ -85,11 +84,11 @@ GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
|
|||
),
|
||||
];
|
||||
|
||||
fields.addAll(schema.query.fields);
|
||||
fields.addAll(schema.queryType.fields);
|
||||
|
||||
return new GraphQLSchema(
|
||||
query: objectType(schema.query.name, fields: fields),
|
||||
mutation: schema.mutation,
|
||||
queryType: objectType(schema.queryType.name, fields: fields),
|
||||
mutationType: schema.mutationType,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -101,11 +100,11 @@ GraphQLObjectType _reflectSchemaTypes() {
|
|||
_typeType.fields.add(
|
||||
field(
|
||||
'ofType',
|
||||
type: _reflectSchemaTypes(),
|
||||
_reflectSchemaTypes(),
|
||||
resolve: (type, _) {
|
||||
if (type is GraphQLListType)
|
||||
return type.innerType;
|
||||
else if (type is GraphQLNonNullableType) return type.innerType;
|
||||
return type.ofType;
|
||||
else if (type is GraphQLNonNullableType) return type.ofType;
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
@ -114,7 +113,7 @@ GraphQLObjectType _reflectSchemaTypes() {
|
|||
_typeType.fields.add(
|
||||
field(
|
||||
'interfaces',
|
||||
type: listType(_reflectSchemaTypes().nonNullable()),
|
||||
listType(_reflectSchemaTypes().nonNullable()),
|
||||
resolve: (type, _) {
|
||||
if (type is GraphQLObjectType) {
|
||||
return type.interfaces;
|
||||
|
@ -128,7 +127,7 @@ GraphQLObjectType _reflectSchemaTypes() {
|
|||
_typeType.fields.add(
|
||||
field(
|
||||
'possibleTypes',
|
||||
type: listType(_reflectSchemaTypes().nonNullable()),
|
||||
listType(_reflectSchemaTypes().nonNullable()),
|
||||
resolve: (type, _) {
|
||||
if (type is GraphQLObjectType && type.isInterface) {
|
||||
return type.possibleTypes;
|
||||
|
@ -150,8 +149,8 @@ GraphQLObjectType _reflectSchemaTypes() {
|
|||
fieldType.fields.add(
|
||||
field(
|
||||
'type',
|
||||
type: _reflectSchemaTypes(),
|
||||
resolve: (f, _) => (f as GraphQLField).type,
|
||||
_reflectSchemaTypes(),
|
||||
resolve: (f, _) => (f as GraphQLObjectField).type,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -163,8 +162,9 @@ GraphQLObjectType _reflectSchemaTypes() {
|
|||
inputValueType.fields.add(
|
||||
field(
|
||||
'type',
|
||||
type: _reflectSchemaTypes(),
|
||||
resolve: (f, _) => (f as GraphQLFieldArgument).type,
|
||||
_reflectSchemaTypes(),
|
||||
resolve: (f, _) =>
|
||||
_fetchFromInputValue(f, (f) => f.type, (f) => f.type),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -193,17 +193,17 @@ GraphQLObjectType _createTypeType() {
|
|||
return objectType('__Type', fields: [
|
||||
field(
|
||||
'name',
|
||||
type: graphQLString,
|
||||
graphQLString,
|
||||
resolve: (type, _) => (type as GraphQLType).name,
|
||||
),
|
||||
field(
|
||||
'description',
|
||||
type: graphQLString,
|
||||
graphQLString,
|
||||
resolve: (type, _) => (type as GraphQLType).description,
|
||||
),
|
||||
field(
|
||||
'kind',
|
||||
type: _typeKindType,
|
||||
_typeKindType,
|
||||
resolve: (type, _) {
|
||||
var t = type as GraphQLType;
|
||||
|
||||
|
@ -211,6 +211,8 @@ GraphQLObjectType _createTypeType() {
|
|||
return 'ENUM';
|
||||
else if (t is GraphQLScalarType)
|
||||
return 'SCALAR';
|
||||
else if (t is GraphQLInputObjectType)
|
||||
return 'INPUT_OBJECT';
|
||||
else if (t is GraphQLObjectType)
|
||||
return t.isInterface ? 'INTERFACE' : 'OBJECT';
|
||||
else if (t is GraphQLListType)
|
||||
|
@ -225,9 +227,9 @@ GraphQLObjectType _createTypeType() {
|
|||
),
|
||||
field(
|
||||
'fields',
|
||||
type: listType(fieldType),
|
||||
arguments: [
|
||||
new GraphQLFieldArgument(
|
||||
listType(fieldType),
|
||||
inputs: [
|
||||
new GraphQLFieldInput(
|
||||
'includeDeprecated',
|
||||
graphQLBoolean,
|
||||
defaultValue: false,
|
||||
|
@ -238,13 +240,13 @@ GraphQLObjectType _createTypeType() {
|
|||
.where(
|
||||
(f) => !f.isDeprecated || args['includeDeprecated'] == true)
|
||||
.toList()
|
||||
: [],
|
||||
: null,
|
||||
),
|
||||
field(
|
||||
'enumValues',
|
||||
type: listType(enumValueType.nonNullable()),
|
||||
arguments: [
|
||||
new GraphQLFieldArgument(
|
||||
listType(enumValueType.nonNullable()),
|
||||
inputs: [
|
||||
new GraphQLFieldInput(
|
||||
'includeDeprecated',
|
||||
graphQLBoolean,
|
||||
defaultValue: false,
|
||||
|
@ -263,10 +265,13 @@ GraphQLObjectType _createTypeType() {
|
|||
),
|
||||
field(
|
||||
'inputFields',
|
||||
type: listType(inputValueType.nonNullable()),
|
||||
listType(inputValueType.nonNullable()),
|
||||
resolve: (obj, _) {
|
||||
// TODO: INPUT_OBJECT type
|
||||
return <GraphQLFieldArgument>[];
|
||||
if (obj is GraphQLInputObjectType) {
|
||||
return obj.inputFields;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
@ -288,46 +293,64 @@ GraphQLObjectType _createFieldType() {
|
|||
return objectType('__Field', fields: [
|
||||
field(
|
||||
'name',
|
||||
type: graphQLString,
|
||||
resolve: (f, _) => (f as GraphQLField).name,
|
||||
graphQLString,
|
||||
resolve: (f, _) => (f as GraphQLObjectField).name,
|
||||
),
|
||||
field(
|
||||
'description',
|
||||
graphQLString,
|
||||
resolve: (f, _) => (f as GraphQLObjectField).description,
|
||||
),
|
||||
field(
|
||||
'isDeprecated',
|
||||
type: graphQLBoolean,
|
||||
resolve: (f, _) => (f as GraphQLField).isDeprecated,
|
||||
graphQLBoolean,
|
||||
resolve: (f, _) => (f as GraphQLObjectField).isDeprecated,
|
||||
),
|
||||
field(
|
||||
'deprecationReason',
|
||||
type: graphQLString,
|
||||
resolve: (f, _) => (f as GraphQLField).deprecationReason,
|
||||
graphQLString,
|
||||
resolve: (f, _) => (f as GraphQLObjectField).deprecationReason,
|
||||
),
|
||||
field(
|
||||
'args',
|
||||
type: listType(inputValueType.nonNullable()).nonNullable(),
|
||||
resolve: (f, _) => (f as GraphQLField).arguments,
|
||||
listType(inputValueType.nonNullable()).nonNullable(),
|
||||
resolve: (f, _) => (f as GraphQLObjectField).inputs,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
GraphQLObjectType _inputValueType;
|
||||
|
||||
T _fetchFromInputValue<T>(x, T Function(GraphQLFieldInput) ifInput,
|
||||
T Function(GraphQLInputObjectField) ifObjectField) {
|
||||
if (x is GraphQLFieldInput) {
|
||||
return ifInput(x);
|
||||
} else if (x is GraphQLInputObjectField) {
|
||||
return ifObjectField(x);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
GraphQLObjectType _reflectInputValueType() {
|
||||
return _inputValueType ??= objectType('__InputValue', fields: [
|
||||
field(
|
||||
'name',
|
||||
type: graphQLString.nonNullable(),
|
||||
resolve: (obj, _) => (obj as GraphQLFieldArgument).name,
|
||||
graphQLString.nonNullable(),
|
||||
resolve: (obj, _) =>
|
||||
_fetchFromInputValue(obj, (f) => f.name, (f) => f.name),
|
||||
),
|
||||
field(
|
||||
'description',
|
||||
type: graphQLString,
|
||||
resolve: (obj, _) => (obj as GraphQLFieldArgument).description,
|
||||
graphQLString,
|
||||
resolve: (obj, _) =>
|
||||
_fetchFromInputValue(obj, (f) => f.description, (f) => f.description),
|
||||
),
|
||||
field(
|
||||
'defaultValue',
|
||||
type: graphQLString,
|
||||
resolve: (obj, _) =>
|
||||
(obj as GraphQLFieldArgument).defaultValue?.toString(),
|
||||
graphQLString,
|
||||
resolve: (obj, _) => _fetchFromInputValue(obj,
|
||||
(f) => f.defaultValue?.toString(), (f) => f.defaultValue?.toString()),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
@ -351,23 +374,23 @@ GraphQLObjectType _reflectDirectiveType() {
|
|||
return _directiveType ??= objectType('__Directive', fields: [
|
||||
field(
|
||||
'name',
|
||||
type: graphQLString.nonNullable(),
|
||||
graphQLString.nonNullable(),
|
||||
resolve: (obj, _) => (obj as DirectiveContext).NAME.span.text,
|
||||
),
|
||||
field(
|
||||
'description',
|
||||
type: graphQLString,
|
||||
graphQLString,
|
||||
resolve: (obj, _) => null,
|
||||
),
|
||||
field(
|
||||
'locations',
|
||||
type: listType(_directiveLocationType.nonNullable()).nonNullable(),
|
||||
listType(_directiveLocationType.nonNullable()).nonNullable(),
|
||||
// TODO: Fetch directiveLocation
|
||||
resolve: (obj, _) => <String>[],
|
||||
),
|
||||
field(
|
||||
'args',
|
||||
type: listType(inputValueType.nonNullable()).nonNullable(),
|
||||
listType(inputValueType.nonNullable()).nonNullable(),
|
||||
resolve: (obj, _) => [],
|
||||
),
|
||||
]);
|
||||
|
@ -381,22 +404,22 @@ GraphQLObjectType _reflectEnumValueType() {
|
|||
fields: [
|
||||
field(
|
||||
'name',
|
||||
type: graphQLString.nonNullable(),
|
||||
graphQLString.nonNullable(),
|
||||
resolve: (obj, _) => (obj as GraphQLEnumValue).name,
|
||||
),
|
||||
field(
|
||||
'description',
|
||||
type: graphQLString,
|
||||
graphQLString,
|
||||
resolve: (obj, _) => (obj as GraphQLEnumValue).description,
|
||||
),
|
||||
field(
|
||||
'isDeprecated',
|
||||
type: graphQLBoolean.nonNullable(),
|
||||
graphQLBoolean.nonNullable(),
|
||||
resolve: (obj, _) => (obj as GraphQLEnumValue).isDeprecated,
|
||||
),
|
||||
field(
|
||||
'deprecationReason',
|
||||
type: graphQLString,
|
||||
graphQLString,
|
||||
resolve: (obj, _) => (obj as GraphQLEnumValue).deprecationReason,
|
||||
),
|
||||
],
|
||||
|
@ -407,24 +430,13 @@ List<GraphQLType> fetchAllTypes(
|
|||
GraphQLSchema schema, List<GraphQLType> allTypes) {
|
||||
var types = <GraphQLType>[];
|
||||
|
||||
types.addAll(_fetchAllTypesFromObject(schema.query));
|
||||
types.addAll(_fetchAllTypesFromObject(schema.queryType));
|
||||
|
||||
if (schema.mutation != null) {
|
||||
types.addAll(_fetchAllTypesFromObject(schema.mutation)
|
||||
.where((t) => t is GraphQLObjectType));
|
||||
if (schema.mutationType != null) {
|
||||
types.addAll(_fetchAllTypesFromObject(schema.mutationType));
|
||||
}
|
||||
|
||||
return types;
|
||||
|
||||
// var types = <GraphQLObjectType>[];
|
||||
//
|
||||
// for (var type in typess) {
|
||||
// if (type is GraphQLObjectType)
|
||||
// types.add(type);
|
||||
// else if (!allTypes.contains(type)) allTypes.add(type);
|
||||
// }
|
||||
//
|
||||
// return types.toSet().toList();
|
||||
}
|
||||
|
||||
List<GraphQLType> _fetchAllTypesFromObject(GraphQLObjectType objectType) {
|
||||
|
@ -433,12 +445,16 @@ List<GraphQLType> _fetchAllTypesFromObject(GraphQLObjectType objectType) {
|
|||
for (var field in objectType.fields) {
|
||||
if (field.type is GraphQLObjectType) {
|
||||
types.addAll(_fetchAllTypesFromObject(field.type as GraphQLObjectType));
|
||||
} else if (field.type is GraphQLInputObjectType) {
|
||||
for (var v in (field.type as GraphQLInputObjectType).inputFields) {
|
||||
types.addAll(_fetchAllTypesFromType(v.type));
|
||||
}
|
||||
} else {
|
||||
types.addAll(_fetchAllTypesFromType(field.type));
|
||||
}
|
||||
|
||||
for (var argument in field.arguments ?? <GraphQLFieldArgument>[]) {
|
||||
types.addAll(_fetchAllTypesFromType(argument.type));
|
||||
for (var input in field.inputs ?? <GraphQLFieldInput>[]) {
|
||||
types.addAll(_fetchAllTypesFromType(input.type));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,12 +469,18 @@ Iterable<GraphQLType> _fetchAllTypesFromType(GraphQLType type) {
|
|||
var types = <GraphQLType>[];
|
||||
|
||||
if (type is GraphQLNonNullableType) {
|
||||
types.addAll(_fetchAllTypesFromType(type.innerType));
|
||||
types.addAll(_fetchAllTypesFromType(type.ofType));
|
||||
} else if (type is GraphQLListType) {
|
||||
types.addAll(_fetchAllTypesFromType(type.innerType));
|
||||
types.addAll(_fetchAllTypesFromType(type.ofType));
|
||||
} else if (type is GraphQLObjectType) {
|
||||
types.addAll(_fetchAllTypesFromObject(type));
|
||||
} else if (type is GraphQLEnumType) {
|
||||
types.add(type);
|
||||
} else if (type is GraphQLInputObjectType) {
|
||||
for (var v in type.inputFields) {
|
||||
types.addAll(_fetchAllTypesFromType(v.type));
|
||||
}
|
||||
|
||||
types.add(type);
|
||||
} else if (type is GraphQLUnionType) {
|
||||
types.add(type);
|
||||
|
|
|
@ -21,8 +21,7 @@ GraphQLObjectType convertDartClass(Type type, [List<Type> typeArguments]) {
|
|||
return convertDartType(type, typeArguments) as GraphQLObjectType;
|
||||
}
|
||||
|
||||
final Map<Type, GraphQLType> _cache =
|
||||
<Type, GraphQLType>{};
|
||||
final Map<Type, GraphQLType> _cache = <Type, GraphQLType>{};
|
||||
|
||||
GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
|
||||
if (type == bool) {
|
||||
|
@ -73,10 +72,9 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
|
|||
GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) {
|
||||
if (_cache[mirror.reflectedType] != null) {
|
||||
return _cache[mirror.reflectedType] as GraphQLObjectType;
|
||||
} else {
|
||||
}
|
||||
} else {}
|
||||
|
||||
var fields = <GraphQLField>[];
|
||||
var fields = <GraphQLObjectField>[];
|
||||
var ready = <Symbol, MethodMirror>{};
|
||||
var forward = <Symbol, MethodMirror>{};
|
||||
|
||||
|
@ -219,7 +217,7 @@ GraphQLEnumType enumTypeFromClassMirror(ClassMirror mirror) {
|
|||
);
|
||||
}
|
||||
|
||||
GraphQLField fieldFromGetter(
|
||||
GraphQLObjectField fieldFromGetter(
|
||||
Symbol name, MethodMirror mirror, Exclude exclude, ClassMirror clazz) {
|
||||
var type = _getProvidedType(mirror.metadata);
|
||||
var wasProvided = type != null;
|
||||
|
@ -242,7 +240,7 @@ GraphQLField fieldFromGetter(
|
|||
|
||||
return field(
|
||||
nameString,
|
||||
type: type,
|
||||
type,
|
||||
deprecationReason: _getDeprecationReason(mirror.metadata),
|
||||
resolve: (obj, _) {
|
||||
if (obj is Map && exclude?.canSerialize != true) {
|
||||
|
|
|
@ -3,10 +3,12 @@ environment:
|
|||
sdk: ">=1.8.0 <3.0.0"
|
||||
dependencies:
|
||||
angel_serialize: ^2.0.0
|
||||
collection: ^1.0.0
|
||||
graphql_schema:
|
||||
path: ../graphql_schema
|
||||
graphql_parser:
|
||||
path: ../graphql_parser
|
||||
meta: ^1.0.0
|
||||
recase: ^1.0.0
|
||||
tuple: ^1.0.0
|
||||
dev_dependencies:
|
||||
|
|
|
@ -4,24 +4,24 @@ import 'package:test/test.dart';
|
|||
|
||||
void main() {
|
||||
test('single element', () async {
|
||||
var todoType = objectType('todo',fields: [
|
||||
var todoType = objectType('todo', fields: [
|
||||
field(
|
||||
'text',
|
||||
type: graphQLString,
|
||||
graphQLString,
|
||||
resolve: (obj, args) => obj.text,
|
||||
),
|
||||
field(
|
||||
'completed',
|
||||
type: graphQLBoolean,
|
||||
graphQLBoolean,
|
||||
resolve: (obj, args) => obj.completed,
|
||||
),
|
||||
]);
|
||||
|
||||
var schema = graphQLSchema(
|
||||
query: objectType('api', fields:[
|
||||
queryType: objectType('api', fields: [
|
||||
field(
|
||||
'todos',
|
||||
type: listType(todoType),
|
||||
listType(todoType),
|
||||
resolve: (_, __) => [
|
||||
new Todo(
|
||||
text: 'Clean your room!',
|
||||
|
|
Loading…
Reference in a new issue