Skip to content

Commit 2ecac0b

Browse files
committed
Multiple WiringFactory registrations
CombinedWiringFactory makes use of multiple WiringFactory instances possible, but RuntimeWiring.Builder does not use it by default. This change enables use of multiple WiringFactory instances through an extra callback on RuntimeWiringConfigurer that allows multiple parties to add their own WiringFactory. See gh-244
1 parent be74ba2 commit 2ecac0b

File tree

4 files changed

+183
-8
lines changed

4 files changed

+183
-8
lines changed

spring-graphql-docs/src/docs/asciidoc/index.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ You can use `RuntimeWiringConfigurer` to register:
213213
`AnnotatedControllerConfigurer`, which detects annotated, `DataFetcher` handler methods.
214214
The Spring Boot starter adds the `AnnotatedControllerConfigurer` by default.
215215

216+
If you need to add a `WiringFactory`, e.g. to make registrations that take into account
217+
schema definitions, implement the alternative `configure` method that accepts both the
218+
`RuntimeWiring.Builder` and an output `List<WiringFactory>`. This allows you to add any
219+
number of factories that are then invoked in sequence.
220+
216221
The Spring for GraphQL Boot starter detects beans of type `RuntimeWiringConfigurer`.
217222

218223

spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilder.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,10 +36,13 @@
3636
import graphql.schema.GraphQLTypeVisitor;
3737
import graphql.schema.SchemaTraverser;
3838
import graphql.schema.TypeResolver;
39+
import graphql.schema.idl.CombinedWiringFactory;
40+
import graphql.schema.idl.NoopWiringFactory;
3941
import graphql.schema.idl.RuntimeWiring;
4042
import graphql.schema.idl.SchemaGenerator;
4143
import graphql.schema.idl.SchemaParser;
4244
import graphql.schema.idl.TypeDefinitionRegistry;
45+
import graphql.schema.idl.WiringFactory;
4346

4447
import org.springframework.core.io.Resource;
4548
import org.springframework.lang.Nullable;
@@ -130,9 +133,7 @@ public GraphQlSource build() {
130133
.map(this::parseSchemaResource).reduce(TypeDefinitionRegistry::merge)
131134
.orElseThrow(MissingSchemaException::new);
132135

133-
RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring();
134-
this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(runtimeWiringBuilder));
135-
RuntimeWiring runtimeWiring = runtimeWiringBuilder.build();
136+
RuntimeWiring runtimeWiring = initRuntimeWiring();
136137

137138
registerDefaultTypeResolver(registry, runtimeWiring);
138139

@@ -154,6 +155,23 @@ public GraphQlSource build() {
154155
return new CachedGraphQlSource(graphQl, schema);
155156
}
156157

158+
private RuntimeWiring initRuntimeWiring() {
159+
RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
160+
this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(builder));
161+
162+
List<WiringFactory> factories = new ArrayList<>();
163+
WiringFactory factory = builder.build().getWiringFactory();
164+
if (!factory.getClass().equals(NoopWiringFactory.class)) {
165+
factories.add(factory);
166+
}
167+
this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(builder, factories));
168+
if (!factories.isEmpty()) {
169+
builder.wiringFactory(new CombinedWiringFactory(factories));
170+
}
171+
172+
return builder.build();
173+
}
174+
157175
private void registerDefaultTypeResolver(TypeDefinitionRegistry registry, RuntimeWiring runtimeWiring) {
158176
TypeResolver typeResolver =
159177
(this.defaultTypeResolver != null ? this.defaultTypeResolver : new ClassNameTypeResolver());
@@ -171,7 +189,7 @@ private TypeDefinitionRegistry parseSchemaResource(Resource schemaResource) {
171189
}
172190
}
173191
catch (IOException ex) {
174-
throw new IllegalArgumentException("Failed to load schema resource: " + schemaResource.toString());
192+
throw new IllegalArgumentException("Failed to load schema resource: " + schemaResource);
175193
}
176194
}
177195

spring-graphql/src/main/java/org/springframework/graphql/execution/RuntimeWiringConfigurer.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,11 +15,14 @@
1515
*/
1616
package org.springframework.graphql.execution;
1717

18+
import java.util.List;
19+
1820
import graphql.schema.idl.RuntimeWiring;
21+
import graphql.schema.idl.WiringFactory;
1922

2023
/**
21-
* Component used to apply changes to the {@link RuntimeWiring.Builder} instance
22-
* used in {@link GraphQlSource.Builder}.
24+
* Callbacks that allow applying changes to the {@link RuntimeWiring.Builder}
25+
* in {@link GraphQlSource.Builder}.
2326
*
2427
* @author Rossen Stoyanchev
2528
* @since 1.0.0
@@ -33,4 +36,15 @@ public interface RuntimeWiringConfigurer {
3336
*/
3437
void configure(RuntimeWiring.Builder builder);
3538

