-
Notifications
You must be signed in to change notification settings - Fork 31
fix: align API status codes and swagger #132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package com.iemr.ecd.dto; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import com.fasterxml.jackson.annotation.JsonFormat; | ||
| import lombok.Builder; | ||
| import lombok.Data; | ||
|
|
||
| @Data | ||
| @Builder | ||
| public class ErrorResponse { | ||
|
|
||
| @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") | ||
| private LocalDateTime timestamp; | ||
| private int status; | ||
| private String error; | ||
| private String message; | ||
| private String path; | ||
|
|
||
| public ErrorResponse(LocalDateTime timestamp, int status, String error, String message, String path) { | ||
| this.timestamp = timestamp; | ||
| this.status = status; | ||
| this.error = error; | ||
| this.message = message; | ||
| this.path = path; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,34 +21,79 @@ | |
| */ | ||
| package com.iemr.ecd.utils.advice.exception_handler; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
| import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; | ||
|
|
||
| @RestControllerAdvice | ||
| public class EcdGlobalExceptionHandler extends ResponseEntityExceptionHandler { | ||
| import com.iemr.ecd.dto.ErrorResponse; | ||
|
|
||
| Logger logger = LoggerFactory.getLogger(this.getClass().getName()); | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
|
|
||
| @ExceptionHandler | ||
| public CustomExceptionResponse handleInvalidRequestParameterException(InvalidRequestException e) { | ||
| logger.error("invalid request exception : " + e); | ||
| CustomExceptionResponse customExceptionResponse = new CustomExceptionResponse(); | ||
| customExceptionResponse.setError(e); | ||
| @RestControllerAdvice | ||
| public class EcdGlobalExceptionHandler { | ||
|
|
||
| return customExceptionResponse; | ||
| private static final Logger logger = LoggerFactory.getLogger(EcdGlobalExceptionHandler.class); | ||
|
|
||
| @ExceptionHandler(InvalidRequestException.class) | ||
| public ResponseEntity<ErrorResponse> handleInvalidRequestException(InvalidRequestException ex, HttpServletRequest request) { | ||
| logger.error("Invalid request: {}", ex.getMessage()); | ||
| ErrorResponse error = ErrorResponse.builder() | ||
| .timestamp(LocalDateTime.now()) | ||
| .status(HttpStatus.BAD_REQUEST.value()) | ||
| .error(HttpStatus.BAD_REQUEST.getReasonPhrase()) | ||
| .message(ex.getMessage()) | ||
| .path(request.getRequestURI()) | ||
| .build(); | ||
| return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); | ||
| } | ||
|
|
||
| @ExceptionHandler | ||
| public CustomExceptionResponse handleGeneralException(ECDException e) { | ||
| logger.error("ECD exception : " + e); | ||
| CustomExceptionResponse customExceptionResponse = new CustomExceptionResponse(); | ||
| customExceptionResponse.setError(e); | ||
| @ExceptionHandler(ECDException.class) | ||
| public ResponseEntity<ErrorResponse> handleECDException(ECDException ex, HttpServletRequest request) { | ||
| logger.error("ECD error: {}", ex.getMessage()); | ||
| ErrorResponse error = ErrorResponse.builder() | ||
| .timestamp(LocalDateTime.now()) | ||
| .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) | ||
| .error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()) | ||
| .message(ex.getMessage()) | ||
| .path(request.getRequestURI()) | ||
| .build(); | ||
| return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); | ||
| } | ||
|
|
||
| return customExceptionResponse; | ||
| @ExceptionHandler(MethodArgumentNotValidException.class) | ||
| public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex, HttpServletRequest request) { | ||
| String errorMessage = ex.getBindingResult().getFieldErrors().stream() | ||
| .map(error -> error.getField() + ": " + error.getDefaultMessage()) | ||
| .collect(Collectors.joining(", ")); | ||
|
|
||
| logger.error("Validation error: {}", errorMessage); | ||
| ErrorResponse error = ErrorResponse.builder() | ||
| .timestamp(LocalDateTime.now()) | ||
| .status(HttpStatus.BAD_REQUEST.value()) | ||
| .error(HttpStatus.BAD_REQUEST.getReasonPhrase()) | ||
| .message(errorMessage) | ||
| .path(request.getRequestURI()) | ||
| .build(); | ||
| return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); | ||
| } | ||
|
|
||
| @ExceptionHandler(Exception.class) | ||
| public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex, HttpServletRequest request) { | ||
| logger.error("Unexpected error: ", ex); | ||
| ErrorResponse error = ErrorResponse.builder() | ||
| .timestamp(LocalDateTime.now()) | ||
| .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) | ||
| .error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()) | ||
| .message("An unexpected error occurred. Please contact administrator.") | ||
| .path(request.getRequestURI()) | ||
| .build(); | ||
| return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); | ||
| } | ||
|
Comment on lines
+87
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
# Verify whether explicit handlers exist for common client-input exceptions
# Expected: no matches currently, indicating they fall through to generic Exception->500.
rg -nC2 --type=java '(`@ExceptionHandler`\()|(HttpMessageNotReadableException|MethodArgumentTypeMismatchException|MissingServletRequestParameterException|ConstraintViolationException)' \
src/main/java/com/iemr/ecd/utils/advice/exception_handler/EcdGlobalExceptionHandler.javaRepository: PSMRI/ECD-API Length of output: 1103 π Script executed: cat -n src/main/java/com/iemr/ecd/utils/advice/exception_handler/EcdGlobalExceptionHandler.javaRepository: PSMRI/ECD-API Length of output: 4781 Add explicit handlers for client-input exceptions before generic 500 fallback. Malformed JSON, type mismatches, missing parameters, and constraint violations currently fall through the generic Add handlers for π€ Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§© Analysis chain
π Web query:
In OpenAPI 3.x, if an operation defines two responses with the same HTTP status code (e.g., two "500" entries), how is that represented in the final schema and UI?π‘ Result:
In OpenAPI 3.x, the operationβs responses are a map keyed by HTTP status code (plus ranges/default). That means you cannot represent βtwo separate 500 response objectsβ under the same status code key; only one
responses['500']entry can exist in the resulting parsed schema/JSON/YAML model because each status code key is unique in the map [1][2]. So if you try to write tworesponses: { '500': ... , '500': ... }in YAML/JSON, the spec structure can only keep one of them (the later one overwrites the earlier one in typical parsers), and there is no native way in OAS3 to merge them as two distinct response definitions under the same HTTP status code. How to model βmultiple shapes/examples for the same status codeβ instead 1) Put the variability inside the singleresponses['500']object: - Use a single response with onecontent[<media-type>].schemathat can represent alternatives, e.g.oneOf(oranyOf/ discriminators) [3][4]. - Optionally provide multipleexampleswithin that one response/media type (supported by Swagger UI for βmultiple examplesβ in the cited discussions) [5]. 2) In the UI (e.g., Swagger UI): - The UI will show one response block for status 500, because there is only one response entry keyed by500in the parsed spec [1][2]. - If you useoneOf/anyOfat the response schema level, many UIs will typically render/select one alternative schema (often the first item) rather than two distinct 500 responses; Swagger UIβs behavior is discussed in its issue/PR history foroneOf/anyOfhandling [6]. - If you instead supply multipleexamplesfor that same 500 response, some Swagger UI versions show them as selectable alternatives (again per the cited guidance) [5]. Practical takeaway - You should not define two500entries at the top level underresponses. - Define a single500response, and then model multiple possible body shapes and/or examples within it usingoneOf(and/or discriminator mapping) andexamples[3][5].Citations:
π Script executed:
Repository: PSMRI/ECD-API
Length of output: 144
π Script executed:
Repository: PSMRI/ECD-API
Length of output: 146
π Script executed:
rg -t java "INTERNAL_SERVER_ERROR_SC_V|DB_EXCEPTION_SC_V" -A 1 -B 1Repository: PSMRI/ECD-API
Length of output: 50369
π Script executed:
Repository: PSMRI/ECD-API
Length of output: 147
π Script executed:
sed -n '50,90p' src/main/java/com/iemr/ecd/controller/associate/BeneficiaryRegistrationController.javaRepository: PSMRI/ECD-API
Length of output: 3661
Merge duplicate
500responses into a single entry.Both
INTERNAL_SERVER_ERROR_SC_VandDB_EXCEPTION_SC_Vmap to500. In OpenAPI 3.x, response objects are keyed by HTTP status codeβhaving two entries with the same code violates the spec structure, with the latter overwriting the former in generated documentation, resulting in inconsistent Swagger output.Consolidate the two
500responses into one with a unified description indicating both error scenarios, or use a single schema that can represent both error types.This pattern appears across multiple endpoints in the codebase.
π€ Prompt for AI Agents