Skip to content

fix schema wiring #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package graphql.validation.schemawiring;

import graphql.GraphQLError;
import graphql.schema.*;
import graphql.validation.interpolation.MessageInterpolator;
import graphql.validation.rules.OnValidationErrorStrategy;
import graphql.validation.rules.TargetedValidationRules;
import graphql.validation.rules.ValidationRule;
import graphql.validation.rules.ValidationRules;
import graphql.validation.util.Util;

import java.util.List;
import java.util.Locale;

public class FieldValidatorDataFetcher implements DataFetcher<Object> {
private final OnValidationErrorStrategy errorStrategy;
private final MessageInterpolator messageInterpolator;
private final DataFetcher<?> defaultDataFetcher;
private final Locale defaultLocale;
private final ValidationRules validationRules;
private TargetedValidationRules applicableRules;

public FieldValidatorDataFetcher(OnValidationErrorStrategy errorStrategy,
MessageInterpolator messageInterpolator,
DataFetcher<?> defaultDataFetcher,
Locale defaultLocale,
ValidationRules validationRules) {
this.errorStrategy = errorStrategy;
this.messageInterpolator = messageInterpolator;
this.defaultDataFetcher = defaultDataFetcher;
this.defaultLocale = defaultLocale;
this.validationRules = validationRules;
this.applicableRules = null;
}

@Override
public Object get(DataFetchingEnvironment environment) throws Exception {
if (!wereApplicableRulesFetched()) {
fetchApplicableRules(environment);
}

// When no validation is performed, this data fetcher is a pass-through
if (applicableRules.isEmpty()) {
return defaultDataFetcher.get(environment);
}

List<GraphQLError> errors = applicableRules.runValidationRules(environment, messageInterpolator, defaultLocale);
if (!errors.isEmpty()) {
if (!errorStrategy.shouldContinue(errors, environment)) {
return errorStrategy.onErrorValue(errors, environment);
}
}

Object returnValue = defaultDataFetcher.get(environment);
if (errors.isEmpty()) {
return returnValue;
}
return Util.mkDFRFromFetchedResult(errors, returnValue);
}

private void fetchApplicableRules(DataFetchingEnvironment environment) {
final GraphQLFieldDefinition field = environment.getFieldDefinition();
final GraphQLFieldsContainer container = asContainer(environment);

applicableRules = validationRules.buildRulesFor(field, container);
}

private GraphQLFieldsContainer asContainer(DataFetchingEnvironment environment) {
final GraphQLType parent = environment.getParentType();
if (parent == null) {
return null;
}
return (GraphQLFieldsContainer) environment.getParentType();
}

private boolean wereApplicableRulesFetched() {
return applicableRules != null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package graphql.validation.schemawiring;

import graphql.GraphQLError;
import graphql.PublicApi;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLFieldDefinition;
Expand All @@ -11,17 +10,15 @@
import graphql.validation.rules.OnValidationErrorStrategy;
import graphql.validation.rules.TargetedValidationRules;
import graphql.validation.rules.ValidationRules;
import graphql.validation.util.Util;

import java.util.List;
import java.util.Locale;

/**
* A {@link graphql.schema.idl.SchemaDirectiveWiring} that can be used to inject validation rules into the data fetchers
* A {@link SchemaDirectiveWiring} that can be used to inject validation rules into the data fetchers
* when the graphql schema is being built. It will use the validation rules and ask each one of they apply to the field and or its
* arguments.
* <p>
* If there are rules that apply then it will it will change the {@link graphql.schema.DataFetcher} of that field so that rules get run
* If there are rules that apply then it will it will change the {@link DataFetcher} of that field so that rules get run
* BEFORE the original field fetch is run.
*/
@PublicApi
Expand All @@ -38,40 +35,29 @@ public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFi
GraphQLFieldsContainer fieldsContainer = env.getFieldsContainer();
GraphQLFieldDefinition fieldDefinition = env.getFieldDefinition();

TargetedValidationRules rules = ruleCandidates.buildRulesFor(fieldDefinition, fieldsContainer);
if (rules.isEmpty()) {
return fieldDefinition; // no rules - no validation needed
}

OnValidationErrorStrategy errorStrategy = ruleCandidates.getOnValidationErrorStrategy();
MessageInterpolator messageInterpolator = ruleCandidates.getMessageInterpolator();
Locale locale = ruleCandidates.getLocale();

final DataFetcher currentDF = env.getCodeRegistry().getDataFetcher(fieldsContainer, fieldDefinition);
final DataFetcher newDF = buildValidatingDataFetcher(rules, errorStrategy, messageInterpolator, currentDF, locale);
final DataFetcher<?> currentDF = env.getCodeRegistry().getDataFetcher(fieldsContainer, fieldDefinition);
final DataFetcher<?> newDF = buildValidatingDataFetcher(errorStrategy, messageInterpolator, currentDF, locale);

env.getCodeRegistry().dataFetcher(fieldsContainer, fieldDefinition, newDF);

return fieldDefinition;
}

private DataFetcher buildValidatingDataFetcher(TargetedValidationRules rules, OnValidationErrorStrategy errorStrategy, MessageInterpolator messageInterpolator, DataFetcher currentDF, final Locale defaultLocale) {
// ok we have some rules that need to be applied to this field and its arguments
return environment -> {
List<GraphQLError> errors = rules.runValidationRules(environment, messageInterpolator, defaultLocale);
if (!errors.isEmpty()) {
// should we continue?
if (!errorStrategy.shouldContinue(errors, environment)) {
return errorStrategy.onErrorValue(errors, environment);
}
}
// we have no validation errors or they said continue so call the underlying data fetcher
Object returnValue = currentDF.get(environment);
if (errors.isEmpty()) {
return returnValue;
}
return Util.mkDFRFromFetchedResult(errors, returnValue);
};
private DataFetcher<Object> buildValidatingDataFetcher(OnValidationErrorStrategy errorStrategy,
MessageInterpolator messageInterpolator,
DataFetcher<?> currentDF,
final Locale defaultLocale) {
return new FieldValidatorDataFetcher(
errorStrategy,
messageInterpolator,
currentDF,
defaultLocale,
ruleCandidates
);
}

}