39+
/**
40+
* Variant of {@link #configure(RuntimeWiring.Builder)} that also collects
41+
* {@link WiringFactory} instances that are then combined as one via
42+
* {@link graphql.schema.idl.CombinedWiringFactory}.
43+
* @param builder the builder to configure
44+
* @param container the list of configured factories to add or insert into
45+
*/
46+
default void configure(RuntimeWiring.Builder builder, List<WiringFactory> container) {
47+
// no-op
48+
}
49+
3650
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.graphql.execution;
17+
18+
import java.util.List;
19+
20+
import graphql.schema.DataFetcher;
21+
import graphql.schema.FieldCoordinates;
22+
import graphql.schema.GraphQLFieldDefinition;
23+
import graphql.schema.GraphQLSchema;
24+
import graphql.schema.idl.FieldWiringEnvironment;
25+
import graphql.schema.idl.RuntimeWiring;
26+
import graphql.schema.idl.WiringFactory;
27+
import org.junit.jupiter.api.Test;
28+
29+
import org.springframework.graphql.GraphQlSetup;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.mockito.Mockito.mock;
33+
34+
/**
35+
* Unit tests for {@link DefaultGraphQlSourceBuilder}.
36+
*
37+
* @author Rossen Stoyanchev
38+
*/
39+
public class DefaultGraphQlSourceBuilderTests {
40+
41+
@Test
42+
void wiringFactoryList() {
43+
44+
String schemaContent = "type Query {" +
45+
" q1: String" +
46+
" q2: String" +
47+
"}";
48+
49+
DataFetcher<?> dataFetcher1 = mock(DataFetcher.class);
50+
DataFetcher<?> dataFetcher2 = mock(DataFetcher.class);
51+
52+
RuntimeWiringConfigurer configurer = new RuntimeWiringConfigurer() {
53+
54+
@Override
55+
public void configure(RuntimeWiring.Builder builder) {
56+
}
57+
58+
@Override
59+
public void configure(RuntimeWiring.Builder builder, List<WiringFactory> container) {
60+
container.add(new DataFetcherWiringFactory("q1", dataFetcher1));
61+
container.add(new DataFetcherWiringFactory("q2", dataFetcher2));
62+
}
63+
};
64+
65+
GraphQLSchema schema = GraphQlSetup.schemaContent(schemaContent)
66+
.runtimeWiring(configurer)
67+
.toGraphQlSource()
68+
.schema();
69+
70+
assertThat(getDataFetcherForQuery(schema, "q1")).isSameAs(dataFetcher1);
71+
assertThat(getDataFetcherForQuery(schema, "q2")).isSameAs(dataFetcher2);
72+
}
73+
74+
@Test
75+
void wiringFactoryListAndBuilderWiringFactory() {
76+
77+
String schemaContent = "type Query {" +
78+
" q1: String" +
79+
" q2: String" +
80+
"}";
81+
82+
DataFetcher<?> dataFetcher1 = mock(DataFetcher.class);
83+
DataFetcher<?> dataFetcher2 = mock(DataFetcher.class);
84+
85+
RuntimeWiringConfigurer configurer = new RuntimeWiringConfigurer() {
86+
87+
@Override
88+
public void configure(RuntimeWiring.Builder builder) {
89+
builder.wiringFactory(new DataFetcherWiringFactory("q1", dataFetcher1));
90+
}
91+
92+
@Override
93+
public void configure(RuntimeWiring.Builder builder, List<WiringFactory> container) {
94+
container.add(new DataFetcherWiringFactory("q2", dataFetcher2));
95+
}
96+
};
97+
98+
GraphQLSchema schema = GraphQlSetup.schemaContent(schemaContent)
99+
.runtimeWiring(configurer)
100+
.toGraphQlSource()
101+
.schema();
102+
103+
assertThat(getDataFetcherForQuery(schema, "q1")).isSameAs(dataFetcher1);
104+
assertThat(getDataFetcherForQuery(schema, "q2")).isSameAs(dataFetcher2);
105+
}
106+
107+
private DataFetcher<?> getDataFetcherForQuery(GraphQLSchema schema, String query) {
108+
FieldCoordinates coordinates = FieldCoordinates.coordinates("Query", query);
109+
GraphQLFieldDefinition fieldDefinition = schema.getFieldDefinition(coordinates);
110+
return schema.getCodeRegistry().getDataFetcher(coordinates, fieldDefinition);
111+
}
112+
113+
114+
private static class DataFetcherWiringFactory implements WiringFactory {
115+
116+
private final String queryName;
117+
118+
private final DataFetcher<?> dataFetcher;
119+
120+
DataFetcherWiringFactory(String queryName, DataFetcher<?> dataFetcher) {
121+
this.queryName = queryName;
122+
this.dataFetcher = dataFetcher;
123+
}
124+
125+
@Override
126+
public boolean providesDataFetcher(FieldWiringEnvironment environment) {
127+
return (environment.getParentType().getName().equals("Query") &&
128+
environment.getFieldDefinition().getName().equals(this.queryName));
129+
}
130+
131+
@Override
132+
public DataFetcher<?> getDataFetcher(FieldWiringEnvironment environment) {
133+
return this.dataFetcher;
134+
}
135+
136+
}
137+
138+
}

0 commit comments

Comments
 (0)