Skip to content

Commit 17084bd

Browse files
HamzaJugonHamza JugonJayGreeeen
authored
Fix bad request handling (#179)
* update controller to use ParameterObject As used in the metadata endpoint we want Swagger to be able to interpret and automatically generate pagination parameters like page, size, and sort. * Add error handling for incorrect sort value * Add test for error handling * Fix workflow to only release main branch to maven central * Update CHANGELOG.md * Add unit test for BeeekeeperExceptionHandler Testing the request response and the errorResponses * Implement builder pattern for ErrorResponse --------- Co-authored-by: Hamza Jugon <[email protected]> Co-authored-by: Jay Green-Stevens <[email protected]>
1 parent eabe909 commit 17084bd

File tree

8 files changed

+181
-10
lines changed

8 files changed

+181
-10
lines changed

.github/workflows/release.yml

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
name: Release to Maven Central
2+
23
on:
34
workflow_dispatch:
4-
inputs:
5-
branch:
6-
description: "The branch to use to release from."
7-
required: true
8-
default: "main"
5+
96
jobs:
107
release:
118
name: Release to Maven Central
@@ -16,8 +13,7 @@ jobs:
1613
uses: actions/checkout@v2
1714
with:
1815
fetch-depth: 0
19-
ref: ${{ github.event.inputs.branch }}
20-
# We need a personal access token to be able to push to a protected branch
16+
ref: main # Hardcoded to main branch
2117
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
2218

2319
- name: Set up JDK

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [3.5.7] - 2024-10-25
8+
### Changed
9+
- Added error handling for bad requests with incorrect sort parameters.
10+
- Added automatic pagination handling in the `/unreferenced-paths` endpoint for improved Swagger documentation.
11+
- Updated the Maven Central release workflow to run exclusively from the main branch.
12+
713
## [3.5.6] - 2024-06-14
814
### Fixed
915
- Added aws-sts-jdk dependency in `beekeeper-metadata-cleanup`, `beekeeper-path-cleanup`, `beekeeper-scheduler-apiary` to solve IRSA unable to assume role issue.

beekeeper-api/src/main/java/com/expediagroup/beekeeper/api/BeekeeperApiApplication.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
@ComponentScan(basePackages = {
3030
"com.expediagroup.beekeeper.api.conf",
3131
"com.expediagroup.beekeeper.api.controller",
32-
"com.expediagroup.beekeeper.api.service" })
32+
"com.expediagroup.beekeeper.api.service",
33+
"com.expediagroup.beekeeper.api.error" })
3334
public class BeekeeperApiApplication {
3435
public static void main(String[] args) {
3536
new SpringApplicationBuilder(BeekeeperApiApplication.class)

beekeeper-api/src/main/java/com/expediagroup/beekeeper/api/controller/BeekeeperController.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public ResponseEntity<Page<HousekeepingPathResponse>> getAllPaths(
106106
@Spec(path = "cleanupTimestamp", params = "deleted_after", spec = GreaterThan.class),
107107
@Spec(path = "creationTimestamp", params = "registered_before", spec = LessThan.class),
108108
@Spec(path = "creationTimestamp", params = "registered_after", spec = GreaterThan.class) }) Specification<HousekeepingPath> spec,
109-
Pageable pageable) {
109+
@ParameterObject Pageable pageable) {
110110
return ResponseEntity.ok(housekeepingEntityService.getAllPaths(spec, pageable));
111111
}
112112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright (C) 2019-2024 Expedia, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.beekeeper.api.error;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
21+
import org.springframework.data.mapping.PropertyReferenceException;
22+
import org.springframework.http.HttpStatus;
23+
import org.springframework.http.ResponseEntity;
24+
import org.springframework.web.bind.annotation.ExceptionHandler;
25+
import org.springframework.web.bind.annotation.RestControllerAdvice;
26+
27+
import java.time.LocalDateTime;
28+
29+
@RestControllerAdvice
30+
public class BeekeeperExceptionHandler {
31+
32+
/**
33+
* Handles invalid sort parameters.
34+
*
35+
* @param exception the exception is thrown when an invalid property is referenced
36+
* @param request the HTTP request
37+
* @return a ResponseEntity containing the error response
38+
*/
39+
40+
@ExceptionHandler(PropertyReferenceException.class)
41+
public ResponseEntity<ErrorResponse> handlePropertyReferenceException(
42+
PropertyReferenceException exception, HttpServletRequest request) {
43+
44+
ErrorResponse errorResponse = ErrorResponse.builder()
45+
.timestamp(LocalDateTime.now().toString())
46+
.status(HttpStatus.BAD_REQUEST.value())
47+
.error(HttpStatus.BAD_REQUEST.getReasonPhrase())
48+
.message(exception.getMessage())
49+
.path(request.getRequestURI())
50+
.build();
51+
52+
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright (C) 2019-2024 Expedia, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.beekeeper.api.error;
18+
19+
import lombok.Builder;
20+
import lombok.Getter;
21+
22+
@Getter
23+
@Builder
24+
public class ErrorResponse {
25+
private final String timestamp;
26+
private final int status;
27+
private final String error;
28+
private final String message;
29+
private final String path;
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Copyright (C) 2019-2023 Expedia, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.expediagroup.beekeeper.api.error;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.http.HttpStatus;
20+
import org.springframework.http.ResponseEntity;
21+
import org.springframework.mock.web.MockHttpServletRequest;
22+
import org.springframework.data.mapping.PropertyReferenceException;
23+
import org.springframework.data.mapping.PropertyPath;
24+
import org.springframework.data.util.ClassTypeInformation;
25+
import org.springframework.data.util.TypeInformation;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
import com.expediagroup.beekeeper.api.error.BeekeeperExceptionHandler;
30+
import com.expediagroup.beekeeper.api.error.ErrorResponse;
31+
import com.expediagroup.beekeeper.core.model.HousekeepingPath;
32+
33+
import java.util.Collections;
34+
import java.util.List;
35+
36+
public class BeekeeperExceptionHandlerTest {
37+
38+
private final BeekeeperExceptionHandler exceptionHandler = new BeekeeperExceptionHandler();
39+
40+
@Test
41+
public void handlePropertyReferenceException_ShouldReturnBadRequest() {
42+
String propertyName = "string";
43+
TypeInformation<?> typeInformation = ClassTypeInformation.from(HousekeepingPath.class);
44+
List<PropertyPath> baseProperty = Collections.emptyList();
45+
PropertyReferenceException exception = new PropertyReferenceException(propertyName, typeInformation, baseProperty);
46+
47+
MockHttpServletRequest request = new MockHttpServletRequest();
48+
request.setRequestURI("/api/v1/database/testDb/table/testTable/unreferenced-paths");
49+
50+
ResponseEntity<ErrorResponse> response = exceptionHandler.handlePropertyReferenceException(exception, request);
51+
52+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
53+
54+
ErrorResponse errorResponse = response.getBody();
55+
assertThat(errorResponse).isNotNull();
56+
assertThat(errorResponse.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
57+
assertThat(errorResponse.getError()).isEqualTo("Bad Request");
58+
assertThat(errorResponse.getMessage()).isEqualTo(exception.getMessage());
59+
assertThat(errorResponse.getPath()).isEqualTo("/api/v1/database/testDb/table/testTable/unreferenced-paths");
60+
assertThat(errorResponse.getTimestamp()).isNotNull();
61+
}
62+
}

beekeeper-integration-tests/src/test/java/com/expediagroup/beekeeper/integration/api/BeekeeperApiIntegrationTest.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.boot.SpringApplication;
4444
import org.springframework.context.ConfigurableApplicationContext;
4545
import org.springframework.data.domain.Page;
46+
import org.springframework.http.HttpStatus;
4647
import org.springframework.util.SocketUtils;
4748

4849
import com.fasterxml.jackson.core.type.TypeReference;
@@ -51,6 +52,7 @@
5152
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
5253

5354
import com.expediagroup.beekeeper.api.BeekeeperApiApplication;
55+
import com.expediagroup.beekeeper.api.error.ErrorResponse;
5456
import com.expediagroup.beekeeper.api.response.HousekeepingMetadataResponse;
5557
import com.expediagroup.beekeeper.api.response.HousekeepingPathResponse;
5658
import com.expediagroup.beekeeper.core.model.HousekeepingMetadata;
@@ -111,7 +113,7 @@ public final void afterEach() {
111113

112114
@Test
113115
public void testGetMetadataWhenTableNotFoundReturnsEmptyList()
114-
throws SQLException, InterruptedException, IOException {
116+
throws SQLException, InterruptedException, IOException {
115117

116118
for (HousekeepingMetadata testMetadata : Arrays.asList(testMetadataB, testMetadataC)) {
117119
insertExpiredMetadata(testMetadata);
@@ -302,4 +304,23 @@ private HousekeepingPath createHousekeepingPath(
302304
.build();
303305
}
304306

307+
@Test
308+
public void testInvalidSortParameter() throws SQLException, IOException, InterruptedException {
309+
insertExpiredMetadata(testMetadataA);
310+
311+
String filters = "?sort=nonExistentProperty,asc";
312+
HttpResponse<String> response = testClient.getMetadata(someDatabase, someTable, filters);
313+
314+
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
315+
316+
String body = response.body();
317+
ErrorResponse errorResponse = mapper.readValue(body, ErrorResponse.class);
318+
319+
assertThat(errorResponse.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
320+
assertThat(errorResponse.getMessage()).isEqualTo("No property 'nonExistentProperty' found for type 'HousekeepingMetadata'");
321+
assertThat(errorResponse.getError()).isEqualTo("Bad Request");
322+
assertThat(errorResponse.getPath()).contains("/api/v1/database/some_database/table/some_table/metadata");
323+
assertThat(errorResponse.getTimestamp()).isNotNull();
324+
}
325+
305326
}

0 commit comments

Comments
 (0)