PersistedQuerySupport.java
package graphql.execution.preparsed.persisted;
import graphql.ExecutionInput;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.PublicSpi;
import graphql.execution.preparsed.PreparsedDocumentEntry;
import graphql.execution.preparsed.PreparsedDocumentProvider;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import static graphql.Assert.assertNotNull;
import static java.util.concurrent.CompletableFuture.completedFuture;
/**
* This abstract class forms the basis for persistent query support. Derived classes
* need to implement the method to work out the query id and you also need
* a {@link PersistedQueryCache} implementation.
*
* @see graphql.execution.preparsed.PreparsedDocumentProvider
* @see graphql.GraphQL.Builder#preparsedDocumentProvider(graphql.execution.preparsed.PreparsedDocumentProvider)
*/
@PublicSpi
public abstract class PersistedQuerySupport implements PreparsedDocumentProvider {
/**
* In order for {@link graphql.ExecutionInput#getQuery()} to never be null, use this to mark
* them so that invariant can be satisfied while assuming that the persisted query id is elsewhere
*/
public static final String PERSISTED_QUERY_MARKER = "PersistedQueryMarker";
private final PersistedQueryCache persistedQueryCache;
public PersistedQuerySupport(PersistedQueryCache persistedQueryCache) {
this.persistedQueryCache = assertNotNull(persistedQueryCache);
}
@Override
public CompletableFuture<PreparsedDocumentEntry> getDocumentAsync(ExecutionInput executionInput, Function<ExecutionInput, PreparsedDocumentEntry> parseAndValidateFunction) {
Optional<Object> queryIdOption = getPersistedQueryId(executionInput);
assertNotNull(queryIdOption, "The class %s MUST return a non null optional query id", this.getClass().getName());
try {
if (queryIdOption.isPresent()) {
Object persistedQueryId = queryIdOption.get();
return persistedQueryCache.getPersistedQueryDocumentAsync(persistedQueryId, executionInput, (queryText) -> {
// we have a miss and they gave us nothing - bah!
if (queryText == null || queryText.isBlank()) {
throw new PersistedQueryNotFound(persistedQueryId);
}
// validate the queryText hash before returning to the cache which we assume will set it
if (persistedQueryIdIsInvalid(persistedQueryId, queryText)) {
throw new PersistedQueryIdInvalid(persistedQueryId);
}
ExecutionInput newEI = executionInput.transform(builder -> builder.query(queryText));
return parseAndValidateFunction.apply(newEI);
});
}
// ok there is no query id - we assume the query is indeed ready to go as is - ie its not a persisted query
return completedFuture(parseAndValidateFunction.apply(executionInput));
} catch (PersistedQueryError e) {
return completedFuture(mkMissingError(e));
}
}
/**
* This method is required for concrete types to work out the query id (often a hash) that should be used to look
* up the persisted query in the cache.
*
* @param executionInput the execution input
*
* @return an optional id of the persisted query
*/
abstract protected Optional<Object> getPersistedQueryId(ExecutionInput executionInput);
protected boolean persistedQueryIdIsInvalid(Object persistedQueryId, String queryText) {
return false;
}
/**
* Allows you to customize the graphql error that is sent back on a missing persisted query
*
* @param persistedQueryError the missing persistent query exception
*
* @return a PreparsedDocumentEntry that holds an error
*/
protected PreparsedDocumentEntry mkMissingError(PersistedQueryError persistedQueryError) {
GraphQLError gqlError = GraphqlErrorBuilder.newError()
.errorType(persistedQueryError).message(persistedQueryError.getMessage())
.extensions(persistedQueryError.getExtensions()).build();
return new PreparsedDocumentEntry(gqlError);
}
}