From 5b0b7af90b22918bdc964eaf8b3ca0cbc4dc60f9 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Wed, 25 Oct 2023 11:06:26 -1000 Subject: [PATCH 1/7] Added a new exception for constraint compilation errors. --- .../ConstraintCompilationException.java | 7 ------- .../ConstraintCompilationException.java | 21 +++++++++++++++++++ .../server/services/ConstraintAction.java | 16 ++++++++++---- 3 files changed, 33 insertions(+), 11 deletions(-) delete mode 100644 constraints/src/main/java/gov/nasa/jpl/aerie/constraints/ConstraintCompilationException.java create mode 100644 merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/exceptions/ConstraintCompilationException.java diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/ConstraintCompilationException.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/ConstraintCompilationException.java deleted file mode 100644 index 85d0d4e265..0000000000 --- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/ConstraintCompilationException.java +++ /dev/null @@ -1,7 +0,0 @@ -package gov.nasa.jpl.aerie.constraints; - -public class ConstraintCompilationException extends RuntimeException { - public ConstraintCompilationException(final String message) { - super(message); - } -} diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/exceptions/ConstraintCompilationException.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/exceptions/ConstraintCompilationException.java new file mode 100644 index 0000000000..b05939eef5 --- /dev/null +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/exceptions/ConstraintCompilationException.java @@ -0,0 +1,21 @@ +package gov.nasa.jpl.aerie.merlin.server.exceptions; + +import gov.nasa.jpl.aerie.merlin.server.services.ConstraintsDSLCompilationService; + +public class ConstraintCompilationException extends Exception { + String constraintName; + ConstraintsDSLCompilationService.ConstraintsDSLCompilationResult.Error error; + public ConstraintCompilationException(String constraintName, ConstraintsDSLCompilationService.ConstraintsDSLCompilationResult.Error error) { + super("Constraint "+constraintName+" compilation failed:\n"+error.toString()); + this.constraintName = constraintName; + this.error = error; + } + + public String getConstraintName() { + return constraintName; + } + + public ConstraintsDSLCompilationService.ConstraintsDSLCompilationResult.Error getErrors() { + return error; + } +} diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java index 20341e2b77..1c3090d6d7 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java @@ -1,6 +1,5 @@ package gov.nasa.jpl.aerie.merlin.server.services; -import gov.nasa.jpl.aerie.constraints.ConstraintCompilationException; import gov.nasa.jpl.aerie.constraints.InputMismatchException; import gov.nasa.jpl.aerie.constraints.model.ActivityInstance; import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; @@ -10,7 +9,9 @@ import gov.nasa.jpl.aerie.constraints.time.Interval; import gov.nasa.jpl.aerie.constraints.tree.Expression; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.merlin.server.exceptions.ConstraintCompilationException; import gov.nasa.jpl.aerie.merlin.server.exceptions.SimulationDatasetMismatchException; +import gov.nasa.jpl.aerie.merlin.server.models.ConstraintsCompilationError; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; import gov.nasa.jpl.aerie.merlin.server.models.SimulationResultsHandle; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; @@ -49,7 +50,7 @@ public ConstraintAction( } public List getViolations(final PlanId planId, final Optional simulationDatasetId) - throws NoSuchPlanException, MissionModelService.NoSuchMissionModelException, SimulationDatasetMismatchException { + throws NoSuchPlanException, MissionModelService.NoSuchMissionModelException, SimulationDatasetMismatchException , ConstraintCompilationException{ final var plan = this.planService.getPlanForValidation(planId); final Optional resultsHandle$; final SimulationDatasetId simDatasetId; @@ -155,6 +156,7 @@ public List getViolations(final PlanId planId, final Optional< final var constraint = entry.getValue(); final Expression expression; + // TODO: cache these results, @JoelCourtney is this in reference to caching the output of the DSL compilation? final var constraintCompilationResult = constraintsDSLCompilationService.compileConstraintsDSL( plan.missionModelId, Optional.of(planId), @@ -165,9 +167,15 @@ public List getViolations(final PlanId planId, final Optional< if (constraintCompilationResult instanceof ConstraintsDSLCompilationService.ConstraintsDSLCompilationResult.Success success) { expression = success.constraintExpression(); } else if (constraintCompilationResult instanceof ConstraintsDSLCompilationService.ConstraintsDSLCompilationResult.Error error) { - throw new ConstraintCompilationException("Constraint compilation failed: " + error); + throw new ConstraintCompilationException(constraint.name(),error); } else { - throw new ConstraintCompilationException("Unhandled variant of ConstraintsDSLCompilationResult: " + constraintCompilationResult); + throw new ConstraintCompilationException(constraint.name(),new ConstraintsDSLCompilationService.ConstraintsDSLCompilationResult.Error(new ArrayList<>(){{ + add(new ConstraintsCompilationError.UserCodeError( + "Unhandled variant of ConstraintsDSLCompilationResult: " + constraintCompilationResult, + "", + new ConstraintsCompilationError.CodeLocation(0, 0), + "")); + }})); } final var names = new HashSet(); From 9539ffd5c4546081d6d07b60185c76befebe607a Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Wed, 25 Oct 2023 11:11:04 -1000 Subject: [PATCH 2/7] Update Constraint Violation response. * Change to a fallible monad so always return something to hasura. Note: This will also be used in a future ticket to prevent the server from short-circuiting when a constraint has compilation issues. --- .../merlin/server/http/MerlinBindings.java | 6 +-- .../server/http/ResponseSerializers.java | 37 ++++++++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java index 5148bd5a58..faa19aa17d 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java @@ -1,10 +1,10 @@ package gov.nasa.jpl.aerie.merlin.server.http; -import gov.nasa.jpl.aerie.constraints.ConstraintCompilationException; import gov.nasa.jpl.aerie.constraints.InputMismatchException; import gov.nasa.jpl.aerie.json.JsonParser; import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; +import gov.nasa.jpl.aerie.merlin.server.exceptions.ConstraintCompilationException; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanDatasetException; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.exceptions.SimulationDatasetMismatchException; @@ -240,6 +240,8 @@ private void getConstraintViolations(final Context ctx) { final var constraintViolations = this.constraintAction.getViolations(planId, simulationDatasetId); ctx.result(ResponseSerializers.serializeConstraintResults(constraintViolations).toString()); + } catch (ConstraintCompilationException ex) { + ctx.result((ResponseSerializers.serializeConstraintCompileException(ex)).toString()); } catch (final InvalidJsonException ex) { ctx.status(400).result(ResponseSerializers.serializeInvalidJsonException(ex).toString()); } catch (final InvalidEntityException ex) { @@ -252,8 +254,6 @@ private void getConstraintViolations(final Context ctx) { ctx.status(404).result(ExceptionSerializers.serializeNoSuchPlanException(ex).toString()); } catch (final InputMismatchException ex) { ctx.status(404).result(ResponseSerializers.serializeInputMismatchException(ex).toString()); - } catch (final ConstraintCompilationException ex) { - ctx.status(404).result(ResponseSerializers.serializeConstraintCompilationException(ex).toString()); } catch (SimulationDatasetMismatchException ex) { ctx.status(404).result(ResponseSerializers.serializeSimulationDatasetMismatchException(ex).toString()); } catch (final PermissionsServiceException ex) { diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java index 1c64d4554f..04eb3074c1 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java @@ -1,6 +1,5 @@ package gov.nasa.jpl.aerie.merlin.server.http; -import gov.nasa.jpl.aerie.constraints.ConstraintCompilationException; import gov.nasa.jpl.aerie.constraints.InputMismatchException; import gov.nasa.jpl.aerie.constraints.model.Violation; import gov.nasa.jpl.aerie.constraints.model.ConstraintResult; @@ -16,6 +15,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; +import gov.nasa.jpl.aerie.merlin.server.exceptions.ConstraintCompilationException; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanDatasetException; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.exceptions.SimulationDatasetMismatchException; @@ -275,7 +275,17 @@ public static JsonValue serializeResourceSamples(final Map list) { - return serializeIterable(ResponseSerializers::serializeConstraintResult, list); + var results = list.stream().map(ConstraintResult -> Json.createObjectBuilder() + .add("success", JsonValue.TRUE) + .add("constraintName",ConstraintResult.constraintName) + .add("errors", Json.createArrayBuilder().build()) + .add("results", serializeConstraintResult(ConstraintResult)) + .build()).collect(Collectors.toList()); + + final var resultsArrayBuilder = Json.createArrayBuilder(); + results.forEach(resultsArrayBuilder::add); + + return resultsArrayBuilder.build(); } public static JsonValue serializeSimulationResultsResponse(final GetSimulationResultsAction.Response response) { @@ -392,6 +402,29 @@ public static JsonValue serializeInvalidJsonException(final InvalidJsonException .build(); } + public static JsonValue serializeConstraintCompileException(final ConstraintCompilationException ex){ + + final var userCodeError = ex.getErrors().errors().stream() + .map(UserCodeError -> Json.createObjectBuilder() + .add("stack", UserCodeError.stack()) + .add("message", "Constraint '"+ex.getConstraintName()+"'"+": "+UserCodeError.message()) + .add("location", Json.createObjectBuilder() + .add("line",UserCodeError.location().line()) + .add("column",UserCodeError.location().column()).build()) + .build()) + .collect(Collectors.toList()); + + final var userCodeErrorArrayBuilder = Json.createArrayBuilder(); + userCodeError.forEach(userCodeErrorArrayBuilder::add); + + return Json.createArrayBuilder().add(Json.createObjectBuilder() + .add("success", JsonValue.FALSE) + .add("constraintName",ex.getConstraintName()) + .add("errors", userCodeErrorArrayBuilder.build()) + .add("results",Json.createObjectBuilder().build()) + .build()).build(); + } + public static JsonValue serializeInvalidEntityException(final InvalidEntityException ex) { return Json.createObjectBuilder() .add("kind", "invalid-entity") From 5db1aa540b1e660ba87d8cf9af79f51940adb742 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Wed, 25 Oct 2023 11:12:05 -1000 Subject: [PATCH 3/7] Update hasura action to use this new monad. --- deployment/hasura/metadata/actions.graphql | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deployment/hasura/metadata/actions.graphql b/deployment/hasura/metadata/actions.graphql index dd40b0ea1f..22496c288a 100644 --- a/deployment/hasura/metadata/actions.graphql +++ b/deployment/hasura/metadata/actions.graphql @@ -105,7 +105,7 @@ type Query { } type Query { - constraintViolations(planId: Int!, simulationDatasetId: Int): [ConstraintResult!]! + constraintViolations(planId: Int!, simulationDatasetId: Int): [ConstraintResponse!]! } type Query { @@ -278,6 +278,12 @@ type ResourceSamplesResponse { resourceSamples: ResourceSamples! } +type ConstraintResponse { + success: String! + errors: [UserCodeError!]! + results: [ConstraintResult!]! +} + type ConstraintResult { violations: [ConstraintViolation!]!, gaps: [Interval!]! From ba6a968655aa6a7647b9bd6acec4fb2216ec2422 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Mon, 30 Oct 2023 13:13:12 -1000 Subject: [PATCH 4/7] Update GQL constraint calls for e2e test --- .../gov/nasa/jpl/aerie/e2e/utils/GQL.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java index 30603ce9ca..45204406d9 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java @@ -28,20 +28,31 @@ mutation AssignTemplateToSimulation($simulation_id: Int!, $simulation_template_i CHECK_CONSTRAINTS(""" query checkConstraints($planId: Int!, $simulationDatasetId: Int) { constraintViolations(planId: $planId, simulationDatasetId: $simulationDatasetId) { - constraintId - constraintName - type - resourceIds - violations { - activityInstanceIds - windows { - start + success + results { + constraintId + constraintName + resourceIds + type + gaps { end + start + } + violations { + activityInstanceIds + windows { + end + start + } } } - gaps { - start - end + errors { + message + stack + location { + column + line + } } } }"""), From 9adad9329ca5b675dd1ed23310c7ea3b516bc0f9 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Mon, 30 Oct 2023 13:13:59 -1000 Subject: [PATCH 5/7] Update Constraint Record to match the server response. --- .../jpl/aerie/e2e/types/ConstraintError.java | 15 +++++++ .../jpl/aerie/e2e/types/ConstraintRecord.java | 42 ++++------------- .../jpl/aerie/e2e/types/ConstraintResult.java | 45 +++++++++++++++++++ 3 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintError.java create mode 100644 e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintResult.java diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintError.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintError.java new file mode 100644 index 0000000000..b8077f6670 --- /dev/null +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintError.java @@ -0,0 +1,15 @@ +package gov.nasa.jpl.aerie.e2e.types; + +import javax.json.JsonObject; + +public record ConstraintError(String message, String stack, Location location ){ + record Location(int column, int line){ + public static Location fromJSON(JsonObject json){ + return new Location(json.getJsonNumber("column").intValue(), json.getJsonNumber("line").intValue()); + } + }; + + public static ConstraintError fromJSON(JsonObject json){ + return new ConstraintError(json.getString("message"),json.getString("stack"),Location.fromJSON(json.getJsonObject("location"))); + } +}; diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java index f51e752bc9..4da7467845 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java @@ -6,41 +6,15 @@ import java.util.List; public record ConstraintRecord( - int constraintId, - String constraintName, - String type, - List resourceIds, - List violations, - List gaps - ) { - public record ConstraintViolation(List activityInstanceIds, List windows) { - public static ConstraintViolation fromJSON(JsonObject json) { - return new ConstraintViolation( - json.getJsonArray("activityInstanceIds") - .getValuesAs(JsonNumber::intValue), - json.getJsonArray("windows") - .getValuesAs(Interval::fromJSON)); - } - } - - public record Interval(long start, long end) { - public static Interval fromJSON(JsonObject json) { - return new Interval(json.getJsonNumber("start").longValue(), json.getJsonNumber("end").longValue()); - } - } - - public static ConstraintRecord fromJSON(JsonObject json) { - final var resourceIds = json.getJsonArray("resourceIds").getValuesAs(JsonString::getString); - final var gaps = json.getJsonArray("gaps").getValuesAs(Interval::fromJSON); - final var violations = json.getJsonArray("violations").getValuesAs(ConstraintViolation::fromJSON); + boolean success, + ConstraintResult result, + List errors + ) { + public static ConstraintRecord fromJSON(JsonObject json){ return new ConstraintRecord( - json.getInt("constraintId"), - json.getString("constraintName"), - json.getString("type"), - resourceIds, - violations, - gaps - ); + json.getBoolean("success"), + json.getJsonObject("results").isEmpty() ? null : ConstraintResult.fromJSON(json.getJsonObject("results")), + json.getJsonArray("errors").getValuesAs(ConstraintError::fromJSON)); } } diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintResult.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintResult.java new file mode 100644 index 0000000000..f801e4f988 --- /dev/null +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintResult.java @@ -0,0 +1,45 @@ +package gov.nasa.jpl.aerie.e2e.types; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import java.util.List; + +public record ConstraintResult(int constraintId, + String constraintName, + String type, + List resourceIds, + List violations, + List gaps){ + public record ConstraintViolation(List activityInstanceIds, List windows) { + + public static ConstraintViolation fromJSON(JsonObject json) { + return new ConstraintViolation( + json.getJsonArray("activityInstanceIds") + .getValuesAs(JsonNumber::intValue), + json.getJsonArray("windows") + .getValuesAs(Interval::fromJSON)); + } + } + + public record Interval(long start, long end) { + public static Interval fromJSON(JsonObject json) { + return new Interval(json.getJsonNumber("start").longValue(), json.getJsonNumber("end").longValue()); + } + } + + public static ConstraintResult fromJSON(JsonObject json) { + final var resourceIds = json.getJsonArray("resourceIds").getValuesAs(JsonString::getString); + final var gaps = json.getJsonArray("gaps").getValuesAs(Interval::fromJSON); + final var violations = json.getJsonArray("violations").getValuesAs(ConstraintViolation::fromJSON); + + return new ConstraintResult( + json.getInt("constraintId"), + json.getString("constraintName"), + json.getString("type"), + resourceIds, + violations, + gaps + ); + } +}; From 7edfc9a20b8eef0bfd32fab3bfde9636584fa943 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Mon, 30 Oct 2023 13:14:20 -1000 Subject: [PATCH 6/7] Update to use ConstraintResult. --- .../gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java index b203a44a0d..663f92bb15 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java @@ -8,7 +8,7 @@ public record CachedConstraintRun( int simDatasetId, String constraintDefinition, boolean definitionOutdated, - Optional results + Optional results ) { public static CachedConstraintRun fromJSON(JsonObject json) { return new CachedConstraintRun( @@ -18,7 +18,7 @@ public static CachedConstraintRun fromJSON(JsonObject json) { json.getBoolean("definition_outdated"), json.getJsonObject("results").isEmpty() ? Optional.empty() : - Optional.of(ConstraintRecord.fromJSON(json.getJsonObject("results"))) + Optional.of(ConstraintResult.fromJSON(json.getJsonObject("results"))) ); } } From 77865d6d4e259c247eb51fb3a80355ff004d0fe7 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Mon, 30 Oct 2023 13:14:30 -1000 Subject: [PATCH 7/7] Update the ConstraintTest --- .../nasa/jpl/aerie/e2e/ConstraintsTests.java | 74 ++++++++++++------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java index 3e425b736f..a0fafe6375 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java @@ -1,6 +1,8 @@ package gov.nasa.jpl.aerie.e2e; import com.microsoft.playwright.Playwright; +import gov.nasa.jpl.aerie.e2e.types.ConstraintError; +import gov.nasa.jpl.aerie.e2e.types.ConstraintResult; import gov.nasa.jpl.aerie.e2e.types.ExternalDataset.ProfileInput; import gov.nasa.jpl.aerie.e2e.types.ExternalDataset.ProfileInput.ProfileSegmentInput; import gov.nasa.jpl.aerie.e2e.types.ValueSchema; @@ -100,11 +102,13 @@ void constraintsFailNoSimData() { @Test void constraintsSucceedOneViolation() throws IOException { hasura.awaitSimulation(planId); - final var constraintsResults = hasura.checkConstraints(planId); - assertEquals(1, constraintsResults.size()); + final var constraintsResponses = hasura.checkConstraints(planId); + assertEquals(1, constraintsResponses.size()); // Check the Result - final var constraintResult = constraintsResults.get(0); + final var constraintResponse = constraintsResponses.get(0); + assertEquals(true,constraintResponse.success()); + final ConstraintResult constraintResult = constraintResponse.result(); assertEquals(constraintId, constraintResult.constraintId()); assertEquals(constraintName, constraintResult.constraintName()); @@ -155,7 +159,9 @@ void constraintCachedViolation() throws IOException { // Check results assertTrue(cachedRun.results().isPresent()); - assertEquals(constraintRuns.get(0), cachedRun.results().get()); + final var constraintResponse = constraintRuns.get(0); + assertEquals(true,constraintResponse.success()); + assertEquals(constraintResponse.result(), cachedRun.results().get()); } @Test @@ -217,11 +223,13 @@ void constraintsWorkMonthLongActivity() throws IOException { "0h", Json.createObjectBuilder().add("duration", thirtyFiveDays).build()); hasura.awaitSimulation(planId); - final var constraintResults = hasura.checkConstraints(planId); - assertEquals(1, constraintResults.size()); + final var constraintsResponses = hasura.checkConstraints(planId); + assertEquals(1, constraintsResponses.size()); // Check the Result - final var constraintResult = constraintResults.get(0); + final var constraintResponse = constraintsResponses.get(0); + assertEquals(true,constraintResponse.success()); + final var constraintResult = constraintResponse.result(); assertEquals(constraintId, constraintResult.constraintId()); assertEquals(constraintName, constraintResult.constraintName()); @@ -253,11 +261,14 @@ void runConstraintsOnOldSimulation() throws IOException { assertEquals(0, newConstraintResults.size()); // Expect one violation on the old simulation - final var oldConstraintResults = hasura.checkConstraints(planId, oldSimDatasetId); - assertEquals(1, oldConstraintResults.size()); + final var oldConstraintsResponses = hasura.checkConstraints(planId, oldSimDatasetId); + assertEquals(1, oldConstraintsResponses.size()); // Check the Result - final var constraintResult = oldConstraintResults.get(0); + final var oldConstraintResponse = oldConstraintsResponses.get(0); + assertEquals(true,oldConstraintResponse.success()); + final var constraintResult = oldConstraintResponse.result(); + assertEquals(constraintId, constraintResult.constraintId()); assertEquals(constraintName, constraintResult.constraintName()); @@ -338,12 +349,15 @@ void oneViolationCurrentSimulation() throws IOException { // Constraint Results w/o SimDatasetId final var noDatasetResults = hasura.checkConstraints(planId); assertEquals(1, noDatasetResults.size()); - final var ndRecord = noDatasetResults.get(0); + assertEquals(true, noDatasetResults.get(0).success()); + + final var ndRecord = noDatasetResults.get(0).result(); // Constraint Results w/ SimDatasetId final var withDatasetResults = hasura.checkConstraints(planId, simDatasetId); assertEquals(1, withDatasetResults.size()); - final var wdRecord = withDatasetResults.get(0); + assertEquals(true, withDatasetResults.get(0).success()); + final var wdRecord = withDatasetResults.get(0).result(); // The results should be the same assertEquals(ndRecord, wdRecord); @@ -390,9 +404,12 @@ void oneViolationOutdatedSimIdPassed() throws IOException { hasura.deleteActivity(planId, activityId); hasura.awaitSimulation(planId); // Check constraints against the old simID (the one with the external dataset) - final var constraintRuns = hasura.checkConstraints(planId, simDatasetId); - assertEquals(1, constraintRuns.size()); - final var record = constraintRuns.get(0); + final var constraintResponses = hasura.checkConstraints(planId, simDatasetId); + assertEquals(1, constraintResponses.size()); + + final var constraintResponse = constraintResponses.get(0); + assertEquals(true,constraintResponse.success()); + final var record = constraintResponse.result(); // Check the Result assertEquals(constraintName, record.constraintName()); @@ -437,14 +454,13 @@ void compilationFailsOutdatedSimulationSimDatasetId() throws IOException { final int newSimDatasetId = hasura.awaitSimulation(planId).simDatasetId(); // This test causes the endpoint to throw an exception when it fails to compile the constraint, // as it cannot find the external dataset resource in the set of known resource types. - // "input mismatch exception" is the return msg for this error - final var exception = assertThrows(RuntimeException.class, () -> hasura.checkConstraints(planId, newSimDatasetId)); - final var message = exception.getMessage().split("\"message\":\"")[1].split("\"}]")[0]; - // Hasura strips the cause message ("Constraint compilation failed -- Error[errors=[UserCodeError[ $ERROR ]]]") - // from the error it returns - if (!message.equals("constraint compilation exception")) { - throw exception; - } + final var constraintResponses = hasura.checkConstraints(planId); + assertEquals(1,constraintResponses.size()); + final var constraintRepsonse = constraintResponses.get(0); + assertEquals(false,constraintRepsonse.success()); + assertEquals(1,constraintRepsonse.errors().size()); + final ConstraintError error = constraintRepsonse.errors().get(0); + assertEquals("Constraint 'fruit_equal_peel': TypeError: TS2345 Argument of type '\"/my_boolean\"' is not assignable to parameter of type 'ResourceName'.",error.message()); } @Test @@ -456,12 +472,14 @@ void compilationFailsOutdatedSimulationNoSimDataset() throws IOException { // The constraint run is expected to fail with the following message because the constraint // DSL isn't being generated for datasets that are outdated. - final var exception = assertThrows(RuntimeException.class, () -> hasura.checkConstraints(planId)); - final var message = exception.getMessage().split("\"message\":\"")[1].split("\"}]")[0]; + final var constraintResponses = hasura.checkConstraints(planId); + assertEquals(1,constraintResponses.size()); + final var constraintRepsonse = constraintResponses.get(0); + assertEquals(false,constraintRepsonse.success()); + assertEquals(1,constraintRepsonse.errors().size()); + final ConstraintError error = constraintRepsonse.errors().get(0); + assertEquals("Constraint 'fruit_equal_peel': TypeError: TS2345 Argument of type '\"/my_boolean\"' is not assignable to parameter of type 'ResourceName'.",error.message()); - if (!message.equals("constraint compilation exception")) { - throw exception; - } } } }