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

View file

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

View file

@ -1,16 +1,33 @@
import 'package:graphql_schema/graphql_schema.dart';
// TODO: How to handle custom types???
GraphQLSchema reflectSchema(GraphQLSchema schema) {
var objectTypes = _fetchAllTypes(schema);
var typeType = _reflectSchemaTypes(schema);
GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
var objectTypes = fetchAllTypes(schema);
allTypes.addAll(objectTypes);
var typeType = _reflectSchemaTypes();
var schemaType = objectType('__Schema', [
var schemaType = objectType('__Schema', fields: [
field(
'types',
type: listType(typeType),
resolve: (_, __) => objectTypes,
),
field(
'queryType',
type: typeType,
resolve: (_, __) => schema.query,
),
field(
'mutationType',
type: typeType,
resolve: (_, __) => schema.mutation,
),
]);
allTypes.addAll([
typeType,
schemaType,
_reflectFields(),
]);
var fields = <GraphQLField>[
@ -37,38 +54,109 @@ GraphQLSchema reflectSchema(GraphQLSchema schema) {
fields.addAll(schema.query.fields);
return new GraphQLSchema(
query: objectType(schema.query.name, fields),
query: objectType(schema.query.name, fields: fields),
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();
return objectType('__Type', [
return objectType('__Type', fields: [
field(
'name',
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(
'kind',
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(
'fields',
type: listType(fieldType),
resolve: (type, _) => (type as GraphQLObjectType).fields,
resolve: (type, _) => type is GraphQLObjectType ? type.fields : [],
),
]);
}
GraphQLObjectType _fieldType;
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>[];
typess.addAll(_fetchAllTypesFromObject(schema.query));

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,14 @@
import 'package:graphql_schema/graphql_schema.dart';
final GraphQLObjectType pokemonType = objectType('Pokemon', [
final GraphQLObjectType pokemonType = objectType('Pokemon', fields:[
field('species', type: graphQLString),
field('catch_date', type: graphQLDate)
]);
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('pokemon_species', type: listType(pokemonType))
]);

View file

@ -10,7 +10,12 @@ class GraphQL {
GraphQL(GraphQLSchema schema, {bool introspect: true}) : _schema = schema {
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;

View file

@ -23,8 +23,13 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
return graphQLBoolean;
} else if (type == int) {
return graphQLInt;
} else if (type == double || type == num) {
} else if (type == double) {
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) {
return graphQLString;
} 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(

View file

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