platform/graphql_server/lib/introspection.dart

522 lines
13 KiB
Dart
Raw Normal View History

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';
2019-08-14 04:44:22 +00:00
/// Performs introspection over a GraphQL [schema], and returns a one, containing
2019-04-18 15:15:40 +00:00
/// introspective information.
2019-04-19 02:02:42 +00:00
///
2019-04-18 15:15:40 +00:00
/// [allTypes] should contain all types, not directly defined in the schema, that you
/// would like to have introspection available for.
2018-08-02 19:22:16 +00:00
GraphQLSchema reflectSchema(GraphQLSchema schema, List<GraphQLType> allTypes) {
var typeType = _reflectSchemaTypes();
2018-08-03 17:26:11 +00:00
var directiveType = _reflectDirectiveType();
2020-01-19 16:01:03 +00:00
2018-08-04 19:18:53 +00:00
Set<GraphQLType> allTypeSet;
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',
2018-08-05 01:49:13 +00:00
listOf(typeType),
2018-08-04 19:18:53 +00:00
resolve: (_, __) => allTypeSet ??= allTypes.toSet(),
2018-08-02 18:33:50 +00:00
),
2018-08-02 19:22:16 +00:00
field(
'queryType',
2018-08-04 19:18:53 +00:00
typeType,
resolve: (_, __) => schema.queryType,
2018-08-02 19:22:16 +00:00
),
field(
'mutationType',
2018-08-04 19:18:53 +00:00
typeType,
resolve: (_, __) => schema.mutationType,
2018-08-02 19:22:16 +00:00
),
2018-08-03 18:31:50 +00:00
field(
'subscriptionType',
2018-08-04 19:18:53 +00:00
typeType,
resolve: (_, __) => schema.subscriptionType,
2018-08-03 18:31:50 +00:00
),
2018-08-03 17:26:11 +00:00
field(
'directives',
2018-08-05 01:49:13 +00:00
listOf(directiveType).nonNullable(),
2018-08-03 17:26:11 +00:00
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,
2020-01-19 16:01:03 +00:00
directiveType,
2018-08-02 19:22:16 +00:00
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
_reflectInputValueType(),
_reflectEnumValueType(),
2018-08-02 18:33:50 +00:00
]);
2018-08-04 19:18:53 +00:00
var fields = <GraphQLObjectField>[
2018-08-02 18:33:50 +00:00
field(
'__schema',
2018-08-04 19:18:53 +00:00
schemaType,
2018-08-02 18:33:50 +00:00
resolve: (_, __) => schemaType,
),
field(
'__type',
2018-08-04 19:18:53 +00:00
typeType,
2019-08-14 04:44:22 +00:00
inputs: [GraphQLFieldInput('name', graphQLString.nonNullable())],
2018-08-02 18:33:50 +00:00
resolve: (_, args) {
var name = args['name'] as String;
2018-08-03 23:09:16 +00:00
return allTypes.firstWhere((t) => t.name == name,
2019-08-14 04:44:22 +00:00
orElse: () => throw GraphQLException.fromMessage(
2018-08-03 21:07:08 +00:00
'No type named "$name" exists.'));
2018-08-02 18:33:50 +00:00
},
),
];
2018-08-04 19:18:53 +00:00
fields.addAll(schema.queryType.fields);
2018-08-02 18:33:50 +00:00
2019-08-14 04:44:22 +00:00
return GraphQLSchema(
2018-08-04 19:18:53 +00:00
queryType: objectType(schema.queryType.name, fields: fields),
mutationType: schema.mutationType,
2019-04-18 00:52:26 +00:00
subscriptionType: schema.subscriptionType,
2018-08-02 18:33:50 +00:00
);
}
2018-08-02 19:22:16 +00:00
GraphQLObjectType _typeType;
GraphQLObjectType _reflectSchemaTypes() {
if (_typeType == null) {
_typeType = _createTypeType();
_typeType.fields.add(
field(
'ofType',
2018-08-04 19:18:53 +00:00
_reflectSchemaTypes(),
2018-08-02 19:22:16 +00:00
resolve: (type, _) {
2020-01-19 16:01:03 +00:00
if (type is GraphQLListType)
2018-08-04 19:18:53 +00:00
return type.ofType;
2020-01-19 16:01:03 +00:00
else if (type is GraphQLNonNullableType) return type.ofType;
2018-08-02 19:22:16 +00:00
return null;
},
),
);
_typeType.fields.add(
field(
'interfaces',
2018-08-05 01:49:13 +00:00
listOf(_reflectSchemaTypes().nonNullable()),
resolve: (type, _) {
if (type is GraphQLObjectType) {
return type.interfaces;
} else {
return <GraphQLType>[];
}
},
),
);
_typeType.fields.add(
field(
'possibleTypes',
2018-08-05 01:49:13 +00:00
listOf(_reflectSchemaTypes().nonNullable()),
resolve: (type, _) {
2018-08-03 23:43:00 +00:00
if (type is GraphQLObjectType && type.isInterface) {
return type.possibleTypes;
2018-08-04 00:22:50 +00:00
} else if (type is GraphQLUnionType) {
return type.possibleTypes;
2018-08-03 23:43:00 +00:00
} else {
return null;
}
},
),
);
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',
2018-08-04 19:18:53 +00:00
_reflectSchemaTypes(),
resolve: (f, _) => (f as GraphQLObjectField).type,
2018-08-02 19:22:16 +00:00
),
);
}
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',
2018-08-04 19:18:53 +00:00
_reflectSchemaTypes(),
resolve: (f, _) =>
_fetchFromInputValue(f, (f) => f.type, (f) => f.type),
2018-08-03 17:45:40 +00:00
),
);
}
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() {
var enumValueType = _reflectEnumValueType();
2018-08-02 18:33:50 +00:00
var fieldType = _reflectFields();
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',
2018-08-04 19:18:53 +00:00
graphQLString,
2018-08-02 19:22:16 +00:00
resolve: (type, _) => (type as GraphQLType).name,
),
field(
'description',
2018-08-04 19:18:53 +00:00
graphQLString,
2018-08-02 19:22:16 +00:00
resolve: (type, _) => (type as GraphQLType).description,
2018-08-02 18:33:50 +00:00
),
field(
'kind',
2018-08-04 19:18:53 +00:00
_typeKindType,
2018-08-02 19:22:16 +00:00
resolve: (type, _) {
var t = type as GraphQLType;
2019-08-14 04:48:18 +00:00
if (t is GraphQLEnumType) {
2018-08-04 15:12:26 +00:00
return 'ENUM';
2019-08-14 04:48:18 +00:00
} else if (t is GraphQLScalarType) {
2018-08-02 19:22:16 +00:00
return 'SCALAR';
2019-08-14 04:48:18 +00:00
} else if (t is GraphQLInputObjectType) {
2018-08-04 19:18:53 +00:00
return 'INPUT_OBJECT';
2019-08-14 04:48:18 +00:00
} else if (t is GraphQLObjectType) {
2018-08-03 23:43:00 +00:00
return t.isInterface ? 'INTERFACE' : 'OBJECT';
2019-08-14 04:48:18 +00:00
} else if (t is GraphQLListType) {
2018-08-02 19:22:16 +00:00
return 'LIST';
2019-08-14 04:48:18 +00:00
} else if (t is GraphQLNonNullableType) {
2018-08-02 19:22:16 +00:00
return 'NON_NULL';
2019-08-14 04:48:18 +00:00
} else if (t is GraphQLUnionType) {
2018-08-04 00:22:50 +00:00
return 'UNION';
2019-08-14 04:48:18 +00:00
} else {
2019-08-14 04:44:22 +00:00
throw UnsupportedError('Cannot get the kind of $t.');
2019-08-14 04:48:18 +00:00
}
2018-08-02 19:22:16 +00:00
},
2018-08-02 18:33:50 +00:00
),
field(
'fields',
2018-08-05 01:49:13 +00:00
listOf(fieldType),
2018-08-04 19:18:53 +00:00
inputs: [
2020-01-19 16:01:03 +00:00
new GraphQLFieldInput(
2018-08-03 17:48:54 +00:00
'includeDeprecated',
graphQLBoolean,
defaultValue: false,
),
],
resolve: (type, args) => type is GraphQLObjectType
? type.fields
.where(
(f) => !f.isDeprecated || args['includeDeprecated'] == true)
.toList()
2018-08-04 19:18:53 +00:00
: null,
2018-08-02 18:33:50 +00:00
),
field(
'enumValues',
2018-08-05 01:49:13 +00:00
listOf(enumValueType.nonNullable()),
2018-08-04 19:18:53 +00:00
inputs: [
2020-01-19 16:01:03 +00:00
new GraphQLFieldInput(
'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;
}
},
),
field(
'inputFields',
2018-08-05 01:49:13 +00:00
listOf(inputValueType.nonNullable()),
resolve: (obj, _) {
2018-08-04 19:18:53 +00:00
if (obj is GraphQLInputObjectType) {
return obj.inputFields;
}
return null;
},
),
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',
2018-08-04 19:18:53 +00:00
graphQLString,
resolve: (f, _) => (f as GraphQLObjectField).name,
),
field(
'description',
graphQLString,
resolve: (f, _) => (f as GraphQLObjectField).description,
2018-08-02 19:22:16 +00:00
),
2018-08-03 17:36:34 +00:00
field(
'isDeprecated',
2018-08-04 19:18:53 +00:00
graphQLBoolean,
resolve: (f, _) => (f as GraphQLObjectField).isDeprecated,
2018-08-03 17:36:34 +00:00
),
field(
'deprecationReason',
2018-08-04 19:18:53 +00:00
graphQLString,
resolve: (f, _) => (f as GraphQLObjectField).deprecationReason,
2018-08-03 17:36:34 +00:00
),
field(
'args',
2018-08-05 01:49:13 +00:00
listOf(inputValueType.nonNullable()).nonNullable(),
2018-08-04 19:18:53 +00:00
resolve: (f, _) => (f as GraphQLObjectField).inputs,
2018-08-03 17:36:34 +00:00
),
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;
2018-08-04 19:18:53 +00:00
T _fetchFromInputValue<T>(x, T Function(GraphQLFieldInput) ifInput,
T Function(GraphQLInputObjectField) ifObjectField) {
if (x is GraphQLFieldInput) {
return ifInput(x);
} else if (x is GraphQLInputObjectField) {
return ifObjectField(x);
} else {
return null;
}
}
2018-08-03 17:45:40 +00:00
GraphQLObjectType _reflectInputValueType() {
return _inputValueType ??= objectType('__InputValue', fields: [
field(
'name',
2018-08-04 19:18:53 +00:00
graphQLString.nonNullable(),
resolve: (obj, _) =>
_fetchFromInputValue(obj, (f) => f.name, (f) => f.name),
2018-08-03 17:45:40 +00:00
),
field(
'description',
2018-08-04 19:18:53 +00:00
graphQLString,
resolve: (obj, _) =>
_fetchFromInputValue(obj, (f) => f.description, (f) => f.description),
2018-08-03 17:45:40 +00:00
),
field(
'defaultValue',
2018-08-04 19:18:53 +00:00
graphQLString,
resolve: (obj, _) => _fetchFromInputValue(obj,
(f) => f.defaultValue?.toString(), (f) => f.defaultValue?.toString()),
2018-08-03 17:45:40 +00:00
),
]);
}
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-04 19:18:53 +00:00
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',
2018-08-04 19:18:53 +00:00
graphQLString,
2018-08-03 17:36:34 +00:00
resolve: (obj, _) => null,
),
field(
'locations',
2018-08-05 01:49:13 +00:00
listOf(_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-05 01:49:13 +00:00
listOf(inputValueType.nonNullable()).nonNullable(),
2018-08-03 17:36:34 +00:00
resolve: (obj, _) => [],
),
2018-08-03 17:26:11 +00:00
]);
}
GraphQLObjectType _enumValueType;
GraphQLObjectType _reflectEnumValueType() {
2018-08-04 15:12:26 +00:00
return _enumValueType ??= objectType(
'__EnumValue',
fields: [
field(
'name',
2018-08-04 19:18:53 +00:00
graphQLString.nonNullable(),
2018-08-04 15:12:26 +00:00
resolve: (obj, _) => (obj as GraphQLEnumValue).name,
),
field(
'description',
2018-08-04 19:18:53 +00:00
graphQLString,
2018-08-04 15:12:26 +00:00
resolve: (obj, _) => (obj as GraphQLEnumValue).description,
),
field(
'isDeprecated',
2018-08-04 19:18:53 +00:00
graphQLBoolean.nonNullable(),
2018-08-04 15:12:26 +00:00
resolve: (obj, _) => (obj as GraphQLEnumValue).isDeprecated,
),
field(
'deprecationReason',
2018-08-04 19:18:53 +00:00
graphQLString,
2018-08-04 15:12:26 +00:00
resolve: (obj, _) => (obj as GraphQLEnumValue).deprecationReason,
),
],
);
}
2018-08-04 00:57:38 +00:00
List<GraphQLType> fetchAllTypes(
2020-01-19 16:01:03 +00:00
GraphQLSchema schema, List<GraphQLType> specifiedTypes) {
return CollectTypes({
schema.queryType,
if (schema.mutationType != null) schema.mutationType,
if (schema.subscriptionType != null) schema.subscriptionType,
...specifiedTypes,
}).types.toList();
}
2018-08-04 00:57:38 +00:00
2020-01-19 16:01:03 +00:00
class CollectTypes {
Set<GraphQLType> traversedTypes = {};
2018-08-02 18:33:50 +00:00
2020-01-19 16:01:03 +00:00
Set<GraphQLType> get types => traversedTypes;
CollectTypes(Iterable<GraphQLType> types) {
types.forEach(_fetchAllTypesFromType);
2018-08-02 18:33:50 +00:00
}
2020-01-19 16:01:03 +00:00
CollectTypes.fromRootObject(GraphQLObjectType type) {
_fetchAllTypesFromObject(type);
2019-04-18 00:52:26 +00:00
}
2020-01-19 16:01:03 +00:00
void _fetchAllTypesFromObject(GraphQLObjectType objectType) {
if (traversedTypes.contains(objectType)) {
return null;
}
2018-08-02 18:33:50 +00:00
2020-01-19 16:01:03 +00:00
traversedTypes.add(objectType);
2018-08-02 18:33:50 +00:00
2020-01-19 16:01:03 +00:00
for (var field in objectType.fields) {
if (field.type is GraphQLObjectType) {
_fetchAllTypesFromObject(field.type as GraphQLObjectType);
} else if (field.type is GraphQLInputObjectType) {
for (var v in (field.type as GraphQLInputObjectType).inputFields) {
_fetchAllTypesFromType(v.type);
}
} else {
_fetchAllTypesFromType(field.type);
2018-08-04 19:18:53 +00:00
}
2018-08-03 23:30:19 +00:00
2020-01-19 16:01:03 +00:00
for (var input in field.inputs ?? <GraphQLFieldInput>[]) {
_fetchAllTypesFromType(input.type);
}
2018-08-03 23:30:19 +00:00
}
2018-08-02 18:33:50 +00:00
2020-01-19 16:01:03 +00:00
for (var i in objectType.interfaces) {
_fetchAllTypesFromObject(i);
}
2018-08-03 23:43:00 +00:00
}
2020-01-19 16:01:03 +00:00
void _fetchAllTypesFromType(GraphQLType type) {
if (traversedTypes.contains(type)) {
return null;
}
2018-08-02 18:33:50 +00:00
2020-01-19 16:01:03 +00:00
/*
* Unwrap generics
*/
if (type is GraphQLNonNullableType) {
return _fetchAllTypesFromType(type.ofType);
}
if (type is GraphQLListType) {
return _fetchAllTypesFromType(type.ofType);
2018-08-04 19:18:53 +00:00
}
2020-01-19 16:01:03 +00:00
/*
* Handle simple types
*/
if (type is GraphQLEnumType) {
traversedTypes.add(type);
return null;
}
if (type is GraphQLUnionType) {
traversedTypes.add(type);
for (var t in type.possibleTypes) {
_fetchAllTypesFromType(t);
}
return null;
}
if (type is GraphQLInputObjectType) {
traversedTypes.add(type);
for (var v in type.inputFields) {
_fetchAllTypesFromType(v.type);
}
return null;
}
2018-08-04 15:01:49 +00:00
2020-01-19 16:01:03 +00:00
/*
* defer to object type traverser
*/
if (type is GraphQLObjectType) {
return _fetchAllTypesFromObject(type);
2018-08-04 00:22:50 +00:00
}
2020-01-19 16:01:03 +00:00
return null;
2018-08-02 18:33:50 +00:00
}
}