diff --git a/clients/client/src/main/java/org/projectnessie/client/rest/ResponseCheckFilter.java b/clients/client/src/main/java/org/projectnessie/client/rest/ResponseCheckFilter.java index f2672dfdc12..eaab8fa5277 100644 --- a/clients/client/src/main/java/org/projectnessie/client/rest/ResponseCheckFilter.java +++ b/clients/client/src/main/java/org/projectnessie/client/rest/ResponseCheckFilter.java @@ -21,8 +21,12 @@ import com.fasterxml.jackson.databind.ObjectReader; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; import org.projectnessie.client.http.ResponseContext; import org.projectnessie.client.http.Status; +import org.projectnessie.error.BaseNessieClientServerException; +import org.projectnessie.error.ErrorCode; +import org.projectnessie.error.ImmutableNessieError; import org.projectnessie.error.NessieConflictException; import org.projectnessie.error.NessieError; import org.projectnessie.error.NessieNotFoundException; @@ -52,6 +56,11 @@ public static void checkResponse(ResponseContext con, ObjectMapper mapper) throw error = decodeErrorObject(status, is, mapper.reader()); } + Optional modelException = ErrorCode.asException(error); + if (modelException.isPresent()) { + throw modelException.get(); + } + switch (status) { case BAD_REQUEST: throw new NessieBadRequestException(error); @@ -60,8 +69,12 @@ public static void checkResponse(ResponseContext con, ObjectMapper mapper) throw case FORBIDDEN: throw new NessieForbiddenException(error); case NOT_FOUND: + // Report a generic NessieNotFoundException if a sub-class could not be determined from the + // NessieError object throw new NessieNotFoundException(error); case CONFLICT: + // Report a generic NessieConflictException if a sub-class could not be determined from the + // NessieError object throw new NessieConflictException(error); case TOO_MANY_REQUESTS: throw new NessieBackendThrottledException(error); @@ -77,11 +90,14 @@ private static NessieError decodeErrorObject( NessieError error; if (inputStream == null) { error = - new NessieError( - status.getCode(), - status.getReason(), - "Could not parse error object in response.", - new RuntimeException("Could not parse error object in response.")); + ImmutableNessieError.builder() + .errorCode(ErrorCode.UNKNOWN) + .status(status.getCode()) + .reason(status.getReason()) + .message("Could not parse error object in response.") + .clientProcessingException( + new RuntimeException("Could not parse error object in response.")) + .build(); } else { try { JsonNode errorData = reader.readTree(inputStream); @@ -92,10 +108,20 @@ private static NessieError decodeErrorObject( // produced by Quarkus and contains the server-side logged error ID. Report the raw JSON // text to the caller for trouble-shooting. error = - new NessieError(errorData.toString(), status.getCode(), status.getReason(), null, e); + ImmutableNessieError.builder() + .message(errorData.toString()) + .status(status.getCode()) + .reason(status.getReason()) + .clientProcessingException(e) + .build(); } } catch (IOException e) { - error = new NessieError(status.getCode(), status.getReason(), null, e); + error = + ImmutableNessieError.builder() + .status(status.getCode()) + .reason(status.getReason()) + .clientProcessingException(e) + .build(); } } return error; diff --git a/clients/client/src/test/java/org/projectnessie/client/TestResultStreamPaginator.java b/clients/client/src/test/java/org/projectnessie/client/TestResultStreamPaginator.java index 8c03791c134..1c80924ebec 100644 --- a/clients/client/src/test/java/org/projectnessie/client/TestResultStreamPaginator.java +++ b/clients/client/src/test/java/org/projectnessie/client/TestResultStreamPaginator.java @@ -28,7 +28,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; -import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.NessieReferenceNotFoundException; import org.projectnessie.model.PaginatedResponse; class TestResultStreamPaginator { @@ -38,10 +38,10 @@ void testNotFoundException() { new ResultStreamPaginator<>( MockPaginatedResponse::getElements, (ref, pageSize, token) -> { - throw new NessieNotFoundException("Ref not found"); + throw new NessieReferenceNotFoundException("Ref not found"); }); assertThatThrownBy(() -> paginator.generateStream("ref", OptionalInt.empty())) - .isInstanceOf(NessieNotFoundException.class) + .isInstanceOf(NessieReferenceNotFoundException.class) .hasMessage("Ref not found"); } diff --git a/clients/client/src/test/java/org/projectnessie/client/rest/TestResponseFilter.java b/clients/client/src/test/java/org/projectnessie/client/rest/TestResponseFilter.java index 9ab010652c4..371c6a87a9e 100644 --- a/clients/client/src/test/java/org/projectnessie/client/rest/TestResponseFilter.java +++ b/clients/client/src/test/java/org/projectnessie/client/rest/TestResponseFilter.java @@ -32,9 +32,13 @@ import org.projectnessie.client.http.ResponseContext; import org.projectnessie.client.http.Status; import org.projectnessie.error.BaseNessieClientServerException; +import org.projectnessie.error.ErrorCode; +import org.projectnessie.error.ImmutableNessieError; import org.projectnessie.error.NessieConflictException; +import org.projectnessie.error.NessieContentsNotFoundException; import org.projectnessie.error.NessieError; import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.NessieReferenceNotFoundException; import software.amazon.awssdk.utils.StringInputStream; public class TestResponseFilter { @@ -43,9 +47,16 @@ public class TestResponseFilter { @ParameterizedTest @MethodSource("provider") - void testResponseFilter(Status responseCode, Class clazz) { - final NessieError error = - new NessieError(responseCode.getCode(), responseCode.getReason(), "xxx", null); + void testResponseFilter( + Status responseCode, ErrorCode errorCode, Class clazz) { + NessieError error = + ImmutableNessieError.builder() + .message("test-error") + .status(responseCode.getCode()) + .errorCode(errorCode) + .reason(responseCode.getReason()) + .serverStackTrace("xxx") + .build(); try { ResponseCheckFilter.checkResponse(new TestResponseContext(responseCode, error), MAPPER); } catch (Exception e) { @@ -63,7 +74,8 @@ void testResponseFilter(Status responseCode, Class clazz) { @Test void testBadReturn() { - final NessieError error = new NessieError("unknown", 415, "xxx", null); + NessieError error = + ImmutableNessieError.builder().message("unknown").status(415).reason("xxx").build(); assertThatThrownBy( () -> ResponseCheckFilter.checkResponse( @@ -137,11 +149,13 @@ public InputStream getErrorStream() { @Test void testBadReturnBadError() { NessieError defaultError = - new NessieError( - Status.UNAUTHORIZED.getCode(), - Status.UNAUTHORIZED.getReason(), - "Could not parse error object in response.", - new RuntimeException("Could not parse error object in response.")); + ImmutableNessieError.builder() + .status(Status.UNAUTHORIZED.getCode()) + .reason(Status.UNAUTHORIZED.getReason()) + .message("Could not parse error object in response.") + .clientProcessingException( + new RuntimeException("Could not parse error object in response.")) + .build(); assertThatThrownBy( () -> ResponseCheckFilter.checkResponse( @@ -159,12 +173,20 @@ void testGood() { private static Stream provider() { return Stream.of( - Arguments.of(Status.BAD_REQUEST, NessieBadRequestException.class), - Arguments.of(Status.UNAUTHORIZED, NessieNotAuthorizedException.class), - Arguments.of(Status.FORBIDDEN, NessieForbiddenException.class), - Arguments.of(Status.NOT_FOUND, NessieNotFoundException.class), - Arguments.of(Status.CONFLICT, NessieConflictException.class), - Arguments.of(Status.INTERNAL_SERVER_ERROR, NessieInternalServerException.class)); + Arguments.of(Status.BAD_REQUEST, ErrorCode.UNKNOWN, NessieBadRequestException.class), + Arguments.of(Status.UNAUTHORIZED, ErrorCode.UNKNOWN, NessieNotAuthorizedException.class), + Arguments.of(Status.FORBIDDEN, ErrorCode.UNKNOWN, NessieForbiddenException.class), + Arguments.of( + Status.NOT_FOUND, ErrorCode.CONTENTS_NOT_FOUND, NessieContentsNotFoundException.class), + Arguments.of( + Status.NOT_FOUND, + ErrorCode.REFERENCE_NOT_FOUND, + NessieReferenceNotFoundException.class), + Arguments.of(Status.NOT_FOUND, ErrorCode.UNKNOWN, NessieNotFoundException.class), + Arguments.of(Status.CONFLICT, ErrorCode.REFERENCE_CONFLICT, NessieConflictException.class), + Arguments.of(Status.CONFLICT, ErrorCode.UNKNOWN, NessieConflictException.class), + Arguments.of( + Status.INTERNAL_SERVER_ERROR, ErrorCode.UNKNOWN, NessieInternalServerException.class)); } private static class TestResponseContext implements ResponseContext { diff --git a/clients/deltalake/src/test/java/org/projectnessie/deltalake/ITDeltaLogBranches.java b/clients/deltalake/src/test/java/org/projectnessie/deltalake/ITDeltaLogBranches.java index 505572fa54b..ea9ee13ae53 100644 --- a/clients/deltalake/src/test/java/org/projectnessie/deltalake/ITDeltaLogBranches.java +++ b/clients/deltalake/src/test/java/org/projectnessie/deltalake/ITDeltaLogBranches.java @@ -35,7 +35,7 @@ import org.projectnessie.client.api.NessieApiV1; import org.projectnessie.client.http.HttpClientBuilder; import org.projectnessie.client.tests.AbstractSparkTest; -import org.projectnessie.error.NessieConflictException; +import org.projectnessie.error.BaseNessieClientServerException; import org.projectnessie.error.NessieNotFoundException; import org.projectnessie.model.Branch; import org.projectnessie.model.Contents; @@ -64,7 +64,7 @@ void createClient() { } @AfterEach - void closeClient() throws NessieNotFoundException, NessieConflictException { + void closeClient() throws BaseNessieClientServerException { Reference ref = null; try { ref = api.getReference().refName("test").get(); @@ -82,7 +82,7 @@ void closeClient() throws NessieNotFoundException, NessieConflictException { } @Test - void testBranches() throws NessieNotFoundException, NessieConflictException { + void testBranches() throws BaseNessieClientServerException { Dataset targetTable = createKVDataSet( Arrays.asList(tuple2(1, 10), tuple2(2, 20), tuple2(3, 30), tuple2(4, 40)), diff --git a/model/src/main/java/org/projectnessie/error/BaseNessieClientServerException.java b/model/src/main/java/org/projectnessie/error/BaseNessieClientServerException.java index 54ae46cd2e8..51c694d44f2 100644 --- a/model/src/main/java/org/projectnessie/error/BaseNessieClientServerException.java +++ b/model/src/main/java/org/projectnessie/error/BaseNessieClientServerException.java @@ -68,6 +68,10 @@ public int getStatus() { return status; } + public ErrorCode getErrorCode() { + return ErrorCode.UNKNOWN; + } + public String getReason() { return reason; } diff --git a/model/src/main/java/org/projectnessie/error/ErrorCode.java b/model/src/main/java/org/projectnessie/error/ErrorCode.java new file mode 100644 index 00000000000..752ff2829fb --- /dev/null +++ b/model/src/main/java/org/projectnessie/error/ErrorCode.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Dremio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.projectnessie.error; + +import java.util.Optional; +import java.util.function.Function; + +/** + * Defines Nessie error codes that are more fine-grained than HTTP status codes and maps them to + * exception classes. + * + *

The enum names also designate error code in the JSON representation of {@link NessieError}. + */ +public enum ErrorCode { + UNKNOWN(null), + REFERENCE_NOT_FOUND(NessieReferenceNotFoundException::new), + REFERENCE_ALREADY_EXISTS(NessieReferenceAlreadyExistsException::new), + CONTENTS_NOT_FOUND(NessieContentsNotFoundException::new), + REFERENCE_CONFLICT(NessieReferenceConflictException::new), + ; + + private final Function exceptionBuilder; + + ErrorCode(Function exceptionBuilder) { + this.exceptionBuilder = exceptionBuilder; + } + + public static Optional asException(NessieError error) { + return Optional.ofNullable(error.getErrorCode()) + .flatMap(e -> Optional.ofNullable(e.exceptionBuilder)) + .map(b -> b.apply(error)); + } +} diff --git a/model/src/main/java/org/projectnessie/error/NessieConflictException.java b/model/src/main/java/org/projectnessie/error/NessieConflictException.java index 1174225dc67..6836bb92a5f 100644 --- a/model/src/main/java/org/projectnessie/error/NessieConflictException.java +++ b/model/src/main/java/org/projectnessie/error/NessieConflictException.java @@ -15,6 +15,14 @@ */ package org.projectnessie.error; +/** + * Base class for all exceptions that are represented by the HTTP {@code Conflict} status code + * (409). + * + *

This exception should not be instantiated directly on the server-side. It may be instantiated + * and thrown on the client side to represent cases when the server responded with the HTTP {@code + * Conflict} status code, but no fine-grained error information was available. + */ public class NessieConflictException extends BaseNessieClientServerException { public NessieConflictException(String message, Throwable cause) { diff --git a/model/src/main/java/org/projectnessie/error/NessieContentsNotFoundException.java b/model/src/main/java/org/projectnessie/error/NessieContentsNotFoundException.java new file mode 100644 index 00000000000..f6d2950e3f6 --- /dev/null +++ b/model/src/main/java/org/projectnessie/error/NessieContentsNotFoundException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 Dremio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.projectnessie.error; + +import org.projectnessie.model.ContentsKey; + +/** This exception is thrown when the requested contents object is not present in the store. */ +public class NessieContentsNotFoundException extends NessieNotFoundException { + + public NessieContentsNotFoundException(ContentsKey key, String ref) { + super(String.format("Could not find contents for key '%s' in reference '%s'.", key, ref)); + } + + public NessieContentsNotFoundException(NessieError error) { + super(error); + } + + @Override + public ErrorCode getErrorCode() { + return ErrorCode.CONTENTS_NOT_FOUND; + } +} diff --git a/model/src/main/java/org/projectnessie/error/NessieError.java b/model/src/main/java/org/projectnessie/error/NessieError.java index c18c1f463ae..c12a4310631 100644 --- a/model/src/main/java/org/projectnessie/error/NessieError.java +++ b/model/src/main/java/org/projectnessie/error/NessieError.java @@ -15,143 +15,74 @@ */ package org.projectnessie.error; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Objects; +import javax.annotation.Nullable; +import org.immutables.value.Value; -public class NessieError { +/** Represents Nessie-specific API error details. */ +@Value.Immutable +@JsonSerialize(as = ImmutableNessieError.class) +@JsonDeserialize(as = ImmutableNessieError.class) +public interface NessieError { - private final String message; - private final int status; - private final String reason; - private final String serverStackTrace; - private final Exception clientProcessingException; + /** HTTP status code of this error. */ + int getStatus(); - /** - * Deserialize error message from the server. - * - * @param message Error message - * @param status HTTP status code - * @param serverStackTrace exception, if present (can be empty or {@code null} - */ - @JsonCreator - public NessieError( - @JsonProperty("message") String message, - @JsonProperty("status") int status, - @JsonProperty("reason") String reason, - @JsonProperty("serverStackTrace") String serverStackTrace) { - this(message, status, reason, serverStackTrace, null); + /** Reason phrase for the HTTP status code. */ + String getReason(); + + /** Nessie-specific Error message. */ + @Value.Default + default String getMessage() { + return getReason(); } - /** - * Create Error. - * - * @param message Message of error. - * @param status Status of error. - * @param reason Reason for status. - * @param serverStackTrace Server stack trace, if available. - * @param processingException Any processing exceptions that happened on the client. - */ - public NessieError( - String message, - int status, - String reason, - String serverStackTrace, - Exception processingException) { - this.message = message; - this.status = status; - this.reason = reason; - this.serverStackTrace = serverStackTrace; - this.clientProcessingException = processingException; + /** Nessie-specific error code. */ + @Value.Default + default ErrorCode getErrorCode() { + return ErrorCode.UNKNOWN; } + /** Server-side exception stack trace related to this error (if available). */ + @Nullable + String getServerStackTrace(); + /** - * Create Error. + * Client-side {@link Exception} related to the processing of the error response. * - * @param statusCode Status of error. - * @param reason Reason for status. - * @param serverStackTrace Server stack trace, if available. - * @param processingException Any processing exceptions that happened on the client. + *

This {@link Exception} generally represents any errors related to the decoding of the + * payload returned by the server to describe the error. */ - public NessieError( - int statusCode, String reason, String serverStackTrace, Exception processingException) { - this.status = statusCode; - this.message = reason; - this.reason = reason; - this.serverStackTrace = serverStackTrace; - this.clientProcessingException = processingException; - } - - public String getMessage() { - return message; - } - - public int getStatus() { - return status; - } - - public String getReason() { - return reason; - } - - public String getServerStackTrace() { - return serverStackTrace; - } - @JsonIgnore - public Exception getClientProcessingException() { - return clientProcessingException; - } + @Value.Auxiliary + @Nullable + Exception getClientProcessingException(); /** - * Get full error message. - * - * @return Full error message. + * The full error message, including HTTP status, reason, server- and client-side exception stack + * traces. */ @JsonIgnore - public String getFullMessage() { + default String getFullMessage() { StringBuilder sb = new StringBuilder(); - if (reason != null) { - sb.append(reason).append(" (HTTP/").append(status).append(')'); - } + sb.append(getReason()).append(" (HTTP/").append(getStatus()).append(')'); + sb.append(": "); + sb.append(getMessage()); - if (message != null) { - if (sb.length() > 0) { - sb.append(": "); - } - sb.append(message); + if (getServerStackTrace() != null) { + sb.append("\n").append(getServerStackTrace()); } - if (serverStackTrace != null) { - sb.append("\n").append(serverStackTrace); - } - if (clientProcessingException != null) { + if (getClientProcessingException() != null) { StringWriter sw = new StringWriter(); - clientProcessingException.printStackTrace(new PrintWriter(sw)); + getClientProcessingException().printStackTrace(new PrintWriter(sw)); sb.append("\n").append(sw); } - return sb.toString(); - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - NessieError error = (NessieError) o; - return status == error.status - && Objects.equals(message, error.message) - && Objects.equals(serverStackTrace, error.serverStackTrace); - } - - @Override - public int hashCode() { - return Objects.hash(message, status, serverStackTrace); + return sb.toString(); } } diff --git a/model/src/main/java/org/projectnessie/error/NessieNotFoundException.java b/model/src/main/java/org/projectnessie/error/NessieNotFoundException.java index dffc27d6b5e..7137472e3e2 100644 --- a/model/src/main/java/org/projectnessie/error/NessieNotFoundException.java +++ b/model/src/main/java/org/projectnessie/error/NessieNotFoundException.java @@ -15,6 +15,14 @@ */ package org.projectnessie.error; +/** + * Base class for all exceptions that are represented by the HTTP {@code Not Found} status code + * (404). + * + *

This exception should not be instantiated directly on the server-side. It may be instantiated + * and thrown on the client side to represent cases when the server responded with the HTTP {@code + * Not Found} status code, but no fine-grained error information was available. + */ public class NessieNotFoundException extends BaseNessieClientServerException { public NessieNotFoundException(String message, Throwable cause) { diff --git a/model/src/main/java/org/projectnessie/error/NessieReferenceAlreadyExistsException.java b/model/src/main/java/org/projectnessie/error/NessieReferenceAlreadyExistsException.java new file mode 100644 index 00000000000..43cdcbaee84 --- /dev/null +++ b/model/src/main/java/org/projectnessie/error/NessieReferenceAlreadyExistsException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Dremio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.projectnessie.error; + +/** + * This exception is thrown when a reference could not be created because another reference with the + * same name is already present in the store. + */ +public class NessieReferenceAlreadyExistsException extends NessieConflictException { + public NessieReferenceAlreadyExistsException(String message, Throwable cause) { + super(message, cause); + } + + public NessieReferenceAlreadyExistsException(String message) { + super(message); + } + + public NessieReferenceAlreadyExistsException(NessieError error) { + super(error); + } + + @Override + public ErrorCode getErrorCode() { + return ErrorCode.REFERENCE_ALREADY_EXISTS; + } +} diff --git a/model/src/main/java/org/projectnessie/error/NessieReferenceConflictException.java b/model/src/main/java/org/projectnessie/error/NessieReferenceConflictException.java new file mode 100644 index 00000000000..caa85451c38 --- /dev/null +++ b/model/src/main/java/org/projectnessie/error/NessieReferenceConflictException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Dremio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.projectnessie.error; + +/** + * This exception is thrown when the expected state of a reference (e.g. the hash of the HEAD of a + * branch) does not match the hash provided by the caller. + */ +public class NessieReferenceConflictException extends NessieConflictException { + public NessieReferenceConflictException(String message, Throwable cause) { + super(message, cause); + } + + public NessieReferenceConflictException(String message) { + super(message); + } + + public NessieReferenceConflictException(NessieError error) { + super(error); + } + + @Override + public ErrorCode getErrorCode() { + return ErrorCode.REFERENCE_CONFLICT; + } +} diff --git a/model/src/main/java/org/projectnessie/error/NessieReferenceNotFoundException.java b/model/src/main/java/org/projectnessie/error/NessieReferenceNotFoundException.java new file mode 100644 index 00000000000..8810595350a --- /dev/null +++ b/model/src/main/java/org/projectnessie/error/NessieReferenceNotFoundException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Dremio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.projectnessie.error; + +/** This exception is thrown when a requested reference is not present in the store. */ +public class NessieReferenceNotFoundException extends NessieNotFoundException { + + public NessieReferenceNotFoundException(String message) { + super(message); + } + + public NessieReferenceNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public NessieReferenceNotFoundException(NessieError error) { + super(error); + } + + @Override + public ErrorCode getErrorCode() { + return ErrorCode.REFERENCE_NOT_FOUND; + } +} diff --git a/model/src/test/java/org/projectnessie/error/TestErrorCode.java b/model/src/test/java/org/projectnessie/error/TestErrorCode.java new file mode 100644 index 00000000000..cb62baa2451 --- /dev/null +++ b/model/src/test/java/org/projectnessie/error/TestErrorCode.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 Dremio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.projectnessie.error; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class TestErrorCode { + + private Optional ex(ErrorCode errorCode) { + NessieError error = + ImmutableNessieError.builder().reason("test").status(1).errorCode(errorCode).build(); + return ErrorCode.asException(error); + } + + @Test + void testUnknown() { + assertThat(ex(ErrorCode.UNKNOWN)).isNotPresent(); + } + + @ParameterizedTest + @EnumSource(value = ErrorCode.class, mode = EnumSource.Mode.EXCLUDE, names = "UNKNOWN") + public void testConversion(ErrorCode errorCode) { + Optional ex = ex(errorCode); + assertThat(ex).isPresent(); + assertThat(ex.get().getErrorCode()).isEqualTo(errorCode); + } +} diff --git a/model/src/test/java/org/projectnessie/error/TestNessieError.java b/model/src/test/java/org/projectnessie/error/TestNessieError.java index 4af61567059..f7b15a44bd9 100644 --- a/model/src/test/java/org/projectnessie/error/TestNessieError.java +++ b/model/src/test/java/org/projectnessie/error/TestNessieError.java @@ -16,31 +16,40 @@ package org.projectnessie.error; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import javax.ws.rs.core.Response; import org.junit.jupiter.api.Test; class TestNessieError { - @Test - void allNulls() { - NessieError e = new NessieError(0, null, null, null); - assertNull(e.getMessage()); - } + private static final ObjectMapper mapper = new ObjectMapper(); @Test - void userContructor() { + void fullMessage() { NessieError e = - new NessieError( - "message", - Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), - Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase(), - "foo.bar.InternalServerError\n" + "\tat some.other.Class", - new Exception("processingException")); - assertEquals("message", e.getMessage()); + ImmutableNessieError.builder() + .message("message") + .errorCode(ErrorCode.UNKNOWN) + .status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) + .reason(Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase()) + .serverStackTrace("foo.bar.InternalServerError\n" + "\tat some.other.Class") + .build(); + assertThat(e.getFullMessage()) + .isEqualTo( + Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase() + + " (HTTP/" + + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() + + "): message\n" + + "foo.bar.InternalServerError\n" + + "\tat some.other.Class"); + + e = + ImmutableNessieError.builder() + .from(e) + .clientProcessingException(new Exception("processingException")) + .build(); assertThat(e.getFullMessage()) .startsWith( Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase() @@ -50,32 +59,29 @@ void userContructor() { + "foo.bar.InternalServerError\n" + "\tat some.other.Class\n" + "java.lang.Exception: processingException\n" - + "\tat org.projectnessie.error.TestNessieError.userContructor(TestNessieError.java:"); + + "\tat org.projectnessie.error.TestNessieError.fullMessage(TestNessieError.java:"); } @Test - void jsonCreator() { - NessieError e = - new NessieError( - "message", - Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), - Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase(), - "foo.bar.InternalServerError\n" + "\tat some.other.Class"); - assertAll( - () -> - assertEquals( - Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase() - + " (HTTP/" - + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() - + "): message\n" - + "foo.bar.InternalServerError\n" - + "\tat some.other.Class", - e.getFullMessage()), - () -> assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e.getStatus()), - () -> assertEquals("message", e.getMessage()), - () -> - assertEquals( - "foo.bar.InternalServerError\n" + "\tat some.other.Class", - e.getServerStackTrace())); + void jsonRoundTrip() throws JsonProcessingException { + NessieError e0 = + ImmutableNessieError.builder() + .message("message") + .errorCode(ErrorCode.UNKNOWN) + .status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) + .reason(Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase()) + .serverStackTrace("foo.bar.InternalServerError\n" + "\tat some.other.Class") + .clientProcessingException(new Exception("processingException")) + .build(); + + String json = mapper.writeValueAsString(e0); + NessieError e1 = mapper.readValue(json, NessieError.class); + + assertThat(e1.getClientProcessingException()).isNull(); // not propagated through JSON + + // Copy e0 without the client error + NessieError e2 = + ImmutableNessieError.builder().from(e0).clientProcessingException(null).build(); + assertThat(e1).isEqualTo(e2); } } diff --git a/servers/jax-rs-tests/src/main/java/org/projectnessie/jaxrs/AbstractTestRest.java b/servers/jax-rs-tests/src/main/java/org/projectnessie/jaxrs/AbstractTestRest.java index 6e377c3fd73..2edcb49deba 100644 --- a/servers/jax-rs-tests/src/main/java/org/projectnessie/jaxrs/AbstractTestRest.java +++ b/servers/jax-rs-tests/src/main/java/org/projectnessie/jaxrs/AbstractTestRest.java @@ -66,8 +66,11 @@ import org.projectnessie.client.http.HttpClientException; import org.projectnessie.client.rest.NessieBadRequestException; import org.projectnessie.client.rest.NessieHttpResponseFilter; +import org.projectnessie.error.BaseNessieClientServerException; import org.projectnessie.error.NessieConflictException; import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.NessieReferenceAlreadyExistsException; +import org.projectnessie.error.NessieReferenceNotFoundException; import org.projectnessie.model.Branch; import org.projectnessie.model.CommitMeta; import org.projectnessie.model.Contents; @@ -138,17 +141,15 @@ public NessieApiV1 getApi() { return api; } - public HttpClient getHttpClient() { - return httpClient; - } - @Test - public void createRecreateDefaultBranch() - throws NessieConflictException, NessieNotFoundException { + public void createRecreateDefaultBranch() throws BaseNessieClientServerException { api.deleteBranch().branch(api.getDefaultBranch()).delete(); - api.createReference().reference(Branch.of("main", null)).create(); - api.getReference().refName("main").get(); + Reference main = api.createReference().reference(Branch.of("main", null)).create(); + assertThat(main).isNotNull(); + assertThat(main.getName()).isEqualTo("main"); + assertThat(main.getHash()).isNotNull(); + assertThat(api.getReference().refName("main").get()).isEqualTo(main); } @Test @@ -169,7 +170,7 @@ public void createReferences() throws NessieNotFoundException { .sourceRefName("unknownSource") .reference(Tag.of(tagName2, null)) .create()) - .isInstanceOf(NessieNotFoundException.class) + .isInstanceOf(NessieReferenceNotFoundException.class) .hasMessageContainingAll("'unknownSource'", "not"), // Tag without sourceRefName & null hash () -> @@ -220,8 +221,7 @@ public void createReferences() throws NessieNotFoundException { @ParameterizedTest @ValueSource(strings = {"normal", "with-no_space", "slash/thing"}) - public void referenceNames(String refNamePart) - throws NessieNotFoundException, NessieConflictException { + public void referenceNames(String refNamePart) throws BaseNessieClientServerException { String tagName = "tag" + refNamePart; String branchName = "branch" + refNamePart; String branchName2 = "branch2" + refNamePart; @@ -340,7 +340,7 @@ public void referenceNames(String refNamePart) } @Test - public void filterCommitLogByAuthor() throws NessieNotFoundException, NessieConflictException { + public void filterCommitLogByAuthor() throws BaseNessieClientServerException { Reference main = api.getReference().refName("main").get(); Branch filterCommitLogByAuthor = Branch.of("filterCommitLogByAuthor", main.getHash()); Reference branch = @@ -426,7 +426,7 @@ public void filterCommitLogByAuthor() throws NessieNotFoundException, NessieConf } @Test - public void filterCommitLogByTimeRange() throws NessieNotFoundException, NessieConflictException { + public void filterCommitLogByTimeRange() throws BaseNessieClientServerException { Reference main = api.getReference().refName("main").get(); Branch filterCommitLogByAuthor = Branch.of("filterCommitLogByTimeRange", main.getHash()); Reference branch = @@ -503,8 +503,7 @@ public void filterCommitLogByTimeRange() throws NessieNotFoundException, NessieC } @Test - public void filterCommitLogByProperties() - throws NessieNotFoundException, NessieConflictException { + public void filterCommitLogByProperties() throws BaseNessieClientServerException { Reference main = api.getReference().refName("main").get(); Branch filterCommitLogByAuthor = Branch.of("filterCommitLogByProperties", main.getHash()); Reference branch = @@ -543,8 +542,7 @@ public void filterCommitLogByProperties() } @Test - public void filterCommitLogByCommitRange() - throws NessieNotFoundException, NessieConflictException { + public void filterCommitLogByCommitRange() throws BaseNessieClientServerException { Reference main = api.getReference().refName("main").get(); Branch b = Branch.of("filterCommitLogByCommitRange", main.getHash()); Reference branch = api.createReference().sourceRefName(main.getName()).reference(b).create(); @@ -584,7 +582,7 @@ public void filterCommitLogByCommitRange() protected void createCommits( Reference branch, int numAuthors, int commitsPerAuthor, String currentHash) - throws NessieNotFoundException, NessieConflictException { + throws BaseNessieClientServerException { for (int j = 0; j < numAuthors; j++) { String author = "author-" + j; for (int i = 0; i < commitsPerAuthor; i++) { @@ -609,12 +607,12 @@ protected void createCommits( } @Test - public void commitLogPagingAndFilteringByAuthor() - throws NessieNotFoundException, NessieConflictException { + public void commitLogPagingAndFilteringByAuthor() throws BaseNessieClientServerException { String someHash = api.getReference().refName("main").get().getHash(); String branchName = "commitLogPagingAndFiltering"; Branch branch = Branch.of(branchName, someHash); - api.createReference().sourceRefName("main").reference(branch).create(); + assertThat(api.createReference().sourceRefName("main").reference(branch).create().getHash()) + .isNotNull(); int numAuthors = 3; int commits = 45; @@ -645,11 +643,12 @@ public void commitLogPagingAndFilteringByAuthor() } @Test - public void commitLogPaging() throws NessieNotFoundException, NessieConflictException { + public void commitLogPaging() throws BaseNessieClientServerException { String someHash = api.getReference().refName("main").get().getHash(); String branchName = "commitLogPaging"; Branch branch = Branch.of(branchName, someHash); - api.createReference().sourceRefName("main").reference(branch).create(); + assertThat(api.createReference().sourceRefName("main").reference(branch).create().getHash()) + .isNotNull(); int commits = 95; int pageSizeHint = 10; @@ -726,7 +725,7 @@ protected void verifyPaging( } @Test - public void multiget() throws NessieNotFoundException, NessieConflictException { + public void multiget() throws BaseNessieClientServerException { final String branch = "foo"; Reference r = api.createReference().sourceRefName("main").reference(Branch.of(branch, null)).create(); @@ -845,8 +844,7 @@ public static Stream contentAndOperationTypes() { } @Test - public void verifyAllContentAndOperationTypes() - throws NessieNotFoundException, NessieConflictException { + public void verifyAllContentAndOperationTypes() throws BaseNessieClientServerException { String branchName = "contentAndOperationAll"; Reference r = api.createReference().sourceRefName("main").reference(Branch.of(branchName, null)).create(); @@ -877,8 +875,7 @@ public void verifyAllContentAndOperationTypes() @ParameterizedTest @MethodSource("contentAndOperationTypes") public void verifyContentAndOperationTypesIndividually( - ContentAndOperationType contentAndOperationType) - throws NessieNotFoundException, NessieConflictException { + ContentAndOperationType contentAndOperationType) throws BaseNessieClientServerException { String branchName = "contentAndOperation_" + contentAndOperationType; Reference r = api.createReference().sourceRefName("main").reference(Branch.of(branchName, null)).create(); @@ -909,7 +906,7 @@ public void verifyContentAndOperationTypesIndividually( } @Test - public void filterEntriesByType() throws NessieNotFoundException, NessieConflictException { + public void filterEntriesByType() throws BaseNessieClientServerException { final String branch = "filterTypes"; Reference r = api.createReference().sourceRefName("main").reference(Branch.of(branch, null)).create(); @@ -968,7 +965,7 @@ public void filterEntriesByType() throws NessieNotFoundException, NessieConflict } @Test - public void filterEntriesByNamespace() throws NessieConflictException, NessieNotFoundException { + public void filterEntriesByNamespace() throws BaseNessieClientServerException { final String branch = "filterEntriesByNamespace"; Reference r = api.createReference().sourceRefName("main").reference(Branch.of(branch, null)).create(); @@ -1048,8 +1045,7 @@ public void filterEntriesByNamespace() throws NessieConflictException, NessieNot } @Test - public void filterEntriesByNamespaceAndPrefixDepth() - throws NessieConflictException, NessieNotFoundException { + public void filterEntriesByNamespaceAndPrefixDepth() throws BaseNessieClientServerException { final String branch = "filterEntriesByNamespaceAndPrefixDepth"; Reference r = api.createReference().sourceRefName("main").reference(Branch.of(branch, null)).create(); @@ -1144,8 +1140,7 @@ public void checkCelScriptFailureReporting() { } @Test - public void checkSpecialCharacterRoundTrip() - throws NessieNotFoundException, NessieConflictException { + public void checkSpecialCharacterRoundTrip() throws BaseNessieClientServerException { final String branch = "specialchar"; Reference r = api.createReference().sourceRefName("main").reference(Branch.of(branch, null)).create(); @@ -1168,18 +1163,37 @@ public void checkSpecialCharacterRoundTrip() } @Test - public void checkServerErrorPropagation() - throws NessieNotFoundException, NessieConflictException { + public void checkServerErrorPropagation() throws BaseNessieClientServerException { final String branch = "bar"; - api.createReference().sourceRefName("main").reference(Branch.of(branch, null)).create(); + Reference ref = + api.createReference().sourceRefName("main").reference(Branch.of(branch, null)).create(); + assertThat(ref.getName()).isEqualTo(branch); + assertThat(ref.getHash()).isNotNull(); + assertThatThrownBy( () -> api.createReference() .sourceRefName("main") .reference(Branch.of(branch, null)) .create()) - .isInstanceOf(NessieConflictException.class) + .isInstanceOf(NessieReferenceAlreadyExistsException.class) .hasMessageContaining("already exists"); + + assertThatThrownBy( + () -> + api.commitMultipleOperations() + .branchName(ref.getName()) + .hash(ref.getHash()) + .commitMeta( + CommitMeta.builder() + .author("author") + .message("committed-by-test") + .committer("disallowed-client-side-committer") + .build()) + .operation(Unchanged.of(ContentsKey.of("table"))) + .commit()) + .isInstanceOf(NessieBadRequestException.class) + .hasMessageContaining("Cannot set the committer on the client side."); } @ParameterizedTest @@ -1581,8 +1595,7 @@ public void testInvalidNamedRefs() { } @Test - public void testValidHashesOnValidNamedRefs() - throws NessieNotFoundException, NessieConflictException { + public void testValidHashesOnValidNamedRefs() throws BaseNessieClientServerException { Reference main = api.getReference().refName("main").get(); Branch b = Branch.of("testValidHashesOnValidNamedRefs", main.getHash()); Reference branch = api.createReference().sourceRefName(main.getName()).reference(b).create(); @@ -1628,8 +1641,7 @@ public void testValidHashesOnValidNamedRefs() } @Test - public void testUnknownHashesOnValidNamedRefs() - throws NessieNotFoundException, NessieConflictException { + public void testUnknownHashesOnValidNamedRefs() throws BaseNessieClientServerException { Reference main = api.getReference().refName("main").get(); Branch b = Branch.of("testUnknownHashesOnValidNamedRefs", main.getHash()); Reference branch = api.createReference().sourceRefName(main.getName()).reference(b).create(); @@ -1681,26 +1693,32 @@ public void testUnknownHashesOnValidNamedRefs() /** Assigning a branch/tag to a fresh main without any commits didn't work in 0.9.2 */ @Test - public void testAssignRefToFreshMain() throws NessieNotFoundException, NessieConflictException { + public void testAssignRefToFreshMain() throws BaseNessieClientServerException { Reference main = api.getReference().refName("main").get(); // make sure main doesn't have any commits LogResponse log = api.getCommitLog().refName(main.getName()).get(); assertThat(log.getOperations()).isEmpty(); String testBranch = "testBranch"; - api.createReference() - .sourceRefName(main.getName()) - .reference(Branch.of(testBranch, null)) - .create(); + assertThat( + api.createReference() + .sourceRefName(main.getName()) + .reference(Branch.of(testBranch, null)) + .create() + .getHash()) + .isNotNull(); Reference testBranchRef = api.getReference().refName(testBranch).get(); api.assignBranch().hash(testBranchRef.getHash()).branchName(testBranch).assignTo(main).assign(); assertThat(testBranchRef.getHash()).isEqualTo(main.getHash()); String testTag = "testTag"; - api.createReference() - .sourceRefName(main.getName()) - .reference(Branch.of(testTag, null)) - .create(); + assertThat( + api.createReference() + .sourceRefName(main.getName()) + .reference(Branch.of(testTag, null)) + .create() + .getHash()) + .isNotNull(); Reference testTagRef = api.getReference().refName(testTag).get(); api.assignTag().hash(testTagRef.getHash()).tagName(testTag).assignTo(main).assign(); assertThat(testTagRef.getHash()).isEqualTo(main.getHash()); diff --git a/servers/quarkus-server/src/test/java/org/projectnessie/server/AbstractTestBasicOperations.java b/servers/quarkus-server/src/test/java/org/projectnessie/server/AbstractTestBasicOperations.java index aee5d58c205..7a36e1e1e95 100644 --- a/servers/quarkus-server/src/test/java/org/projectnessie/server/AbstractTestBasicOperations.java +++ b/servers/quarkus-server/src/test/java/org/projectnessie/server/AbstractTestBasicOperations.java @@ -25,8 +25,7 @@ import org.junit.jupiter.api.function.Executable; import org.projectnessie.client.api.NessieApiV1; import org.projectnessie.client.http.HttpClientBuilder; -import org.projectnessie.error.NessieConflictException; -import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.BaseNessieClientServerException; import org.projectnessie.model.Branch; import org.projectnessie.model.CommitMeta; import org.projectnessie.model.ContentsKey; @@ -47,7 +46,7 @@ void closeClient() { } } - void getCatalog(String branch) throws NessieNotFoundException, NessieConflictException { + void getCatalog(String branch) throws BaseNessieClientServerException { api = HttpClientBuilder.builder() .withUri("http://localhost:19121/api/v1") @@ -65,7 +64,7 @@ void tryEndpointPass(Executable runnable) { @TestSecurity( user = "admin_user", roles = {"admin", "user"}) - void testAdmin() throws NessieNotFoundException, NessieConflictException { + void testAdmin() throws BaseNessieClientServerException { getCatalog("testx"); Branch branch = (Branch) api.getReference().refName("testx").get(); List tables = api.getEntries().refName("testx").get().getEntries(); @@ -118,7 +117,7 @@ void testAdmin() throws NessieNotFoundException, NessieConflictException { @Test @TestSecurity(authorizationEnabled = false) - void testUserCleanup() throws NessieNotFoundException, NessieConflictException { + void testUserCleanup() throws BaseNessieClientServerException { getCatalog(null); Branch r = (Branch) api.getReference().refName("testx").get(); api.deleteBranch().branch(r).delete(); diff --git a/servers/quarkus-server/src/test/java/org/projectnessie/server/TestAuthorizationRules.java b/servers/quarkus-server/src/test/java/org/projectnessie/server/TestAuthorizationRules.java index 1585b2127e7..1cd344a3930 100644 --- a/servers/quarkus-server/src/test/java/org/projectnessie/server/TestAuthorizationRules.java +++ b/servers/quarkus-server/src/test/java/org/projectnessie/server/TestAuthorizationRules.java @@ -28,7 +28,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.projectnessie.client.api.CommitMultipleOperationsBuilder; import org.projectnessie.client.rest.NessieForbiddenException; -import org.projectnessie.error.NessieConflictException; +import org.projectnessie.error.BaseNessieClientServerException; import org.projectnessie.error.NessieNotFoundException; import org.projectnessie.model.Branch; import org.projectnessie.model.CommitMeta; @@ -49,19 +49,18 @@ class TestAuthorizationRules extends BaseClientAuthTest { @ParameterizedTest @ValueSource(booleans = {true, false}) @TestSecurity(user = "test_user") - void testAllOpsWithTestUser(boolean shouldFail) - throws NessieNotFoundException, NessieConflictException { + void testAllOpsWithTestUser(boolean shouldFail) throws BaseNessieClientServerException { testAllOps("allowedBranchForTestUser", "test_user", shouldFail); } @Test @TestSecurity(user = "admin_user") - void testAdminUserIsAllowedEverything() throws NessieNotFoundException, NessieConflictException { + void testAdminUserIsAllowedEverything() throws BaseNessieClientServerException { testAllOps("testAdminUserIsAllowedAllBranch", "admin_user", false); } private void testAllOps(String branchName, String role, boolean shouldFail) - throws NessieConflictException, NessieNotFoundException { + throws BaseNessieClientServerException { ContentsKey key = ContentsKey.of("allowed", "x"); if (shouldFail) { branchName = "disallowedBranchForTestUser"; @@ -97,8 +96,7 @@ private void testAllOps(String branchName, String role, boolean shouldFail) @Test // test_user2 has all permissions on a Branch, but not permissions on a Key @TestSecurity(user = "test_user2") - void testCanCommitButNotUpdateOrDeleteEntity() - throws NessieNotFoundException, NessieConflictException { + void testCanCommitButNotUpdateOrDeleteEntity() throws BaseNessieClientServerException { String role = "test_user2"; ContentsKey key = ContentsKey.of("allowed", "some"); String branchName = "allowedBranchForTestUser2"; @@ -177,7 +175,7 @@ private Branch retrieveBranch(String branchName, String role, boolean shouldFail } private void createBranch(Branch branch, String role, boolean shouldFail) - throws NessieConflictException, NessieNotFoundException { + throws BaseNessieClientServerException { if (shouldFail) { assertThatThrownBy( () -> api().createReference().sourceRefName("main").reference(branch).create()) @@ -192,7 +190,7 @@ private void createBranch(Branch branch, String role, boolean shouldFail) } private void deleteBranch(Branch branch, String role, boolean shouldFail) - throws NessieConflictException, NessieNotFoundException { + throws BaseNessieClientServerException { if (shouldFail) { assertThatThrownBy(() -> api().deleteBranch().branch(branch).delete()) .isInstanceOf(NessieForbiddenException.class) @@ -265,7 +263,7 @@ private void getCommitLog(String branchName, String role, boolean shouldFail) } private void addContent(Branch branch, Put put, String role, boolean shouldFail) - throws NessieNotFoundException, NessieConflictException { + throws BaseNessieClientServerException { CommitMultipleOperationsBuilder commitOp = api() @@ -289,7 +287,7 @@ private void addContent(Branch branch, Put put, String role, boolean shouldFail) } private void deleteContent(Branch branch, Delete delete, String role, boolean shouldFail) - throws NessieConflictException, NessieNotFoundException { + throws BaseNessieClientServerException { CommitMultipleOperationsBuilder commitOp = api() diff --git a/servers/quarkus-server/src/test/java/org/projectnessie/server/error/ErrorTestService.java b/servers/quarkus-server/src/test/java/org/projectnessie/server/error/ErrorTestService.java index d4d7161e9d9..c77916895d0 100644 --- a/servers/quarkus-server/src/test/java/org/projectnessie/server/error/ErrorTestService.java +++ b/servers/quarkus-server/src/test/java/org/projectnessie/server/error/ErrorTestService.java @@ -34,6 +34,7 @@ import javax.ws.rs.core.MediaType; import org.mockito.Mockito; import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.NessieReferenceNotFoundException; import org.projectnessie.versioned.BackendLimitExceededException; import org.projectnessie.versioned.StringStoreWorker; import org.projectnessie.versioned.persist.adapter.DatabaseAdapter; @@ -73,7 +74,8 @@ public String blankParameterQueryGet(@NotBlank @QueryParam("hash") String hash) @Path("nessieNotFound") @GET public String nessieNotFound() throws NessieNotFoundException { - throw new NessieNotFoundException("not-there-message", new Exception("not-there-exception")); + throw new NessieReferenceNotFoundException( + "not-there-message", new Exception("not-there-exception")); } @Path("basicEntity") diff --git a/servers/rest-services/src/main/java/org/projectnessie/services/rest/BaseExceptionMapper.java b/servers/rest-services/src/main/java/org/projectnessie/services/rest/BaseExceptionMapper.java index 70c0a9ff3a9..a58356c232b 100644 --- a/servers/rest-services/src/main/java/org/projectnessie/services/rest/BaseExceptionMapper.java +++ b/servers/rest-services/src/main/java/org/projectnessie/services/rest/BaseExceptionMapper.java @@ -21,6 +21,9 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.ext.ExceptionMapper; +import org.projectnessie.error.BaseNessieClientServerException; +import org.projectnessie.error.ErrorCode; +import org.projectnessie.error.ImmutableNessieError; import org.projectnessie.error.NessieError; import org.projectnessie.services.config.ServerConfig; import org.slf4j.Logger; @@ -52,7 +55,20 @@ protected Response buildExceptionResponse( Consumer responseHandler) { String stack = includeExceptionStackTrace ? Throwables.getStackTraceAsString(e) : null; - NessieError error = new NessieError(message, status, reason, stack); + + ErrorCode errorCode = ErrorCode.UNKNOWN; + if (e instanceof BaseNessieClientServerException) { + errorCode = ((BaseNessieClientServerException) e).getErrorCode(); + } + + NessieError error = + ImmutableNessieError.builder() + .message(message) + .status(status) + .errorCode(errorCode) + .reason(reason) + .serverStackTrace(stack) + .build(); LOGGER.debug( "Failure on server, propagated to client. Status: {} {}, Message: {}.", status, diff --git a/servers/services/src/main/java/org/projectnessie/services/impl/BaseApiImpl.java b/servers/services/src/main/java/org/projectnessie/services/impl/BaseApiImpl.java index 7fa5317577d..580a0a7244a 100644 --- a/servers/services/src/main/java/org/projectnessie/services/impl/BaseApiImpl.java +++ b/servers/services/src/main/java/org/projectnessie/services/impl/BaseApiImpl.java @@ -20,6 +20,7 @@ import java.util.UUID; import javax.annotation.Nullable; import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.NessieReferenceNotFoundException; import org.projectnessie.model.CommitMeta; import org.projectnessie.model.Contents; import org.projectnessie.services.authz.AccessChecker; @@ -64,7 +65,7 @@ WithHash namedRefWithHashOrThrow(@Nullable String namedRef, @Nullable } namedRefWithHash = WithHash.of(refWithHash.getHash(), (NamedRef) ref); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage()); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } try { @@ -83,7 +84,7 @@ WithHash namedRefWithHashOrThrow(@Nullable String namedRef, @Nullable getStore().hashOnReference(namedRefWithHash.getValue(), Optional.of(Hash.of(hashOnRef))), namedRefWithHash.getValue()); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage()); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } } diff --git a/servers/services/src/main/java/org/projectnessie/services/impl/ContentsApiImpl.java b/servers/services/src/main/java/org/projectnessie/services/impl/ContentsApiImpl.java index fe863b4561b..027c256efcf 100644 --- a/servers/services/src/main/java/org/projectnessie/services/impl/ContentsApiImpl.java +++ b/servers/services/src/main/java/org/projectnessie/services/impl/ContentsApiImpl.java @@ -21,7 +21,9 @@ import java.util.Optional; import java.util.stream.Collectors; import org.projectnessie.api.ContentsApi; +import org.projectnessie.error.NessieContentsNotFoundException; import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.NessieReferenceNotFoundException; import org.projectnessie.model.CommitMeta; import org.projectnessie.model.Contents; import org.projectnessie.model.ContentsKey; @@ -56,10 +58,9 @@ public Contents getContents(ContentsKey key, String namedRef, String hashOnRef) if (obj != null) { return obj; } - throw new NessieNotFoundException("Requested contents do not exist for specified reference."); + throw new NessieContentsNotFoundException(key, namedRef); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException( - String.format("Provided reference [%s] does not exist.", namedRef), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } } @@ -82,7 +83,7 @@ public MultiGetContentsResponse getMultipleContents( return ImmutableMultiGetContentsResponse.builder().contents(output).build(); } catch (ReferenceNotFoundException ex) { - throw new NessieNotFoundException("Unable to find the requested ref.", ex); + throw new NessieReferenceNotFoundException(ex.getMessage(), ex); } } diff --git a/servers/services/src/main/java/org/projectnessie/services/impl/TreeApiImpl.java b/servers/services/src/main/java/org/projectnessie/services/impl/TreeApiImpl.java index 597fe6cfdf4..98a8bfafbf1 100644 --- a/servers/services/src/main/java/org/projectnessie/services/impl/TreeApiImpl.java +++ b/servers/services/src/main/java/org/projectnessie/services/impl/TreeApiImpl.java @@ -39,6 +39,9 @@ import org.projectnessie.cel.tools.ScriptException; import org.projectnessie.error.NessieConflictException; import org.projectnessie.error.NessieNotFoundException; +import org.projectnessie.error.NessieReferenceAlreadyExistsException; +import org.projectnessie.error.NessieReferenceConflictException; +import org.projectnessie.error.NessieReferenceNotFoundException; import org.projectnessie.model.Branch; import org.projectnessie.model.CommitMeta; import org.projectnessie.model.Contents; @@ -97,7 +100,7 @@ public Reference getReferenceByName(String refName) throws NessieNotFoundExcepti try { return makeRef(getStore().toRef(refName)); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage(), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } } @@ -119,7 +122,7 @@ public Reference createReference(String sourceRefName, Reference reference) } private Hash createReference(NamedRef reference, String hash) - throws NessieNotFoundException, NessieConflictException { + throws NessieNotFoundException, NessieReferenceAlreadyExistsException { if (reference instanceof TagName && hash == null) { throw new IllegalArgumentException( "Tag-creation requires a target named-reference and hash."); @@ -128,9 +131,9 @@ private Hash createReference(NamedRef reference, String hash) try { return getStore().create(reference, toHash(hash, false)); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException("Failure while searching for provided targeted hash.", e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } catch (ReferenceAlreadyExistsException e) { - throw new NessieConflictException(e.getMessage(), e); + throw new NessieReferenceAlreadyExistsException(e.getMessage(), e); } } @@ -201,8 +204,7 @@ public LogResponse getCommitLog(String namedRef, CommitLogParams params) } return ImmutableLogResponse.builder().addAllOperations(items).build(); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException( - String.format("Unable to find the requested ref [%s].", namedRef), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } } @@ -252,9 +254,9 @@ public void transplantCommitsIntoBranch( } getStore().transplant(BranchName.of(branchName), toHash(hash, true), transplants); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage(), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } catch (ReferenceConflictException e) { - throw new NessieConflictException(e.getMessage(), e); + throw new NessieReferenceConflictException(e.getMessage(), e); } } @@ -264,13 +266,13 @@ public void mergeRefIntoBranch(String branchName, String hash, Merge merge) try { getStore() .merge( - toHash(merge.getFromRefName(), merge.getFromHash(), true).get(), + toHash(merge.getFromRefName(), merge.getFromHash()), BranchName.of(branchName), toHash(hash, true)); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage(), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } catch (ReferenceConflictException e) { - throw new NessieConflictException(e.getMessage(), e); + throw new NessieReferenceConflictException(e.getMessage(), e); } } @@ -306,8 +308,7 @@ public EntriesResponse getEntries(String namedRef, EntriesParams params) } return EntriesResponse.builder().addAllEntries(entries).build(); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException( - String.format("Unable to find the reference [%s].", namedRef), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } } @@ -390,17 +391,16 @@ protected Hash doOps( Optional.ofNullable(hash).map(Hash::of), meta(getPrincipal(), commitMeta), operations); - } catch (IllegalArgumentException | ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage(), e); + } catch (ReferenceNotFoundException e) { + throw new NessieReferenceNotFoundException(e.getMessage(), e); } catch (ReferenceConflictException e) { - throw new NessieConflictException(e.getMessage(), e); + throw new NessieReferenceConflictException(e.getMessage(), e); } } - private static CommitMeta meta(Principal principal, CommitMeta commitMeta) - throws NessieConflictException { + private static CommitMeta meta(Principal principal, CommitMeta commitMeta) { if (commitMeta.getCommitter() != null) { - throw new NessieConflictException( + throw new IllegalArgumentException( "Cannot set the committer on the client side. It is set by the server."); } String committer = principal == null ? "" : principal.getName(); @@ -413,20 +413,20 @@ private static CommitMeta meta(Principal principal, CommitMeta commitMeta) .build(); } - private Optional toHash(String referenceName, String hashOnReference, boolean required) - throws NessieConflictException, ReferenceNotFoundException { + private Hash toHash(String referenceName, String hashOnReference) + throws ReferenceNotFoundException { if (hashOnReference == null) { WithHash hash = getStore().toRef(referenceName); - return Optional.of(hash.getHash()); + return hash.getHash(); } - return toHash(hashOnReference, required); + return toHash(hashOnReference, true) + .orElseThrow(() -> new IllegalStateException("Required hash is missing")); } - private static Optional toHash(String hash, boolean required) - throws NessieConflictException { + private static Optional toHash(String hash, boolean required) { if (hash == null || hash.isEmpty()) { if (required) { - throw new NessieConflictException("Must provide expected hash value for operation."); + throw new IllegalArgumentException("Must provide expected hash value for operation."); } return Optional.empty(); } @@ -438,9 +438,9 @@ protected void deleteReference(NamedRef ref, String hash) try { getStore().delete(ref, toHash(hash, true)); } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage(), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } catch (ReferenceConflictException e) { - throw new NessieConflictException(e.getMessage(), e); + throw new NessieReferenceConflictException(e.getMessage(), e); } } @@ -454,18 +454,14 @@ protected void assignReference(NamedRef ref, String oldHash, Reference assignTo) .assign( (NamedRef) resolvedRef, toHash(oldHash, true), - toHash(assignTo.getName(), assignTo.getHash(), true) - .orElseThrow( - () -> - new NessieConflictException( - "Must provide target hash value for operation."))); + toHash(assignTo.getName(), assignTo.getHash())); } else { throw new IllegalArgumentException("Can only assign branch and tag types."); } } catch (ReferenceNotFoundException e) { - throw new NessieNotFoundException(e.getMessage(), e); + throw new NessieReferenceNotFoundException(e.getMessage(), e); } catch (ReferenceConflictException e) { - throw new NessieConflictException(e.getMessage(), e); + throw new NessieReferenceConflictException(e.getMessage(), e); } } diff --git a/versioned/spi/src/main/java/org/projectnessie/versioned/ReferenceNotFoundException.java b/versioned/spi/src/main/java/org/projectnessie/versioned/ReferenceNotFoundException.java index 84875a74881..68e50531a86 100644 --- a/versioned/spi/src/main/java/org/projectnessie/versioned/ReferenceNotFoundException.java +++ b/versioned/spi/src/main/java/org/projectnessie/versioned/ReferenceNotFoundException.java @@ -20,7 +20,7 @@ import javax.annotation.Nonnull; -/** Exception thrown when an reference is not present in the store. */ +/** Exception thrown when a reference is not present in the store. */ public class ReferenceNotFoundException extends VersionStoreException { private static final long serialVersionUID = -4231207387427624751L;