Fixed case of recursive types in mirrors

This commit is contained in:
Tobe O 2018-08-03 23:05:51 -04:00
parent a2454be927
commit cf9d94d123
20 changed files with 429 additions and 23 deletions

View file

@ -3,6 +3,7 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/angel_graphql/angel_graphql.iml" filepath="$PROJECT_DIR$/angel_graphql/angel_graphql.iml" />
<module fileurl="file://$PROJECT_DIR$/example_star_wars/example_star_wars.iml" filepath="$PROJECT_DIR$/example_star_wars/example_star_wars.iml" />
<module fileurl="file://$PROJECT_DIR$/graphql.iml" filepath="$PROJECT_DIR$/graphql.iml" />
<module fileurl="file://$PROJECT_DIR$/graphql_parser/graphql_parser.iml" filepath="$PROJECT_DIR$/graphql_parser/graphql_parser.iml" />
<module fileurl="file://$PROJECT_DIR$/graphql_schema/graphql_schema.iml" filepath="$PROJECT_DIR$/graphql_schema/graphql_schema.iml" />

View file

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="server.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
<option name="filePath" value="$PROJECT_DIR$/example_star_wars/bin/server.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$/example_star_wars" />
<method />
</configuration>
</component>

View file

@ -56,7 +56,7 @@ main() async {
);
app.all('/graphql', graphQLHttp(new GraphQL(schema)));
app.get('/graphiql', graphiql());
app.get('/graphiql', graphiQL());
await todoService
.create({'text': 'Clean your room!', 'completion_status': 'COMPLETE'});

View file

