Skip to content

Commit ba5e032

Browse files
author
kanjars
committed
- Arrange application.yml
- Use ProblemDetails - Added Example Separate API and API Doc
1 parent e6f7623 commit ba5e032

File tree

7 files changed

+240
-124
lines changed

7 files changed

+240
-124
lines changed

ss-api/src/main/java/ss/mod/demo/api/constant/ContField.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ public class ContField {
1515
public static final String CLAIMS = "claims";
1616
public static final String CLAIM_RESOURCE_ACCESS = "resource_access";
1717
public static final String CLAIM_ROLES = "roles";
18+
public static final String TIMESTAMP = "timestamp";
19+
public static final String ERRORS = "errors";
20+
public static final String PROPERTY = "property";
21+
public static final String ARGUMENT = "argument";
1822
}

ss-api/src/main/java/ss/mod/demo/api/exception/ErrorObject.java

Lines changed: 0 additions & 20 deletions
This file was deleted.

ss-api/src/main/java/ss/mod/demo/api/model/response/BodyResponse.java renamed to ss-api/src/main/java/ss/mod/demo/api/model/response/ResponseBody.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121
@AllArgsConstructor
2222
@Getter
2323
@Setter
24-
public class BodyResponse<T> implements Serializable {
24+
public class ResponseBody<T> implements Serializable {
2525
@Serial
2626
private static final long serialVersionUID = 7137695452659404087L;
2727

2828
private String message;
2929
private T body;
3030

31-
public static <T> BodyResponse<T> of(String message, T body) {
32-
return new BodyResponse<>(message, body);
31+
public static <T> ResponseBody<T> of(String message, T body) {
32+
return new ResponseBody<>(message, body);
3333
}
3434
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package ss.mod.demo.web;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.media.ExampleObject;
6+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
7+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
8+
9+
import java.lang.annotation.ElementType;
10+
import java.lang.annotation.Retention;
11+
import java.lang.annotation.RetentionPolicy;
12+
import java.lang.annotation.Target;
13+
14+
public class UserManagementResourceApiDocs {
15+
@Target(ElementType.METHOD)
16+
@Retention(RetentionPolicy.RUNTIME)
17+
@Operation(description = "Create User based on Provided Data", summary = "Create User")
18+
@ApiResponses(value = {
19+
@ApiResponse(responseCode = "400", description = "Any kind of Input validation", content = {
20+
@Content(examples = {
21+
@ExampleObject(name = "Any kind of Input validation", value = """
22+
{
23+
"code": "400-1",
24+
"message": "Invalid request content.",
25+
"status": 400,
26+
"errorParams": {
27+
"age": [
28+
"You are underage"
29+
]
30+
}
31+
}
32+
""")
33+
})}),
34+
@ApiResponse(responseCode = "200", description = "User Detail", content = {
35+
@Content(examples = {
36+
@ExampleObject(name = "User Detail", value = """
37+
{
38+
"message": "User Created successfully",
39+
"body": {
40+
"id": "248b97f0-6f3a-4f56-af04-c0da600125b1",
41+
"name": "Name Surname",
42+
"age": 19,
43+
"city": "Some City",
44+
"country": "Some Country"
45+
}
46+
}
47+
""")
48+
})
49+
})})
50+
public @interface CreateUserApiDocs {
51+
}
52+
53+
@Target({ElementType.TYPE, ElementType.METHOD})
54+
@Retention(RetentionPolicy.RUNTIME)
55+
@ApiResponses(value = {
56+
@ApiResponse(responseCode = "500", description = "Any other internal server error", content = {
57+
@Content(examples = {
58+
@ExampleObject(name = "Internal server error", value = """
59+
{
60+
"code": "500-1",
61+
"message": "Something went wrong",
62+
"status": 500
63+
}
64+
""")
65+
})})})
66+
public @interface Common500 {
67+
}
68+
}

ss-web/src/main/java/ss/mod/demo/web/handler/GlobalExceptionHandler.java

Lines changed: 109 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,22 @@
77
import jakarta.validation.ConstraintViolationException;
88
import lombok.AllArgsConstructor;
99
import lombok.extern.slf4j.Slf4j;
10+
import org.apache.commons.lang3.exception.ExceptionUtils;
11+
import org.springframework.data.mapping.PropertyReferenceException;
1012
import org.springframework.http.HttpStatus;
11-
import org.springframework.http.ResponseEntity;
13+
import org.springframework.http.ProblemDetail;
1214
import org.springframework.validation.FieldError;
1315
import org.springframework.web.bind.MethodArgumentNotValidException;
1416
import org.springframework.web.bind.annotation.ControllerAdvice;
1517
import org.springframework.web.bind.annotation.ExceptionHandler;
16-
import ss.mod.demo.api.constant.ContMessage;
18+
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
19+
import ss.mod.demo.api.constant.ContField;
1720
import ss.mod.demo.api.exception.BadDataException;
18-
import ss.mod.demo.api.exception.ErrorObject;
1921
import ss.mod.demo.service.entity.BaseService;
2022

23+
import java.util.HashMap;
2124
import java.util.List;
2225
import java.util.Map;
23-
import java.util.stream.Collectors;
2426

2527
/**
2628
* Global exception handler
@@ -32,57 +34,115 @@
3234
@Slf4j
3335
@AllArgsConstructor
3436
public class GlobalExceptionHandler extends BaseService {
35-
@ExceptionHandler({MethodArgumentNotValidException.class})
36-
public ResponseEntity<ErrorObject> validatorExceptionHandle(MethodArgumentNotValidException ex) {
37-
Map<String, List<String>> errors = ex.getBindingResult()
38-
.getFieldErrors()
39-
.stream()
40-
.collect(Collectors.groupingBy(FieldError::getField, Collectors.mapping(FieldError::getDefaultMessage, Collectors.toList())));
41-
return new ResponseEntity<>(
42-
ErrorObject.builder()
43-
.message(resolveMessage(ex.getBody().getDetail()))
44-
.errorParams(errors)
45-
.code("400-1")
46-
.status(HttpStatus.BAD_REQUEST.value())
47-
.build(),
48-
HttpStatus.BAD_REQUEST);
37+
38+
/**
39+
* Handle validation problem detail.
40+
*
41+
* @param e the e
42+
* @return the problem detail
43+
*/
44+
@ExceptionHandler(MethodArgumentNotValidException.class)
45+
ProblemDetail handleValidation(MethodArgumentNotValidException e) {
46+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ExceptionUtils.getMessage(e));
47+
problemDetail.setTitle("Invalid data provided");
48+
problemDetail.setProperty(ContField.TIMESTAMP, System.currentTimeMillis());
49+
problemDetail.setProperty(ContField.ERRORS, handleValidationError(e.getFieldErrors()));
50+
return problemDetail;
51+
}
52+
53+
/**
54+
* Handle validation problem detail.
55+
*
56+
* @param exception the exception
57+
* @return the problem detail
58+
*/
59+
@ExceptionHandler(ConstraintViolationException.class)
60+
ProblemDetail handleValidation(ConstraintViolationException exception) {
61+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ExceptionUtils.getMessage(exception));
62+
problemDetail.setTitle("Invalid data provided");
63+
problemDetail.setProperty(ContField.TIMESTAMP, System.currentTimeMillis());
64+
problemDetail.setProperty(ContField.ERRORS, exception.getConstraintViolations().stream().map(ConstraintViolation::getMessage).toList());
65+
return problemDetail;
66+
}
67+
68+
/**
69+
* Handle bad data exception problem detail.
70+
*
71+
* @param e the e
72+
* @return the problem detail
73+
*/
74+
@ExceptionHandler(BadDataException.class)
75+
ProblemDetail handleBadDataException(BadDataException e) {
76+
String errorMsg = ExceptionUtils.getMessage(e);
77+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, errorMsg);
78+
problemDetail.setTitle(errorMsg);
79+
problemDetail.setProperty(ContField.TIMESTAMP, System.currentTimeMillis());
80+
return problemDetail;
81+
}
82+
83+
/**
84+
* Handle property reference exception problem detail.
85+
*
86+
* @param exception the exception
87+
* @return the problem detail
88+
*/
89+
@ExceptionHandler(PropertyReferenceException.class)
90+
ProblemDetail handlePropertyReferenceException(PropertyReferenceException exception) {
91+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ExceptionUtils.getMessage(exception));
92+
problemDetail.setTitle(ExceptionUtils.getMessage(exception));
93+
problemDetail.setProperty(ContField.TIMESTAMP, System.currentTimeMillis());
94+
problemDetail.setProperty(ContField.PROPERTY, exception.getPropertyName());
95+
return problemDetail;
4996
}
5097

