Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.opendevstack.apiservice.projectusers.exception;

import org.opendevstack.apiservice.projectusers.controller.ProjectUserController;
import org.opendevstack.apiservice.projectusers.model.ValidationErrorResponse;
import org.opendevstack.apiservice.projectusers.model.BaseApiResponse;
import org.opendevstack.apiservice.projectusers.model.FieldError;
Expand Down Expand Up @@ -32,7 +33,7 @@
* messages.
*/
@Slf4j
@ControllerAdvice
@ControllerAdvice(assignableTypes = ProjectUserController.class)
public class GlobalExceptionHandler {

/**
Expand Down Expand Up @@ -69,17 +70,17 @@ public ResponseEntity<ValidationErrorResponse> handleMethodArgumentNotValidExcep
fieldErrors.add(fieldError);
});

String errorMessage = String.format(
ErrorMessages.REQUEST_VALIDATION_FAILED,
fieldErrors.size());

ValidationErrorResponse errorResponse = new ValidationErrorResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setErrorCode(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setFieldErrors(fieldErrors);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
String errorMessage = String.format(
ErrorMessages.REQUEST_VALIDATION_FAILED,
fieldErrors.size());

ValidationErrorResponse errorResponse = new ValidationErrorResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setErrorCode(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setFieldErrors(fieldErrors);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

/**
Expand All @@ -96,13 +97,13 @@ public ResponseEntity<BaseApiResponse> handleConstraintViolationException(
.map(this::formatConstraintViolation)
.toList();

String errorMessage = String.format(ErrorMessages.PARAMETER_VALIDATION_FAILED, String.join("; ", errors));
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
String errorMessage = String.format(ErrorMessages.PARAMETER_VALIDATION_FAILED, String.join("; ", errors));
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

/**
Expand All @@ -115,8 +116,8 @@ public ResponseEntity<BaseApiResponse> handleHttpMessageNotReadableException(

log.warn("Invalid request body: {}", ex.getMessage());

String errorMessage = ErrorMessages.INVALID_REQUEST_BODY;
String errorCode = ErrorCodes.PROJECT_USER_ERROR;
String errorMessage = ErrorMessages.INVALID_REQUEST_BODY;
String errorCode = ErrorCodes.PROJECT_USER_ERROR;

Throwable cause = ex.getCause();

Expand Down Expand Up @@ -172,15 +173,15 @@ public ResponseEntity<BaseApiResponse> handleMissingPathVariableException(

log.warn("Missing path variable: {}", ex.getMessage());

String errorMessage = String.format(
ErrorMessages.REQUIRED_PATH_PARAMETER_MISSING,
ex.getVariableName());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
String errorMessage = String.format(
ErrorMessages.REQUIRED_PATH_PARAMETER_MISSING,
ex.getVariableName());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

/**
Expand All @@ -192,15 +193,15 @@ public ResponseEntity<BaseApiResponse> handleMissingServletRequestParameterExcep

log.warn("Missing request parameter: {}", ex.getMessage());

String errorMessage = String.format(
ErrorMessages.REQUIRED_REQUEST_PARAMETER_MISSING,
ex.getParameterName(), ex.getParameterType());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
String errorMessage = String.format(
ErrorMessages.REQUIRED_REQUEST_PARAMETER_MISSING,
ex.getParameterName(), ex.getParameterType());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

/**
Expand All @@ -212,17 +213,17 @@ public ResponseEntity<BaseApiResponse> handleMethodArgumentTypeMismatchException

log.warn("Method argument type mismatch: {}", ex.getMessage());

String errorMessage = String.format(
ErrorMessages.PARAMETER_TYPE_CONVERSION_FAILED,
ex.getName(),
ex.getValue(),
ex.getRequiredType() != null ? ex.getRequiredType().getSimpleName() : "unknown");
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.INVALID_ROLE);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
String errorMessage = String.format(
ErrorMessages.PARAMETER_TYPE_CONVERSION_FAILED,
ex.getName(),
ex.getValue(),
ex.getRequiredType() != null ? ex.getRequiredType().getSimpleName() : "unknown");
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(errorMessage);
errorResponse.setError(ErrorCodes.INVALID_ROLE);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

/**
Expand All @@ -233,11 +234,11 @@ public ResponseEntity<BaseApiResponse> handleProjectNotFoundException(
ProjectNotFoundException ex) {

log.warn("Project not found: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}

Expand All @@ -249,11 +250,11 @@ public ResponseEntity<BaseApiResponse> handleUserNotFoundException(
UserNotFoundException ex) {

log.warn("User not found: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}

Expand Down Expand Up @@ -297,11 +298,11 @@ public ResponseEntity<BaseApiResponse> handleInvalidRoleException(
InvalidRoleException ex) {

log.warn("Invalid role: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

Expand All @@ -313,12 +314,12 @@ public ResponseEntity<BaseApiResponse> handleAutomationPlatformException(
AutomationPlatformException ex) {

log.error("Automation platform error: {}", ex.getMessage(), ex);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(String.format(ErrorMessages.EXTERNAL_SERVICE_ERROR, ex.getMessage()));
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(errorResponse);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(String.format(ErrorMessages.EXTERNAL_SERVICE_ERROR, ex.getMessage()));
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(errorResponse);
}

/**
Expand All @@ -327,12 +328,12 @@ public ResponseEntity<BaseApiResponse> handleAutomationPlatformException(
@ExceptionHandler(ProjectUserException.class)
public ResponseEntity<BaseApiResponse> handleProjectUserException(ProjectUserException ex) {
log.error("Project user operation failed: {}", ex.getMessage(), ex);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(String.format(ErrorMessages.OPERATION_FAILED, ex.getMessage()));
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(String.format(ErrorMessages.OPERATION_FAILED, ex.getMessage()));
errorResponse.setError(ex.getErrorCode());
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}

/**
Expand All @@ -341,12 +342,12 @@ public ResponseEntity<BaseApiResponse> handleProjectUserException(ProjectUserExc
@ExceptionHandler(Exception.class)
public ResponseEntity<BaseApiResponse> handleGenericException(Exception ex) {
log.error("Unexpected error occurred: {}", ex.getMessage(), ex);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ErrorMessages.UNEXPECTED_ERROR);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ErrorMessages.UNEXPECTED_ERROR);
errorResponse.setError(ErrorCodes.PROJECT_USER_ERROR);
errorResponse.setTimestamp(java.time.OffsetDateTime.now());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}

/**
Expand Down
11 changes: 9 additions & 2 deletions api-project/openapi/api-project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,15 @@ components:
projectDescription:
type: string
description: Description of the project.
required:
- projectName
projectFlavor:
type: string
description: Flavor of the project. Either projectFlavor or configurationItem must be provided.
configurationItem:
type: string
description: Configuration item for the project. Either projectFlavor or configurationItem must be provided.
location:
type: string
description: Location of the project.
CreateProjectResponse:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.opendevstack.apiservice.project.model.CreateProjectRequest;
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
import org.opendevstack.apiservice.project.exception.ProjectKeyGenerationException;
import org.opendevstack.apiservice.project.validation.ProjectRequestValidator;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -30,9 +31,12 @@ public class ProjectController implements ProjectsApi {

private final ProjectsFacade projectsFacade;

private final ProjectRequestValidator projectRequestValidator;

@PostMapping
@Override
public ResponseEntity<CreateProjectResponse> createProject(@Valid @RequestBody CreateProjectRequest createProjectRequest) {
projectRequestValidator.validate(createProjectRequest);
try {
return ResponseEntity
.status(HttpStatus.OK)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.opendevstack.apiservice.project.controller.advice;

import lombok.extern.slf4j.Slf4j;
import org.opendevstack.apiservice.project.controller.ProjectController;
import org.opendevstack.apiservice.project.exception.ErrorKey;
import org.opendevstack.apiservice.project.exception.ProjectValidationException;
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

@RestControllerAdvice(assignableTypes = ProjectController.class)
@Slf4j
public class ProjectExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<CreateProjectResponse> handleMethodArgumentNotValidException(
MethodArgumentNotValidException ex) {
log.warn("Request body validation error: {}", ex.getMessage());

String validationMessage = ex.getBindingResult().getFieldErrors().stream()
.map(this::formatFieldError)
.collect(Collectors.joining("; "));

if (validationMessage.isBlank()) {
validationMessage = ErrorKey.BAD_REQUEST_BODY.getMessage();
}

CreateProjectResponse response = new CreateProjectResponse();
response.setLocation(ProjectController.API_BASE_PATH);
response.setError(HttpStatus.BAD_REQUEST.getReasonPhrase());
response.setErrorKey(ErrorKey.BAD_REQUEST_BODY.getKey());
response.setMessage(validationMessage);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

@ExceptionHandler(ProjectValidationException.class)
public ResponseEntity<CreateProjectResponse> handleValidationException(ProjectValidationException ex) {
log.warn("Validation error: {}", ex.getMessage());
ErrorKey errorKey = ex.getErrorKey();
CreateProjectResponse response = new CreateProjectResponse();
response.setLocation(ProjectController.API_BASE_PATH);
response.setError(HttpStatus.BAD_REQUEST.getReasonPhrase());
response.setErrorKey(errorKey.getKey());
response.setMessage(errorKey.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

private String formatFieldError(FieldError error) {
return error.getField() + " " + error.getDefaultMessage();
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.opendevstack.apiservice.project.exception;

import lombok.Getter;

@Getter
public class ProjectValidationException extends RuntimeException {

private final ErrorKey errorKey;

public ProjectValidationException(ErrorKey errorKey) {
super(errorKey.getMessage());
this.errorKey = errorKey;
}
}

Loading
Loading