@ -2,11 +2,15 @@ import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
RequestHandler graphiql({String graphqlEndpoint: '/graphql'}) {
/// Returns a simple [RequestHandler] that renders the GraphiQL visual interface for GraphQL.
///
/// By default, the interface expects your backend to be mounted at `/graphql`; this is configurable
/// via [graphQLEndpoint].
RequestHandler graphiQL({String graphQLEndpoint: '/graphql'}) {
return (req, res) {
res
..contentType = new ContentType('text', 'html')
..write(renderGraphiql(graphqlEndpoint: graphqlEndpoint))
..write(renderGraphiql(graphqlEndpoint: graphQLEndpoint))
..end();
};
}

View file

@ -15,7 +15,11 @@ final Validator graphQlPostBody = new Validator({
'variables': predicate((v) => v == null || v is String || v is Map),
});
RequestHandler graphQLHttp(GraphQL graphQl) {
/// A [RequestHandler] that serves a spec-compliant GraphQL backend.
///
/// Follows the guidelines listed here:
/// https://graphql.org/learn/serving-over-http/
RequestHandler graphQLHttp(GraphQL graphQL) {
return (req, res) async {
executeMap(Map map) async {
var text = req.body['query'] as String;
@ -27,7 +31,7 @@ RequestHandler graphQLHttp(GraphQL graphQl) {
}
return {
'data': await graphQl.parseAndExecute(
'data': await graphQL.parseAndExecute(
text,
sourceUrl: 'input',
operationName: operationName,
@ -45,7 +49,7 @@ RequestHandler graphQLHttp(GraphQL graphQl) {
if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) {
var text = utf8.decode(await req.lazyOriginalBuffer());
return {
'data': await graphQl.parseAndExecute(text, sourceUrl: 'input')
'data': await graphQL.parseAndExecute(text, sourceUrl: 'input')
};
} else if (req.headers.contentType?.mimeType == 'application/json') {
if (await validate(graphQlPostBody)(req, res)) {

93
example_star_wars/.gitignore vendored Normal file
View file

@ -0,0 +1,93 @@
# See https://www.dartlang.org/tools/private-files.html
# Files and directories created by pub
.buildlog
.packages
.project
.pub/
.scripts-bin/
build/
**/packages/
# Files created by dart2js
# (Most Dart developers will use pub build to compile Dart, use/modify these
# rules if you intend to use dart2js directly
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
# differentiate from explicit Javascript files)
*.dart.js
*.part.js
*.js.deps
*.js.map
*.info.json
# Directory created by dartdoc
doc/api/
# Don't commit pubspec lock file
# (Library packages only! Remove pattern if developing an application package)
pubspec.lock
### Dart template
# See https://www.dartlang.org/tools/private-files.html
# Files and directories created by pub
# SDK 1.20 and later (no longer creates packages directories)
# Older SDK versions
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
# Files created by dart2js
# (Most Dart developers will use pub build to compile Dart, use/modify these
# rules if you intend to use dart2js directly
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
# differentiate from explicit Javascript files)
# Directory created by dartdoc
# Don't commit pubspec lock file
# (Library packages only! Remove pattern if developing an application package)
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
../.idea/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

21
example_star_wars/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 The Angel Framework
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,3 @@
analyzer:
strong-mode:
implicit-casts: false

View file

@ -0,0 +1,30 @@
import 'dart:async';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_hot/angel_hot.dart';
import 'package:logging/logging.dart';
import 'package:star_wars/src/pretty_logging.dart' as star_wars;
import 'package:star_wars/star_wars.dart' as star_wars;
main() async {
Future<Angel> createServer() async {
var app = new Angel()
..lazyParseBodies = true
..storeOriginalBuffer = true;
app.logger = new Logger('star_wars')..onRecord.listen(star_wars.prettyLog);
await app.configure(star_wars.configureServer);
return app;
}
hierarchicalLoggingEnabled = true;
var hot = new HotReloader(createServer, [new Directory('lib')]);
var server = await hot.startServer('127.0.0.1', 3000);
var serverUrl =
new Uri(scheme: 'http', host: server.address.address, port: server.port);
var graphiQLUrl = serverUrl.replace(path: '/graphiql');
print('Listening at $serverUrl');
print('GraphiQL endpoint: $graphiQLUrl');
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

View file

@ -0,0 +1,11 @@
import 'episode.dart';
abstract class Character {
String get id;
String get name;
List<Episode> get appearsIn;
List<Character> get friends;
}

View file

@ -0,0 +1,13 @@
import 'package:angel_model/angel_model.dart';
import 'character.dart';
import 'episode.dart';
class Droid extends Model implements Character {
String name;
List<Character> friends;
List<Episode> appearsIn;
String primaryFunction;
Droid({this.name, this.friends, this.appearsIn, this.primaryFunction});
}

View file

@ -0,0 +1,5 @@
enum Episode {
NEWHOPE,
EMPIRE,
JEDI,
}

View file

@ -0,0 +1,20 @@
import 'package:angel_model/angel_model.dart';
import 'character.dart';
import 'episode.dart';
import 'starship.dart';
class Human extends Model implements Character {
String name;
List<Character> friends;
List<Episode> appearsIn;
List<Starship> starships;
int totalCredits;
Human(
{this.name,
this.friends,
this.appearsIn,
this.starships,
this.totalCredits});
}

View file

@ -0,0 +1,5 @@
export 'character.dart';
export 'droid.dart';
export 'episode.dart';
export 'human.dart';
export 'starship.dart';

View file

@ -0,0 +1,8 @@
import 'package:angel_model/angel_model.dart';
class Starship extends Model {
String name;
int length;
Starship({this.name, this.length});
}

View file

@ -0,0 +1,35 @@
import 'package:angel_http_exception/angel_http_exception.dart';
import 'package:logging/logging.dart';
import 'package:io/ansi.dart';
/// Prints the contents of a [LogRecord] with pretty colors.
void prettyLog(LogRecord record) {
var code = chooseLogColor(record.level);
if (record.error == null) print(code.wrap(record.toString()));
if (record.error != null) {
var err = record.error;
if (err is AngelHttpException && err.statusCode != 500) return;
print(code.wrap(record.toString() + '\n'));
print(code.wrap(err.toString()));
if (record.stackTrace != null) {
print(code.wrap(record.stackTrace.toString()));
}
}
}
/// Chooses a color based on the logger [level].
AnsiCode chooseLogColor(Level level) {
if (level == Level.SHOUT)
return backgroundRed;
else if (level == Level.SEVERE)
return red;
else if (level == Level.WARNING)
return yellow;
else if (level == Level.INFO)
return cyan;
else if (level == Level.FINER || level == Level.FINEST) return lightGray;
return resetAll;
}

View file

@ -0,0 +1,55 @@
import 'dart:async';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_graphql/angel_graphql.dart';
import 'package:graphql_schema/graphql_schema.dart';
import 'package:graphql_server/graphql_server.dart';
import 'package:graphql_server/mirrors.dart';
import 'src/models/models.dart';
Future configureServer(Angel app) async {
// Create standard Angel services. Note that these will also *automatically* be
// exposed via a REST API as well.
var droidService = mountService<Droid>(app, '/api/droids');
var humansService = mountService<Human>(app, '/api/humans');
var starshipService = mountService<Starship>(app, '/api/starships');
// Create the GraphQL schema.
// This code uses dart:mirrors to easily create GraphQL types from Dart PODO's.
//var droidType = convertDartType(Droid);
//var episodeType = convertDartType(Episode);
var humanType = convertDartType(Human);
// Create the query type.
//
// Use the `resolveViaServiceIndex` helper to load data directly from an
// Angel service.
var queryType = objectType('StarWarsQuery', fields: [
field(
'humans',
type: listType(humanType.nonNullable()),
resolve: resolveViaServiceIndex(humansService),
),
]);
// Finally, create the schema.
var schema = graphQLSchema(query: queryType);
// Next, create a GraphQL object, which will be passed to `graphQLHttp`, and
// used to mount a spec-compliant GraphQL endpoint on the server.
var graphQL = new GraphQL(schema);
// Mount the GraphQL endpoint.
app.all('/graphql', graphQLHttp(graphQL));
// In development, we'll want to mount GraphiQL, for easy management of the database.
if (!app.isProduction) {
app.get('/graphiql', graphiQL());
}
}
Service mountService<T extends Model>(Angel app, String path) => app.use(
path,
new TypedService(new MapService(
autoIdAndDateFields: false, autoSnakeCaseNames: false))) as Service;

View file

@ -0,0 +1,8 @@
name: star_wars
publish_to: none
dependencies:
#angel_file_service: ^1.0.0
angel_graphql:
path: ../angel_graphql
angel_hot: ^1.0.0
io: ^0.3.2

View file

@ -3,20 +3,21 @@ import 'dart:mirrors';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:graphql_schema/graphql_schema.dart';
import 'package:recase/recase.dart';
import 'package:tuple/tuple.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.
GraphQLType convertDartType(Type type, [List<Type> typeArguments]) {
var tuple = new Tuple2(type, typeArguments);
return _cache.putIfAbsent(
tuple, () => _objectTypeFromDartType(type, typeArguments));
if (_cache[type] != null) {
return _cache[type];
} else {
return _objectTypeFromDartType(type, typeArguments);
}
}
final Map<Tuple2<Type, List<Type>>, GraphQLType> _cache =
<Tuple2<Type, List<Type>>, GraphQLType>{};
final Map<Type, GraphQLType> _cache =
<Type, GraphQLType>{};
GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
if (type == bool) {
@ -46,6 +47,17 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
var clazz = mirror as ClassMirror;
if (clazz.isAssignableTo(reflectType(Iterable))) {
if (clazz.typeArguments.isNotEmpty) {
var inner = convertDartType(clazz.typeArguments[0].reflectedType);
//if (inner == null) return null;
return listType(inner.nonNullable());
}
throw new ArgumentError(
'Cannot convert ${clazz.reflectedType}, an iterable WITHOUT a type argument, into a GraphQL type.');
}
if (clazz.isEnum) {
return enumTypeFromClassMirror(clazz);
}
@ -54,7 +66,14 @@ GraphQLType _objectTypeFromDartType(Type type, [List<Type> typeArguments]) {
}
GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) {
if (_cache[mirror.reflectedType] != null) {
return _cache[mirror.reflectedType] as GraphQLObjectType;
} else {
}
var fields = <GraphQLField>[];
var ready = <Symbol, MethodMirror>{};
var forward = <Symbol, MethodMirror>{};
void walkMap(Map<Symbol, MethodMirror> map) {
for (var name in map.keys) {
@ -64,13 +83,47 @@ GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) {
name != #runtimeType &&
!methodMirror.isPrivate &&
exclude?.canSerialize != true;
if (methodMirror.isGetter && canAdd) {
fields.add(fieldFromGetter(name, methodMirror, exclude, mirror));
}
}
}
walkMap(mirror.instanceMembers);
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);
if (mirror.isAbstract) {
var decls = <Symbol, MethodMirror>{};
@ -81,7 +134,11 @@ GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) {
}
});
walkMap(decls);
ready.clear();
forward.clear();
prepReadyForward(decls);
walkMap(ready);
//walkMap(decls);
}
var inheritsFrom = <GraphQLObjectType>[];
@ -116,13 +173,21 @@ GraphQLObjectType objectTypeFromClassMirror(ClassMirror mirror) {
walk(mirror.superclass);
mirror.superinterfaces.forEach(walk);
return objectType(
MirrorSystem.getName(mirror.simpleName),
fields: fields,
isInterface: mirror.isAbstract,
interfaces: inheritsFrom,
description: _getDescription(mirror.metadata),
);
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;
}
GraphQLEnumType enumTypeFromClassMirror(ClassMirror mirror) {
@ -155,8 +220,12 @@ GraphQLField fieldFromGetter(
var wasProvided = type != null;
if (!wasProvided) {
type = convertDartType(mirror.returnType.reflectedType,
mirror.returnType.typeArguments.map((t) => t.reflectedType).toList());
var returnType = mirror.returnType;
if (!clazz.isAssignableTo(returnType)) {
type = convertDartType(returnType.reflectedType,
mirror.returnType.typeArguments.map((t) => t.reflectedType).toList());
}
}
var nameString = _getSerializedName(name, mirror, clazz);