Type introspection base

This commit is contained in:
Tobe O 2018-08-02 15:22:16 -04:00
parent 908502c66e
commit 1a80b66ea5
11 changed files with 182 additions and 31 deletions

View file

@ -1,10 +1,10 @@
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_graphql/angel_graphql.dart'; import 'package:angel_graphql/angel_graphql.dart';
import 'package:angel_serialize/angel_serialize.dart'; import 'package:angel_serialize/angel_serialize.dart';
import 'package:logging/logging.dart';
import 'package:graphql_schema/graphql_schema.dart'; import 'package:graphql_schema/graphql_schema.dart';
import 'package:graphql_server/graphql_server.dart'; import 'package:graphql_server/graphql_server.dart';
import 'package:graphql_server/mirrors.dart'; import 'package:graphql_server/mirrors.dart';
import 'package:logging/logging.dart';
main() async { main() async {
var app = new Angel(); var app = new Angel();
@ -19,7 +19,7 @@ main() async {
var todoService = app.use('api/todos', new MapService()) as Service; var todoService = app.use('api/todos', new MapService()) as Service;
var api = objectType('api', [ var api = objectType('api', fields: [
field( field(
'todo', 'todo',
type: listType(objectTypeFromDartType(Todo)), type: listType(objectTypeFromDartType(Todo)),

View file

@ -1,7 +1,7 @@
import 'package:graphql_schema/graphql_schema.dart'; import 'package:graphql_schema/graphql_schema.dart';
final GraphQLSchema todoSchema = new GraphQLSchema( final GraphQLSchema todoSchema = new GraphQLSchema(
query: objectType('Todo', [ query: objectType('Todo', fields: [
field( field(
'text', 'text',
type: graphQLString.nonNullable(), type: graphQLString.nonNullable(),

View file

@ -1,16 +1,33 @@
import 'package:graphql_schema/graphql_schema.dart'; import 'package:graphql_schema/graphql_schema.dart';
// TODO: How to handle custom types??? // TODO: How to handle custom types???
GraphQLSchema reflectSchema(GraphQLSchema schema) { GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
var objectTypes = _fetchAllTypes(schema); var objectTypes = fetchAllTypes(schema);
var typeType = _reflectSchemaTypes(schema); allTypes.addAll(objectTypes);
var typeType = _reflectSchemaTypes();
var schemaType = objectType('__Schema', [ var schemaType = objectType('__Schema', fields: [
field( field(
'types', 'types',
type: listType(typeType), type: listType(typeType),
resolve: (_, __) => objectTypes, resolve: (_, __) => objectTypes,
), ),
field(
'queryType',
type: typeType,
resolve: (_, __) => schema.query,
),
field(
'mutationType',
type: typeType,
resolve: (_, __) => schema.mutation,
),
]);
allTypes.addAll([
typeType,
schemaType,
_reflectFields(),
]); ]);
var fields = <GraphQLField>[ var fields = <GraphQLField>[
@ -37,38 +54,109 @@ GraphQLSchema reflectSchema(GraphQLSchema schema) {
fields.addAll(schema.query.fields); fields.addAll(schema.query.fields);
return new GraphQLSchema( return new GraphQLSchema(
query: objectType(schema.query.name, fields), query: objectType(schema.query.name, fields: fields),
mutation: schema.mutation, mutation: schema.mutation,
); );
} }
GraphQLObjectType _reflectSchemaTypes(GraphQLSchema schema) { GraphQLObjectType _typeType;
GraphQLObjectType _reflectSchemaTypes() {
if (_typeType == null) {
_typeType = _createTypeType();
_typeType.fields.add(
field(
'ofType',
type: _reflectSchemaTypes(),
resolve: (type, _) {
if (type is GraphQLListType)
return type.innerType;
else if (type is GraphQLNonNullableType) return type.innerType;
return null;
},
),
);
var fieldType = _reflectFields();
var typeField = fieldType.fields
.firstWhere((f) => f.name == 'type', orElse: () => null);
if (typeField == null) {
fieldType.fields.add(
field(
'type',
type: _reflectSchemaTypes(),
resolve: (f, _) => (f as GraphQLField).type,
),
);
}
}
return _typeType;
}
GraphQLObjectType _createTypeType() {
var fieldType = _reflectFields(); var fieldType = _reflectFields();
return objectType('__Type', [ return objectType('__Type', fields: [
field( field(
'name', 'name',
type: graphQLString, type: graphQLString,
resolve: (type, _) => (type as GraphQLObjectType).name, resolve: (type, _) => (type as GraphQLType).name,
),
field(
'description',
type: graphQLString,
resolve: (type, _) => (type as GraphQLType).description,
), ),
field( field(
'kind', 'kind',
type: graphQLString, type: graphQLString,
resolve: (type, _) => 'OBJECT', // TODO: Union, interface resolve: (type, _) {
var t = type as GraphQLType;
if (t is GraphQLScalarType)
return 'SCALAR';
else if (t is GraphQLObjectType)
return 'OBJECT';
else if (t is GraphQLListType)
return 'LIST';
else if (t is GraphQLNonNullableType)
return 'NON_NULL';
else
throw new UnsupportedError(
'Cannot get the kind of $t.'); // TODO: Interface + union
},
), ),
field( field(
'fields', 'fields',
type: listType(fieldType), type: listType(fieldType),
resolve: (type, _) => (type as GraphQLObjectType).fields, resolve: (type, _) => type is GraphQLObjectType ? type.fields : [],
), ),
]); ]);
} }
GraphQLObjectType _fieldType;
GraphQLObjectType _reflectFields() { GraphQLObjectType _reflectFields() {
return objectType('__Field', []); if (_fieldType == null) {
_fieldType = _createFieldType();
}
return _fieldType;
} }
List<GraphQLObjectType> _fetchAllTypes(GraphQLSchema schema) { GraphQLObjectType _createFieldType() {
return objectType('__Field', fields: [
field(
'name',
type: graphQLString,
resolve: (f, _) => (f as GraphQLField).name,
),
]);
}
List<GraphQLObjectType> fetchAllTypes(GraphQLSchema schema) {
var typess = <GraphQLType>[]; var typess = <GraphQLType>[];
typess.addAll(_fetchAllTypesFromObject(schema.query)); typess.addAll(_fetchAllTypesFromObject(schema.query));

View file

@ -1,8 +1,8 @@
part of graphql_schema.src.schema; part of graphql_schema.src.schema;
GraphQLObjectType objectType(String name, GraphQLObjectType objectType(String name,
[Iterable<GraphQLField> fields = const []]) => {String description, Iterable<GraphQLField> fields = const []}) =>
new GraphQLObjectType(name)..fields.addAll(fields ?? []); new GraphQLObjectType(name, description)..fields.addAll(fields ?? []);
GraphQLField<T, Serialized> field<T, Serialized>(String name, GraphQLField<T, Serialized> field<T, Serialized>(String name,
{Iterable<GraphQLFieldArgument<T, Serialized>> arguments: const [], {Iterable<GraphQLFieldArgument<T, Serialized>> arguments: const [],

View file

@ -4,9 +4,10 @@ class GraphQLObjectType
extends GraphQLType<Map<String, dynamic>, Map<String, dynamic>> extends GraphQLType<Map<String, dynamic>, Map<String, dynamic>>
with _NonNullableMixin<Map<String, dynamic>, Map<String, dynamic>> { with _NonNullableMixin<Map<String, dynamic>, Map<String, dynamic>> {
final String name; final String name;
final String description;
final List<GraphQLField> fields = []; final List<GraphQLField> fields = [];
GraphQLObjectType(this.name); GraphQLObjectType(this.name, this.description);
@override @override
ValidationResult<Map<String, dynamic>> validate(String key, Map input) { ValidationResult<Map<String, dynamic>> validate(String key, Map input) {

View file

@ -17,15 +17,20 @@ final GraphQLScalarType<DateTime, String> graphQLDate =
new _GraphQLDateType._(); new _GraphQLDateType._();
/// A signed 32bit integer. /// A signed 32bit integer.
final GraphQLScalarType<int, int> graphQLInt = final GraphQLScalarType<int, int> graphQLInt = new _GraphQLNumType<int>(
new _GraphQLNumType<int>((x) => x is int, 'an integer'); 'Int', 'A signed 64-bit integer.', (x) => x is int, 'an integer');
/// A signed double-precision floating-point value. /// A signed double-precision floating-point value.
final GraphQLScalarType<double, double> graphQLFloat = final GraphQLScalarType<double, double> graphQLFloat =
new _GraphQLNumType<double>((x) => x is double, 'a float'); new _GraphQLNumType<double>(
'Float',
'A signed double-precision floating-point value.',
(x) => x is double,
'a float');
abstract class GraphQLScalarType<Value, Serialized> abstract class GraphQLScalarType<Value, Serialized>
extends GraphQLType<Value, Serialized> with _NonNullableMixin<Value, Serialized> {} extends GraphQLType<Value, Serialized>
with _NonNullableMixin<Value, Serialized> {}
typedef bool _NumVerifier(x); typedef bool _NumVerifier(x);
@ -35,6 +40,12 @@ class _GraphQLBoolType extends GraphQLScalarType<bool, bool> {
return value; return value;
} }
@override
String get name => 'Boolean';
@override
String get description => 'A boolean value; can be either true or false.';
@override @override
ValidationResult<bool> validate(String key, input) { ValidationResult<bool> validate(String key, input) {
if (input != null && input is! bool) if (input != null && input is! bool)
@ -50,10 +61,12 @@ class _GraphQLBoolType extends GraphQLScalarType<bool, bool> {
} }
class _GraphQLNumType<T extends num> extends GraphQLScalarType<T, T> { class _GraphQLNumType<T extends num> extends GraphQLScalarType<T, T> {
final String name;
final String description;
final _NumVerifier verifier; final _NumVerifier verifier;
final String expected; final String expected;
_GraphQLNumType(this.verifier, this.expected); _GraphQLNumType(this.name, this.description, this.verifier, this.expected);
@override @override
ValidationResult<T> validate(String key, input) { ValidationResult<T> validate(String key, input) {
@ -78,6 +91,12 @@ class _GraphQLNumType<T extends num> extends GraphQLScalarType<T, T> {
class _GraphQLStringType extends GraphQLScalarType<String, String> { class _GraphQLStringType extends GraphQLScalarType<String, String> {
_GraphQLStringType._(); _GraphQLStringType._();
@override
String get name => 'String';
@override
String get description => 'A character sequence.';
@override @override
String serialize(String value) => value; String serialize(String value) => value;
@ -95,6 +114,12 @@ class _GraphQLDateType extends GraphQLScalarType<DateTime, String>
with _NonNullableMixin<DateTime, String> { with _NonNullableMixin<DateTime, String> {
_GraphQLDateType._(); _GraphQLDateType._();
@override
String get name => 'Date';
@override
String get description => 'An ISO0-8601 Date.';
@override @override
String serialize(DateTime value) => value.toIso8601String(); String serialize(DateTime value) => value.toIso8601String();

View file

@ -1,9 +1,16 @@
part of graphql_schema.src.schema; part of graphql_schema.src.schema;
abstract class GraphQLType<Value, Serialized> { abstract class GraphQLType<Value, Serialized> {
String get name;
String get description;
Serialized serialize(Value value); Serialized serialize(Value value);
Value deserialize(Serialized serialized); Value deserialize(Serialized serialized);
ValidationResult<Serialized> validate(String key, Serialized input); ValidationResult<Serialized> validate(String key, Serialized input);
GraphQLType<Value, Serialized> nonNullable(); GraphQLType<Value, Serialized> nonNullable();
} }
@ -16,8 +23,16 @@ class GraphQLListType<Value, Serialized>
extends GraphQLType<List<Value>, List<Serialized>> extends GraphQLType<List<Value>, List<Serialized>>
with _NonNullableMixin<List<Value>, List<Serialized>> { with _NonNullableMixin<List<Value>, List<Serialized>> {
final GraphQLType<Value, Serialized> innerType; final GraphQLType<Value, Serialized> innerType;
GraphQLListType(this.innerType); GraphQLListType(this.innerType);
@override
String get name => null;
@override
String get description =>
'A list of items of type ${innerType.name ?? '(${innerType.description}).'}';
@override @override
ValidationResult<List<Serialized>> validate( ValidationResult<List<Serialized>> validate(
String key, List<Serialized> input) { String key, List<Serialized> input) {
@ -55,6 +70,7 @@ class GraphQLListType<Value, Serialized>
abstract class _NonNullableMixin<Value, Serialized> abstract class _NonNullableMixin<Value, Serialized>
implements GraphQLType<Value, Serialized> { implements GraphQLType<Value, Serialized> {
GraphQLType<Value, Serialized> _nonNullableCache; GraphQLType<Value, Serialized> _nonNullableCache;
GraphQLType<Value, Serialized> nonNullable() => _nonNullableCache ??= GraphQLType<Value, Serialized> nonNullable() => _nonNullableCache ??=
new GraphQLNonNullableType<Value, Serialized>._(this); new GraphQLNonNullableType<Value, Serialized>._(this);
} }
@ -62,8 +78,16 @@ abstract class _NonNullableMixin<Value, Serialized>
class GraphQLNonNullableType<Value, Serialized> class GraphQLNonNullableType<Value, Serialized>
extends GraphQLType<Value, Serialized> { extends GraphQLType<Value, Serialized> {
final GraphQLType<Value, Serialized> innerType; final GraphQLType<Value, Serialized> innerType;
GraphQLNonNullableType._(this.innerType); GraphQLNonNullableType._(this.innerType);
@override
String get name => innerType.name;
@override
String get description =>
'A non-nullable binding to ${innerType.name ?? '(${innerType.description}).'}';
@override @override
GraphQLType<Value, Serialized> nonNullable() { GraphQLType<Value, Serialized> nonNullable() {
throw new UnsupportedError( throw new UnsupportedError(

View file

@ -1,14 +1,14 @@
import 'package:graphql_schema/graphql_schema.dart'; import 'package:graphql_schema/graphql_schema.dart';
final GraphQLObjectType pokemonType = objectType('Pokemon', [ final GraphQLObjectType pokemonType = objectType('Pokemon', fields:[
field('species', type: graphQLString), field('species', type: graphQLString),
field('catch_date', type: graphQLDate) field('catch_date', type: graphQLDate)
]); ]);
final GraphQLObjectType trainerType = final GraphQLObjectType trainerType =
objectType('Trainer', [field('name', type: graphQLString)]); objectType('Trainer', fields:[field('name', type: graphQLString)]);
final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', [ final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', fields:[
field('trainer', type: trainerType), field('trainer', type: trainerType),
field('pokemon_species', type: listType(pokemonType)) field('pokemon_species', type: listType(pokemonType))
]); ]);

View file

@ -10,7 +10,12 @@ class GraphQL {
GraphQL(GraphQLSchema schema, {bool introspect: true}) : _schema = schema { GraphQL(GraphQLSchema schema, {bool introspect: true}) : _schema = schema {
if (introspect) { if (introspect) {
_schema = reflectSchema(_schema); var allTypes = <GraphQLType>[];
_schema = reflectSchema(_schema, allTypes);
for (var type in allTypes) {
customTypes[type.name] = type;
}
} }
if (_schema.query != null) customTypes[_schema.query.name] = _schema.query; if (_schema.query != null) customTypes[_schema.query.name] = _schema.query;

View file

@ -23,8 +23,13 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
return graphQLBoolean; return graphQLBoolean;
} else if (type == int) { } else if (type == int) {
return graphQLInt; return graphQLInt;
} else if (type == double || type == num) { } else if (type == double) {
return graphQLFloat; return graphQLFloat;
} else if (type == num) {
throw new UnsupportedError(
'Cannot convert `num` to a GraphQL type. Choose `int` or `float` instead.');
} else if (type == Null) {
throw new UnsupportedError('Cannot convert `Null` to a GraphQL type.');
} else if (type == String) { } else if (type == String) {
return graphQLString; return graphQLString;
} else if (type == DateTime) { } else if (type == DateTime) {
@ -57,7 +62,10 @@ GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) {
} }
} }
return objectType(MirrorSystem.getName(mirror.simpleName), fields); return objectType(
MirrorSystem.getName(mirror.simpleName),
fields: fields,
);
} }
GraphQLField fieldFromGetter( GraphQLField fieldFromGetter(

View file

@ -4,7 +4,7 @@ import 'package:test/test.dart';
void main() { void main() {
test('single element', () async { test('single element', () async {
var todoType = objectType('todo', [ var todoType = objectType('todo',fields: [
field( field(
'text', 'text',
type: graphQLString, type: graphQLString,
@ -18,7 +18,7 @@ void main() {
]); ]);
var schema = graphQLSchema( var schema = graphQLSchema(
query: objectType('api', [ query: objectType('api', fields:[
field( field(
'todos', 'todos',
type: listType(todoType), type: listType(todoType),