2018-08-02 17:02:00 +00:00
|
|
|
import 'dart:mirrors';
|
|
|
|
|
|
|
|
import 'package:angel_serialize/angel_serialize.dart';
|
|
|
|
import 'package:graphql_schema/graphql_schema.dart';
|
|
|
|
import 'package:recase/recase.dart';
|
|
|
|
|
|
|
|
/// Reflects upon a given [type] and dynamically generates a [GraphQLType] that corresponds to it.
|
|
|
|
///
|
|
|
|
/// This function is aware of the annotations from `package:angel_serialize`, and works seamlessly
|
|
|
|
/// with them.
|
2018-08-03 22:41:13 +00:00
|
|
|
GraphQLType convertDartType(Type type, [List<Type> typeArguments]) {
|
2018-08-04 03:05:51 +00:00
|
|
|
if (_cache[type] != null) {
|
|
|
|
return _cache[type];
|
|
|
|
} else {
|
|
|
|
return _objectTypeFromDartType(type, typeArguments);
|
|
|
|
}
|
2018-08-02 17:19:30 +00:00
|
|
|
}
|
|
|
|
|
2018-08-04 15:01:49 +00:00
|
|
|
/// Shorthand for [convertDartType], for when you know the result will be an object type.
|
|
|
|
GraphQLObjectType convertDartClass(Type type, [List<Type> typeArguments]) {
|
|
|
|
return convertDartType(type, typeArguments) as GraphQLObjectType;
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
final Map<Type, GraphQLType> _cache = <Type, GraphQLType>{};
|
2018-08-02 17:19:30 +00:00
|
|
|
|
|
|
|
GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
|
2018-08-02 17:02:00 +00:00
|
|
|
if (type == bool) {
|
|
|
|
return graphQLBoolean;
|
|
|
|
} else if (type == int) {
|
|
|
|
return graphQLInt;
|
2018-08-02 19:22:16 +00:00
|
|
|
} else if (type == double) {
|
2018-08-02 17:02:00 +00:00
|
|
|
return graphQLFloat;
|
2018-08-02 19:22:16 +00:00
|
|
|
} 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.');
|
2018-08-02 17:02:00 +00:00
|
|
|
} else if (type == String) {
|
|
|
|
return graphQLString;
|
2018-08-02 17:19:30 +00:00
|
|
|
} else if (type == DateTime) {
|
|
|
|
return graphQLDate;
|
2018-08-02 17:02:00 +00:00
|
|
|
}
|
|
|
|
|
2018-08-02 17:19:30 +00:00
|
|
|
var mirror = reflectType(
|
|
|
|
type, typeArguments?.isNotEmpty == true ? typeArguments : null);
|
2018-08-02 17:02:00 +00:00
|
|
|
|
|
|
|
if (mirror is! ClassMirror) {
|
|
|
|
throw new StateError(
|
|
|
|
'$type is not a class, and therefore cannot be converted into a GraphQL object type.');
|
|
|
|
}
|
|
|
|
|
2018-08-03 22:41:13 +00:00
|
|
|
var clazz = mirror as ClassMirror;
|
|
|
|
|
2018-08-04 03:05:51 +00:00
|
|
|
if (clazz.isAssignableTo(reflectType(Iterable))) {
|
|
|
|
if (clazz.typeArguments.isNotEmpty) {
|
|
|
|
var inner = convertDartType(clazz.typeArguments[0].reflectedType);
|
|
|
|
//if (inner == null) return null;
|
2018-08-05 01:49:13 +00:00
|
|
|
return listOf(inner.nonNullable());
|
2018-08-04 03:05:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throw new ArgumentError(
|
|
|
|
'Cannot convert ${clazz.reflectedType}, an iterable WITHOUT a type argument, into a GraphQL type.');
|
|
|
|
}
|
|
|
|
|
2018-08-03 22:41:13 +00:00
|
|
|
if (clazz.isEnum) {
|
|
|
|
return enumTypeFromClassMirror(clazz);
|
|
|
|
}
|
|
|
|
|
|
|
|
return objectTypeFromClassMirror(clazz);
|
2018-08-02 17:02:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) {
|
2018-08-04 03:05:51 +00:00
|
|
|
if (_cache[mirror.reflectedType] != null) {
|
|
|
|
return _cache[mirror.reflectedType] as GraphQLObjectType;
|
2018-08-04 19:18:53 +00:00
|
|
|
} else {}
|
2018-08-04 03:05:51 +00:00
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
var fields = <GraphQLObjectField>[];
|
2018-08-04 03:05:51 +00:00
|
|
|
var ready = <Symbol, MethodMirror>{};
|
|
|
|
var forward = <Symbol, MethodMirror>{};
|
2018-08-02 17:02:00 +00:00
|
|
|
|
2018-08-04 00:05:51 +00:00
|
|
|
void walkMap(Map<Symbol, MethodMirror> map) {
|
|
|
|
for (var name in map.keys) {
|
|
|
|
var methodMirror = map[name];
|
|
|
|
var exclude = _getExclude(name, methodMirror);
|
|
|
|
var canAdd = name != #hashCode &&
|
|
|
|
name != #runtimeType &&
|
|
|
|
!methodMirror.isPrivate &&
|
|
|
|
exclude?.canSerialize != true;
|
2018-08-04 03:05:51 +00:00
|
|
|
|
2018-08-04 00:05:51 +00:00
|
|
|
if (methodMirror.isGetter && canAdd) {
|
|
|
|
fields.add(fieldFromGetter(name, methodMirror, exclude, mirror));
|
|
|
|
}
|
2018-08-02 17:02:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-04 03:05:51 +00:00
|
|
|
bool isReady(TypeMirror returnType) {
|
|
|
|
var canContinue = returnType.reflectedType != mirror.reflectedType;
|
|
|
|
|
|
|
|
if (canContinue &&
|
|
|
|
returnType.isAssignableTo(reflectType(Iterable)) &&
|
|
|
|
returnType.typeArguments.isNotEmpty &&
|
|
|
|
!isReady(returnType.typeArguments[0])) {
|
|
|
|
canContinue = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return canContinue;
|
|
|
|
}
|
|
|
|
|
|
|
|
void prepReadyForward(Map<Symbol, MethodMirror> map) {
|
|
|
|
map.forEach((name, methodMirror) {
|
|
|
|
if (methodMirror.isGetter &&
|
|
|
|
name != #_identityHashCode &&
|
|
|
|
name != #runtimeType &&
|
|
|
|
name != #hashCode &&
|
|
|
|
MirrorSystem.getName(name) != '_identityHashCode') {
|
|
|
|
var returnType = methodMirror.returnType;
|
|
|
|
|
|
|
|
if (isReady(returnType)) {
|
|
|
|
ready[name] = methodMirror;
|
|
|
|
} else {
|
|
|
|
forward[name] = methodMirror;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
prepReadyForward(mirror.instanceMembers);
|
|
|
|
|
|
|
|
walkMap(ready);
|
2018-08-04 00:05:51 +00:00
|
|
|
|
|
|
|
if (mirror.isAbstract) {
|
|
|
|
var decls = <Symbol, MethodMirror>{};
|
|
|
|
|
|
|
|
mirror.declarations.forEach((name, decl) {
|
|
|
|
if (decl is MethodMirror) {
|
|
|
|
decls[name] = decl;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-08-04 03:05:51 +00:00
|
|
|
ready.clear();
|
|
|
|
forward.clear();
|
|
|
|
prepReadyForward(decls);
|
|
|
|
walkMap(ready);
|
|
|
|
//walkMap(decls);
|
2018-08-04 00:05:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var inheritsFrom = <GraphQLObjectType>[];
|
|
|
|
var primitiveTypes = const <Type>[
|
|
|
|
String,
|
|
|
|
bool,
|
|
|
|
num,
|
|
|
|
int,
|
|
|
|
double,
|
|
|
|
Object,
|
|
|
|
dynamic,
|
|
|
|
Null,
|
|
|
|
Type,
|
|
|
|
Symbol
|
|
|
|
];
|
|
|
|
|
|
|
|
void walk(ClassMirror parent) {
|
|
|
|
if (!primitiveTypes.contains(parent.reflectedType)) {
|
|
|
|
if (parent.isAbstract) {
|
|
|
|
var obj = convertDartType(parent.reflectedType);
|
|
|
|
|
|
|
|
if (obj is GraphQLObjectType && !inheritsFrom.contains(obj)) {
|
|
|
|
inheritsFrom.add(obj);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
walk(parent.superclass);
|
|
|
|
parent.superinterfaces.forEach(walk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
walk(mirror.superclass);
|
|
|
|
mirror.superinterfaces.forEach(walk);
|
|
|
|
|
2018-08-04 03:05:51 +00:00
|
|
|
var result = _cache[mirror.reflectedType];
|
|
|
|
|
|
|
|
if (result == null) {
|
|
|
|
result = objectType(
|
|
|
|
MirrorSystem.getName(mirror.simpleName),
|
|
|
|
fields: fields,
|
|
|
|
isInterface: mirror.isAbstract,
|
|
|
|
interfaces: inheritsFrom,
|
|
|
|
description: _getDescription(mirror.metadata),
|
|
|
|
);
|
|
|
|
_cache[mirror.reflectedType] = result;
|
|
|
|
walkMap(forward);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result as GraphQLObjectType;
|
2018-08-02 17:02:00 +00:00
|
|
|
}
|
|
|
|
|
2018-08-03 22:41:13 +00:00
|
|
|
GraphQLEnumType enumTypeFromClassMirror(ClassMirror mirror) {
|
|
|
|
var values = <GraphQLEnumValue>[];
|
|
|
|
|
|
|
|
for (var name in mirror.staticMembers.keys) {
|
2018-08-03 23:09:16 +00:00
|
|
|
if (name != #values) {
|
|
|
|
var methodMirror = mirror.staticMembers[name];
|
|
|
|
values.add(
|
|
|
|
new GraphQLEnumValue(
|
|
|
|
MirrorSystem.getName(name),
|
2018-08-04 00:05:51 +00:00
|
|
|
mirror.getField(name).reflectee,
|
2018-08-03 23:09:16 +00:00
|
|
|
description: _getDescription(methodMirror.metadata),
|
|
|
|
deprecationReason: _getDeprecationReason(methodMirror.metadata),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2018-08-03 22:41:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return new GraphQLEnumType(
|
|
|
|
MirrorSystem.getName(mirror.simpleName),
|
|
|
|
values,
|
|
|
|
description: _getDescription(mirror.metadata),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
GraphQLObjectField fieldFromGetter(
|
2018-08-02 17:02:00 +00:00
|
|
|
Symbol name, MethodMirror mirror, Exclude exclude, ClassMirror clazz) {
|
2018-08-04 00:12:23 +00:00
|
|
|
var type = _getProvidedType(mirror.metadata);
|
|
|
|
var wasProvided = type != null;
|
|
|
|
|
|
|
|
if (!wasProvided) {
|
2018-08-04 03:05:51 +00:00
|
|
|
var returnType = mirror.returnType;
|
|
|
|
|
|
|
|
if (!clazz.isAssignableTo(returnType)) {
|
|
|
|
type = convertDartType(returnType.reflectedType,
|
|
|
|
mirror.returnType.typeArguments.map((t) => t.reflectedType).toList());
|
|
|
|
}
|
2018-08-04 00:12:23 +00:00
|
|
|
}
|
2018-08-02 17:02:00 +00:00
|
|
|
|
|
|
|
var nameString = _getSerializedName(name, mirror, clazz);
|
|
|
|
var defaultValue = _getDefaultValue(mirror);
|
|
|
|
|
2018-08-04 00:12:23 +00:00
|
|
|
if (!wasProvided && (nameString == 'id' && _autoNames(clazz))) {
|
2018-08-02 17:02:00 +00:00
|
|
|
type = graphQLId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return field(
|
|
|
|
nameString,
|
2018-08-04 19:18:53 +00:00
|
|
|
type,
|
2018-08-03 18:59:31 +00:00
|
|
|
deprecationReason: _getDeprecationReason(mirror.metadata),
|
2018-08-02 17:02:00 +00:00
|
|
|
resolve: (obj, _) {
|
|
|
|
if (obj is Map && exclude?.canSerialize != true) {
|
|
|
|
return obj[nameString];
|
|
|
|
} else if (obj != null && exclude?.canSerialize != true) {
|
|
|
|
return reflect(obj).getField(name);
|
|
|
|
} else {
|
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Exclude _getExclude(Symbol name, MethodMirror mirror) {
|
|
|
|
for (var obj in mirror.metadata) {
|
|
|
|
if (obj.reflectee is Exclude) {
|
|
|
|
var exclude = obj.reflectee as Exclude;
|
|
|
|
return exclude;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
String _getSerializedName(Symbol name, MethodMirror mirror, ClassMirror clazz) {
|
|
|
|
// First search for an @Alias()
|
|
|
|
for (var obj in mirror.metadata) {
|
|
|
|
if (obj.reflectee is Alias) {
|
|
|
|
var alias = obj.reflectee as Alias;
|
|
|
|
return alias.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, search for a @Serializable()
|
|
|
|
for (var obj in clazz.metadata) {
|
|
|
|
if (obj.reflectee is Serializable) {
|
|
|
|
var ann = obj.reflectee as Serializable;
|
|
|
|
|
|
|
|
if (ann.autoSnakeCaseNames != false) {
|
|
|
|
return new ReCase(MirrorSystem.getName(name)).snakeCase;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return MirrorSystem.getName(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
dynamic _getDefaultValue(MethodMirror mirror) {
|
|
|
|
// Search for a @DefaultValue
|
|
|
|
for (var obj in mirror.metadata) {
|
|
|
|
if (obj.reflectee is DefaultValue) {
|
|
|
|
var ann = obj.reflectee as DefaultValue;
|
|
|
|
return ann.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _autoNames(ClassMirror clazz) {
|
|
|
|
// Search for a @Serializable()
|
|
|
|
for (var obj in clazz.metadata) {
|
|
|
|
if (obj.reflectee is Serializable) {
|
|
|
|
var ann = obj.reflectee as Serializable;
|
|
|
|
return ann.autoIdAndDateFields != false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2018-08-03 18:59:31 +00:00
|
|
|
|
|
|
|
String _getDeprecationReason(List<InstanceMirror> metadata) {
|
|
|
|
for (var obj in metadata) {
|
|
|
|
if (obj.reflectee is Deprecated) {
|
|
|
|
var expires = (obj.reflectee as Deprecated).expires;
|
|
|
|
|
|
|
|
if (expires == deprecated.expires) {
|
|
|
|
return 'Expires after $expires';
|
|
|
|
} else {
|
2018-08-03 21:51:30 +00:00
|
|
|
return deprecated.expires;
|
2018-08-03 18:59:31 +00:00
|
|
|
}
|
2018-08-03 21:51:30 +00:00
|
|
|
} else if (obj.reflectee is GraphQLDocumentation) {
|
|
|
|
return (obj.reflectee as GraphQLDocumentation).deprecationReason;
|
2018-08-03 18:59:31 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-03 21:07:08 +00:00
|
|
|
|
|
|
|
return null;
|
2018-08-03 18:59:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String _getDescription(List<InstanceMirror> metadata) {
|
|
|
|
for (var obj in metadata) {
|
|
|
|
if (obj.reflectee is GraphQLDocumentation) {
|
|
|
|
return (obj.reflectee as GraphQLDocumentation).description;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
2018-08-04 00:12:23 +00:00
|
|
|
|
|
|
|
GraphQLType _getProvidedType(List<InstanceMirror> metadata) {
|
|
|
|
for (var obj in metadata) {
|
|
|
|
if (obj.reflectee is GraphQLDocumentation) {
|
|
|
|
return (obj.reflectee as GraphQLDocumentation).type();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|