Skip to content

Commit

Permalink
Merge branch 'master' into feature/505-input-prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
oliemansm authored Jan 5, 2021
2 parents c6993ae + 036b720 commit a0c6b77
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 14 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ the classpath. Use the `schemaLocationPattern` property to customize this patter

https://github.com/Enigmatis/graphql-java-annotations

The GraphQL Annotations library is used instead of GraphQL Java Tools if the `graphql-spring-boot-starter`
dependency is replaced by `graphql-kickstart-spring-boot-starter-graphql-annotations`.

The schema will be built using the GraphQL Annotations library in a code-first approach - instead of
writing it manually, the schema will be constructed based on the Java code. Please see the
documentation of the GraphQL Annotations library for a detailed documentation of the available
Expand Down
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ subprojects {
jcenter()
maven { url "https://dl.bintray.com/graphql-java-kickstart/releases" }
maven { url "https://repo.spring.io/libs-milestone" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
maven { url "https://oss.jfrog.org/artifactory/oss-snapshot-local" }
}

Expand Down Expand Up @@ -88,6 +89,10 @@ subprojects {
}
}

jacoco {
toolVersion = "0.8.7-SNAPSHOT"
}

jacocoTestReport {
reports {
xml.enabled = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,27 @@ static class CodeMirror {
@Data
static class Props {

private Variables variables = new Variables();
private GraphiQLVariables variables = new GraphiQLVariables();

/**
* See https://github.com/graphql/graphiql/tree/main/packages/graphiql#props
*/
@Data
static class Variables {
static class GraphiQLVariables {

private String query;
private String variables;
private String headers;
private String operationName;
private String response;
private String defaultQuery;
private boolean defaultVariableEditorOpen;
private boolean defaultSecondaryEditorOpen;
private String editorTheme;
private boolean readOnly;
private boolean docsExplorerOpen;
private boolean headerEditorEnabled;
private boolean shouldPersistHeaders;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
import graphql.schema.GraphQLSchema;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
Expand Down Expand Up @@ -187,11 +189,15 @@ private void registerGraphQLInterfaceImplementations(
final Reflections reflections,
final AnnotationsSchemaCreator.Builder builder
) {
Predicate<Class<?>> implementationQualifiesForInclusion =
type -> !(graphQLAnnotationsProperties.isIgnoreAbstractInterfaceImplementations()
&& Modifier.isAbstract(type.getModifiers()));
reflections.getMethodsAnnotatedWith(GraphQLField.class).stream()
.map(Method::getDeclaringClass)
.filter(Class::isInterface)
.forEach(graphQLInterface ->
reflections.getSubTypesOf(graphQLInterface)
reflections.getSubTypesOf(graphQLInterface).stream()
.filter(implementationQualifiesForInclusion)
.forEach(implementation -> {
log.info("Registering {} as an implementation of GraphQL interface {}",
implementation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,12 @@ public class GraphQLAnnotationsProperties {
* If not configured the default suffix of the GraphQL-Java Annotations library is used.
*/
private String inputSuffix;

/**
* If set to <code>true</code> abstract classes implementing a GraphQL interface will not be added to the schema.
* Defaults to <code>false</code> for backward compatibility.
*/
@Builder.Default
private boolean ignoreAbstractInterfaceImplementations = false;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package graphql.kickstart.graphql.annotations;

import com.graphql.spring.boot.test.GraphQLResponse;
import com.graphql.spring.boot.test.GraphQLTestTemplate;
import graphql.kickstart.graphql.annotations.test.interfaces.Car;
import graphql.kickstart.graphql.annotations.test.interfaces.Truck;
import graphql.schema.GraphQLNamedType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;

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

@DisplayName("Testing interface handling (ignore abstract implementations).")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = "graphql.annotations.ignore-abstract-interface-implementations=true")
@ActiveProfiles({"test", "interface-test"})
class GraphQLInterfaceQueryIgnoreAbstractInterfaceImplementationsTest {

@Autowired
private GraphQLTestTemplate graphQLTestTemplate;

@Autowired
private GraphQLSchema graphQLSchema;

@Test
@DisplayName("Assert that GraphQL interfaces and their implementations are registered correctly.")
void testInterfaceQuery() throws IOException {
// WHEN
final GraphQLResponse actual = graphQLTestTemplate
.postForResource("queries/test-interface-query.graphql");
// THEN
assertThat(actual.get("$.data.vehicles[0]", Car.class))
.usingRecursiveComparison().ignoringAllOverriddenEquals()
.isEqualTo(Car.builder().numberOfSeats(4).registrationNumber("ABC-123").build());
assertThat(actual.get("$.data.vehicles[1]", Truck.class))
.usingRecursiveComparison().ignoringAllOverriddenEquals()
.isEqualTo(Truck.builder().cargoWeightCapacity(12).registrationNumber("CBA-321").build());
}

@Test
@DisplayName("Assert that abstract GraphQL interface implementations are excluded from the schema.")
void testInterfaceImplementationDetection() {
// THEN
Set<String> vehicleDomainTypes = graphQLSchema.getAllTypesAsList().stream()
.filter(type -> !(type instanceof GraphQLScalarType))
.map(GraphQLNamedType::getName)
.filter(name -> !name.startsWith("__"))
.filter(name -> !"PageInfo".equals(name))
.collect(Collectors.toSet());
// Must not contain "AbstractVehicle"
assertThat(vehicleDomainTypes)
.containsExactlyInAnyOrder("InterfaceQuery", "Vehicle", "Car", "Truck");
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import graphql.kickstart.graphql.annotations.test.interfaces.Car;
import graphql.kickstart.graphql.annotations.test.interfaces.Truck;
import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;
import graphql.schema.GraphQLNamedType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -21,6 +26,9 @@ class GraphQLInterfaceQueryTest {
@Autowired
private GraphQLTestTemplate graphQLTestTemplate;

@Autowired
private GraphQLSchema graphQLSchema;

@Test
@DisplayName("Assert that GraphQL interfaces and their implementations are registered correctly.")
void testInterfaceQuery() throws IOException {
Expand All @@ -35,5 +43,20 @@ void testInterfaceQuery() throws IOException {
.usingRecursiveComparison().ignoringAllOverriddenEquals()
.isEqualTo(Truck.builder().cargoWeightCapacity(12).registrationNumber("CBA-321").build());
}

@Test
@DisplayName("Assert that abstract GraphQL interface implementations are added to the schema.")
void testInterfaceImplementationDetection() {
// THEN
Set<String> vehicleDomainTypes = graphQLSchema.getAllTypesAsList().stream()
.filter(type -> !(type instanceof GraphQLScalarType))
.map(GraphQLNamedType::getName)
.filter(name -> !name.startsWith("__"))
.filter(name -> !"PageInfo".equals(name))
.collect(Collectors.toSet());
// Should contain "AbstractVehicle"
assertThat(vehicleDomainTypes)
.containsExactlyInAnyOrder("InterfaceQuery", "Vehicle", "AbstractVehicle", "Car", "Truck");
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package graphql.kickstart.graphql.annotations.test.interfaces;

import graphql.annotations.annotationTypes.GraphQLField;
import graphql.annotations.annotationTypes.GraphQLNonNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public abstract class AbstractVehicle implements Vehicle {

/**
* Note that you have to repeat the annotations from the interface method!
*/
@GraphQLField
@GraphQLNonNull
private String registrationNumber;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@
import graphql.annotations.annotationTypes.GraphQLField;
import graphql.annotations.annotationTypes.GraphQLNonNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@Data
@Builder
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class Car implements Vehicle {

/**
* Note that you have to repeat the annotations from the interface method!
*/
@GraphQLField
@GraphQLNonNull
private String registrationNumber;
@EqualsAndHashCode(callSuper = true)
// “implements Vehicle” has to be repeated here although already inherited from AbstractVehicle
// because otherwise GraphQL-Java Annotations would not find this class.
public class Car extends AbstractVehicle implements Vehicle {

@GraphQLField
@GraphQLNonNull
private int numberOfSeats;

public Car(String registrationNumber, int numberOfSeats) {
super(registrationNumber);
this.numberOfSeats = numberOfSeats;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
// Truck intentionally does not extend AbstractVehicle in order to have one inheritance
// hierarchy free from abstract classes.
public class Truck implements Vehicle {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html>

<head>
<meta charset=utf-8/>
<meta charset="utf-8"/>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<title th:text="${pageTitle}"></title>
<link rel="stylesheet" th:href="${cssUrl}" />
Expand Down

0 comments on commit a0c6b77

Please sign in to comment.