Skip to content

Commit

Permalink
Minimum to expose _service.sdl for Apollo federation. (#2640)
Browse files Browse the repository at this point in the history
* Minimum to expose _service.sdl for Apollo federation.

* Added configuration options to enable/disable apollo federation.

* Fixed checkstyle issues
  • Loading branch information
aklish authored May 6, 2022
1 parent 8adc443 commit af3e1d9
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class ElideSettings {
@Getter private final Map<Class, Serde> serdes;
@Getter private final boolean enableJsonLinks;
@Getter private final boolean strictQueryParams;
@Getter private final boolean enableGraphQLFederation;
@Getter private final String baseUrl;
@Getter private final String jsonApiPath;
@Getter private final String graphQLApiPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public class ElideSettingsBuilder {
private int defaultPageSize = PaginationImpl.DEFAULT_PAGE_LIMIT;
private int updateStatusCode;
private boolean enableJsonLinks;

private boolean enableGraphQLFederation;
private boolean strictQueryParams = true;
private String baseUrl = "";
private String jsonApiPath;
Expand All @@ -85,6 +87,7 @@ public ElideSettingsBuilder(DataStore dataStore) {
updateStatusCode = HttpStatus.SC_NO_CONTENT;
this.serdes = new LinkedHashMap<>();
this.enableJsonLinks = false;
this.enableGraphQLFederation = false;

//By default, Elide supports epoch based dates.
this.withEpochDates();
Expand Down Expand Up @@ -128,6 +131,7 @@ public ElideSettings build() {
serdes,
enableJsonLinks,
strictQueryParams,
enableGraphQLFederation,
baseUrl,
jsonApiPath,
graphQLApiPath,
Expand Down Expand Up @@ -259,4 +263,9 @@ public ElideSettingsBuilder withStrictQueryParams(boolean enabled) {
this.strictQueryParams = enabled;
return this;
}

public ElideSettingsBuilder withGraphQLFederation(boolean enabled) {
this.enableGraphQLFederation = enabled;
return this;
}
}
6 changes: 5 additions & 1 deletion elide-graphql/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@
<artifactId>javax.persistence-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.apollographql.federation</groupId>
<artifactId>federation-graphql-java-support</artifactId>
<version>2.0.0-alpha.5</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLInputObjectField.newInputObjectField;
import static graphql.schema.GraphQLObjectType.newObject;

import com.yahoo.elide.ElideSettings;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.dictionary.RelationshipType;
import com.yahoo.elide.core.type.ClassType;
import com.yahoo.elide.core.type.Type;
import com.apollographql.federation.graphqljava.Federation;
import org.apache.commons.collections4.CollectionUtils;
import graphql.Scalars;
import graphql.schema.DataFetcher;
Expand Down Expand Up @@ -72,6 +75,8 @@ public class ModelBuilder {
private Set<Type<?>> excludedEntities;
private Set<GraphQLObjectType> objectTypes;

private boolean enableFederation;

/**
* Class constructor, constructs the custom arguments to handle mutations.
* @param entityDictionary elide entity dictionary
Expand All @@ -80,6 +85,7 @@ public class ModelBuilder {
*/
public ModelBuilder(EntityDictionary entityDictionary,
NonEntityDictionary nonEntityDictionary,
ElideSettings settings,
DataFetcher dataFetcher, String apiVersion) {
objectTypes = new HashSet<>();
this.generator = new GraphQLConversionUtils(entityDictionary, nonEntityDictionary);
Expand All @@ -88,6 +94,7 @@ public ModelBuilder(EntityDictionary entityDictionary,
this.nameUtils = new GraphQLNameUtils(entityDictionary);
this.dataFetcher = dataFetcher;
this.apiVersion = apiVersion;
this.enableFederation = settings.isEnableGraphQLFederation();

relationshipOpArg = newArgument()
.name(ARGUMENT_OPERATION)
Expand Down Expand Up @@ -218,6 +225,8 @@ public GraphQLSchema build() {
inputObjectRegistry.values())))
.build();

//Enable Apollo Federation
schema = (enableFederation) ? Federation.transform(schema).build() : schema;
return schema;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public QueryRunner(Elide elide, String apiVersion) {

PersistentResourceFetcher fetcher = new PersistentResourceFetcher(nonEntityDictionary);
ModelBuilder builder = new ModelBuilder(elide.getElideSettings().getDictionary(),
nonEntityDictionary, fetcher, apiVersion);
nonEntityDictionary, elide.getElideSettings(), fetcher, apiVersion);

api = GraphQL.newGraphQL(builder.build())
.queryExecutionStrategy(new AsyncSerialExecutionStrategy())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,13 @@ private void addRootProjection(SelectionSet selectionSet) {
Field rootSelectionField = (Field) rootSelection;
String entityName = rootSelectionField.getName();
String aliasName = rootSelectionField.getAlias();
if (SCHEMA.hasName(entityName) || TYPE.hasName(entityName)) {
// '__schema' and '__type' would not be handled by entity projection

//_service comes from Apollo federation spec
if ("_service".equals(entityName) || SCHEMA.hasName(entityName) || TYPE.hasName(entityName)) {
// '_service' and '__schema' and '__type' would not be handled by entity projection
return;
}

Type<?> entityType = getRootEntity(rootSelectionField.getName(), apiVersion);
if (entityType == null) {
throw new InvalidEntityBodyException(String.format("Unknown entity {%s}.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,15 @@ public void testAliasPartialQueryAmbiguous() throws Exception {
assertParsingFails(loadGraphQLRequest("fetch/aliasPartialQueryAmbiguous.graphql"));
}

@Test
public void testFederationServiceIntrospection() throws Exception {
String graphQLRequest = "{ _service { sdl }}";

ElideResponse response = runGraphQLRequest(graphQLRequest, new HashMap<>());

assertTrue(! response.getBody().contains("errors"));
}

@Test
public void testSchemaIntrospection() throws Exception {
String graphQLRequest = "{"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;

import com.yahoo.elide.ElideSettings;
import com.yahoo.elide.core.dictionary.ArgumentType;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.request.Sorting;
Expand Down Expand Up @@ -102,8 +104,10 @@ public ModelBuilderTest() {
@Test
public void testInternalModelConflict() {
DataFetcher fetcher = mock(DataFetcher.class);
ElideSettings settings = mock(ElideSettings.class);
ModelBuilder builder = new ModelBuilder(dictionary,
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup), fetcher, NO_VERSION);
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup),
settings, fetcher, NO_VERSION);

GraphQLSchema schema = builder.build();

Expand All @@ -124,8 +128,10 @@ public void testInternalModelConflict() {
@Test
public void testPageInfoObject() {
DataFetcher fetcher = mock(DataFetcher.class);
ElideSettings settings = mock(ElideSettings.class);
ModelBuilder builder = new ModelBuilder(dictionary,
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup), fetcher, NO_VERSION);
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup),
settings, fetcher, NO_VERSION);

GraphQLSchema schema = builder.build();

Expand All @@ -136,8 +142,10 @@ public void testPageInfoObject() {
@Test
public void testRelationshipParameters() {
DataFetcher fetcher = mock(DataFetcher.class);
ElideSettings settings = mock(ElideSettings.class);
ModelBuilder builder = new ModelBuilder(dictionary,
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup), fetcher, NO_VERSION);
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup),
settings, fetcher, NO_VERSION);

GraphQLSchema schema = builder.build();
GraphQLObjectType root = schema.getQueryType();
Expand Down Expand Up @@ -173,8 +181,10 @@ public void testRelationshipParameters() {
@Test
public void testBuild() {
DataFetcher fetcher = mock(DataFetcher.class);
ElideSettings settings = mock(ElideSettings.class);
ModelBuilder builder = new ModelBuilder(dictionary,
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup), fetcher, NO_VERSION);
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup),
settings, fetcher, NO_VERSION);

GraphQLSchema schema = builder.build();

Expand Down Expand Up @@ -251,8 +261,10 @@ public void checkAttributeArguments() {
dictionary.addArgumentsToAttribute(ClassType.of(Book.class), FIELD_PUBLISH_DATE, arguments);

DataFetcher fetcher = mock(DataFetcher.class);
ElideSettings settings = mock(ElideSettings.class);
ModelBuilder builder = new ModelBuilder(dictionary,
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup), fetcher, NO_VERSION);
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup),
settings, fetcher, NO_VERSION);

GraphQLSchema schema = builder.build();

Expand All @@ -270,8 +282,10 @@ public void checkModelArguments() {
dictionary.addArgumentToEntity(ClassType.of(Author.class), new ArgumentType("filterAuthor", ClassType.STRING_TYPE));

DataFetcher fetcher = mock(DataFetcher.class);
ElideSettings settings = mock(ElideSettings.class);
ModelBuilder builder = new ModelBuilder(dictionary,
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup), fetcher, NO_VERSION);
new NonEntityDictionary(DefaultClassScanner.getInstance(), CoerceUtil::lookup),
settings, fetcher, NO_VERSION);

GraphQLSchema schema = builder.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public void initializeQueryRunner() {
.withEntityDictionary(dictionary)
.withJoinFilterDialect(filterDialect)
.withSubqueryFilterDialect(filterDialect)
.withGraphQLFederation(true)
.withISO8601Dates("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC"))
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ public RefreshableElide getRefreshableElide(EntityDictionary dictionary,
builder.withExportApiPath(settings.getAsync().getExport().getPath());
}

if (settings.getGraphql() != null && settings.getGraphql().enableFederation) {
builder.withGraphQLFederation(true);
}

if (settings.getJsonApi() != null
&& settings.getJsonApi().isEnabled()
&& settings.getJsonApi().isEnableLinks()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class ElideConfigProperties {
/**
* Settings for the GraphQL controller.
*/
private ControllerProperties graphql;
private GraphQLControllerProperties graphql;

/**
* Settings for the Swagger document controller.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2022, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/
package com.yahoo.elide.spring.config;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* Extra controller properties for the GraphQL endpoint.
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GraphQLControllerProperties extends ControllerProperties {

/**
* Turns on/off Apollo federation schema.
*/
boolean enableFederation = false;
}

0 comments on commit af3e1d9

Please sign in to comment.