feat(core): improve exception handling and validation with Inputs/Outputs

- Added InputOutputValidationException to represent Inputs/Outputs
  validation issues and added handler to it in ErrorsController
- Added support for throwing multiple constraint violations for the same
  input
- Added support for throwing multiple constraints at MultiselectInput
- Refactored exception handling at FlowInputOutput
- Added merge() function to combine constraint violation messages and
  added test for it at InputsTest
- Fixed the failed tests
This commit is contained in:
mustafatarek
2025-12-10 16:21:13 +02:00
committed by Florian Hussonnois
parent 22f0b3ffdf
commit f409657e8a
16 changed files with 197 additions and 107 deletions

View File

@@ -36,6 +36,10 @@ public class ErrorController {
public HttpResponse<JsonError> error(HttpRequest<?> request, JsonParseException e) {
return jsonError(request, e, HttpStatus.UNPROCESSABLE_ENTITY, "Invalid json");
}
@Error(global = true)
public HttpResponse<JsonError> error(HttpRequest<?> request, InputOutputValidationException e) {
return jsonError(request, e, HttpStatus.UNPROCESSABLE_ENTITY, "Invalid entity");
}
@Error(global = true)
public HttpResponse<JsonError> error(HttpRequest<?> request, ConversionErrorException e) {

View File

@@ -2670,7 +2670,12 @@ public class ExecutionController {
) {
}
public static ApiValidateExecutionInputsResponse of(String id, String namespace, List<Check> checks, List<InputAndValue> inputs) {
public static ApiValidateExecutionInputsResponse of(
String id,
String namespace,
List<Check> checks,
List<InputAndValue> inputs
) {
return new ApiValidateExecutionInputsResponse(
id,
namespace,
@@ -2679,14 +2684,21 @@ public class ExecutionController {
it.value(),
it.enabled(),
it.isDefault(),
Optional.ofNullable(it.exception()).map(exception ->
exception.getConstraintViolations()
.stream()
.map(cv -> new ApiInputError(cv.getMessage()))
// Map the Set<InputOutputValidationException> to ApiInputError
Optional.ofNullable(it.exceptions())
.map(exSet -> exSet.stream()
.map(e -> new ApiInputError(e.getMessage()))
.toList()
).orElse(List.of())
)
.orElse(List.of())
)).toList(),
checks.stream().map(check -> new ApiCheckFailure(check.getMessage(), check.getStyle(), check.getBehavior())).toList()
checks.stream()
.map(check -> new ApiCheckFailure(
check.getMessage(),
check.getStyle(),
check.getBehavior()
))
.toList()
);
}
}

View File

@@ -242,7 +242,7 @@ class ExecutionControllerRunnerTest {
String response = e.getResponse().getBody(String.class).orElseThrow();
assertThat(response).contains("Invalid entity");
assertThat(response).contains("Invalid input for `validatedString`");
assertThat(response).contains("Invalid value for input `validatedString`");
}
@Test
@@ -1071,7 +1071,7 @@ class ExecutionControllerRunnerTest {
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE)
));
assertThat(exception.getStatus().getCode()).isEqualTo(422);
assertThat(exception.getMessage()).isEqualTo("Invalid entity: asked: Invalid input for `asked`, missing required input, but received `null`");
assertThat(exception.getMessage()).isEqualTo("Invalid entity: Missing required input:asked");
}
@Test