Skip to content

Commit

Permalink
feat: integrate extended GraphQL scalars
Browse files Browse the repository at this point in the history
  • Loading branch information
BlasiusSecundus committed Feb 1, 2021
1 parent f9125bf commit 291140c
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 16 deletions.
54 changes: 40 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and join the team!
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**

- [WARNING: NoClassDefFoundError when using GraphQL Java Tools > 5.4.x](#warning-noclassdeffounderror-when-using-graphql-java-tools--54x)
- [WARNING: NoClassDefFoundError when using GraphQL Java Tools > 5.4.x](#warning-noclassdeffounderror-when-using-graphql-java-tools--54x)
- [Using Gradle](#using-gradle)
- [Using Maven](#using-maven)
- [Documentation](#documentation)
Expand All @@ -26,21 +26,22 @@ and join the team!
- [Enable Graph*i*QL](#enable-graphiql)
- [Enable Altair](#enable-altair)
- [Enable GraphQL Playground](#enable-graphql-playground)
- [Basic settings](#basic-settings)
- [CDN](#cdn)
- [Custom static resources](#custom-static-resources)
- [Customizing GraphQL Playground](#customizing-graphql-playground)
- [Tabs](#tabs)
- [Basic settings](#basic-settings)
- [CDN](#cdn)
- [Custom static resources](#custom-static-resources)
- [Customizing GraphQL Playground](#customizing-graphql-playground)
- [Tabs](#tabs)
- [Supported GraphQL-Java Libraries](#supported-graphql-java-libraries)
- [GraphQL Java Tools](#graphql-java-tools)
- [GraphQL Annotations](#graphql-annotations)
- [Configuration](#configuration)
- [Root resolvers, directives, type extensions](#root-resolvers-directives-type-extensions)
- [Interfaces](#interfaces)
- [Custom scalars and type functions](#custom-scalars-and-type-functions)
- [Custom Relay and GraphQL Annotation Processor](#custom-relay-and-graphql-annotation-processor)
- [GraphQL Java Tools](#graphql-java-tools)
- [GraphQL Annotations](#graphql-annotations)
- [Configuration](#configuration)
- [Root resolvers, directives, type extensions](#root-resolvers-directives-type-extensions)
- [Interfaces](#interfaces)
- [Custom scalars and type functions](#custom-scalars-and-type-functions)
- [Custom Relay and GraphQL Annotation Processor](#custom-relay-and-graphql-annotation-processor)
- [Extended scalars](#extended-scalars)
- [Tracing and Metrics](#tracing-and-metrics)
- [Usage](#usage)
- [Usage](#usage)
- [Contributions](#contributions)
- [Licenses](#licenses)

Expand Down Expand Up @@ -537,6 +538,31 @@ It is possible to define a bean implementing `Relay` and/or `GraphQLAnnotations`
will be passed to the schema builder. Spring dependency injection works as usual. Note that GraphQL
Annotations provides default implementation for these which should be sufficient is most cases.
## Extended scalars
[Extended scalars](https://github.com/graphql-java/graphql-java-extended-scalars) can be enabled by using the
`graphql.extended-scalars` configuration property, e. g.:
```yaml
graphql:
extended-scalars: BigDecimal, Date
```

The available scalars are the following: `BigDecimal`, `BigInteger`, `Byte`, `Char`, `Date`, `DateTime`, `JSON`,
`Locale`, `Long`, `NegativeFloat`, `NegativeInt`, `NonNegativeFloat`, `NonNegativeInt`, `NonPositiveFloat`,
`NonPositiveInt`, `Object`, `PositiveFloat`, `PositiveInt`, `Short`, `Time`, `Url`.

This setting works with both the [GraphQL Java Tools](#graphql-java-tools) and the
[GraphQL Annotations](#graphql-annotations) integration.

When using the [GraphQL Java Tools](#graphql-java-tools) integration, the scalars must also be declared in the GraphQL
Schema:

```graphql
scalar BigDecimal
scalar Date
```

# Tracing and Metrics

[Apollo style tracing](https://github.com/apollographql/apollo-tracing) along with two levels of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ spring:
name: graphql-subscription-example
server:
port: 9001

graphql:
extended-scalars: BigDecimal
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ schema {
subscription : Subscription
}

scalar BigDecimal

type Query {
hello : String
}
Expand All @@ -17,6 +19,6 @@ type Subscription {
type StockPriceUpdate {
dateTime : String
stockCode : String
stockPrice : Float
stockPriceChange : Float!
stockPrice : BigDecimal
stockPriceChange : BigDecimal!
}
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ SOURCE_COMPATIBILITY=1.8
TARGET_COMPATIBILITY=1.8
### Dependencies
LIB_GRAPHQL_JAVA_VER=16.1
LIB_EXTENDED_SCALARS_VERSION=15.0.0
LIB_SPRING_BOOT_VER=2.4.2
LIB_GRAPHQL_SERVLET_VER=11.0.0
LIB_GRAPHQL_JAVA_TOOLS_VER=11.0.0
Expand Down
1 change: 1 addition & 0 deletions graphql-spring-boot-autoconfigure/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
api(project(":graphql-kickstart-spring-boot-starter-tools"))
api(project(":graphql-kickstart-spring-support"))
implementation "org.springframework.boot:spring-boot-autoconfigure"
api "com.graphql-java:graphql-java-extended-scalars:$LIB_EXTENDED_SCALARS_VERSION"
api "com.graphql-java-kickstart:graphql-java-kickstart:$LIB_GRAPHQL_SERVLET_VER"
api "com.graphql-java-kickstart:graphql-java-servlet:$LIB_GRAPHQL_SERVLET_VER"
api "com.graphql-java:graphql-java:$LIB_GRAPHQL_JAVA_VER"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package graphql.kickstart.spring.web.boot;

import graphql.scalars.ExtendedScalars;
import graphql.schema.GraphQLScalarType;
import lombok.NoArgsConstructor;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@NoArgsConstructor
public class GraphQLExtendedScalarsInitializer implements ApplicationContextInitializer<GenericApplicationContext> {

@Override
public void initialize(final GenericApplicationContext applicationContext) {
final Collection<String> enabledExtendedScalars = getEnabledExtendedScalars(applicationContext);
final Collection<String> validScalarNames = new HashSet<>();
ReflectionUtils.doWithFields(ExtendedScalars.class, scalarField -> {
if (Modifier.isPublic(scalarField.getModifiers()) && Modifier.isStatic(scalarField.getModifiers())
&& scalarField.getType().equals(GraphQLScalarType.class)) {
final GraphQLScalarType graphQLScalarType = (GraphQLScalarType) scalarField.get(null);
if (enabledExtendedScalars.contains(graphQLScalarType.getName())) {
applicationContext.registerBean(
graphQLScalarType.getName(),
GraphQLScalarType.class,
() -> graphQLScalarType
);
}
validScalarNames.add(graphQLScalarType.getName());
}
});
verifyEnabledScalars(enabledExtendedScalars, validScalarNames);
}

private void verifyEnabledScalars(
final Collection<String> enabledExtendedScalars,
final Collection<String> validScalarNames
) {
final Collection<String> invalidScalarNames = new HashSet<>(enabledExtendedScalars);
invalidScalarNames.removeAll(validScalarNames);
if (!invalidScalarNames.isEmpty()) {
throw new ApplicationContextException(String.format(
"Invalid extended scalar name(s) found: %s. Valid names are: %s.",
joinNames(invalidScalarNames),
joinNames(validScalarNames)
)
);
}
}

private String joinNames(final Collection<String> names) {
return names.stream().sorted().collect(Collectors.joining(", "));
}

@SuppressWarnings("unchecked")
private Set<String> getEnabledExtendedScalars(final GenericApplicationContext applicationContext) {
return (Set<String>) applicationContext.getEnvironment()
.getProperty("graphql.extended-scalars", Collection.class, Collections.emptySet())
.stream().map(String::valueOf).collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@
}
],
"properties": [
{
"name": "graphql.extended-scalars",
"type": "java.util.Set",
"description": "List of extended scalars to be used."
}
]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
org.springframework.context.ApplicationContextInitializer=\
graphql.kickstart.spring.web.boot.GraphQLExtendedScalarsInitializer
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
graphql.kickstart.spring.web.boot.GraphQLWebAutoConfiguration,\
graphql.kickstart.spring.web.boot.GraphQLWebsocketAutoConfiguration,\
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package graphql.kickstart.spring.web.boot.test.extendedscalars;

import graphql.scalars.ExtendedScalars;
import graphql.schema.GraphQLScalarType;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestPropertySource;

import java.util.AbstractMap;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = ExtendedScalarAutoConfigurationTest.ExtendedScalarsTestApplication.class
)
@TestPropertySource(properties = "graphql.extended-scalars=BigDecimal")
@DisplayName("Testing extended scalars auto configuration")
public class ExtendedScalarAutoConfigurationTest {

@Autowired
private ApplicationContext applicationContext;

@Test
@DisplayName("The extended scalars initializer should be properly picked up by Spring auto configuration.")
void testAutoConfiguration() {
assertThat(applicationContext.getBeansOfType(GraphQLScalarType.class))
.containsOnly(new AbstractMap.SimpleEntry<>("BigDecimal", ExtendedScalars.GraphQLBigDecimal));
}

@SpringBootApplication
public static class ExtendedScalarsTestApplication {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package graphql.kickstart.spring.web.boot.test.extendedscalars;

import graphql.kickstart.spring.web.boot.GraphQLExtendedScalarsInitializer;
import graphql.scalars.ExtendedScalars;
import graphql.schema.GraphQLScalarType;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;

import java.util.AbstractMap;
import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

@DisplayName("Testing extended scalars configuration")
public class ExtendedScalarsTest {

@Test
@DisplayName("Should throw exception at context initialization when invalid extended scalar name is provided.")
void shouldThrowErrorOnStartupIfExtendedScalarDoesNotExists() {
// GIVEN
final SpringApplication application = setupTestApplication("Long,Short,Datee,BadDecimal");
// THEN
assertThatExceptionOfType(ApplicationContextException.class)
.isThrownBy(application::run)
.withMessage("Invalid extended scalar name(s) found: BadDecimal, Datee. Valid names are: BigDecimal, " +
"BigInteger, Byte, Char, Date, DateTime, JSON, Locale, Long, NegativeFloat, NegativeInt, " +
"NonNegativeFloat, NonNegativeInt, NonPositiveFloat, NonPositiveInt, Object, PositiveFloat, " +
"PositiveInt, Short, Time, Url.");
}

@Test
@DisplayName("Should not create any extended scalars by default.")
void shouldNotDeclareAnyExtendedScalarsByDefault() {
// GIVEN
final SpringApplication application = setupTestApplication(null);
// WHEN
final ConfigurableApplicationContext context = application.run();
// THEN
assertThat(context.getBeansOfType(GraphQLScalarType.class)).isEmpty();
}

@Test
@DisplayName("Should declare the configured extended scalars.")
void shouldDeclareTheConfiguredScalars() {
// GIVEN
final SpringApplication application = setupTestApplication("Long,Short,BigDecimal,Date");
// WHEN
final ConfigurableApplicationContext context = application.run();
// THEN
assertThat(context.getBeansOfType(GraphQLScalarType.class))
.containsOnly(
new AbstractMap.SimpleEntry<>("Long", ExtendedScalars.GraphQLLong),
new AbstractMap.SimpleEntry<>("Short", ExtendedScalars.GraphQLShort),
new AbstractMap.SimpleEntry<>("BigDecimal", ExtendedScalars.GraphQLBigDecimal),
new AbstractMap.SimpleEntry<>("Date", ExtendedScalars.Date)
);
}

private SpringApplication setupTestApplication(final String extendedScalarValue) {
final StandardEnvironment standardEnvironment = new StandardEnvironment();
standardEnvironment.getPropertySources().addFirst(new MapPropertySource("testProperties",
Collections.singletonMap("graphql.extended-scalars", extendedScalarValue)));
final SpringApplication application = new SpringApplication(GraphQLExtendedScalarsInitializer.class);
application.setWebApplicationType(WebApplicationType.NONE);
application.setEnvironment(standardEnvironment);
return application;
}
}

0 comments on commit 291140c

Please sign in to comment.