51-
@ExceptionHandler({ConstraintViolationException.class})
52-
public ResponseEntity<ErrorObject> validatorExceptionHandle(ConstraintViolationException ex) {
53-
Map<String, List<String>> errors = ex.getConstraintViolations()
54-
.stream()
55-
.collect(Collectors.groupingBy(c -> c.getPropertyPath().toString(), Collectors.mapping(ConstraintViolation::getMessage, Collectors.toList())));
98+
/**
99+
* Handle method argument type mismatch exception problem detail.
100+
*
101+
* @param exception the exception
102+
* @return the problem detail
103+
*/
104+
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
105+
ProblemDetail handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) {
106+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ExceptionUtils.getMessage(exception));
107+
problemDetail.setTitle(ExceptionUtils.getMessage(exception));
108+
problemDetail.setProperty(ContField.TIMESTAMP, System.currentTimeMillis());
109+
problemDetail.setProperty(ContField.ARGUMENT, exception.getName());
110+
return problemDetail;
111+
}
56112

57-
return new ResponseEntity<>(
58-
ErrorObject.builder()
59-
.message(ex.getMessage())
60-
.errorParams(errors)
61-
.code("400-2")
62-
.status(HttpStatus.BAD_REQUEST.value())
63-
.build(),
64-
HttpStatus.BAD_REQUEST);
113+
/**
114+
* Handle illegal argument exception problem detail.
115+
*
116+
* @param exception the exception
117+
* @return the problem detail
118+
*/
119+
@ExceptionHandler(IllegalArgumentException.class)
120+
ProblemDetail handleIllegalArgumentException(IllegalArgumentException exception) {
121+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ExceptionUtils.getMessage(exception));
122+
problemDetail.setTitle(ExceptionUtils.getMessage(exception));
123+
problemDetail.setProperty(ContField.TIMESTAMP, System.currentTimeMillis());
124+
return problemDetail;
65125
}
66126

