Type introspection base
This commit is contained in:
parent
908502c66e
commit
1a80b66ea5
11 changed files with 182 additions and 31 deletions
|
@ -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)),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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 [],
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -17,15 +17,20 @@ final GraphQLScalarType<DateTime, String> graphQLDate =
|
|||
new _GraphQLDateType._();
|
||||
|
||||
/// A signed 32‐bit 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();
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
]);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue