Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.polaris.core.exceptions;

import jakarta.ws.rs.core.Response;

/**
* A {@link PolarisException} implementation for when Polaris is unable to create an entity that
* already exists.
Expand All @@ -30,4 +32,9 @@ public AlreadyExistsException(String message) {
public AlreadyExistsException(String message, Throwable cause) {
super(message, cause);
}

@Override
public int httpStatusCode() {
return Response.Status.CONFLICT.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.apache.polaris.core.exceptions;

import com.google.errorprone.annotations.FormatMethod;
import jakarta.ws.rs.core.Response;

public class CommitConflictException extends PolarisException {
public CommitConflictException(String message) {
Expand All @@ -39,4 +40,9 @@ public CommitConflictException(Throwable cause, String message, Object... args)
public CommitConflictException(String message, Throwable cause) {
super(message, cause);
}

@Override
public int httpStatusCode() {
return Response.Status.CONFLICT.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.polaris.core.exceptions;

import jakarta.ws.rs.core.Response;

/**
* A {@link PolarisException} implementation for when an UnknownHostException happens during File IO
* to S3, GCS, or Azure.
Expand All @@ -26,4 +28,9 @@ public class FileIOUnknownHostException extends PolarisException {
public FileIOUnknownHostException(String message, Throwable cause) {
super(message, cause);
}

@Override
public int httpStatusCode() {
return Response.Status.INTERNAL_SERVER_ERROR.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* Base class for Polaris-specific runtime exceptions.
*
* <p>All custom exceptions in Polaris should extend this class to provide specific error details.
* Subclasses must implement {@link #httpStatusCode()} to declare their HTTP response status.
*/
public abstract class PolarisException extends RuntimeException {

Expand All @@ -32,4 +33,10 @@ public PolarisException(String message) {
public PolarisException(String message, Throwable cause) {
super(message, cause);
}

/**
* Returns the HTTP status code that should be used when this exception is mapped to an HTTP
* response.
*/
public abstract int httpStatusCode();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.polaris.core.exceptions;

import com.google.errorprone.annotations.FormatMethod;
import jakarta.ws.rs.core.Response;

/**
* Signals a transient failure that the client may resolve by retrying. Mapped to HTTP 503 (Service
Expand All @@ -37,4 +38,9 @@ public PolarisServiceUnavailableException(int retryAfterSeconds, String message,
public int getRetryAfterSeconds() {
return retryAfterSeconds;
}

@Override
public int httpStatusCode() {
return Response.Status.SERVICE_UNAVAILABLE.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.polaris.core.persistence;

import com.google.errorprone.annotations.FormatMethod;
import jakarta.ws.rs.core.Response;
import org.apache.polaris.core.exceptions.PolarisException;
import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;

Expand All @@ -45,4 +46,9 @@ public PolicyMappingAlreadyExistsException(String message, Object... arg) {
public PolarisPolicyMappingRecord getExistingRecord() {
return this.existingRecord;
}

@Override
public int httpStatusCode() {
return Response.Status.CONFLICT.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.polaris.core.policy.exceptions;

import jakarta.ws.rs.core.Response;
import org.apache.polaris.core.exceptions.PolarisException;

public class NoSuchPolicyException extends PolarisException {
Expand All @@ -29,4 +30,9 @@ public NoSuchPolicyException(String message) {
public NoSuchPolicyException(String message, Throwable cause) {
super(message, cause);
}

@Override
public int httpStatusCode() {
return Response.Status.NOT_FOUND.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.polaris.core.policy.exceptions;

import com.google.errorprone.annotations.FormatMethod;
import jakarta.ws.rs.core.Response;
import org.apache.polaris.core.exceptions.PolarisException;

public class PolicyAttachException extends PolarisException {
Expand All @@ -31,4 +32,9 @@ public PolicyAttachException(String message, Object... args) {
public PolicyAttachException(Throwable cause, String message, Object... args) {
super(String.format(message, args), cause);
}

@Override
public int httpStatusCode() {
return Response.Status.BAD_REQUEST.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.polaris.core.policy.exceptions;

import com.google.errorprone.annotations.FormatMethod;
import jakarta.ws.rs.core.Response;
import org.apache.polaris.core.exceptions.PolarisException;

public class PolicyInUseException extends PolarisException {
Expand All @@ -31,4 +32,9 @@ public PolicyInUseException(String message, Object... args) {
public PolicyInUseException(Throwable cause, String message, Object... args) {
super(String.format(message, args), cause);
}

@Override
public int httpStatusCode() {
return Response.Status.BAD_REQUEST.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.polaris.core.policy.exceptions;

import jakarta.ws.rs.core.Response;
import org.apache.polaris.core.exceptions.PolarisException;

public class PolicyVersionMismatchException extends PolarisException {
Expand All @@ -28,4 +29,9 @@ public PolicyVersionMismatchException(String message) {
public PolicyVersionMismatchException(String message, Throwable cause) {
super(message, cause);
}

@Override
public int httpStatusCode() {
return Response.Status.CONFLICT.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.polaris.core.policy.validator;

import jakarta.ws.rs.core.Response;
import org.apache.polaris.core.exceptions.PolarisException;

/** Exception thrown when a policy is invalid or violates defined rules. */
Expand All @@ -33,4 +34,9 @@ public InvalidPolicyException(String message, Throwable cause) {
public InvalidPolicyException(Throwable cause) {
super("Invalid policy", cause);
}

@Override
public int httpStatusCode() {
return Response.Status.BAD_REQUEST.getStatusCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,64 +25,37 @@
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import org.apache.iceberg.rest.responses.ErrorResponse;
import org.apache.polaris.core.exceptions.AlreadyExistsException;
import org.apache.polaris.core.exceptions.CommitConflictException;
import org.apache.polaris.core.exceptions.PolarisException;
import org.apache.polaris.core.exceptions.PolarisServiceUnavailableException;
import org.apache.polaris.core.persistence.PolicyMappingAlreadyExistsException;
import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException;
import org.apache.polaris.core.policy.exceptions.PolicyAttachException;
import org.apache.polaris.core.policy.exceptions.PolicyInUseException;
import org.apache.polaris.core.policy.exceptions.PolicyVersionMismatchException;
import org.apache.polaris.core.policy.validator.InvalidPolicyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;

/**
* An {@link ExceptionMapper} implementation for {@link PolarisException}s modeled after {@link
* IcebergExceptionMapper}
* An {@link ExceptionMapper} implementation for {@link PolarisException}s. Delegates HTTP status
* resolution to {@link PolarisException#httpStatusCode()}.
*/
@Provider
public class PolarisExceptionMapper implements ExceptionMapper<PolarisException> {

private static final Logger LOGGER = LoggerFactory.getLogger(PolarisExceptionMapper.class);

private Response.Status getStatus(PolarisException exception) {
return switch (exception) {
case PolarisServiceUnavailableException polarisServiceUnavailableException ->
Response.Status.SERVICE_UNAVAILABLE;
case AlreadyExistsException alreadyExistsException -> Response.Status.CONFLICT;
case CommitConflictException commitConflictException -> Response.Status.CONFLICT;
case InvalidPolicyException invalidPolicyException -> Response.Status.BAD_REQUEST;
case PolicyAttachException policyAttachException -> Response.Status.BAD_REQUEST;
case NoSuchPolicyException noSuchPolicyException -> Response.Status.NOT_FOUND;
case PolicyVersionMismatchException policyVersionMismatchException ->
Response.Status.CONFLICT;
case PolicyMappingAlreadyExistsException policyMappingAlreadyExistsException ->
Response.Status.CONFLICT;
case PolicyInUseException policyInUseException -> Response.Status.BAD_REQUEST;
default -> Response.Status.INTERNAL_SERVER_ERROR;
};
}

@Override
public Response toResponse(PolarisException exception) {
Response.Status status = getStatus(exception);
int statusCode = exception.httpStatusCode();
getLogger()
.atLevel(
status.getFamily() == Response.Status.Family.SERVER_ERROR ? Level.INFO : Level.DEBUG)
.atLevel(statusCode >= 500 ? Level.INFO : Level.DEBUG)
.setCause(exception)
.log("Full PolarisException");

ErrorResponse errorResponse =
ErrorResponse.builder()
.responseCode(status.getStatusCode())
.responseCode(statusCode)
.withType(exception.getClass().getSimpleName())
.withMessage(exception.getMessage())
.build();
Response.ResponseBuilder builder =
Response.status(status).entity(errorResponse).type(MediaType.APPLICATION_JSON_TYPE);
Response.status(statusCode).entity(errorResponse).type(MediaType.APPLICATION_JSON_TYPE);
if (exception instanceof PolarisServiceUnavailableException e
&& e.getRetryAfterSeconds() != 0) {
builder.header(HttpHeaders.RETRY_AFTER, e.getRetryAfterSeconds());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@
import org.apache.iceberg.exceptions.RuntimeIOException;
import org.apache.polaris.core.exceptions.AlreadyExistsException;
import org.apache.polaris.core.exceptions.CommitConflictException;
import org.apache.polaris.core.exceptions.FileIOUnknownHostException;
import org.apache.polaris.core.exceptions.PolarisException;
import org.apache.polaris.core.exceptions.PolarisServiceUnavailableException;
import org.apache.polaris.core.persistence.PolicyMappingAlreadyExistsException;
import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException;
import org.apache.polaris.core.policy.exceptions.PolicyAttachException;
import org.apache.polaris.core.policy.exceptions.PolicyInUseException;
import org.apache.polaris.core.policy.exceptions.PolicyVersionMismatchException;
import org.apache.polaris.core.policy.validator.InvalidPolicyException;
import org.jboss.logmanager.Level;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -111,6 +119,30 @@ public void testServiceUnavailableWithRetryAfter() {
assertThat(response.getHeaderString(HttpHeaders.RETRY_AFTER)).isEqualTo("3");
}

@ParameterizedTest
@MethodSource("polarisExceptionStatusCodes")
public void testPolarisExceptionStatusCodes(PolarisException exception, int expectedStatus) {
assertThat(exception.httpStatusCode()).isEqualTo(expectedStatus);

PolarisExceptionMapper mapper = new PolarisExceptionMapper();
Response response = mapper.toResponse(exception);
assertThat(response.getStatus()).isEqualTo(expectedStatus);
}

static Stream<Arguments> polarisExceptionStatusCodes() {
return Stream.of(
Arguments.of(new AlreadyExistsException("msg"), 409),
Arguments.of(new CommitConflictException("msg"), 409),
Arguments.of(new PolarisServiceUnavailableException(0, "msg"), 503),
Arguments.of(new InvalidPolicyException("msg"), 400),
Arguments.of(new PolicyAttachException("msg"), 400),
Arguments.of(new PolicyInUseException("msg"), 400),
Arguments.of(new NoSuchPolicyException("msg"), 404),
Arguments.of(new PolicyVersionMismatchException("msg"), 409),
Arguments.of(new PolicyMappingAlreadyExistsException("msg"), 409),
Arguments.of(new FileIOUnknownHostException("msg", new RuntimeException()), 500));
}

static Stream<Arguments> testFullExceptionIsLogged() {
// ConstraintViolationException isn't included because it doesn't propagate any info to its
// inherited Exception
Expand Down
Loading