From 95bfe2e318db856ddc57fb5d26136de64c37aac8 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 13 Oct 2020 16:16:35 +0200 Subject: [PATCH] Support programmatic GraphQLSchema creation Prior to this commit, the `GraphQLAutoConfiguration` would only consider the case where the GraphQL schema is read as an SDL from a configured location. GraphQL also supports the programmatic creation of the schema and this case needs to be supported by the auto-configuration. This commit updates the auto-configuration to disable the schema creation from the SDL if a `GraphQL.Builder` bean is already contributed by the application. Fixes gh-4 --- .../graphql/GraphQLAutoConfiguration.java | 45 ++++++---- .../MissingGraphQLSchemaException.java | 37 ++++++++ .../GraphQLAutoConfigurationTests.java | 87 +++++++++++++++++++ 3 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 spring-graphql-web/src/main/java/org/springframework/boot/graphql/MissingGraphQLSchemaException.java create mode 100644 spring-graphql-web/src/test/java/org/springframework/boot/graphql/GraphQLAutoConfigurationTests.java diff --git a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/GraphQLAutoConfiguration.java b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/GraphQLAutoConfiguration.java index 1063ae8..04415b9 100644 --- a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/GraphQLAutoConfiguration.java +++ b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/GraphQLAutoConfiguration.java @@ -39,25 +39,36 @@ @EnableConfigurationProperties(GraphQLProperties.class) public class GraphQLAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public RuntimeWiring runtimeWiring(ObjectProvider customizers) { - RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring(); - customizers.orderedStream().forEach(customizer -> customizer.customize(builder)); - return builder.build(); - } + @Configuration + @ConditionalOnMissingBean(GraphQL.Builder.class) + static class SdlConfiguration { - @Bean - public GraphQL.Builder graphQLBuilder(GraphQLProperties properties, RuntimeWiring runtimeWiring) throws FileNotFoundException { - File schemaFile = ResourceUtils.getFile(properties.getSchema()); - GraphQLSchema schema = buildSchema(schemaFile, runtimeWiring); - return GraphQL.newGraphQL(schema); - } + @Bean + @ConditionalOnMissingBean + public RuntimeWiring runtimeWiring(ObjectProvider customizers) { + RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring(); + customizers.orderedStream().forEach(customizer -> customizer.customize(builder)); + return builder.build(); + } + + @Bean + public GraphQL.Builder graphQLBuilder(GraphQLProperties properties, RuntimeWiring runtimeWiring) { + try { + File schemaFile = ResourceUtils.getFile(properties.getSchema()); + GraphQLSchema schema = buildSchema(schemaFile, runtimeWiring); + return GraphQL.newGraphQL(schema); + } + catch (FileNotFoundException ex) { + throw new MissingGraphQLSchemaException(properties.getSchema()); + } + } + + private GraphQLSchema buildSchema(File schemaFile, RuntimeWiring runtimeWiring) { + TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaFile); + SchemaGenerator schemaGenerator = new SchemaGenerator(); + return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); + } - private GraphQLSchema buildSchema(File schemaFile, RuntimeWiring runtimeWiring) { - TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaFile); - SchemaGenerator schemaGenerator = new SchemaGenerator(); - return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); } } diff --git a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/MissingGraphQLSchemaException.java b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/MissingGraphQLSchemaException.java new file mode 100644 index 0000000..2943ee3 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/MissingGraphQLSchemaException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2020 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.boot.graphql; + +import org.springframework.util.StringUtils; + +/** + * Exception thrown when no GraphQL schema is available. + */ +public class MissingGraphQLSchemaException extends RuntimeException { + + private final String path; + + MissingGraphQLSchemaException(String path) { + super(StringUtils.hasText(path) ? "Path to GraphQL schema not configured" : "Cannot find schema file at: " + + path + " (please add a schema file or check your GraphQL configuration)"); + this.path = path; + } + + public String getPath() { + return this.path; + } +} diff --git a/spring-graphql-web/src/test/java/org/springframework/boot/graphql/GraphQLAutoConfigurationTests.java b/spring-graphql-web/src/test/java/org/springframework/boot/graphql/GraphQLAutoConfigurationTests.java new file mode 100644 index 0000000..28e9b8d --- /dev/null +++ b/spring-graphql-web/src/test/java/org/springframework/boot/graphql/GraphQLAutoConfigurationTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2020 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.boot.graphql; + +import graphql.GraphQL; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLSchema; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static graphql.Scalars.GraphQLString; +import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +import static graphql.schema.GraphQLObjectType.newObject; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GraphQLAutoConfiguration} + */ +class GraphQLAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(GraphQLAutoConfiguration.class)); + + + @Test + void shouldFailWhenSchemaFileIsMissing() { + contextRunner.run((context) -> { + assertThat(context).hasFailed(); + assertThat(context).getFailure().getRootCause().isInstanceOf(MissingGraphQLSchemaException.class); + }); + } + + @Test + void shouldCreateBuilderWithSdl() { + contextRunner + .withPropertyValues("spring.graphql.schema:classpath:books/schema.graphqls") + .run((context) -> { + assertThat(context).hasSingleBean(GraphQL.Builder.class); + }); + } + + @Test + void shouldUseProgrammaticallyDefinedBuilder() { + contextRunner + .withPropertyValues("spring.graphql.schema:classpath:books/schema.graphqls") + .withUserConfiguration(CustomGraphQLBuilderConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("customGraphQLBuilder"); + assertThat(context).hasSingleBean(GraphQL.Builder.class); + }); + } + + @Configuration + static class CustomGraphQLBuilderConfiguration { + + @Bean + public GraphQL.Builder customGraphQLBuilder() { + GraphQLObjectType queryType = newObject() + .name("helloWorldQuery") + .field(newFieldDefinition() + .type(GraphQLString) + .name("hello")) + .build(); + GraphQLSchema schema = GraphQLSchema.newSchema().query(queryType).build(); + return GraphQL.newGraphQL(schema); + } + } + +}