Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit ceb2aad

Browse files
authored
Merge pull request #525 from BlasiusSecundus/feature/extended-scalars-integration
feat: integrate extended GraphQL scalars
2 parents f9125bf + 1265aed commit ceb2aad

File tree

10 files changed

+237
-16
lines changed

10 files changed

+237
-16
lines changed

README.md

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ and join the team!
1616
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
1717
**Table of Contents**
1818

19-
- [WARNING: NoClassDefFoundError when using GraphQL Java Tools > 5.4.x](#warning-noclassdeffounderror-when-using-graphql-java-tools--54x)
19+
- [WARNING: NoClassDefFoundError when using GraphQL Java Tools > 5.4.x](#warning-noclassdeffounderror-when-using-graphql-java-tools--54x)
2020
- [Using Gradle](#using-gradle)
2121
- [Using Maven](#using-maven)
2222
- [Documentation](#documentation)
@@ -26,21 +26,22 @@ and join the team!
2626
- [Enable Graph*i*QL](#enable-graphiql)
2727
- [Enable Altair](#enable-altair)
2828
- [Enable GraphQL Playground](#enable-graphql-playground)
29-
- [Basic settings](#basic-settings)
30-
- [CDN](#cdn)
31-
- [Custom static resources](#custom-static-resources)
32-
- [Customizing GraphQL Playground](#customizing-graphql-playground)
33-
- [Tabs](#tabs)
29+
- [Basic settings](#basic-settings)
30+
- [CDN](#cdn)
31+
- [Custom static resources](#custom-static-resources)
32+
- [Customizing GraphQL Playground](#customizing-graphql-playground)
33+
- [Tabs](#tabs)
3434
- [Supported GraphQL-Java Libraries](#supported-graphql-java-libraries)
35-
- [GraphQL Java Tools](#graphql-java-tools)
36-
- [GraphQL Annotations](#graphql-annotations)
37-
- [Configuration](#configuration)
38-
- [Root resolvers, directives, type extensions](#root-resolvers-directives-type-extensions)
39-
- [Interfaces](#interfaces)
40-
- [Custom scalars and type functions](#custom-scalars-and-type-functions)
41-
- [Custom Relay and GraphQL Annotation Processor](#custom-relay-and-graphql-annotation-processor)
35+
- [GraphQL Java Tools](#graphql-java-tools)
36+
- [GraphQL Annotations](#graphql-annotations)
37+
- [Configuration](#configuration)
38+
- [Root resolvers, directives, type extensions](#root-resolvers-directives-type-extensions)
39+
- [Interfaces](#interfaces)
40+
- [Custom scalars and type functions](#custom-scalars-and-type-functions)
41+
- [Custom Relay and GraphQL Annotation Processor](#custom-relay-and-graphql-annotation-processor)
42+
- [Extended scalars](#extended-scalars)
4243
- [Tracing and Metrics](#tracing-and-metrics)
43-
- [Usage](#usage)
44+
- [Usage](#usage)
4445
- [Contributions](#contributions)
4546
- [Licenses](#licenses)
4647

@@ -537,6 +538,31 @@ It is possible to define a bean implementing `Relay` and/or `GraphQLAnnotations`
537538
will be passed to the schema builder. Spring dependency injection works as usual. Note that GraphQL
538539
Annotations provides default implementation for these which should be sufficient is most cases.
539540
541+
## Extended scalars
542+
543+
[Extended scalars](https://github.com/graphql-java/graphql-java-extended-scalars) can be enabled by using the
544+
`graphql.extended-scalars` configuration property, e. g.:
545+
546+
```yaml
547+
graphql:
548+
extended-scalars: BigDecimal, Date
549+
```
550+
551+
The available scalars are the following: `BigDecimal`, `BigInteger`, `Byte`, `Char`, `Date`, `DateTime`, `JSON`,
552+
`Locale`, `Long`, `NegativeFloat`, `NegativeInt`, `NonNegativeFloat`, `NonNegativeInt`, `NonPositiveFloat`,
553+
`NonPositiveInt`, `Object`, `PositiveFloat`, `PositiveInt`, `Short`, `Time`, `Url`.
554+
555+
This setting works with both the [GraphQL Java Tools](#graphql-java-tools) and the
556+
[GraphQL Annotations](#graphql-annotations) integration.
557+
558+
When using the [GraphQL Java Tools](#graphql-java-tools) integration, the scalars must also be declared in the GraphQL
559+
Schema:
560+
561+
```graphql
562+
scalar BigDecimal
563+
scalar Date
564+
```
565+
540566
# Tracing and Metrics
541567

542568
[Apollo style tracing](https://github.com/apollographql/apollo-tracing) along with two levels of

example-graphql-subscription/src/main/resources/application.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ spring:
33
name: graphql-subscription-example
44
server:
55
port: 9001
6+
7+
graphql:
8+
extended-scalars: BigDecimal

example-graphql-subscription/src/main/resources/subscription.graphqls

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ schema {
66
subscription : Subscription
77
}
88

9+
scalar BigDecimal
10+
911
type Query {
1012
hello : String
1113
}
@@ -17,6 +19,6 @@ type Subscription {
1719
type StockPriceUpdate {
1820
dateTime : String
1921
stockCode : String
20-
stockPrice : Float
21-
stockPriceChange : Float!
22+
stockPrice : BigDecimal
23+
stockPriceChange : BigDecimal!
2224
}

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ SOURCE_COMPATIBILITY=1.8
3232
TARGET_COMPATIBILITY=1.8
3333
### Dependencies
3434
LIB_GRAPHQL_JAVA_VER=16.1
35+
LIB_EXTENDED_SCALARS_VERSION=16.0.0
3536
LIB_SPRING_BOOT_VER=2.4.2
3637
LIB_GRAPHQL_SERVLET_VER=11.0.0
3738
LIB_GRAPHQL_JAVA_TOOLS_VER=11.0.0

graphql-spring-boot-autoconfigure/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies {
2121
api(project(":graphql-kickstart-spring-boot-starter-tools"))
2222
api(project(":graphql-kickstart-spring-support"))
2323
implementation "org.springframework.boot:spring-boot-autoconfigure"
24+
api "com.graphql-java:graphql-java-extended-scalars:$LIB_EXTENDED_SCALARS_VERSION"
2425
api "com.graphql-java-kickstart:graphql-java-kickstart:$LIB_GRAPHQL_SERVLET_VER"
2526
api "com.graphql-java-kickstart:graphql-java-servlet:$LIB_GRAPHQL_SERVLET_VER"
2627
api "com.graphql-java:graphql-java:$LIB_GRAPHQL_JAVA_VER"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package graphql.kickstart.spring.web.boot;
2+
3+
import graphql.scalars.ExtendedScalars;
4+
import graphql.schema.GraphQLScalarType;
5+
import lombok.NoArgsConstructor;
6+
import org.springframework.context.ApplicationContextException;
7+
import org.springframework.context.ApplicationContextInitializer;
8+
import org.springframework.context.support.GenericApplicationContext;
9+
import org.springframework.util.ReflectionUtils;
10+
11+
import java.lang.reflect.Modifier;
12+
import java.util.Collection;
13+
import java.util.Collections;
14+
import java.util.HashSet;
15+
import java.util.Set;
16+
import java.util.stream.Collectors;
17+
18+
@NoArgsConstructor
19+
public class GraphQLExtendedScalarsInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
20+
21+
@Override
22+
public void initialize(final GenericApplicationContext applicationContext) {
23+
final Collection<String> enabledExtendedScalars = getEnabledExtendedScalars(applicationContext);
24+
final Collection<String> validScalarNames = new HashSet<>();
25+
ReflectionUtils.doWithFields(ExtendedScalars.class, scalarField -> {
26+
if (Modifier.isPublic(scalarField.getModifiers()) && Modifier.isStatic(scalarField.getModifiers())
27+
&& scalarField.getType().equals(GraphQLScalarType.class)) {
28+
final GraphQLScalarType graphQLScalarType = (GraphQLScalarType) scalarField.get(null);
29+
if (enabledExtendedScalars.contains(graphQLScalarType.getName())) {
30+
applicationContext.registerBean(
31+
graphQLScalarType.getName(),
32+
GraphQLScalarType.class,
33+
() -> graphQLScalarType
34+
);
35+
}
36+
validScalarNames.add(graphQLScalarType.getName());
37+
}
38+
});
39+
verifyEnabledScalars(enabledExtendedScalars, validScalarNames);
40+
}
41+
42+
private void verifyEnabledScalars(
43+
final Collection<String> enabledExtendedScalars,
44+
final Collection<String> validScalarNames
45+
) {
46+
final Collection<String> invalidScalarNames = new HashSet<>(enabledExtendedScalars);
47+
invalidScalarNames.removeAll(validScalarNames);
48+
if (!invalidScalarNames.isEmpty()) {
49+
throw new ApplicationContextException(String.format(
50+
"Invalid extended scalar name(s) found: %s. Valid names are: %s.",
51+
joinNames(invalidScalarNames),
52+
joinNames(validScalarNames)
53+
)
54+
);
55+
}
56+
}
57+
58+
private String joinNames(final Collection<String> names) {
59+
return names.stream().sorted().collect(Collectors.joining(", "));
60+
}
61+
62+
@SuppressWarnings("unchecked")
63+
private Set<String> getEnabledExtendedScalars(final GenericApplicationContext applicationContext) {
64+
return (Set<String>) applicationContext.getEnvironment()
65+
.getProperty("graphql.extended-scalars", Collection.class, Collections.emptySet())
66+
.stream().map(String::valueOf).collect(Collectors.toSet());
67+
}
68+
}

graphql-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@
55
}
66
],
77
"properties": [
8+
{
9+
"name": "graphql.extended-scalars",
10+
"type": "java.util.Set",
11+
"description": "List of extended scalars to be used."
12+
}
813
]
914
}

graphql-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
org.springframework.context.ApplicationContextInitializer=\
2+
graphql.kickstart.spring.web.boot.GraphQLExtendedScalarsInitializer
13
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
24
graphql.kickstart.spring.web.boot.GraphQLWebAutoConfiguration,\
35
graphql.kickstart.spring.web.boot.GraphQLWebsocketAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package graphql.kickstart.spring.web.boot.test.extendedscalars;
2+
3+
import graphql.scalars.ExtendedScalars;
4+
import graphql.schema.GraphQLScalarType;
5+
import org.junit.jupiter.api.DisplayName;
6+
import org.junit.jupiter.api.Test;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.boot.autoconfigure.SpringBootApplication;
9+
import org.springframework.boot.test.context.SpringBootTest;
10+
import org.springframework.context.ApplicationContext;
11+
import org.springframework.test.context.TestPropertySource;
12+
13+
import java.util.AbstractMap;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
17+
@SpringBootTest(
18+
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
19+
classes = ExtendedScalarAutoConfigurationTest.ExtendedScalarsTestApplication.class
20+
)
21+
@TestPropertySource(properties = "graphql.extended-scalars=BigDecimal")
22+
@DisplayName("Testing extended scalars auto configuration")
23+
public class ExtendedScalarAutoConfigurationTest {
24+
25+
@Autowired
26+
private ApplicationContext applicationContext;
27+
28+
@Test
29+
@DisplayName("The extended scalars initializer should be properly picked up by Spring auto configuration.")
30+
void testAutoConfiguration() {
31+
assertThat(applicationContext.getBeansOfType(GraphQLScalarType.class))
32+
.containsOnly(new AbstractMap.SimpleEntry<>("BigDecimal", ExtendedScalars.GraphQLBigDecimal));
33+
}
34+
35+
@SpringBootApplication
36+
public static class ExtendedScalarsTestApplication {
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package graphql.kickstart.spring.web.boot.test.extendedscalars;
2+
3+
import graphql.kickstart.spring.web.boot.GraphQLExtendedScalarsInitializer;
4+
import graphql.scalars.ExtendedScalars;
5+
import graphql.schema.GraphQLScalarType;
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
import org.springframework.boot.SpringApplication;
9+
import org.springframework.boot.WebApplicationType;
10+
import org.springframework.context.ApplicationContextException;
11+
import org.springframework.context.ConfigurableApplicationContext;
12+
import org.springframework.core.env.MapPropertySource;
13+
import org.springframework.core.env.StandardEnvironment;
14+
15+
import java.util.AbstractMap;
16+
import java.util.Collections;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20+
21+
@DisplayName("Testing extended scalars configuration")
22+
public class ExtendedScalarsTest {
23+
24+
@Test
25+
@DisplayName("Should throw exception at context initialization when invalid extended scalar name is provided.")
26+
void shouldThrowErrorOnStartupIfExtendedScalarDoesNotExists() {
27+
// GIVEN
28+
final SpringApplication application = setupTestApplication("Long,Short,Datee,BadDecimal");
29+
// THEN
30+
assertThatExceptionOfType(ApplicationContextException.class)
31+
.isThrownBy(application::run)
32+
.withMessage("Invalid extended scalar name(s) found: BadDecimal, Datee. Valid names are: BigDecimal, " +
33+
"BigInteger, Byte, Char, Date, DateTime, JSON, Locale, Long, NegativeFloat, NegativeInt, " +
34+
"NonNegativeFloat, NonNegativeInt, NonPositiveFloat, NonPositiveInt, Object, PositiveFloat, " +
35+
"PositiveInt, Short, Time, Url.");
36+
}
37+
38+
@Test
39+
@DisplayName("Should not create any extended scalars by default.")
40+
void shouldNotDeclareAnyExtendedScalarsByDefault() {
41+
// GIVEN
42+
final SpringApplication application = setupTestApplication(null);
43+
// WHEN
44+
final ConfigurableApplicationContext context = application.run();
45+
// THEN
46+
assertThat(context.getBeansOfType(GraphQLScalarType.class)).isEmpty();
47+
}
48+
49+
@Test
50+
@DisplayName("Should declare the configured extended scalars.")
51+
void shouldDeclareTheConfiguredScalars() {
52+
// GIVEN
53+
final SpringApplication application = setupTestApplication("Long,Short,BigDecimal,Date");
54+
// WHEN
55+
final ConfigurableApplicationContext context = application.run();
56+
// THEN
57+
assertThat(context.getBeansOfType(GraphQLScalarType.class))
58+
.containsOnly(
59+
new AbstractMap.SimpleEntry<>("Long", ExtendedScalars.GraphQLLong),
60+
new AbstractMap.SimpleEntry<>("Short", ExtendedScalars.GraphQLShort),
61+
new AbstractMap.SimpleEntry<>("BigDecimal", ExtendedScalars.GraphQLBigDecimal),
62+
new AbstractMap.SimpleEntry<>("Date", ExtendedScalars.Date)
63+
);
64+
}
65+
66+
private SpringApplication setupTestApplication(final String extendedScalarValue) {
67+
final StandardEnvironment standardEnvironment = new StandardEnvironment();
68+
standardEnvironment.getPropertySources().addFirst(new MapPropertySource("testProperties",
69+
Collections.singletonMap("graphql.extended-scalars", extendedScalarValue)));
70+
final SpringApplication application = new SpringApplication(GraphQLExtendedScalarsInitializer.class);
71+
application.setWebApplicationType(WebApplicationType.NONE);
72+
application.setEnvironment(standardEnvironment);
73+
return application;
74+
}
75+
}

0 commit comments

Comments
 (0)