2018-08-03 17:26:11 +00:00
|
|
|
import 'package:graphql_parser/graphql_parser.dart';
|
2018-08-02 18:33:50 +00:00
|
|
|
import 'package:graphql_schema/graphql_schema.dart';
|
|
|
|
|
|
|
|
// TODO: How to handle custom types???
|
2018-08-02 19:22:16 +00:00
|
|
|
GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
|
2018-08-03 23:09:16 +00:00
|
|
|
var objectTypes = fetchAllTypes(schema, allTypes);
|
2018-08-02 19:22:16 +00:00
|
|
|
var typeType = _reflectSchemaTypes();
|
2018-08-03 17:26:11 +00:00
|
|
|
var directiveType = _reflectDirectiveType();
|
|
|
|
allTypes.addAll(objectTypes);
|
2018-08-02 18:33:50 +00:00
|
|
|
|
2018-08-02 19:22:16 +00:00
|
|
|
var schemaType = objectType('__Schema', fields: [
|
2018-08-02 18:33:50 +00:00
|
|
|
field(
|
|
|
|
'types',
|
|
|
|
type: listType(typeType),
|
2018-08-03 18:31:50 +00:00
|
|
|
resolve: (_, __) => allTypes,
|
2018-08-02 18:33:50 +00:00
|
|
|
),
|
2018-08-02 19:22:16 +00:00
|
|
|
field(
|
|
|
|
'queryType',
|
|
|
|
type: typeType,
|
|
|
|
resolve: (_, __) => schema.query,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'mutationType',
|
|
|
|
type: typeType,
|
|
|
|
resolve: (_, __) => schema.mutation,
|
|
|
|
),
|
2018-08-03 18:31:50 +00:00
|
|
|
field(
|
|
|
|
'subscriptionType',
|
|
|
|
type: typeType,
|
|
|
|
resolve: (_, __) => schema.subscription,
|
|
|
|
),
|
2018-08-03 17:26:11 +00:00
|
|
|
field(
|
|
|
|
'directives',
|
|
|
|
type: listType(directiveType).nonNullable(),
|
|
|
|
resolve: (_, __) => [], // TODO: Actually fetch directives
|
|
|
|
),
|
2018-08-02 19:22:16 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
allTypes.addAll([
|
2018-08-03 18:31:50 +00:00
|
|
|
graphQLBoolean,
|
|
|
|
graphQLString,
|
|
|
|
graphQLId,
|
|
|
|
graphQLDate,
|
|
|
|
graphQLFloat,
|
|
|
|
graphQLInt,
|
2018-08-03 17:26:11 +00:00
|
|
|
directiveType,
|
2018-08-02 19:22:16 +00:00
|
|
|
typeType,
|
|
|
|
schemaType,
|
2018-08-03 23:09:16 +00:00
|
|
|
_typeKindType,
|
|
|
|
_directiveLocationType,
|
2018-08-02 19:22:16 +00:00
|
|
|
_reflectFields(),
|
2018-08-03 17:45:40 +00:00
|
|
|
_reflectDirectiveType(),
|
|
|
|
_reflectInputValueType(),
|
2018-08-03 17:57:28 +00:00
|
|
|
_reflectEnumValueType(),
|
2018-08-02 18:33:50 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
var fields = <GraphQLField>[
|
|
|
|
field(
|
|
|
|
'__schema',
|
|
|
|
type: schemaType,
|
|
|
|
resolve: (_, __) => schemaType,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'__type',
|
|
|
|
type: typeType,
|
|
|
|
arguments: [
|
|
|
|
new GraphQLFieldArgument('name', graphQLString.nonNullable())
|
|
|
|
],
|
|
|
|
resolve: (_, args) {
|
|
|
|
var name = args['name'] as String;
|
2018-08-03 23:09:16 +00:00
|
|
|
return allTypes.firstWhere((t) => t.name == name,
|
2018-08-03 21:07:08 +00:00
|
|
|
orElse: () => throw new GraphQLException.fromMessage(
|
|
|
|
'No type named "$name" exists.'));
|
2018-08-02 18:33:50 +00:00
|
|
|
},
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
fields.addAll(schema.query.fields);
|
|
|
|
|
|
|
|
return new GraphQLSchema(
|
2018-08-02 19:22:16 +00:00
|
|
|
query: objectType(schema.query.name, fields: fields),
|
2018-08-02 18:33:50 +00:00
|
|
|
mutation: schema.mutation,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-08-02 19:22:16 +00:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
2018-08-03 17:57:28 +00:00
|
|
|
_typeType.fields.add(
|
|
|
|
field(
|
|
|
|
'interfaces',
|
|
|
|
type: listType(_reflectSchemaTypes().nonNullable()),
|
|
|
|
resolve: (type, _) {
|
|
|
|
if (type is GraphQLObjectType) {
|
|
|
|
return type.interfaces;
|
|
|
|
} else {
|
|
|
|
return <GraphQLType>[];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
_typeType.fields.add(
|
|
|
|
field(
|
|
|
|
'possibleTypes',
|
|
|
|
type: listType(_reflectSchemaTypes().nonNullable()),
|
|
|
|
resolve: (type, _) {
|
|
|
|
// TODO: Interface and union types
|
|
|
|
return <GraphQLType>[];
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
2018-08-02 19:22:16 +00:00
|
|
|
var fieldType = _reflectFields();
|
2018-08-03 17:45:40 +00:00
|
|
|
var inputValueType = _reflectInputValueType();
|
2018-08-02 19:22:16 +00:00
|
|
|
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,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2018-08-03 17:45:40 +00:00
|
|
|
|
2018-08-03 18:31:50 +00:00
|
|
|
typeField = inputValueType.fields
|
2018-08-03 17:45:40 +00:00
|
|
|
.firstWhere((f) => f.name == 'type', orElse: () => null);
|
|
|
|
|
|
|
|
if (typeField == null) {
|
|
|
|
inputValueType.fields.add(
|
|
|
|
field(
|
|
|
|
'type',
|
|
|
|
type: _reflectSchemaTypes(),
|
|
|
|
resolve: (f, _) => (f as GraphQLFieldArgument).type,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2018-08-02 19:22:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return _typeType;
|
|
|
|
}
|
|
|
|
|
2018-08-03 23:09:16 +00:00
|
|
|
final GraphQLEnumType<String> _typeKindType =
|
|
|
|
enumTypeFromStrings('__TypeKind', [
|
|
|
|
'SCALAR',
|
|
|
|
'OBJECT',
|
|
|
|
'INTERFACE',
|
|
|
|
'UNION',
|
|
|
|
'ENUM',
|
|
|
|
'INPUT_OBJECT',
|
|
|
|
'LIST',
|
|
|
|
'NON_NULL'
|
|
|
|
]);
|
|
|
|
|
2018-08-02 19:22:16 +00:00
|
|
|
GraphQLObjectType _createTypeType() {
|
2018-08-03 17:57:28 +00:00
|
|
|
var enumValueType = _reflectEnumValueType();
|
2018-08-02 18:33:50 +00:00
|
|
|
var fieldType = _reflectFields();
|
2018-08-03 17:57:28 +00:00
|
|
|
var inputValueType = _reflectInputValueType();
|
2018-08-02 18:33:50 +00:00
|
|
|
|
2018-08-02 19:22:16 +00:00
|
|
|
return objectType('__Type', fields: [
|
2018-08-02 18:33:50 +00:00
|
|
|
field(
|
|
|
|
'name',
|
|
|
|
type: graphQLString,
|
2018-08-02 19:22:16 +00:00
|
|
|
resolve: (type, _) => (type as GraphQLType).name,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'description',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (type, _) => (type as GraphQLType).description,
|
2018-08-02 18:33:50 +00:00
|
|
|
),
|
|
|
|
field(
|
|
|
|
'kind',
|
2018-08-03 23:09:16 +00:00
|
|
|
type: _typeKindType,
|
2018-08-02 19:22:16 +00:00
|
|
|
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';
|
2018-08-03 23:09:16 +00:00
|
|
|
else if (t is GraphQLEnumType)
|
|
|
|
return 'ENUM';
|
2018-08-02 19:22:16 +00:00
|
|
|
else
|
|
|
|
throw new UnsupportedError(
|
|
|
|
'Cannot get the kind of $t.'); // TODO: Interface + union
|
|
|
|
},
|
2018-08-02 18:33:50 +00:00
|
|
|
),
|
|
|
|
field(
|
|
|
|
'fields',
|
|
|
|
type: listType(fieldType),
|
2018-08-03 17:48:54 +00:00
|
|
|
arguments: [
|
|
|
|
new GraphQLFieldArgument(
|
|
|
|
'includeDeprecated',
|
|
|
|
graphQLBoolean,
|
|
|
|
defaultValue: false,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
resolve: (type, args) => type is GraphQLObjectType
|
|
|
|
? type.fields
|
|
|
|
.where(
|
|
|
|
(f) => !f.isDeprecated || args['includeDeprecated'] == true)
|
|
|
|
.toList()
|
|
|
|
: [],
|
2018-08-02 18:33:50 +00:00
|
|
|
),
|
2018-08-03 17:57:28 +00:00
|
|
|
field(
|
|
|
|
'enumValues',
|
|
|
|
type: listType(enumValueType.nonNullable()),
|
|
|
|
arguments: [
|
|
|
|
new GraphQLFieldArgument(
|
|
|
|
'includeDeprecated',
|
|
|
|
graphQLBoolean,
|
|
|
|
defaultValue: false,
|
|
|
|
),
|
|
|
|
],
|
2018-08-03 23:09:16 +00:00
|
|
|
resolve: (obj, args) {
|
|
|
|
if (obj is GraphQLEnumType) {
|
|
|
|
return obj.values
|
|
|
|
.where(
|
|
|
|
(f) => !f.isDeprecated || args['includeDeprecated'] == true)
|
|
|
|
.toList();
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
},
|
2018-08-03 17:57:28 +00:00
|
|
|
),
|
|
|
|
field(
|
|
|
|
'inputFields',
|
|
|
|
type: listType(inputValueType.nonNullable()),
|
|
|
|
resolve: (obj, _) {
|
|
|
|
// TODO: INPUT_OBJECT type
|
|
|
|
return <GraphQLFieldArgument>[];
|
|
|
|
},
|
|
|
|
),
|
2018-08-02 18:33:50 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2018-08-02 19:22:16 +00:00
|
|
|
GraphQLObjectType _fieldType;
|
|
|
|
|
2018-08-02 18:33:50 +00:00
|
|
|
GraphQLObjectType _reflectFields() {
|
2018-08-02 19:22:16 +00:00
|
|
|
if (_fieldType == null) {
|
|
|
|
_fieldType = _createFieldType();
|
|
|
|
}
|
|
|
|
|
|
|
|
return _fieldType;
|
|
|
|
}
|
|
|
|
|
|
|
|
GraphQLObjectType _createFieldType() {
|
2018-08-03 17:45:40 +00:00
|
|
|
var inputValueType = _reflectInputValueType();
|
|
|
|
|
2018-08-02 19:22:16 +00:00
|
|
|
return objectType('__Field', fields: [
|
|
|
|
field(
|
|
|
|
'name',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (f, _) => (f as GraphQLField).name,
|
|
|
|
),
|
2018-08-03 17:36:34 +00:00
|
|
|
field(
|
|
|
|
'isDeprecated',
|
2018-08-03 17:45:40 +00:00
|
|
|
type: graphQLBoolean,
|
2018-08-03 17:36:34 +00:00
|
|
|
resolve: (f, _) => (f as GraphQLField).isDeprecated,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'deprecationReason',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (f, _) => (f as GraphQLField).deprecationReason,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'args',
|
2018-08-03 17:45:40 +00:00
|
|
|
type: listType(inputValueType.nonNullable()).nonNullable(),
|
2018-08-03 17:36:34 +00:00
|
|
|
resolve: (f, _) => (f as GraphQLField).arguments,
|
|
|
|
),
|
2018-08-02 19:22:16 +00:00
|
|
|
]);
|
2018-08-02 18:33:50 +00:00
|
|
|
}
|
|
|
|
|
2018-08-03 17:45:40 +00:00
|
|
|
GraphQLObjectType _inputValueType;
|
|
|
|
|
|
|
|
GraphQLObjectType _reflectInputValueType() {
|
|
|
|
return _inputValueType ??= objectType('__InputValue', fields: [
|
|
|
|
field(
|
|
|
|
'name',
|
2018-08-03 18:31:50 +00:00
|
|
|
type: graphQLString.nonNullable(),
|
2018-08-03 17:45:40 +00:00
|
|
|
resolve: (obj, _) => (obj as GraphQLFieldArgument).name,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'description',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (obj, _) => (obj as GraphQLFieldArgument).description,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'defaultValue',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (obj, _) =>
|
|
|
|
(obj as GraphQLFieldArgument).defaultValue?.toString(),
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2018-08-03 18:31:50 +00:00
|
|
|
GraphQLObjectType _directiveType;
|
|
|
|
|
2018-08-03 23:09:16 +00:00
|
|
|
final GraphQLEnumType<String> _directiveLocationType =
|
|
|
|
enumTypeFromStrings('__DirectiveLocation', [
|
|
|
|
'QUERY',
|
|
|
|
'MUTATION',
|
|
|
|
'FIELD',
|
|
|
|
'FRAGMENT_DEFINITION',
|
|
|
|
'FRAGMENT_SPREAD',
|
|
|
|
'INLINE_FRAGMENT'
|
|
|
|
]);
|
|
|
|
|
2018-08-03 17:26:11 +00:00
|
|
|
GraphQLObjectType _reflectDirectiveType() {
|
2018-08-03 17:45:40 +00:00
|
|
|
var inputValueType = _reflectInputValueType();
|
|
|
|
|
2018-08-03 17:36:34 +00:00
|
|
|
// TODO: What actually is this???
|
2018-08-03 18:31:50 +00:00
|
|
|
return _directiveType ??= objectType('__Directive', fields: [
|
2018-08-03 17:26:11 +00:00
|
|
|
field(
|
|
|
|
'name',
|
2018-08-03 17:36:34 +00:00
|
|
|
type: graphQLString.nonNullable(),
|
2018-08-03 17:26:11 +00:00
|
|
|
resolve: (obj, _) => (obj as DirectiveContext).NAME.span.text,
|
|
|
|
),
|
2018-08-03 17:36:34 +00:00
|
|
|
field(
|
|
|
|
'description',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (obj, _) => null,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'locations',
|
2018-08-03 23:09:16 +00:00
|
|
|
type: listType(_directiveLocationType.nonNullable()).nonNullable(),
|
2018-08-03 22:54:12 +00:00
|
|
|
// TODO: Fetch directiveLocation
|
2018-08-03 18:31:50 +00:00
|
|
|
resolve: (obj, _) => <String>[],
|
2018-08-03 17:36:34 +00:00
|
|
|
),
|
|
|
|
field(
|
|
|
|
'args',
|
2018-08-03 17:45:40 +00:00
|
|
|
type: listType(inputValueType.nonNullable()).nonNullable(),
|
2018-08-03 17:36:34 +00:00
|
|
|
resolve: (obj, _) => [],
|
|
|
|
),
|
2018-08-03 17:26:11 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2018-08-03 17:57:28 +00:00
|
|
|
GraphQLObjectType _enumValueType;
|
|
|
|
|
|
|
|
GraphQLObjectType _reflectEnumValueType() {
|
2018-08-03 22:54:12 +00:00
|
|
|
return _enumValueType ??
|
|
|
|
objectType(
|
|
|
|
'__EnumValue',
|
|
|
|
fields: [
|
|
|
|
field(
|
|
|
|
'name',
|
|
|
|
type: graphQLString.nonNullable(),
|
|
|
|
resolve: (obj, _) => (obj as GraphQLEnumValue).name,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'description',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (obj, _) => (obj as GraphQLEnumValue).description,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'isDeprecated',
|
|
|
|
type: graphQLBoolean.nonNullable(),
|
|
|
|
resolve: (obj, _) => (obj as GraphQLEnumValue).isDeprecated,
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'deprecationReason',
|
|
|
|
type: graphQLString,
|
|
|
|
resolve: (obj, _) => (obj as GraphQLEnumValue).deprecationReason,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
2018-08-03 17:57:28 +00:00
|
|
|
}
|
|
|
|
|
2018-08-03 23:09:16 +00:00
|
|
|
List<GraphQLObjectType> fetchAllTypes(
|
|
|
|
GraphQLSchema schema, List<GraphQLType> allTypes) {
|
2018-08-02 18:33:50 +00:00
|
|
|
var typess = <GraphQLType>[];
|
|
|
|
typess.addAll(_fetchAllTypesFromObject(schema.query));
|
|
|
|
|
|
|
|
if (schema.mutation != null) {
|
|
|
|
typess.addAll(_fetchAllTypesFromObject(schema.mutation)
|
|
|
|
.where((t) => t is GraphQLObjectType));
|
|
|
|
}
|
|
|
|
|
|
|
|
var types = <GraphQLObjectType>[];
|
|
|
|
|
|
|
|
for (var type in typess) {
|
2018-08-03 23:09:16 +00:00
|
|
|
if (type is GraphQLObjectType)
|
|
|
|
types.add(type);
|
|
|
|
else if (!allTypes.contains(type)) allTypes.add(type);
|
2018-08-02 18:33:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return types.toSet().toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
List<GraphQLType> _fetchAllTypesFromObject(GraphQLObjectType objectType) {
|
|
|
|
var types = <GraphQLType>[objectType];
|
|
|
|
|
|
|
|
for (var field in objectType.fields) {
|
|
|
|
if (field.type is GraphQLObjectType) {
|
|
|
|
types.addAll(_fetchAllTypesFromObject(field.type as GraphQLObjectType));
|
|
|
|
} else {
|
|
|
|
types.addAll(_fetchAllTypesFromType(field.type));
|
|
|
|
}
|
2018-08-03 23:30:19 +00:00
|
|
|
|
|
|
|
for (var argument in field.arguments ?? <GraphQLFieldArgument>[]) {
|
|
|
|
types.addAll(_fetchAllTypesFromType(argument.type));
|
|
|
|
}
|
2018-08-02 18:33:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return types;
|
|
|
|
}
|
|
|
|
|
|
|
|
Iterable<GraphQLType> _fetchAllTypesFromType(GraphQLType type) {
|
|
|
|
var types = <GraphQLType>[];
|
|
|
|
|
|
|
|
if (type is GraphQLNonNullableType) {
|
|
|
|
types.addAll(_fetchAllTypesFromType(type.innerType));
|
|
|
|
} else if (type is GraphQLListType) {
|
|
|
|
types.addAll(_fetchAllTypesFromType(type.innerType));
|
|
|
|
} else if (type is GraphQLObjectType) {
|
|
|
|
types.addAll(_fetchAllTypesFromObject(type));
|
2018-08-03 22:54:12 +00:00
|
|
|
} else if (type is GraphQLEnumType) {
|
|
|
|
types.add(type);
|
2018-08-02 18:33:50 +00:00
|
|
|
}
|
|
|
|
|
2018-08-03 22:54:12 +00:00
|
|
|
// TODO: Interface, union
|
2018-08-02 18:33:50 +00:00
|
|
|
return types;
|
|
|
|
}
|