Add FailureAnalyzer for schema location issues
Prior to this commit, the schema locations configured with `spring.graphql.schema.locations` could fail in several cases: * one of the provided locations cannot be resolved as a Resource * none of the provided locations holds an actual schema file The latter can happen if several classpath locations match a given location and the wrong one is considered first. This commit introduces a new `MissingSchemaException` that holds the locations information. We're adding the relevant FailureAnalyzer to help developers troubleshoot such configurations issues. Fixes gh-117
This commit is contained in:
@@ -24,6 +24,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import graphql.GraphQL;
|
||||
import graphql.execution.instrumentation.Instrumentation;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -51,6 +53,8 @@ import org.springframework.graphql.execution.RuntimeWiringConfigurer;
|
||||
@EnableConfigurationProperties(GraphQlProperties.class)
|
||||
public class GraphQlAutoConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GraphQlAutoConfiguration.class);
|
||||
|
||||
private static final String[] SCHEMA_FILES_EXTENSIONS = new String[] {"*.graphqls", "*.graphql", "*.gql", "*.gqls"};
|
||||
|
||||
@Bean
|
||||
@@ -74,9 +78,17 @@ public class GraphQlAutoConfiguration {
|
||||
List<Resource> schemaResources = new ArrayList<>();
|
||||
for (String location : schemaLocations) {
|
||||
for (String extension : SCHEMA_FILES_EXTENSIONS) {
|
||||
schemaResources.addAll(Arrays.asList(resolver.getResources(location + extension)));
|
||||
try {
|
||||
schemaResources.addAll(Arrays.asList(resolver.getResources(location + extension)));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.debug("Could not resolve schema location: '" + location + extension + "'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (schemaResources.isEmpty()) {
|
||||
throw new MissingSchemaException(schemaLocations, resolver);
|
||||
}
|
||||
return schemaResources;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.boot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
|
||||
/**
|
||||
* {@link MissingSchemaException} thrown when no schema can be found in the provided locations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class MissingSchemaException extends RuntimeException {
|
||||
|
||||
private final List<SchemaLocation> schemaLocations;
|
||||
|
||||
public MissingSchemaException(List<String> locations, ResourcePatternResolver resolver) {
|
||||
List<SchemaLocation> providedLocations = new ArrayList<>();
|
||||
for (String location : locations) {
|
||||
try {
|
||||
String uri = resolver.getResource(location).getURI().toASCIIString();
|
||||
providedLocations.add(new SchemaLocation(location, uri));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
providedLocations.add(new SchemaLocation(location, ""));
|
||||
}
|
||||
}
|
||||
this.schemaLocations = Collections.unmodifiableList(providedLocations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of provided locations where to look for schemas.
|
||||
*/
|
||||
public List<SchemaLocation> getSchemaLocations() {
|
||||
return this.schemaLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* The location where to look for schemas.
|
||||
*/
|
||||
public static class SchemaLocation {
|
||||
|
||||
private final String location;
|
||||
|
||||
private final String uri;
|
||||
|
||||
SchemaLocation(String location, String uri) {
|
||||
this.location = location;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location String to be resolved by a {@link ResourcePatternResolver}.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resolved URI String for this location, an empty String if resolution failed.
|
||||
*/
|
||||
public String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.boot;
|
||||
|
||||
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
|
||||
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||
|
||||
/**
|
||||
* An implementation of {@link AbstractFailureAnalyzer} to analyze failures caused by
|
||||
* {@link MissingSchemaException}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class MissingSchemaExceptionFailureAnalyzer extends AbstractFailureAnalyzer<MissingSchemaException> {
|
||||
|
||||
@Override
|
||||
protected FailureAnalysis analyze(Throwable rootFailure, MissingSchemaException cause) {
|
||||
String message = "Could not find any GraphQL schema file under configured locations.";
|
||||
StringBuilder action = new StringBuilder("Check that the following locations contain schema files: " + System.lineSeparator());
|
||||
for (MissingSchemaException.SchemaLocation schemaLocation : cause.getSchemaLocations()) {
|
||||
action.append(String.format("- '%s' (%s)" + System.lineSeparator(), schemaLocation.getUri(), schemaLocation.getLocation()));
|
||||
}
|
||||
return new FailureAnalysis(message, action.toString(), cause);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,10 @@ org.springframework.graphql.boot.security.GraphQlWebMvcSecurityAutoConfiguration
|
||||
org.springframework.graphql.boot.data.GraphQlWebMvcQuerydslAutoConfiguration,\
|
||||
org.springframework.graphql.boot.data.GraphQlWebFluxQuerydslAutoConfiguration
|
||||
|
||||
# Failure Analyzers
|
||||
org.springframework.boot.diagnostics.FailureAnalyzer=\
|
||||
org.springframework.graphql.boot.MissingSchemaExceptionFailureAnalyzer
|
||||
|
||||
# Spring Test @AutoConfigureGraphQlTester
|
||||
org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester=\
|
||||
org.springframework.graphql.boot.test.tester.WebTestClientMockMvcAutoConfiguration,\
|
||||
|
||||
@@ -40,7 +40,7 @@ class GraphQlAutoConfigurationTests {
|
||||
void shouldFailWhenSchemaFileIsMissing() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context).getFailure().getRootCause().hasMessage("'schemaResources' should not be empty");
|
||||
assertThat(context).getFailure().getRootCause().isInstanceOf(MissingSchemaException.class);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user