Skip to content

Commit 5106d3d

Browse files
author
lijinglun
committed
feat: add ExceptionHandler
1 parent 4ff059a commit 5106d3d

5 files changed

Lines changed: 211 additions & 7 deletions

File tree

java/lance-namespace-adapter/src/main/java/com/lancedb/lance/namespace/adapter/ClientToServerResponse.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.lancedb.lance.namespace.server.springboot.model.DescribeTransactionResponse;
2222
import com.lancedb.lance.namespace.server.springboot.model.DropNamespaceResponse;
2323
import com.lancedb.lance.namespace.server.springboot.model.DropTableResponse;
24+
import com.lancedb.lance.namespace.server.springboot.model.ErrorResponse;
2425
import com.lancedb.lance.namespace.server.springboot.model.ListNamespacesResponse;
2526
import com.lancedb.lance.namespace.server.springboot.model.NamespaceExistsResponse;
2627
import com.lancedb.lance.namespace.server.springboot.model.RegisterTableResponse;
@@ -117,6 +118,17 @@ public static DeregisterTableResponse deregisterTable(
117118
return converted;
118119
}
119120

121+
public static ErrorResponse errorResponse(
122+
com.lancedb.lance.namespace.model.ErrorResponse errorResponse) {
123+
ErrorResponse converted = new ErrorResponse();
124+
converted.setType(errorResponse.getType());
125+
converted.setTitle(errorResponse.getTitle());
126+
converted.setDetail(errorResponse.getDetail());
127+
converted.setStatus(errorResponse.getStatus());
128+
converted.setInstance(errorResponse.getInstance());
129+
return converted;
130+
}
131+
120132
public static DescribeTransactionResponse describeTransaction(
121133
com.lancedb.lance.namespace.model.DescribeTransactionResponse response) {
122134
DescribeTransactionResponse converted = new DescribeTransactionResponse();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.lancedb.lance.namespace.adapter;
15+
16+
import com.lancedb.lance.namespace.LanceNamespaceException;
17+
import com.lancedb.lance.namespace.server.springboot.model.ErrorResponse;
18+
19+
import org.springframework.http.ResponseEntity;
20+
import org.springframework.web.bind.annotation.ControllerAdvice;
21+
import org.springframework.web.bind.annotation.ExceptionHandler;
22+
23+
@ControllerAdvice
24+
public class GlobalExceptionHandler {
25+
@ExceptionHandler(LanceNamespaceException.class)
26+
public ResponseEntity<ErrorResponse> handleLanceNamespaceException(LanceNamespaceException ex) {
27+
com.lancedb.lance.namespace.model.ErrorResponse errorResponse = ex.getErrorResponse();
28+
return ResponseEntity.status(errorResponse.getStatus())
29+
.body(ClientToServerResponse.errorResponse(errorResponse));
30+
}
31+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.lancedb.lance.namespace.adapter;
15+
16+
import com.lancedb.lance.namespace.LanceNamespaceException;
17+
18+
import org.springframework.web.bind.annotation.GetMapping;
19+
import org.springframework.web.bind.annotation.RequestParam;
20+
import org.springframework.web.bind.annotation.RestController;
21+
22+
@RestController
23+
public class ExceptionController {
24+
@GetMapping("/testNotFound")
25+
public String testNotFound(@RequestParam(required = false) String param) {
26+
throw LanceNamespaceException.notFound(String.format("%s not found", param));
27+
}
28+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.lancedb.lance.namespace.adapter;
15+
16+
import com.lancedb.lance.namespace.LanceNamespace;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
21+
import org.springframework.boot.test.mock.mockito.MockBean;
22+
import org.springframework.test.web.servlet.MockMvc;
23+
24+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
25+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
26+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
27+
28+
@WebMvcTest(controllers = ExceptionController.class)
29+
public class TestGlobalExceptionHandler {
30+
@MockBean private LanceNamespace lanceNamespace;
31+
32+
@Autowired private MockMvc mockMvc;
33+
34+
@Test
35+
public void testNotFound() throws Exception {
36+
mockMvc
37+
.perform(get("/testNotFound").queryParam("param", "foo"))
38+
.andExpect(status().is(404))
39+
.andExpect(jsonPath("$.type").value("/errors/not-found-error"))
40+
.andExpect(jsonPath("$.title").value("Not found Error"))
41+
.andExpect(jsonPath("$.instance").value("/v1/namespaces"))
42+
.andExpect(jsonPath("$.detail").value("foo not found"));
43+
}
44+
}

java/lance-namespace-core/src/main/java/com/lancedb/lance/namespace/LanceNamespaceException.java

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,113 @@
1414
package com.lancedb.lance.namespace;
1515

1616
import com.lancedb.lance.namespace.client.apache.ApiException;
17+
import com.lancedb.lance.namespace.model.ErrorResponse;
1718

1819
public class LanceNamespaceException extends RuntimeException {
1920

20-
private final int code;
21-
private final String responseBody;
21+
private final ErrorResponse errorResponse;
2222

2323
public LanceNamespaceException(ApiException e) {
2424
// TODO: properly parse into ErrorResponse model
2525
super(e.getResponseBody(), e);
26-
this.code = e.getCode();
27-
this.responseBody = e.getResponseBody();
26+
27+
this.errorResponse = new ErrorResponse();
28+
errorResponse.setStatus(e.getCode());
29+
errorResponse.type("/errors/api-exception");
30+
errorResponse.setTitle("Api Exception");
31+
errorResponse.setInstance("/v1/namespaces");
32+
errorResponse.setDetail(e.getResponseBody());
33+
}
34+
35+
public LanceNamespaceException(ErrorResponse errorResponse) {
36+
this.errorResponse = errorResponse;
2837
}
2938

3039
public int getCode() {
31-
return code;
40+
return errorResponse.getStatus();
41+
}
42+
43+
public ErrorResponse getErrorResponse() {
44+
return errorResponse;
45+
}
46+
47+
public static LanceNamespaceException badRequest(String detail) {
48+
ErrorResponse errorResponse = new ErrorResponse();
49+
errorResponse.type("/errors/bad-request");
50+
errorResponse.setTitle("Malformed request");
51+
errorResponse.setStatus(400);
52+
errorResponse.setDetail(detail);
53+
errorResponse.setInstance("/v1/namespaces");
54+
return new LanceNamespaceException(errorResponse);
55+
}
56+
57+
public static LanceNamespaceException unauthorized(String detail) {
58+
ErrorResponse errorResponse = new ErrorResponse();
59+
errorResponse.type("/errors/unauthorized-request");
60+
errorResponse.setTitle("No valid authentication credentials for the operation");
61+
errorResponse.setStatus(401);
62+
errorResponse.setDetail(detail);
63+
errorResponse.setInstance("/v1/namespaces");
64+
return new LanceNamespaceException(errorResponse);
65+
}
66+
67+
public static LanceNamespaceException forbidden(String detail) {
68+
ErrorResponse errorResponse = new ErrorResponse();
69+
errorResponse.type("/errors/forbidden-request");
70+
errorResponse.setTitle("Not authorized to make this request");
71+
errorResponse.setStatus(403);
72+
errorResponse.setDetail(detail);
73+
errorResponse.setInstance("/v1/namespaces");
74+
return new LanceNamespaceException(errorResponse);
75+
}
76+
77+
public static LanceNamespaceException notFound(String detail) {
78+
ErrorResponse errorResponse = new ErrorResponse();
79+
errorResponse.type("/errors/not-found-error");
80+
errorResponse.setTitle("Not found Error");
81+
errorResponse.setStatus(404);
82+
errorResponse.setDetail(detail);
83+
errorResponse.setInstance("/v1/namespaces");
84+
return new LanceNamespaceException(errorResponse);
85+
}
86+
87+
public static LanceNamespaceException unsupportedOperation(String detail) {
88+
ErrorResponse errorResponse = new ErrorResponse();
89+
errorResponse.type("/errors/unsupported-operation");
90+
errorResponse.setTitle("The server does not support this operation");
91+
errorResponse.setStatus(406);
92+
errorResponse.setDetail(detail);
93+
errorResponse.setInstance("/v1/namespaces");
94+
return new LanceNamespaceException(errorResponse);
95+
}
96+
97+
public static LanceNamespaceException conflict(String ns, String detail) {
98+
ErrorResponse errorResponse = new ErrorResponse();
99+
errorResponse.type("/errors/conflict");
100+
errorResponse.setTitle("The namespace has been concurrently modified");
101+
errorResponse.setStatus(409);
102+
errorResponse.setDetail(detail);
103+
errorResponse.setInstance(String.format("/v1/namespaces/%s", ns));
104+
return new LanceNamespaceException(errorResponse);
105+
}
106+
107+
public static LanceNamespaceException serviceUnavailable(String detail) {
108+
ErrorResponse errorResponse = new ErrorResponse();
109+
errorResponse.type("/errors/service-unavailable");
110+
errorResponse.setTitle("Slow down");
111+
errorResponse.setStatus(504);
112+
errorResponse.setDetail(detail);
113+
errorResponse.setInstance("/v1/namespaces");
114+
return new LanceNamespaceException(errorResponse);
32115
}
33116

34-
public String getResponseBody() {
35-
return responseBody;
117+
public static LanceNamespaceException serverError(String detail) {
118+
ErrorResponse errorResponse = new ErrorResponse();
119+
errorResponse.type("/errors/server-error");
120+
errorResponse.setTitle("Internal Server Error");
121+
errorResponse.setStatus(500);
122+
errorResponse.setDetail(detail);
123+
errorResponse.setInstance("/v1/namespaces");
124+
return new LanceNamespaceException(errorResponse);
36125
}
37126
}

0 commit comments

Comments
 (0)