Mysterious returns of "null"
This commit is contained in:
parent
9c876a643c
commit
943a3cb53b
14 changed files with 329 additions and 85 deletions
|
@ -1,11 +0,0 @@
|
|||
targets:
|
||||
_standalone:
|
||||
sources:
|
||||
- lib/src/models/character.dart
|
||||
- lib/src/models/droid.dart
|
||||
- lib/src/models/starship.dart
|
||||
$default:
|
||||
dependencies:
|
||||
- ":_standalone"
|
||||
sources:
|
||||
- lib/src/models/human.dart
|
|
@ -1,12 +1,12 @@
|
|||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'episode.dart';
|
||||
part 'character.g.dart';
|
||||
|
||||
@graphQLClass
|
||||
abstract class Character {
|
||||
String get id;
|
||||
|
||||
String get name;
|
||||
|
||||
List<Episode> get appearsIn;
|
||||
|
||||
List<Character> get friends;
|
||||
// List<Episode> get appearsIn;
|
||||
}
|
||||
|
|
13
example_star_wars/lib/src/models/character.g.dart
Normal file
13
example_star_wars/lib/src/models/character.g.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'character.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Character].
|
||||
final GraphQLObjectType characterGraphQLType = objectType('Character',
|
||||
isInterface: true,
|
||||
interfaces: [],
|
||||
fields: [field('id', graphQLString), field('name', graphQLString)]);
|
|
@ -1,17 +1,23 @@
|
|||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'character.dart';
|
||||
import 'episode.dart';
|
||||
part 'droid.g.dart';
|
||||
|
||||
@serializable
|
||||
abstract class _Droid extends Model implements Character {
|
||||
@graphQLClass
|
||||
@GraphQLDocumentation(description: 'Beep! Boop!')
|
||||
abstract class _Droid extends Model implements Character {
|
||||
String get id;
|
||||
|
||||
String get name;
|
||||
|
||||
List<Episode> get appearsIn;
|
||||
@GraphQLDocumentation(
|
||||
description: 'The list of episodes this droid appears in.')
|
||||
List<Episode> get appearsIn;
|
||||
|
||||
/// Doc comments automatically become GraphQL descriptions.
|
||||
List<Character> get friends;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ class Droid extends _Droid {
|
|||
Droid(
|
||||
{this.id,
|
||||
this.name,
|
||||
List<dynamic> appearsIn,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
this.createdAt,
|
||||
this.updatedAt})
|
||||
|
@ -25,7 +25,7 @@ class Droid extends _Droid {
|
|||
final String name;
|
||||
|
||||
@override
|
||||
final List<dynamic> appearsIn;
|
||||
final List<Episode> appearsIn;
|
||||
|
||||
@override
|
||||
final List<Character> friends;
|
||||
|
@ -39,7 +39,7 @@ class Droid extends _Droid {
|
|||
Droid copyWith(
|
||||
{String id,
|
||||
String name,
|
||||
List<dynamic> appearsIn,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt}) {
|
||||
|
@ -56,7 +56,7 @@ class Droid extends _Droid {
|
|||
return other is _Droid &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
const ListEquality<dynamic>(const DefaultEquality())
|
||||
const ListEquality<Episode>(const DefaultEquality<Episode>())
|
||||
.equals(other.appearsIn, appearsIn) &&
|
||||
const ListEquality<Character>(const DefaultEquality<Character>())
|
||||
.equals(other.friends, friends) &&
|
||||
|
@ -84,7 +84,7 @@ abstract class DroidSerializer {
|
|||
id: map['id'] as String,
|
||||
name: map['name'] as String,
|
||||
appearsIn: map['appears_in'] is Iterable
|
||||
? (map['appears_in'] as Iterable).cast<dynamic>().toList()
|
||||
? (map['appears_in'] as Iterable).cast<Episode>().toList()
|
||||
: null,
|
||||
friends: map['friends'] is Iterable
|
||||
? (map['friends'] as Iterable).cast<Character>().toList()
|
||||
|
@ -138,3 +138,27 @@ abstract class DroidFields {
|
|||
|
||||
static const String updatedAt = 'updated_at';
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Droid].
|
||||
final GraphQLObjectType droidGraphQLType = objectType('Droid',
|
||||
isInterface: false,
|
||||
description: 'Beep! Boop!',
|
||||
interfaces: [
|
||||
characterGraphQLType
|
||||
],
|
||||
fields: [
|
||||
field('id', graphQLString),
|
||||
field('name', graphQLString),
|
||||
field('appears_in', listOf(episodeGraphQLType),
|
||||
description: 'The list of episodes this droid appears in.'),
|
||||
field('friends', listOf(characterGraphQLType),
|
||||
description:
|
||||
'Doc comments automatically become GraphQL descriptions.'),
|
||||
field('created_at', graphQLDate),
|
||||
field('updated_at', graphQLDate),
|
||||
field('idAsInt', graphQLInt)
|
||||
]);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
part 'episode.g.dart';
|
||||
|
||||
@GraphQLDocumentation(
|
||||
description: 'The episodes of the Star Wars original trilogy.')
|
||||
@graphQLClass
|
||||
enum Episode {
|
||||
NEWHOPE,
|
||||
EMPIRE,
|
||||
|
|
12
example_star_wars/lib/src/models/episode.g.dart
Normal file
12
example_star_wars/lib/src/models/episode.g.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'episode.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Episode].
|
||||
final GraphQLEnumType<String> episodeGraphQLType = enumTypeFromStrings(
|
||||
'Episode', const ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
description: 'The episodes of the Star Wars original trilogy.');
|
|
@ -4,10 +4,10 @@ import 'package:collection/collection.dart';
|
|||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'character.dart';
|
||||
import 'episode.dart';
|
||||
import 'starship.dart';
|
||||
part 'human.g.dart';
|
||||
|
||||
@serializable
|
||||
@graphQLClass
|
||||
abstract class _Human extends Model implements Character {
|
||||
// @GraphQLDocumentation(description: "This human's name, of course.")
|
||||
// String name;
|
||||
|
@ -26,8 +26,6 @@ abstract class _Human extends Model implements Character {
|
|||
|
||||
int get totalCredits;
|
||||
|
||||
List<Starship> get starships;
|
||||
|
||||
// Human(
|
||||
// {this.name,
|
||||
// this.friends,
|
||||
|
|
|
@ -11,15 +11,13 @@ class Human extends _Human {
|
|||
Human(
|
||||
{this.id,
|
||||
this.name,
|
||||
List<dynamic> appearsIn,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
this.totalCredits,
|
||||
List<dynamic> starships,
|
||||
this.createdAt,
|
||||
this.updatedAt})
|
||||
: this.appearsIn = new List.unmodifiable(appearsIn ?? []),
|
||||
this.friends = new List.unmodifiable(friends ?? []),
|
||||
this.starships = new List.unmodifiable(starships ?? []);
|
||||
this.friends = new List.unmodifiable(friends ?? []);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
|
@ -28,7 +26,7 @@ class Human extends _Human {
|
|||
final String name;
|
||||
|
||||
@override
|
||||
final List<dynamic> appearsIn;
|
||||
final List<Episode> appearsIn;
|
||||
|
||||
@override
|
||||
final List<Character> friends;
|
||||
|
@ -36,9 +34,6 @@ class Human extends _Human {
|
|||
@override
|
||||
final int totalCredits;
|
||||
|
||||
@override
|
||||
final List<dynamic> starships;
|
||||
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
|
@ -48,10 +43,9 @@ class Human extends _Human {
|
|||
Human copyWith(
|
||||
{String id,
|
||||
String name,
|
||||
List<dynamic> appearsIn,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
int totalCredits,
|
||||
List<dynamic> starships,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt}) {
|
||||
return new Human(
|
||||
|
@ -60,7 +54,6 @@ class Human extends _Human {
|
|||
appearsIn: appearsIn ?? this.appearsIn,
|
||||
friends: friends ?? this.friends,
|
||||
totalCredits: totalCredits ?? this.totalCredits,
|
||||
starships: starships ?? this.starships,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt);
|
||||
}
|
||||
|
@ -69,29 +62,19 @@ class Human extends _Human {
|
|||
return other is _Human &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
const ListEquality<dynamic>(const DefaultEquality())
|
||||
const ListEquality<Episode>(const DefaultEquality<Episode>())
|
||||
.equals(other.appearsIn, appearsIn) &&
|
||||
const ListEquality<Character>(const DefaultEquality<Character>())
|
||||
.equals(other.friends, friends) &&
|
||||
other.totalCredits == totalCredits &&
|
||||
const ListEquality<dynamic>(const DefaultEquality())
|
||||
.equals(other.starships, starships) &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashObjects([
|
||||
id,
|
||||
name,
|
||||
appearsIn,
|
||||
friends,
|
||||
totalCredits,
|
||||
starships,
|
||||
createdAt,
|
||||
updatedAt
|
||||
]);
|
||||
return hashObjects(
|
||||
[id, name, appearsIn, friends, totalCredits, createdAt, updatedAt]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
@ -109,15 +92,12 @@ abstract class HumanSerializer {
|
|||
id: map['id'] as String,
|
||||
name: map['name'] as String,
|
||||
appearsIn: map['appears_in'] is Iterable
|
||||
? (map['appears_in'] as Iterable).cast<dynamic>().toList()
|
||||
? (map['appears_in'] as Iterable).cast<Episode>().toList()
|
||||
: null,
|
||||
friends: map['friends'] is Iterable
|
||||
? (map['friends'] as Iterable).cast<Character>().toList()
|
||||
: null,
|
||||
totalCredits: map['total_credits'] as int,
|
||||
starships: map['starships'] is Iterable
|
||||
? (map['starships'] as Iterable).cast<dynamic>().toList()
|
||||
: null,
|
||||
createdAt: map['created_at'] != null
|
||||
? (map['created_at'] is DateTime
|
||||
? (map['created_at'] as DateTime)
|
||||
|
@ -140,7 +120,6 @@ abstract class HumanSerializer {
|
|||
'appears_in': model.appearsIn,
|
||||
'friends': model.friends,
|
||||
'total_credits': model.totalCredits,
|
||||
'starships': model.starships,
|
||||
'created_at': model.createdAt?.toIso8601String(),
|
||||
'updated_at': model.updatedAt?.toIso8601String()
|
||||
};
|
||||
|
@ -154,7 +133,6 @@ abstract class HumanFields {
|
|||
appearsIn,
|
||||
friends,
|
||||
totalCredits,
|
||||
starships,
|
||||
createdAt,
|
||||
updatedAt
|
||||
];
|
||||
|
@ -169,9 +147,26 @@ abstract class HumanFields {
|
|||
|
||||
static const String totalCredits = 'total_credits';
|
||||
|
||||
static const String starships = 'starships';
|
||||
|
||||
static const String createdAt = 'created_at';
|
||||
|
||||
static const String updatedAt = 'updated_at';
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Human].
|
||||
final GraphQLObjectType humanGraphQLType =
|
||||
objectType('Human', isInterface: false, interfaces: [
|
||||
characterGraphQLType
|
||||
], fields: [
|
||||
field('id', graphQLString),
|
||||
field('name', graphQLString),
|
||||
field('appears_in', listOf(episodeGraphQLType)),
|
||||
field('friends', listOf(characterGraphQLType)),
|
||||
field('total_credits', graphQLInt),
|
||||
field('created_at', graphQLDate),
|
||||
field('updated_at', graphQLDate),
|
||||
field('idAsInt', graphQLInt)
|
||||
]);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
part 'starship.g.dart';
|
||||
|
||||
@serializable
|
||||
@graphQLClass
|
||||
abstract class _Starship extends Model {
|
||||
String get name;
|
||||
int get length;
|
||||
|
|
|
@ -113,3 +113,18 @@ abstract class StarshipFields {
|
|||
|
||||
static const String updatedAt = 'updated_at';
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Starship].
|
||||
final GraphQLObjectType starshipGraphQLType =
|
||||
objectType('Starship', isInterface: false, interfaces: [], fields: [
|
||||
field('id', graphQLString),
|
||||
field('name', graphQLString),
|
||||
field('length', graphQLInt),
|
||||
field('created_at', graphQLDate),
|
||||
field('updated_at', graphQLDate),
|
||||
field('idAsInt', graphQLInt)
|
||||
]);
|
||||
|
|
|
@ -2,30 +2,24 @@ import 'dart:async';
|
|||
import 'dart:math';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_graphql/angel_graphql.dart';
|
||||
import 'package:angel_typed_service/angel_typed_service.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');
|
||||
var droidService = app.use('/api/droids', MapService());
|
||||
var humansService = app.use('/api/humans', MapService());
|
||||
var starshipService = app.use('/api/starships', MapService());
|
||||
var rnd = Random();
|
||||
|
||||
// Create the GraphQL schema.
|
||||
// This code uses dart:mirrors to easily create GraphQL types from Dart PODO's.
|
||||
var droidType = convertDartClass(Droid);
|
||||
var episodeType = convertDartType(Episode);
|
||||
var humanType = convertDartClass(Human);
|
||||
var starshipType = convertDartType(Starship);
|
||||
// `package:graphql_generator` has generated schemas for some of our
|
||||
// classes.
|
||||
|
||||
// A Hero can be either a Droid or Human; create a union type that represents this.
|
||||
var heroType = GraphQLUnionType('Hero', [droidType, humanType]);
|
||||
var heroType = GraphQLUnionType('Hero', [droidGraphQLType, humanGraphQLType]);
|
||||
|
||||
// Create the query type.
|
||||
//
|
||||
|
@ -37,19 +31,19 @@ Future configureServer(Angel app) async {
|
|||
fields: [
|
||||
field(
|
||||
'droids',
|
||||
listOf(droidType.nonNullable()),
|
||||
listOf(droidGraphQLType.nonNullable()),
|
||||
description: 'All droids in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(droidService),
|
||||
),
|
||||
field(
|
||||
'humans',
|
||||
listOf(humanType.nonNullable()),
|
||||
listOf(humanGraphQLType.nonNullable()),
|
||||
description: 'All humans in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(humansService),
|
||||
),
|
||||
field(
|
||||
'starships',
|
||||
listOf(starshipType.nonNullable()),
|
||||
listOf(starshipGraphQLType.nonNullable()),
|
||||
description: 'All starships in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(starshipService),
|
||||
),
|
||||
|
@ -59,7 +53,7 @@ Future configureServer(Angel app) async {
|
|||
description:
|
||||
'Finds a random hero within the known galaxy, whether a Droid or Human.',
|
||||
inputs: [
|
||||
GraphQLFieldInput('ep', episodeType),
|
||||
GraphQLFieldInput('ep', episodeGraphQLType),
|
||||
],
|
||||
resolve: randomHeroResolver(droidService, humansService, rnd),
|
||||
),
|
||||
|
@ -68,7 +62,7 @@ Future configureServer(Angel app) async {
|
|||
|
||||
// Convert our object types to input objects, so that they can be passed to
|
||||
// mutations.
|
||||
var humanChangesType = humanType.toInputObject('HumanChanges');
|
||||
var humanChangesType = humanGraphQLType.toInputObject('HumanChanges');
|
||||
|
||||
// Create the mutation type.
|
||||
var mutationType = objectType(
|
||||
|
@ -77,7 +71,7 @@ Future configureServer(Angel app) async {
|
|||
// We'll use the `modify_human` mutation to modify a human in the database.
|
||||
field(
|
||||
'modify_human',
|
||||
humanType.nonNullable(),
|
||||
humanGraphQLType.nonNullable(),
|
||||
description: 'Modifies a human in the database.',
|
||||
inputs: [
|
||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||
|
@ -135,10 +129,6 @@ Future configureServer(Angel app) async {
|
|||
});
|
||||
}
|
||||
|
||||
Service<String, dynamic> mountService<T extends Model>(
|
||||
Angel app, String path) =>
|
||||
app.use(path, TypedService<String, T>(MapService()));
|
||||
|
||||
GraphQLFieldResolver randomHeroResolver(
|
||||
Service droidService, Service humansService, Random rnd) {
|
||||
return (_, args) async {
|
||||
|
|
|
@ -6,7 +6,6 @@ dependencies:
|
|||
path: ../angel_graphql
|
||||
angel_hot: ^2.0.0-alpha
|
||||
angel_serialize: ^2.0.0
|
||||
angel_typed_service: ^1.0.0
|
||||
io: ^0.3.2
|
||||
dev_dependencies:
|
||||
angel_serialize_generator: ^2.0.0
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_serialize_generator/build_context.dart';
|
||||
import 'package:angel_serialize_generator/context.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:code_builder/code_builder.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
|
||||
/// Generates GraphQL schemas, statically.
|
||||
|
@ -12,23 +16,218 @@ Builder graphQLBuilder(_) {
|
|||
return SharedPartBuilder([_GraphQLGenerator()], 'graphql_generator');
|
||||
}
|
||||
|
||||
var _docComment = RegExp(r'^/// ', multiLine: true);
|
||||
var _graphQLDoc = TypeChecker.fromRuntime(GraphQLDocumentation);
|
||||
var _graphQLClassTypeChecker = TypeChecker.fromRuntime(GraphQLClass);
|
||||
|
||||
class _GraphQLGenerator extends GeneratorForAnnotation<GraphQLClass> {
|
||||
@override
|
||||
Future<String> generateForAnnotatedElement(
|
||||
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
||||
if (element is ClassElement) {
|
||||
var ctx = await buildContext(
|
||||
element, annotation, buildStep, buildStep.resolver, false);
|
||||
var lib = buildSchemaLibrary(ctx);
|
||||
var ctx = element.isEnum
|
||||
? null
|
||||
: await buildContext(
|
||||
element,
|
||||
annotation,
|
||||
buildStep,
|
||||
buildStep.resolver,
|
||||
serializableTypeChecker.hasAnnotationOf(element));
|
||||
var lib = buildSchemaLibrary(element, ctx, annotation);
|
||||
return lib.accept(DartEmitter()).toString();
|
||||
} else {
|
||||
throw UnsupportedError('@GraphQLClass() is only supported on classes.');
|
||||
}
|
||||
}
|
||||
|
||||
Library buildSchemaLibrary(BuildContext ctx) {
|
||||
bool isInterface(ClassElement clazz) {
|
||||
return clazz.isAbstract && !serializableTypeChecker.hasAnnotationOf(clazz);
|
||||
}
|
||||
|
||||
bool _isGraphQLClass(InterfaceType clazz) {
|
||||
var search = clazz;
|
||||
|
||||
while (search != null) {
|
||||
if (_graphQLClassTypeChecker.hasAnnotationOf(search.element)) return true;
|
||||
search = search.superclass;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Expression _inferType(String className, String name, DartType type) {
|
||||
// Firstly, check if it's a GraphQL class.
|
||||
if (type is InterfaceType && _isGraphQLClass(type)) {
|
||||
var c = type;
|
||||
var name = serializableTypeChecker.hasAnnotationOf(c.element) &&
|
||||
c.name.startsWith('_')
|
||||
? c.name.substring(1)
|
||||
: c.name;
|
||||
var rc = ReCase(name);
|
||||
return refer('${rc.snakeCase}GraphQLType');
|
||||
}
|
||||
|
||||
// Next, check if this is the "id" field of a `Model`.
|
||||
if (TypeChecker.fromRuntime(Model).isAssignableFromType(type) &&
|
||||
name == 'id') {
|
||||
return refer('graphQLId');
|
||||
}
|
||||
|
||||
var primitive = {
|
||||
String: 'graphQLString',
|
||||
int: 'graphQLInt',
|
||||
double: 'graphQLFloat',
|
||||
bool: 'graphQLBoolean',
|
||||
DateTime: 'graphQLDate'
|
||||
};
|
||||
|
||||
// Check to see if it's a primitive type.
|
||||
for (var entry in primitive.entries) {
|
||||
if (TypeChecker.fromRuntime(entry.key).isAssignableFromType(type)) {
|
||||
return refer(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, check to see if it's a List.
|
||||
if (type is InterfaceType &&
|
||||
type.typeArguments.isNotEmpty &&
|
||||
TypeChecker.fromRuntime(Iterable).isAssignableFromType(type)) {
|
||||
var arg = type.typeArguments[0];
|
||||
var inner = _inferType(className, name, arg);
|
||||
return refer('listOf').call([inner]);
|
||||
}
|
||||
|
||||
// Nothing else is allowed.
|
||||
throw 'Cannot infer the GraphQL type for field $className.$name (type=$type).';
|
||||
}
|
||||
|
||||
void _applyDescription(
|
||||
Map<String, Expression> named, Element element, String docComment) {
|
||||
String docString = docComment;
|
||||
|
||||
if (docString == null && _graphQLDoc.hasAnnotationOf(element)) {
|
||||
var ann = _graphQLDoc.firstAnnotationOf(element);
|
||||
var cr = ConstantReader(ann);
|
||||
docString = cr.peek('description')?.stringValue;
|
||||
}
|
||||
|
||||
if (docString != null) {
|
||||
named['description'] = literalString(
|
||||
docString.replaceAll(_docComment, '').replaceAll('\n', '\\n'));
|
||||
}
|
||||
}
|
||||
|
||||
Library buildSchemaLibrary(
|
||||
ClassElement clazz, BuildContext ctx, ConstantReader ann) {
|
||||
return Library((b) {
|
||||
var clazz = ctx.clazz;
|
||||
// Generate a top-level xGraphQLType object
|
||||
|
||||
if (clazz.isEnum) {
|
||||
b.body.add(Field((b) {
|
||||
// enumTypeFromStrings(String name, List<String> values, {String description}
|
||||
var args = <Expression>[literalString(clazz.name)];
|
||||
var values =
|
||||
clazz.fields.where((f) => f.isEnumConstant).map((f) => f.name);
|
||||
var named = <String, Expression>{};
|
||||
_applyDescription(named, clazz, clazz.documentationComment);
|
||||
args.add(literalConstList(values.map(literalString).toList()));
|
||||
|
||||
b
|
||||
..name = ReCase(clazz.name).snakeCase + 'GraphQLType'
|
||||
..docs.add('/// Auto-generated from [${clazz.name}].')
|
||||
..type = TypeReference((b) => b
|
||||
..symbol = 'GraphQLEnumType'
|
||||
..types.add(refer('String')))
|
||||
..modifier = FieldModifier.final$
|
||||
..assignment = refer('enumTypeFromStrings').call(args, named).code;
|
||||
}));
|
||||
} else {
|
||||
b.body.add(Field((b) {
|
||||
var args = <Expression>[literalString(ctx.modelClassName)];
|
||||
var named = <String, Expression>{
|
||||
'isInterface': literalBool(isInterface(clazz))
|
||||
};
|
||||
|
||||
// Add documentation
|
||||
_applyDescription(named, clazz, clazz.documentationComment);
|
||||
|
||||
// Add interfaces
|
||||
var interfaces = clazz.interfaces.where(_isGraphQLClass).map((c) {
|
||||
var name = serializableTypeChecker.hasAnnotationOf(c.element) &&
|
||||
c.name.startsWith('_')
|
||||
? c.name.substring(1)
|
||||
: c.name;
|
||||
var rc = ReCase(name);
|
||||
return refer('${rc.snakeCase}GraphQLType');
|
||||
});
|
||||
named['interfaces'] = literalList(interfaces);
|
||||
|
||||
// Add fields
|
||||
var ctxFields = ctx.fields.toList();
|
||||
|
||||
// Also incorporate parent fields.
|
||||
var search = clazz.type;
|
||||
while (search != null &&
|
||||
!TypeChecker.fromRuntime(Object).isExactlyType(search)) {
|
||||
for (var field in search.element.fields) {
|
||||
if (!ctxFields.any((f) => f.name == field.name)) {
|
||||
ctxFields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
search = search.superclass;
|
||||
}
|
||||
|
||||
var fields = <Expression>[];
|
||||
for (var field in ctxFields) {
|
||||
var named = <String, Expression>{};
|
||||
var originalField = clazz.fields
|
||||
.firstWhere((f) => f.name == field.name, orElse: () => null);
|
||||
|
||||
// Check if it is deprecated.
|
||||
var depEl = originalField?.getter ?? originalField ?? field;
|
||||
var depAnn =
|
||||
TypeChecker.fromRuntime(Deprecated).firstAnnotationOf(depEl);
|
||||
if (depAnn != null) {
|
||||
var dep = ConstantReader(depAnn);
|
||||
var reason = dep.peek('messages')?.stringValue ??
|
||||
dep.peek('expires')?.stringValue ??
|
||||
'Expires: ${deprecated.message}.';
|
||||
named['deprecationReason'] = literalString(reason);
|
||||
}
|
||||
|
||||
// Description finder...
|
||||
_applyDescription(
|
||||
named,
|
||||
originalField?.getter ?? originalField ?? field,
|
||||
originalField?.getter?.documentationComment ??
|
||||
originalField?.documentationComment);
|
||||
|
||||
// Pick the type.
|
||||
var doc = _graphQLDoc.firstAnnotationOf(depEl);
|
||||
Expression type;
|
||||
if (doc != null) {
|
||||
var cr = ConstantReader(doc);
|
||||
var typeName = cr.peek('typeName')?.symbolValue;
|
||||
if (typeName != null)
|
||||
type = refer(MirrorSystem.getName(typeName));
|
||||
}
|
||||
|
||||
fields.add(refer('field').call([
|
||||
literalString(ctx.resolveFieldName(field.name)),
|
||||
type ??= _inferType(clazz.name, field.name, field.type)
|
||||
], named));
|
||||
}
|
||||
named['fields'] = literalList(fields);
|
||||
|
||||
b
|
||||
..name = ctx.modelClassNameRecase.snakeCase + 'GraphQLType'
|
||||
..docs.add('/// Auto-generated from [${ctx.modelClassName}].')
|
||||
..type = refer('GraphQLObjectType')
|
||||
..modifier = FieldModifier.final$
|
||||
..assignment = refer('objectType').call(args, named).code;
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue