Add support for global variables in GraphQL
This commit is contained in:
parent
532f698aa2
commit
15e1d18224
4 changed files with 67 additions and 33 deletions
|
@ -22,6 +22,11 @@ final Validator graphQlPostBody = new Validator({
|
||||||
/// https://graphql.org/learn/serving-over-http/
|
/// https://graphql.org/learn/serving-over-http/
|
||||||
RequestHandler graphQLHttp(GraphQL graphQL) {
|
RequestHandler graphQLHttp(GraphQL graphQL) {
|
||||||
return (req, res) async {
|
return (req, res) async {
|
||||||
|
var globalVariables = <String, dynamic>{
|
||||||
|
'__requestctx': req,
|
||||||
|
'__responsectx': res,
|
||||||
|
};
|
||||||
|
|
||||||
executeMap(Map map) async {
|
executeMap(Map map) async {
|
||||||
var text = req.body['query'] as String;
|
var text = req.body['query'] as String;
|
||||||
var operationName = req.body['operation_name'] as String;
|
var operationName = req.body['operation_name'] as String;
|
||||||
|
@ -37,6 +42,7 @@ RequestHandler graphQLHttp(GraphQL graphQL) {
|
||||||
sourceUrl: 'input',
|
sourceUrl: 'input',
|
||||||
operationName: operationName,
|
operationName: operationName,
|
||||||
variableValues: foldToStringDynamic(variables as Map),
|
variableValues: foldToStringDynamic(variables as Map),
|
||||||
|
globalVariables: globalVariables,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -50,7 +56,11 @@ RequestHandler graphQLHttp(GraphQL graphQL) {
|
||||||
if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) {
|
if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) {
|
||||||
var text = utf8.decode(await req.lazyOriginalBuffer());
|
var text = utf8.decode(await req.lazyOriginalBuffer());
|
||||||
return {
|
return {
|
||||||
'data': await graphQL.parseAndExecute(text, sourceUrl: 'input')
|
'data': await graphQL.parseAndExecute(
|
||||||
|
text,
|
||||||
|
sourceUrl: 'input',
|
||||||
|
globalVariables: globalVariables,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} else if (req.headers.contentType?.mimeType == 'application/json') {
|
} else if (req.headers.contentType?.mimeType == 'application/json') {
|
||||||
if (await validate(graphQlPostBody)(req, res)) {
|
if (await validate(graphQlPostBody)(req, res)) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:angel_model/angel_model.dart';
|
import 'package:angel_model/angel_model.dart';
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
|
import 'package:graphql_schema/graphql_schema.dart';
|
||||||
|
|
||||||
import 'character.dart';
|
import 'character.dart';
|
||||||
import 'episode.dart';
|
import 'episode.dart';
|
||||||
|
@ -7,6 +8,7 @@ import 'starship.dart';
|
||||||
|
|
||||||
@serializable
|
@serializable
|
||||||
class Human extends Model implements Character {
|
class Human extends Model implements Character {
|
||||||
|
@GraphQLDocumentation(description: "This human's name, of course.")
|
||||||
String name;
|
String name;
|
||||||
List<Character> friends;
|
List<Character> friends;
|
||||||
List<Episode> appearsIn;
|
List<Episode> appearsIn;
|
||||||
|
|
|
@ -23,7 +23,8 @@ class GraphQLObjectType
|
||||||
GraphQLObjectType(this.name, this.description, {this.isInterface: false});
|
GraphQLObjectType(this.name, this.description, {this.isInterface: false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GraphQLType<Map<String, dynamic>, Map<String, dynamic>> coerceToInputObject() {
|
GraphQLType<Map<String, dynamic>, Map<String, dynamic>>
|
||||||
|
coerceToInputObject() {
|
||||||
return asInputObject('${name}Input', description: description);
|
return asInputObject('${name}Input', description: description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +32,9 @@ class GraphQLObjectType
|
||||||
GraphQLInputObjectType asInputObject(String name, {String description}) {
|
GraphQLInputObjectType asInputObject(String name, {String description}) {
|
||||||
return new GraphQLInputObjectType(name,
|
return new GraphQLInputObjectType(name,
|
||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
inputFields:
|
inputFields: fields.map((f) => new GraphQLInputObjectField(
|
||||||
fields.map((f) => new GraphQLInputObjectField(f.name, f.type.coerceToInputObject())));
|
f.name, f.type.coerceToInputObject(),
|
||||||
|
description: f.description)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void inheritFrom(GraphQLObjectType other) {
|
void inheritFrom(GraphQLObjectType other) {
|
||||||
|
@ -242,7 +244,8 @@ class GraphQLInputObjectType
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GraphQLType<Map<String, dynamic>, Map<String, dynamic>> coerceToInputObject() => this;
|
GraphQLType<Map<String, dynamic>, Map<String, dynamic>>
|
||||||
|
coerceToInputObject() => this;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GraphQLInputObjectField<Value, Serialized> {
|
class GraphQLInputObjectField<Value, Serialized> {
|
||||||
|
|
|
@ -73,7 +73,8 @@ class GraphQL {
|
||||||
{String operationName,
|
{String operationName,
|
||||||
sourceUrl,
|
sourceUrl,
|
||||||
Map<String, dynamic> variableValues: const {},
|
Map<String, dynamic> variableValues: const {},
|
||||||
initialValue}) {
|
initialValue,
|
||||||
|
Map<String, dynamic> globalVariables}) {
|
||||||
var tokens = scan(text, sourceUrl: sourceUrl);
|
var tokens = scan(text, sourceUrl: sourceUrl);
|
||||||
var parser = new Parser(tokens);
|
var parser = new Parser(tokens);
|
||||||
var document = parser.parseDocument();
|
var document = parser.parseDocument();
|
||||||
|
@ -86,26 +87,31 @@ class GraphQL {
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return executeRequest(_schema, document,
|
return executeRequest(
|
||||||
|
_schema,
|
||||||
|
document,
|
||||||
operationName: operationName,
|
operationName: operationName,
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
variableValues: variableValues);
|
variableValues: variableValues,
|
||||||
|
globalVariables: globalVariables,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> executeRequest(
|
Future<Map<String, dynamic>> executeRequest(
|
||||||
GraphQLSchema schema, DocumentContext document,
|
GraphQLSchema schema, DocumentContext document,
|
||||||
{String operationName,
|
{String operationName,
|
||||||
Map<String, dynamic> variableValues: const {},
|
Map<String, dynamic> variableValues: const {},
|
||||||
initialValue}) async {
|
initialValue,
|
||||||
|
Map<String, dynamic> globalVariables}) async {
|
||||||
var operation = getOperation(document, operationName);
|
var operation = getOperation(document, operationName);
|
||||||
var coercedVariableValues =
|
var coercedVariableValues =
|
||||||
coerceVariableValues(schema, operation, variableValues ?? {});
|
coerceVariableValues(schema, operation, variableValues ?? {});
|
||||||
if (operation.isQuery)
|
if (operation.isQuery)
|
||||||
return await executeQuery(
|
return await executeQuery(document, operation, schema,
|
||||||
document, operation, schema, coercedVariableValues, initialValue);
|
coercedVariableValues, initialValue, globalVariables);
|
||||||
else {
|
else {
|
||||||
return executeMutation(
|
return executeMutation(document, operation, schema, coercedVariableValues,
|
||||||
document, operation, schema, coercedVariableValues, initialValue);
|
initialValue, globalVariables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,11 +181,12 @@ class GraphQL {
|
||||||
OperationDefinitionContext query,
|
OperationDefinitionContext query,
|
||||||
GraphQLSchema schema,
|
GraphQLSchema schema,
|
||||||
Map<String, dynamic> variableValues,
|
Map<String, dynamic> variableValues,
|
||||||
initialValue) async {
|
initialValue,
|
||||||
|
Map<String, dynamic> globalVariables) async {
|
||||||
var queryType = schema.queryType;
|
var queryType = schema.queryType;
|
||||||
var selectionSet = query.selectionSet;
|
var selectionSet = query.selectionSet;
|
||||||
return await executeSelectionSet(
|
return await executeSelectionSet(document, selectionSet, queryType,
|
||||||
document, selectionSet, queryType, initialValue, variableValues);
|
initialValue, variableValues, globalVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> executeMutation(
|
Future<Map<String, dynamic>> executeMutation(
|
||||||
|
@ -187,7 +194,8 @@ class GraphQL {
|
||||||
OperationDefinitionContext mutation,
|
OperationDefinitionContext mutation,
|
||||||
GraphQLSchema schema,
|
GraphQLSchema schema,
|
||||||
Map<String, dynamic> variableValues,
|
Map<String, dynamic> variableValues,
|
||||||
initialValue) async {
|
initialValue,
|
||||||
|
Map<String, dynamic> globalVariables) async {
|
||||||
var mutationType = schema.mutationType;
|
var mutationType = schema.mutationType;
|
||||||
|
|
||||||
if (mutationType == null) {
|
if (mutationType == null) {
|
||||||
|
@ -196,8 +204,8 @@ class GraphQL {
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectionSet = mutation.selectionSet;
|
var selectionSet = mutation.selectionSet;
|
||||||
return await executeSelectionSet(
|
return await executeSelectionSet(document, selectionSet, mutationType,
|
||||||
document, selectionSet, mutationType, initialValue, variableValues);
|
initialValue, variableValues, globalVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> executeSelectionSet(
|
Future<Map<String, dynamic>> executeSelectionSet(
|
||||||
|
@ -205,7 +213,8 @@ class GraphQL {
|
||||||
SelectionSetContext selectionSet,
|
SelectionSetContext selectionSet,
|
||||||
GraphQLObjectType objectType,
|
GraphQLObjectType objectType,
|
||||||
objectValue,
|
objectValue,
|
||||||
Map<String, dynamic> variableValues) async {
|
Map<String, dynamic> variableValues,
|
||||||
|
Map<String, dynamic> globalVariables) async {
|
||||||
var groupedFieldSet =
|
var groupedFieldSet =
|
||||||
collectFields(document, objectType, selectionSet, variableValues);
|
collectFields(document, objectType, selectionSet, variableValues);
|
||||||
var resultMap = <String, dynamic>{};
|
var resultMap = <String, dynamic>{};
|
||||||
|
@ -224,8 +233,16 @@ class GraphQL {
|
||||||
.firstWhere((f) => f.name == fieldName, orElse: () => null)
|
.firstWhere((f) => f.name == fieldName, orElse: () => null)
|
||||||
?.type;
|
?.type;
|
||||||
if (fieldType == null) continue;
|
if (fieldType == null) continue;
|
||||||
responseValue = await executeField(document, fieldName, objectType,
|
responseValue = await executeField(
|
||||||
objectValue, fields, fieldType, variableValues);
|
document,
|
||||||
|
fieldName,
|
||||||
|
objectType,
|
||||||
|
objectValue,
|
||||||
|
fields,
|
||||||
|
fieldType,
|
||||||
|
new Map<String, dynamic>.from(globalVariables)
|
||||||
|
..addAll(variableValues),
|
||||||
|
globalVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
resultMap[responseKey] = responseValue;
|
resultMap[responseKey] = responseValue;
|
||||||
|
@ -242,14 +259,15 @@ class GraphQL {
|
||||||
objectValue,
|
objectValue,
|
||||||
List<SelectionContext> fields,
|
List<SelectionContext> fields,
|
||||||
GraphQLType fieldType,
|
GraphQLType fieldType,
|
||||||
Map<String, dynamic> variableValues) async {
|
Map<String, dynamic> variableValues,
|
||||||
|
Map<String, dynamic> globalVariables) async {
|
||||||
var field = fields[0];
|
var field = fields[0];
|
||||||
var argumentValues =
|
var argumentValues =
|
||||||
coerceArgumentValues(objectType, field, variableValues);
|
coerceArgumentValues(objectType, field, variableValues);
|
||||||
var resolvedValue = await resolveFieldValue(
|
var resolvedValue = await resolveFieldValue(
|
||||||
objectType, objectValue, field.field.fieldName.name, argumentValues);
|
objectType, objectValue, field.field.fieldName.name, argumentValues);
|
||||||
return completeValue(
|
return completeValue(document, fieldName, fieldType, fields, resolvedValue,
|
||||||
document, fieldName, fieldType, fields, resolvedValue, variableValues);
|
variableValues, globalVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> coerceArgumentValues(GraphQLObjectType objectType,
|
Map<String, dynamic> coerceArgumentValues(GraphQLObjectType objectType,
|
||||||
|
@ -365,11 +383,12 @@ class GraphQL {
|
||||||
GraphQLType fieldType,
|
GraphQLType fieldType,
|
||||||
List<SelectionContext> fields,
|
List<SelectionContext> fields,
|
||||||
result,
|
result,
|
||||||
Map<String, dynamic> variableValues) async {
|
Map<String, dynamic> variableValues,
|
||||||
|
Map<String, dynamic> globalVariables) async {
|
||||||
if (fieldType is GraphQLNonNullableType) {
|
if (fieldType is GraphQLNonNullableType) {
|
||||||
var innerType = fieldType.ofType;
|
var innerType = fieldType.ofType;
|
||||||
var completedResult = completeValue(
|
var completedResult = completeValue(document, fieldName, innerType,
|
||||||
document, fieldName, innerType, fields, result, variableValues);
|
fields, result, variableValues, globalVariables);
|
||||||
|
|
||||||
if (completedResult == null) {
|
if (completedResult == null) {
|
||||||
throw new GraphQLException.fromMessage(
|
throw new GraphQLException.fromMessage(
|
||||||
|
@ -394,7 +413,7 @@ class GraphQL {
|
||||||
|
|
||||||
for (var resultItem in (result as Iterable)) {
|
for (var resultItem in (result as Iterable)) {
|
||||||
out.add(await completeValue(document, '(item in "$fieldName")',
|
out.add(await completeValue(document, '(item in "$fieldName")',
|
||||||
innerType, fields, resultItem, variableValues));
|
innerType, fields, resultItem, variableValues, globalVariables));
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
@ -425,8 +444,8 @@ class GraphQL {
|
||||||
}
|
}
|
||||||
|
|
||||||
var subSelectionSet = mergeSelectionSets(fields);
|
var subSelectionSet = mergeSelectionSets(fields);
|
||||||
return await executeSelectionSet(
|
return await executeSelectionSet(document, subSelectionSet, objectType,
|
||||||
document, subSelectionSet, objectType, result, variableValues);
|
result, variableValues, globalVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnsupportedError('Unsupported type: $fieldType');
|
throw new UnsupportedError('Unsupported type: $fieldType');
|
||||||
|
|
Loading…
Reference in a new issue