diff --git a/.idea/runConfigurations/tests_in_mirrors_test_dart.xml b/.idea/runConfigurations/tests_in_mirrors_test_dart.xml new file mode 100644 index 00000000..c9450200 --- /dev/null +++ b/.idea/runConfigurations/tests_in_mirrors_test_dart.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/angel_graphql/example/main.dart b/angel_graphql/example/main.dart index 4ff9916f..99f57686 100644 --- a/angel_graphql/example/main.dart +++ b/angel_graphql/example/main.dart @@ -25,7 +25,7 @@ main() async { fields: [ field( 'todo', - type: listType(objectTypeFromDartType(Todo).nonNullable()), + type: listType(convertDartType(Todo).nonNullable()), resolve: resolveFromService(todoService), arguments: [ new GraphQLFieldArgument('id', graphQLId), diff --git a/graphql_schema/lib/src/enum.dart b/graphql_schema/lib/src/enum.dart index d85a7eca..3d68cc2c 100644 --- a/graphql_schema/lib/src/enum.dart +++ b/graphql_schema/lib/src/enum.dart @@ -1,39 +1,54 @@ part of graphql_schema.src.schema; -GraphQLEnumType enumType(String name, List values, +GraphQLEnumType enumType(String name, Map values, {String description}) { - return new GraphQLEnumType( - name, values.map((s) => new GraphQLEnumValue(s)).toList(), + return new GraphQLEnumType( + name, values.keys.map((k) => new GraphQLEnumValue(k, values[k])).toList(), description: description); } -class GraphQLEnumType extends _GraphQLStringType { +GraphQLEnumType enumTypeFromStrings(String name, List values, + {String description}) { + return new GraphQLEnumType( + name, values.map((s) => new GraphQLEnumValue(s, s)).toList(), + description: description); +} + +class GraphQLEnumType extends GraphQLScalarType + with _NonNullableMixin { final String name; - final List values; + final List> values; final String description; - GraphQLEnumType(this.name, this.values, {this.description}) : super._(); + GraphQLEnumType(this.name, this.values, {this.description}); + + @override + String serialize(Value value) { + return values.firstWhere((v) => v.value == value).name; + } + + @override + Value deserialize(String serialized) { + return values.firstWhere((v) => v.name == serialized).value; + } @override ValidationResult validate(String key, String input) { - var result = super.validate(key, input); - - if (result.successful && - !values.map((v) => v.name).contains(result.value)) { - return result._asFailure() - ..errors.add( - '"${result.value}" is not a valid value for the enum "$name".'); + if (!values.any((v) => v.name == input)) { + return new ValidationResult._failure( + ['"$input" is not a valid value for the enum "$name".']); } - return result; + return new ValidationResult._ok(input); } } -class GraphQLEnumValue { +class GraphQLEnumValue { final String name; + final Value value; final String deprecationReason; - GraphQLEnumValue(this.name, {this.deprecationReason}); + GraphQLEnumValue(this.name, this.value, {this.deprecationReason}); bool get isDeprecated => deprecationReason != null; diff --git a/graphql_schema/test/validation_test.dart b/graphql_schema/test/validation_test.dart index bfe23bed..ce29838c 100644 --- a/graphql_schema/test/validation_test.dart +++ b/graphql_schema/test/validation_test.dart @@ -2,7 +2,7 @@ import 'package:graphql_schema/graphql_schema.dart'; import 'package:test/test.dart'; void main() { - var typeType = enumType('Type', [ + var typeType = enumTypeFromStrings('Type', [ 'FIRE', 'WATER', 'GRASS', diff --git a/graphql_server/lib/mirrors.dart b/graphql_server/lib/mirrors.dart index 362eb221..36e8ef34 100644 --- a/graphql_server/lib/mirrors.dart +++ b/graphql_server/lib/mirrors.dart @@ -9,7 +9,7 @@ import 'package:tuple/tuple.dart'; /// /// This function is aware of the annotations from `package:angel_serialize`, and works seamlessly /// with them. -GraphQLType objectTypeFromDartType(Type type, [List typeArguments]) { +GraphQLType convertDartType(Type type, [List typeArguments]) { var tuple = new Tuple2(type, typeArguments); return _cache.putIfAbsent( tuple, () => _objectTypeFromDartType(type, typeArguments)); @@ -44,7 +44,13 @@ GraphQLType _objectTypeFromDartType(Type type, [List typeArguments]) { '$type is not a class, and therefore cannot be converted into a GraphQL object type.'); } - return objectTypeFromClassMirror(mirror as ClassMirror); + var clazz = mirror as ClassMirror; + + if (clazz.isEnum) { + return enumTypeFromClassMirror(clazz); + } + + return objectTypeFromClassMirror(clazz); } GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) { @@ -69,9 +75,30 @@ GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) { ); } +GraphQLEnumType enumTypeFromClassMirror(ClassMirror mirror) { + var values = []; + + for (var name in mirror.staticMembers.keys) { + var methodMirror = mirror.staticMembers[name]; + values.add( + new GraphQLEnumValue( + MirrorSystem.getName(name), + mirror.getField(name).reflectee, + deprecationReason: _getDeprecationReason(methodMirror.metadata), + ), + ); + } + + return new GraphQLEnumType( + MirrorSystem.getName(mirror.simpleName), + values, + description: _getDescription(mirror.metadata), + ); +} + GraphQLField fieldFromGetter( Symbol name, MethodMirror mirror, Exclude exclude, ClassMirror clazz) { - var type = objectTypeFromDartType(mirror.returnType.reflectedType, + var type = convertDartType(mirror.returnType.reflectedType, mirror.returnType.typeArguments.map((t) => t.reflectedType).toList()); var nameString = _getSerializedName(name, mirror, clazz); diff --git a/graphql_server/test/mirrors_test.dart b/graphql_server/test/mirrors_test.dart new file mode 100644 index 00000000..f6cfeee5 --- /dev/null +++ b/graphql_server/test/mirrors_test.dart @@ -0,0 +1,46 @@ +import 'package:graphql_schema/graphql_schema.dart'; +import 'package:graphql_server/mirrors.dart'; +import 'package:test/test.dart'; + +void main() { + group('convertDartType', () { + group('on enum', () { + var type = convertDartType(RomanceLanguage); + var asEnumType = type as GraphQLEnumType; + + test('produces enum type', () { + expect(type is GraphQLEnumType, true); + }); + + test('rejects invalid value', () { + expect(asEnumType.validate('@root', 'GERMAN').successful, false); + }); + + test('accepts valid value', () { + expect(asEnumType.validate('@root', 'SPANISH').successful, true); + }); + + test('deserializes to concrete value', () { + expect(asEnumType.deserialize('ITALIAN'), RomanceLanguage.ITALIAN); + }); + + test('serializes to concrete value', () { + expect(asEnumType.serialize(RomanceLanguage.FRANCE), 'FRANCE'); + }); + + test('fails to serialize invalid value', () { + expect(() => asEnumType.serialize(34), throwsStateError); + }); + + test('fails to deserialize invalid value', () { + expect(() => asEnumType.deserialize('JAPANESE'), throwsStateError); + }); + }); + }); +} + +enum RomanceLanguage { + SPANISH, + FRANCE, + ITALIAN, +}