All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xlrit.gears.server.graphql.CustomExceptionResolver Maven / Gradle / Ivy

There is a newer version: 1.17.6
Show newest version
package com.xlrit.gears.server.graphql;

import java.util.List;
import java.util.Map;

import graphql.ErrorClassification;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import graphql.schema.DataFetchingEnvironment;
import lombok.RequiredArgsConstructor;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.stereotype.Component;

import com.xlrit.gears.base.exception.AuthException;
import com.xlrit.gears.base.exception.NotFoundException;
import com.xlrit.gears.base.exception.SpecException;

@Component
public class CustomExceptionResolver extends DataFetcherExceptionResolverAdapter {

	@Value("${gears.testing.enabled}")
	private boolean enableTesting;

	@Override
	protected GraphQLError resolveToSingleError(Throwable throwable, DataFetchingEnvironment env) {
		if (throwable instanceof SpecException) {
			SpecException e = (SpecException) throwable;
			Map extensions = Map.of(
				"errorId",   e.getId(),
				"location",  enableTesting ? e.getLocation() : ""
			);
			return new CustomGraphQLError(e.getBaseMessage(), CustomErrorType.FROM_SPEC, extensions);
		}
		if (throwable instanceof NotFoundException) {
			NotFoundException e = (NotFoundException) throwable;
			Map extensions = Map.of(
				"errorId",   e.getId(),
				"what",      e.getWhat(),
				"by",        e.getBy(),
				"value",     e.getValue()
			);
			return new CustomGraphQLError(throwable.getMessage(), ErrorType.NOT_FOUND, extensions);
		}
		if (throwable instanceof FlowableObjectNotFoundException) {
			return new CustomGraphQLError(throwable.getMessage(), ErrorType.NOT_FOUND, null);
		}
		if (throwable instanceof AuthException) {
			return new CustomGraphQLError(throwable.getMessage(), ErrorType.UNAUTHORIZED, null);
		}
		if (throwable instanceof org.springframework.security.access.AccessDeniedException) {
			return new CustomGraphQLError(throwable.getMessage(), ErrorType.FORBIDDEN, null);
		}
		return null;
	}
}

@RequiredArgsConstructor
class CustomGraphQLError implements GraphQLError {

	private final String message;
	private final ErrorClassification errorType;
	private final Map extensions;

	@Override
	public String getMessage() {
		return message;
	}

	@Override
	public ErrorClassification getErrorType() {
		return errorType;
	}

	@Override
	public Map getExtensions() {
		return extensions;
	}

	@Override
	public List getLocations() {
		return null;
	}
}

