Skip to content

Commit

Permalink
[FSADT1-712] feat: add client submit validation (#423)
Browse files Browse the repository at this point in the history
* [FSADT1-712] feat: add client submit validation

* resolve conflict

* Fix docs

* Fixes

* Fixes
  • Loading branch information
brunoMarchiEncora authored Mar 28, 2023
1 parent b58020e commit 01a226a
Show file tree
Hide file tree
Showing 28 changed files with 871 additions and 126 deletions.
5 changes: 5 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package ca.bc.gov.app.controller;

import ca.bc.gov.app.exception.InvalidRequestObjectException;
import java.util.function.Function;
import ca.bc.gov.app.dto.ValidationError;
import ca.bc.gov.app.exception.ValidationException;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
Expand All @@ -22,17 +22,19 @@ protected <S> void validate(S target) {
Errors errors = new BeanPropertyBindingResult(target, contentClass.getName());
validator.validate(target, errors);
if (errors.hasErrors()) {
throw new InvalidRequestObjectException(getErrorMessages().apply(errors));
throw new ValidationException(getValidationErrors(errors));
}
}

private static Function<Errors, String> getErrorMessages() {
return errors ->
errors
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getCode)
.reduce(StringUtils.EMPTY,
(message1, message2) -> String.join(",", message1, message2));
private List<ValidationError> getValidationErrors(Errors errors) {
return errors
.getFieldErrors()
.stream()
.map(fieldError ->
new ValidationError()
.withFieldId(fieldError.getField())
.withErrorMsg(fieldError.getCode())
)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ca.bc.gov.app.controller;

import ca.bc.gov.app.exception.ValidationException;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -50,15 +52,25 @@ private Mono<ServerResponse> renderErrorResponse(
ServerRequest request, ErrorAttributes errorAttributes) {

Throwable exception = errorAttributes.getError(request).fillInStackTrace();
String errorMessage = exception.getMessage();
HttpStatusCode errorStatus = HttpStatus.INTERNAL_SERVER_ERROR;

log.error(
"An error was generated during request for {} {}",
request.method(),
request.requestPath(),
exception);

if (exception instanceof ValidationException validationException) {
log.error("Failed Validations: {}",
Arrays.toString(validationException.getErrors().toArray()));
return ServerResponse.status(validationException.getStatusCode())
.header("Reason", validationException.getReason())
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(validationException.getErrors()));
}

String errorMessage = exception.getMessage();
HttpStatusCode errorStatus = HttpStatus.INTERNAL_SERVER_ERROR;

if (exception instanceof ResponseStatusException responseStatusException) {
errorMessage = responseStatusException.getReason();
errorStatus = responseStatusException.getStatusCode();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package ca.bc.gov.app.controller.client;

import ca.bc.gov.app.controller.AbstractController;
import ca.bc.gov.app.dto.client.ClientSubmissionDto;
import ca.bc.gov.app.exception.InvalidRequestObjectException;
import ca.bc.gov.app.service.client.ClientService;
import ca.bc.gov.app.validator.client.ClientSubmitRequestValidator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
Expand All @@ -22,18 +23,24 @@
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@Slf4j
@Tag(
name = "FSA Clients",
description = "The FSA Client endpoint, responsible for handling client data"
)
@RestController
@Slf4j
@RequestMapping(value = "/api/clients", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
public class ClientSubmissionController {
public class ClientSubmissionController extends
AbstractController<ClientSubmissionDto, ClientSubmitRequestValidator> {

private final ClientService clientService;

public ClientSubmissionController(
ClientService clientService, ClientSubmitRequestValidator validator) {
super(ClientSubmissionDto.class, validator);
this.clientService = clientService;
}

@PostMapping("/submissions")
@Operation(
summary = "Submit client data",
Expand Down Expand Up @@ -73,19 +80,21 @@ public Mono<Void> submit(
.switchIfEmpty(
Mono.error(new InvalidRequestObjectException("no request body was provided"))
)
.doOnNext(this::validate)
.flatMap(clientService::submit)
.doOnNext(submissionId -> {
serverResponse
.setStatusCode(HttpStatus.CREATED);
serverResponse
.setStatusCode(HttpStatus.CREATED);

HttpHeaders headers = serverResponse.getHeaders();
headers
.add(
"Location",
String.format("/api/clients/submissions/%d", submissionId));
headers.add(
"x-sub-id", String.valueOf(submissionId));
})
HttpHeaders headers = serverResponse.getHeaders();
headers
.add(
"Location",
String.format("/api/clients/submissions/%d", submissionId));
headers.add(
"x-sub-id", String.valueOf(submissionId));
}
)
.then();
}
}
15 changes: 15 additions & 0 deletions backend/src/main/java/ca/bc/gov/app/dto/ValidationError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ca.bc.gov.app.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;

@Data
@With
@NoArgsConstructor
@AllArgsConstructor
public class ValidationError {
private String fieldId;
private String errorMsg;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

public record ClientAddressDto(
String streetAddress,
String country,
String province,
ClientValueTextDto country,
ClientValueTextDto province,
String city,
String postalCode,

int index,
List<ClientContactDto> contacts
) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ca.bc.gov.app.dto.client;

public record ClientBusinessTypeDto(ClientTypeDto clientType) {
public record ClientBusinessTypeDto(ClientValueTextDto clientType) {
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
package ca.bc.gov.app.dto.client;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.With;

@Schema(
description = "Client information contact",
title = "ClientContact",
example = """
{
"email": "[email protected]",
"firstName": "JAMES",
"index": 0,
"lastName": "BAXTER",
"middleInitial": "GRAHAM",
"contactType": "person"
}"""
{
"type": "person",
"firstName": "JAMES",
"lastName": "BAXTER",
"phoneNumber": "123456789"
"email": "[email protected]",
}"""
)
@With
public record ClientContactDto(
@Schema(description = "The type of contact",
example = "person")
String contactType,
String type,

@Schema(description = "The first name of the contact",
example = "JAMES")
String firstName,

@Schema(description = "Last name of the contact",
example = "BAXTER")
String lastName,
@Schema(description = "Contact phone number", example = "555 555 5555")

@Schema(description = "Contact phone number", example = "123456789")
String phoneNumber,

@Schema(description = "Email address to get in touch with contact",
example = "[email protected]")
String email,
@Schema(description = "The index for this address. It is used to order the addresses",
example = "3")

int index
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ca.bc.gov.app.dto.client;

import lombok.With;

@With
public record ClientDetailsAddressDto(
String streetAddress,
ClientValueTextDto country,
ClientValueTextDto province,
String city,
String postalCode,
int index
) {
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ca.bc.gov.app.exception;

import ca.bc.gov.app.dto.ValidationError;
import java.util.List;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ResponseStatusException;

@Getter
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class ValidationException extends ResponseStatusException {
private final List<ValidationError> errors;

public ValidationException(List<ValidationError> errors) {
super(HttpStatus.BAD_REQUEST, "Validation failed");
this.errors = errors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface ProvinceCodeRepository extends ReactiveCrudRepository<ProvinceC

Mono<ProvinceCodeEntity> findByCountryCodeAndProvinceCode(String countryCode,
String provinceCode);

Mono<ProvinceCodeEntity> findByProvinceCode(String provinceCode);
}
Loading

0 comments on commit 01a226a

Please sign in to comment.