Skip to content

Commit 5672d82

Browse files
committed
enum values provider
Introducing the possibility to define a Java value for each graphql enum value, when defining a schema via IDL.
1 parent c583601 commit 5672d82

File tree

6 files changed

+153
-14
lines changed

6 files changed

+153
-14
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package graphql.schema.idl;
2+
3+
import graphql.PublicSpi;
4+
5+
/**
6+
* Provides the Java runtime value for each graphql Enum value. Used for IDL driven schema creation.
7+
* <p>
8+
* Enum values are considered static: This is called when a schema is created. It is not used when a query is executed.
9+
*/
10+
@PublicSpi
11+
public interface EnumValuesProvider {
12+
13+
/**
14+
* @param name
15+
* @return not null
16+
*/
17+
Object getValue(String name);
18+
19+
}

src/main/java/graphql/schema/idl/RuntimeWiring.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ public class RuntimeWiring {
2121
private final Map<String, Map<String, DataFetcher>> dataFetchers;
2222
private final Map<String, GraphQLScalarType> scalars;
2323
private final Map<String, TypeResolver> typeResolvers;
24+
private Map<String, EnumValuesProvider> enumValuesProviders;
2425
private final WiringFactory wiringFactory;
2526

26-
private RuntimeWiring(Map<String, Map<String, DataFetcher>> dataFetchers, Map<String, GraphQLScalarType> scalars, Map<String, TypeResolver> typeResolvers, WiringFactory wiringFactory) {
27+
private RuntimeWiring(Map<String, Map<String, DataFetcher>> dataFetchers, Map<String, GraphQLScalarType> scalars, Map<String, TypeResolver> typeResolvers, Map<String, EnumValuesProvider> enumValuesProviders, WiringFactory wiringFactory) {
2728
this.dataFetchers = dataFetchers;
2829
this.scalars = scalars;
2930
this.typeResolvers = typeResolvers;
3031
this.wiringFactory = wiringFactory;
32+
this.enumValuesProviders = enumValuesProviders;
3133
}
3234

3335
public Map<String, GraphQLScalarType> getScalars() {
@@ -46,6 +48,10 @@ public Map<String, TypeResolver> getTypeResolvers() {
4648
return typeResolvers;
4749
}
4850

51+
public Map<String, EnumValuesProvider> getEnumValuesProviders() {
52+
return this.enumValuesProviders;
53+
}
54+
4955
public WiringFactory getWiringFactory() {
5056
return wiringFactory;
5157
}
@@ -62,6 +68,7 @@ public static class Builder {
6268
private final Map<String, Map<String, DataFetcher>> dataFetchers = new LinkedHashMap<>();
6369
private final Map<String, GraphQLScalarType> scalars = new LinkedHashMap<>();
6470
private final Map<String, TypeResolver> typeResolvers = new LinkedHashMap<>();
71+
private final Map<String, EnumValuesProvider> enumValuesProviders = new LinkedHashMap<>();
6572
private WiringFactory wiringFactory = new NoopWiringFactory();
6673

6774
private Builder() {
@@ -72,7 +79,6 @@ private Builder() {
7279
* Adds a wiring factory into the runtime wiring
7380
*
7481
* @param wiringFactory the wiring factory to add
75-
*
7682
* @return this outer builder
7783
*/
7884
public Builder wiringFactory(WiringFactory wiringFactory) {
@@ -85,7 +91,6 @@ public Builder wiringFactory(WiringFactory wiringFactory) {
8591
* This allows you to add in new custom Scalar implementations beyond the standard set.
8692
*
8793
* @param scalarType the new scalar implementation
88-
*
8994
* @return the runtime wiring builder
9095
*/
9196
public Builder scalar(GraphQLScalarType scalarType) {
@@ -97,7 +102,6 @@ public Builder scalar(GraphQLScalarType scalarType) {
97102
* This allows you to add a new type wiring via a builder
98103
*
99104
* @param builder the type wiring builder to use
100-
*
101105
* @return this outer builder
102106
*/
103107
public Builder type(TypeRuntimeWiring.Builder builder) {
@@ -109,7 +113,6 @@ public Builder type(TypeRuntimeWiring.Builder builder) {
109113
*
110114
* @param typeName the name of the type to wire
111115
* @param builderFunction a function that will be given the builder to use
112-
*
113116
* @return the runtime wiring builder
114117
*/
115118
public Builder type(String typeName, UnaryOperator<TypeRuntimeWiring.Builder> builderFunction) {
@@ -121,7 +124,6 @@ public Builder type(String typeName, UnaryOperator<TypeRuntimeWiring.Builder> bu
121124
* This adds a type wiring
122125
*
123126
* @param typeRuntimeWiring the new type wiring
124-
*
125127
* @return the runtime wiring builder
126128
*/
127129
public Builder type(TypeRuntimeWiring typeRuntimeWiring) {
@@ -133,14 +135,19 @@ public Builder type(TypeRuntimeWiring typeRuntimeWiring) {
133135
if (typeResolver != null) {
134136
this.typeResolvers.put(typeName, typeResolver);
135137
}
138+
139+
EnumValuesProvider enumValuesProvider = typeRuntimeWiring.getEnumValuesProvider();
140+
if (enumValuesProvider != null) {
141+
this.enumValuesProviders.put(typeName, enumValuesProvider);
142+
}
136143
return this;
137144
}
138145

139146
/**
140147
* @return the built runtime wiring
141148
*/
142149
public RuntimeWiring build() {
143-
return new RuntimeWiring(dataFetchers, scalars, typeResolvers, wiringFactory);
150+
return new RuntimeWiring(dataFetchers, scalars, typeResolvers, enumValuesProviders, wiringFactory);
144151
}
145152

146153
}

src/main/java/graphql/schema/idl/SchemaGenerator.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ private <T extends GraphQLOutputType> T buildOutputType(BuildContext buildCtx, T
227227
} else if (typeDefinition instanceof UnionTypeDefinition) {
228228
outputType = buildUnionType(buildCtx, (UnionTypeDefinition) typeDefinition);
229229
} else if (typeDefinition instanceof EnumTypeDefinition) {
230-
outputType = buildEnumType((EnumTypeDefinition) typeDefinition);
230+
outputType = buildEnumType(buildCtx, (EnumTypeDefinition) typeDefinition);
231231
} else if (typeDefinition instanceof ScalarTypeDefinition) {
232232
outputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
233233
} else {
@@ -260,7 +260,7 @@ private GraphQLInputType buildInputType(BuildContext buildCtx, Type rawType) {
260260
if (typeDefinition instanceof InputObjectTypeDefinition) {
261261
inputType = buildInputObjectType(buildCtx, (InputObjectTypeDefinition) typeDefinition);
262262
} else if (typeDefinition instanceof EnumTypeDefinition) {
263-
inputType = buildEnumType((EnumTypeDefinition) typeDefinition);
263+
inputType = buildEnumType(buildCtx, (EnumTypeDefinition) typeDefinition);
264264
} else if (typeDefinition instanceof ScalarTypeDefinition) {
265265
inputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
266266
} else {
@@ -363,13 +363,23 @@ private GraphQLUnionType buildUnionType(BuildContext buildCtx, UnionTypeDefiniti
363363
return builder.build();
364364
}
365365

366-
private GraphQLEnumType buildEnumType(EnumTypeDefinition typeDefinition) {
366+
private GraphQLEnumType buildEnumType(BuildContext buildCtx, EnumTypeDefinition typeDefinition) {
367367
GraphQLEnumType.Builder builder = GraphQLEnumType.newEnum();
368368
builder.name(typeDefinition.getName());
369369
builder.description(buildDescription(typeDefinition));
370370

371-
typeDefinition.getEnumValueDefinitions().forEach(evd -> builder.value(evd.getName(), evd.getName(), buildDescription(evd)));
372-
// typeDefinition.getEnumValueDefinitions().forEach(evd -> builder.value(evd.getName()));
371+
EnumValuesProvider enumValuesProvider = buildCtx.getWiring().getEnumValuesProviders().get(typeDefinition.getName());
372+
typeDefinition.getEnumValueDefinitions().forEach(evd -> {
373+
String description = buildDescription(evd);
374+
Object value;
375+
if (enumValuesProvider != null) {
376+
value = enumValuesProvider.getValue(evd.getName());
377+
assertNotNull(value, String.format("EnumValuesProvider for %s returned null for %s", typeDefinition.getName(), evd.getName()));
378+
} else {
379+
value = evd.getName();
380+
}
381+
builder.value(evd.getName(), value, description);
382+
});
373383
return builder.build();
374384
}
375385

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package graphql.schema.idl;
2+
3+
import graphql.Assert;
4+
import graphql.PublicApi;
5+
6+
/**
7+
* Simple EnumValuesProvided which maps the GraphQL Enum name to the Java Enum instance.
8+
*
9+
* @param <T>
10+
*/
11+
@PublicApi
12+
public class StaticEnumValuesProvider<T extends Enum<T>> implements EnumValuesProvider {
13+
14+
15+
private final Class<T> enumType;
16+
17+
public StaticEnumValuesProvider(Class<T> enumType) {
18+
Assert.assertNotNull(enumType, "enumType can't be null");
19+
this.enumType = enumType;
20+
}
21+
22+
@Override
23+
public T getValue(String name) {
24+
return Enum.valueOf(enumType, name);
25+
}
26+
}

src/main/java/graphql/schema/idl/TypeRuntimeWiring.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ public class TypeRuntimeWiring {
1919
private final String typeName;
2020
private final Map<String, DataFetcher> fieldDataFetchers;
2121
private final TypeResolver typeResolver;
22+
private final EnumValuesProvider enumValuesProvider;
2223

23-
private TypeRuntimeWiring(String typeName, Map<String, DataFetcher> fieldDataFetchers, TypeResolver typeResolver) {
24+
private TypeRuntimeWiring(String typeName, Map<String, DataFetcher> fieldDataFetchers, TypeResolver typeResolver, EnumValuesProvider enumValuesProvider) {
2425
this.typeName = typeName;
2526
this.fieldDataFetchers = fieldDataFetchers;
2627
this.typeResolver = typeResolver;
28+
this.enumValuesProvider = enumValuesProvider;
2729
}
2830

2931
public String getTypeName() {
@@ -38,6 +40,10 @@ public TypeResolver getTypeResolver() {
3840
return typeResolver;
3941
}
4042

43+
public EnumValuesProvider getEnumValuesProvider() {
44+
return enumValuesProvider;
45+
}
46+
4147
/**
4248
* Creates a new type wiring builder
4349
*
@@ -66,6 +72,7 @@ public static class Builder {
6672
private String typeName;
6773
private final Map<String, DataFetcher> fieldDataFetchers = new LinkedHashMap<>();
6874
private TypeResolver typeResolver;
75+
private EnumValuesProvider enumValuesProvider;
6976

7077
/**
7178
* Sets the type name for this type wiring. You MUST set this.
@@ -121,12 +128,18 @@ public Builder typeResolver(TypeResolver typeResolver) {
121128
return this;
122129
}
123130

131+
public Builder enumValues(EnumValuesProvider enumValuesProvider) {
132+
assertNotNull(enumValuesProvider, "you must provide a type resolver");
133+
this.enumValuesProvider = enumValuesProvider;
134+
return this;
135+
}
136+
124137
/**
125138
* @return the built type wiring
126139
*/
127140
public TypeRuntimeWiring build() {
128141
assertNotNull(typeName, "you must provide a type name");
129-
return new TypeRuntimeWiring(typeName, fieldDataFetchers, typeResolver);
142+
return new TypeRuntimeWiring(typeName, fieldDataFetchers, typeResolver, enumValuesProvider);
130143
}
131144
}
132145

src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,4 +791,68 @@ class SchemaGeneratorTest extends Specification {
791791
schema.getQueryType().description == " description 1\n description 2"
792792
schema.getQueryType().getFieldDefinition("foo").description == " description 3\n description 4"
793793
}
794+
795+
enum ExampleEnum {
796+
A,
797+
B,
798+
C
799+
}
800+
801+
def "static enum values provider"() {
802+
given:
803+
def spec = """
804+
type Query {
805+
foo: Enum
806+
}
807+
enum Enum {
808+
A
809+
B
810+
C
811+
}
812+
schema {
813+
query: Query
814+
}
815+
"""
816+
def enumValuesProvider = new StaticEnumValuesProvider<ExampleEnum>(ExampleEnum.class);
817+
when:
818+
819+
def wiring = RuntimeWiring.newRuntimeWiring()
820+
.type("Enum", { TypeRuntimeWiring.Builder it -> it.enumValues(enumValuesProvider) } as UnaryOperator)
821+
.build()
822+
def schema = generateSchema(spec, wiring)
823+
GraphQLEnumType enumType = schema.getType("Enum") as GraphQLEnumType
824+
825+
then:
826+
enumType.getValue("A").value == ExampleEnum.A
827+
enumType.getValue("B").value == ExampleEnum.B
828+
enumType.getValue("C").value == ExampleEnum.C
829+
}
830+
831+
def "enum with no values provider: value is the name"() {
832+
given:
833+
def spec = """
834+
type Query {
835+
foo: Enum
836+
}
837+
enum Enum {
838+
A
839+
B
840+
C
841+
}
842+
schema {
843+
query: Query
844+
}
845+
"""
846+
when:
847+
def wiring = RuntimeWiring.newRuntimeWiring()
848+
.build()
849+
def schema = generateSchema(spec, wiring)
850+
GraphQLEnumType enumType = schema.getType("Enum") as GraphQLEnumType
851+
852+
then:
853+
enumType.getValue("A").value == "A"
854+
enumType.getValue("B").value == "B"
855+
enumType.getValue("C").value == "C"
856+
857+
}
794858
}

0 commit comments

Comments
 (0)