enum CustomErrorType implements ErrorClassification {
	FROM_SPEC
}
/*
// NOTE client errors are the errors we want to expose to the user
@RequiredArgsConstructor
class CustomGraphQLErrorHandler implements GraphQLErrorHandler {
	private static final Logger LOG = LoggerFactory.getLogger(CustomGraphQLErrorHandler.class);

	private final boolean enableTesting;

	@Override
	public List processErrors(List errors) {
		Map> partitioned =
			errors.stream()
				.map(this::normalizeError)
				.collect(Collectors.partitioningBy(this::isClientError));

		List clientErrors = partitioned.get(true);
		List nonClientErrors = partitioned.get(false);

		// log non-client errors
		if (!nonClientErrors.isEmpty()) {
			// Some errors were filtered out to hide implementation - put a generic error in place.
			clientErrors.add(new GenericGraphQLError(String.format(
				"GEARS runtime encountered %d error(s) while executing the query. Consult the runtime log for detailed information",
				nonClientErrors.size())
			));

			// log non-client errors
			nonClientErrors.forEach(this::logError);
		}

		return clientErrors;
	}

	//**
	// * Convert a generic GraphQLError to a more specific GraphQLError when possible.
	// *
	private GraphQLError normalizeError(GraphQLError error) {
		// unwrap ExceptionWhileDataFetching if it contains a GraphQLError
		if (error instanceof ExceptionWhileDataFetching) {
			ExceptionWhileDataFetching exceptionError = (ExceptionWhileDataFetching) error;
			Throwable exception = exceptionError.getException();

			if (exception instanceof GraphQLError) {
				// return the wrapped error
				return (GraphQLError) exception;
			}
			else if (exception instanceof RuntimeException) {
				// convert particular runtime exceptions to GraphQLErrors
				GraphQLError replacement = toGearsGraphQLError(exception);
				if (replacement != null) return replacement;
			}
		}
		else if (error instanceof NonNullableFieldWasNullError) {
			return new RenderableNonNullableFieldWasNullError((NonNullableFieldWasNullError) error);
		}
		return error;
	}

	//**
	// * Converts runtime exceptions to (Gears)GraphQLErrors when possible.
	// *
	private GraphQLError toGearsGraphQLError(Throwable throwable) {
		if (throwable instanceof SpecException) {
			SpecException e = (SpecException) throwable;
			Map extensions = Map.of(
				"errorType", "Spec",
				"errorId",   e.getId(),
				"location",  enableTesting ? e.getLocation() : ""
			);
			return new ExtendedGraphQLError(e.getBaseMessage(), extensions);
		}
		if (throwable instanceof NotFoundException) {
			NotFoundException e = (NotFoundException) throwable;
			Map extensions = Map.of(
				"errorType", "NotFound",
				"errorId",   e.getId(),
				"what",      e.getWhat(),
				"by",        e.getBy(),
				"value",     e.getValue()
			);
			return new ExtendedGraphQLError(throwable.getMessage(), extensions);
		}
		if (throwable instanceof FlowableObjectNotFoundException) {
			return new GenericGraphQLError(throwable.getMessage());
		}
		if (throwable instanceof org.springframework.security.access.AccessDeniedException) {
			return new ExtendedGraphQLError(throwable.getMessage(), Map.of("errorType", "AccessDenied"));
		}
		else if (throwable instanceof UnexpectedRollbackException) {
			if (throwable.getCause() != null)
				return toGearsGraphQLError(throwable.getCause());
		}
		return null;
	}

	protected boolean isClientError(GraphQLError error) {
		if (error instanceof ExceptionWhileDataFetching) {
			Throwable cause = ((ExceptionWhileDataFetching) error).getException();
			return cause instanceof GraphQLError
				  || cause instanceof AuthException;
		}
		return true;
	}

	protected void logError(GraphQLError error) {
		if (error instanceof ExceptionWhileDataFetching) {
			LOG.error("Error executing query {}", error.getMessage(), ((ExceptionWhileDataFetching) error).getException());
		} else {
			LOG.error("Error executing query ({}): {}", error.getClass().getSimpleName(), error.getMessage());
		}
	}
}

// copied from graphql.kickstart.execution.error.RenderableNonNullableFieldWasNullError, which is non-public
class RenderableNonNullableFieldWasNullError implements GraphQLError {
	private final NonNullableFieldWasNullError delegate;

	public RenderableNonNullableFieldWasNullError(
		NonNullableFieldWasNullError nonNullableFieldWasNullError) {
		this.delegate = nonNullableFieldWasNullError;
	}

	@Override
	public String getMessage() {
		return delegate.getMessage();
	}

	@Override
	@JsonInclude(JsonInclude.Include.NON_NULL)
	public List getLocations() {
		return delegate.getLocations();
	}

	@Override
	public ErrorClassification getErrorType() {
		return delegate.getErrorType();
	}

	@Override
	public List getPath() {
		return delegate.getPath();
	}

	@Override
	public Map toSpecification() {
		return delegate.toSpecification();
	}

	@Override
	@JsonInclude(JsonInclude.Include.NON_NULL)
	public Map getExtensions() {
		return delegate.getExtensions();
	}
}

class ExtendedGraphQLError extends GenericGraphQLError {
	private final Map extensions;

	public ExtendedGraphQLError(String message, Map extensions) {
		super(message);
		this.extensions = extensions;
	}

	@Override
	public Map getExtensions() {
		return extensions;
	}
}
*/