67-
@ExceptionHandler({BadDataException.class})
68-
public ResponseEntity<ErrorObject> badDataExceptionHandle(BadDataException ex) {
69-
return new ResponseEntity<>(
70-
ErrorObject.builder()
71-
.message(resolveMessage(ex.getMessage()))
72-
.code(ex.getCode() != null ? ex.getCode() : "400-3")
73-
.status(HttpStatus.BAD_REQUEST.value())
74-
.build(),
75-
HttpStatus.BAD_REQUEST);
127+
/**
128+
* Handle exception problem detail.
129+
*
130+
* @param e the e
131+
* @return the problem detail
132+
*/
133+
@ExceptionHandler(Exception.class)
134+
ProblemDetail handleException(Exception e) {
135+
log.error("Error ", e);
136+
ProblemDetail problemDetail;
137+
problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ExceptionUtils.getMessage(e));
138+
problemDetail.setTitle(ExceptionUtils.getMessage(e));
139+
problemDetail.setProperty(ContField.TIMESTAMP, System.currentTimeMillis());
140+
return problemDetail;
76141
}
77142

78-
@ExceptionHandler({Exception.class})
79-
public ResponseEntity<ErrorObject> globalHandler(Exception ex) {
80-
log.error(ex.getMessage(), ex);
81-
return new ResponseEntity<>(
82-
ErrorObject.builder()
83-
.message(resolveMessage(ContMessage.SOMETHING_WENT_WRONG))
84-
.code("500-1")
85-
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
86-
.build(), HttpStatus.INTERNAL_SERVER_ERROR);
143+
private Map<String, String> handleValidationError(List<FieldError> fieldErrors) {
144+
Map<String, String> messages = new HashMap<>();
145+
fieldErrors.forEach(fieldError -> messages.put(fieldError.getField(), fieldError.getDefaultMessage()));
146+
return messages;
87147
}
88148
}

ss-web/src/main/java/ss/mod/demo/web/resource/UserManagementResource.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
import ss.mod.demo.api.constant.ContURI;
1515
import ss.mod.demo.api.model.request.FilterWrapper;
1616
import ss.mod.demo.api.model.request.UserRequest;
17-
import ss.mod.demo.api.model.response.BodyResponse;
1817
import ss.mod.demo.api.model.response.PageResponse;
18+
import ss.mod.demo.api.model.response.ResponseBody;
1919
import ss.mod.demo.api.model.response.UserResponse;
2020
import ss.mod.demo.service.UserManagementService;
21+
import ss.mod.demo.web.UserManagementResourceApiDocs.Common500;
22+
import ss.mod.demo.web.UserManagementResourceApiDocs.CreateUserApiDocs;
2123

2224
/**
2325
* Provide endpoint related to User management
@@ -28,14 +30,15 @@
2830
@AllArgsConstructor
2931
@RestController
3032
@Slf4j
33+
@Common500
3134
public class UserManagementResource extends BaseResource {
3235
private final UserManagementService userManagementService;
3336

34-
@Operation(summary = "Create User")
37+
@CreateUserApiDocs
3538
@PostMapping(value = ContURI.USER, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
36-
public BodyResponse<UserResponse> createUser(@Valid @RequestBody UserRequest userRequest) {
39+
public ResponseBody<UserResponse> createUser(@Valid @RequestBody UserRequest userRequest) {
3740
UserResponse user = userManagementService.createUser(userRequest);
38-
return BodyResponse.of(resolveMessage(ContMessage.USER_CREATED), user);
41+
return ResponseBody.of(resolveMessage(ContMessage.USER_CREATED), user);
3942
}
4043

4144
@Operation(summary = "Get User")

0 commit comments

Comments
 (0)