Skip to content

Commit

Permalink
🐛 Handle BsonNull in GraphQL ValueUnboxer as null values
Browse files Browse the repository at this point in the history
Custom ValueUnboxer to treat BsonNull as null, prevent typeResolvers from executing on null values, avoiding related issues.
  • Loading branch information
ujibang committed May 17, 2024
1 parent 181bda3 commit 694fe46
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.function.Function;
import java.util.stream.Collectors;

import org.bson.BsonNull;
import org.dataloader.DataLoaderRegistry;
import org.restheart.configuration.ConfigurationException;
import org.restheart.exchange.BadRequestException;
Expand Down Expand Up @@ -72,6 +73,7 @@
import graphql.ExecutionResultImpl;
import graphql.GraphQL;
import graphql.GraphQLError;
import graphql.execution.ValueUnboxer;
import graphql.execution.instrumentation.ChainedInstrumentation;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation;
Expand Down Expand Up @@ -207,6 +209,7 @@ public void handle(GraphQLRequest req, GraphQLResponse res) throws Exception {
chainedInstrumentations.add(new MaxQueryTimeInstrumentation(this.queryTimeLimit));

this.gql = GraphQL.newGraphQL(graphQLApp.getExecutableSchema())
.valueUnboxer((Object object) -> object instanceof BsonNull ? null : ValueUnboxer.DEFAULT.unbox(object))
.instrumentation(new ChainedInstrumentation(chainedInstrumentations))
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,11 @@
import graphql.schema.GraphQLObjectType;


public class GQLBatchDataFetcher extends GraphQLDataFetcher{

public class GQLBatchDataFetcher extends GraphQLDataFetcher {
public GQLBatchDataFetcher(QueryMapping queryMapping) {
super(queryMapping);
}


@Override
public Object get(DataFetchingEnvironment env) throws Exception {
// store the root object in the context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
*/
package org.restheart.graphql.datafetchers;

import graphql.schema.DataFetchingEnvironment;
import java.util.Arrays;
import java.util.regex.Pattern;

import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.restheart.graphql.models.FieldRenaming;

import java.util.Arrays;
import java.util.regex.Pattern;
import graphql.schema.DataFetchingEnvironment;

public class GQLRenamingDataFetcher extends GraphQLDataFetcher {

Expand All @@ -43,8 +43,13 @@ public Object get(DataFetchingEnvironment env) throws Exception {

var alias = ((FieldRenaming) this.fieldMapping).getAlias();

BsonDocument parentDocument = env.getSource();
return getValues(parentDocument, alias);
BsonValue source = env.getSource();

if (source == null || source.isNull()) {
return null;
}

return getValues(source, alias);
}


Expand Down
84 changes: 38 additions & 46 deletions graphql/src/main/java/org/restheart/graphql/models/GraphQLApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@
import graphql.TypeResolutionEnvironment;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.UnionTypeDefinition;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.TypeResolver;
import graphql.schema.idl.MapEnumValuesProvider;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
Expand Down Expand Up @@ -169,58 +167,52 @@ public GraphQLApp build() throws IllegalStateException {
typeRegistry.types().entrySet().stream().filter(e -> e.getValue() instanceof UnionTypeDefinition).forEach(e ->{
var unionMapping = this.unionMappings.get(e.getKey());

RWBuilder.type(TypeRuntimeWiring.newTypeWiring(e.getKey()).typeResolver(new TypeResolver() {
@Override
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
var obj = env.getObject();
final Optional<Entry<String, Predicate>> match;
if (obj instanceof BsonValue value) {
match = unionMapping.entrySet().stream()
.filter(p -> p.getValue().resolve(ExchangeWithBsonValue.exchange(value)))
.findFirst();
} else {
// predicates can only resolve on BsonValues
LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
return null;
}

if (match.isPresent()) {
return env.getSchema().getObjectType(match.get().getKey());
} else {
return null;
}
RWBuilder.type(TypeRuntimeWiring.newTypeWiring(e.getKey()).typeResolver((TypeResolutionEnvironment env) -> {
var obj = env.getObject();
final Optional<Entry<String, Predicate>> match;

if (obj instanceof BsonValue value) {
match = unionMapping.entrySet().stream()
.filter(p -> p.getValue().resolve(ExchangeWithBsonValue.exchange(value)))
.findFirst();
} else {
// predicates can only resolve on BsonValues
LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
return null;
}

if (match.isPresent()) {
return env.getSchema().getObjectType(match.get().getKey());
} else {
LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
return null;
}
}).build());
});

// Interfaces
typeRegistry.types().entrySet().stream().filter(e -> e.getValue() instanceof InterfaceTypeDefinition).forEach(e ->{
LOGGER.debug("Interface: {} -> {}", e.getKey(), e.getValue());

var interfaceMapping = this.interfacesMappings.get(e.getKey());

LOGGER.debug("\tmapping: {}", interfaceMapping);

RWBuilder.type(TypeRuntimeWiring.newTypeWiring(e.getKey()).typeResolver(new TypeResolver() {
@Override
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
var obj = env.getObject();
final Optional<Entry<String, Predicate>> match;
if (obj instanceof BsonValue value) {
match = interfaceMapping.entrySet().stream()
.filter(p -> p.getValue().resolve(ExchangeWithBsonValue.exchange(value)))
.findFirst();
} else {
// predicates can resolve on BsonValues
LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
return null;
}

if (match.isPresent()) {
return env.getSchema().getObjectType(match.get().getKey());
} else {
return null;
}
RWBuilder.type(TypeRuntimeWiring.newTypeWiring(e.getKey()).typeResolver((TypeResolutionEnvironment env) -> {
var obj = env.getObject();
final Optional<Entry<String, Predicate>> match;

if (obj instanceof BsonValue value) {
match = interfaceMapping.entrySet().stream()
.filter(p -> p.getValue().resolve(ExchangeWithBsonValue.exchange(value)))
.findFirst();
} else {
// predicates can resolve on BsonValues
LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
return null;
}

if (match.isPresent()) {
return env.getSchema().getObjectType(match.get().getKey());
} else {
LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
return null;
}
}).build());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,22 @@
*/
package org.restheart.graphql.models;

import java.util.Map;

import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeRuntimeWiring;

import java.util.Map;

public class ObjectMapping extends TypeMapping {
public ObjectMapping(String typeName, Map<String, FieldMapping> fieldMappingMap){
super(typeName, fieldMappingMap);
}

@Override
public TypeRuntimeWiring.Builder getTypeWiring(TypeDefinitionRegistry typeRegistry) {
final var tWBuilder = TypeRuntimeWiring.newTypeWiring(this.typeName);

TypeRuntimeWiring.Builder TWBuilder = TypeRuntimeWiring.newTypeWiring(this.typeName);

this.fieldMappingMap.forEach(((fieldName, fieldMapping) -> {
TWBuilder.dataFetcher(fieldName, fieldMapping.getDataFetcher());
}));
fieldMappingMap.forEach(((fieldName, fieldMapping) -> tWBuilder.dataFetcher(fieldName, fieldMapping.getDataFetcher())));

return TWBuilder;
return tWBuilder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
package org.restheart.graphql.predicates;

import org.bson.BsonValue;

import io.undertow.server.HttpServerExchange;
import io.undertow.server.ServerConnection;
import io.undertow.util.AttachmentKey;
Expand All @@ -43,5 +44,5 @@ public static BsonValue value(HttpServerExchange exchange) {
return exchange.getAttachment(DOC_KEY);
}

private static AttachmentKey<BsonValue> DOC_KEY = AttachmentKey.create(BsonValue.class);
private static final AttachmentKey<BsonValue> DOC_KEY = AttachmentKey.create(BsonValue.class);
}

0 comments on commit 694fe46

Please sign in to comment.