diff --git a/src/core/EdFi.DataManagementService.Core.Tests.Unit/Middleware/ValidateEqualityConstraintMiddlewareTests.cs b/src/core/EdFi.DataManagementService.Core.Tests.Unit/Middleware/ValidateEqualityConstraintMiddlewareTests.cs index e109ea2a1..6f6235d77 100644 --- a/src/core/EdFi.DataManagementService.Core.Tests.Unit/Middleware/ValidateEqualityConstraintMiddlewareTests.cs +++ b/src/core/EdFi.DataManagementService.Core.Tests.Unit/Middleware/ValidateEqualityConstraintMiddlewareTests.cs @@ -187,11 +187,11 @@ public void It_returns_status_400() [Test] public void It_returns_message_body_with_failures() { - _context?.FrontendResponse.Body.Should().Contain("Bad Request"); + _context?.FrontendResponse.Body.Should().Contain("Data Validation Failed"); _context ?.FrontendResponse.Body.Should() .Contain( - "Constraint failure: document paths $.classPeriods[*].classPeriodReference.schoolId and $.schoolReference.schoolId must have the same values" + "All values supplied for 'schoolId' must match. Review all references (including those higher up in the resource's data) and align the following conflicting values: '2', '1'" ); } } diff --git a/src/core/EdFi.DataManagementService.Core/Middleware/ValidateEqualityConstraintMiddleware.cs b/src/core/EdFi.DataManagementService.Core/Middleware/ValidateEqualityConstraintMiddleware.cs index 0566829c0..7e7f043e8 100644 --- a/src/core/EdFi.DataManagementService.Core/Middleware/ValidateEqualityConstraintMiddleware.cs +++ b/src/core/EdFi.DataManagementService.Core/Middleware/ValidateEqualityConstraintMiddleware.cs @@ -37,21 +37,21 @@ public async Task Execute(PipelineContext context, Func next) context.FrontendRequest.TraceId ); - string[] errors = _equalityConstraintValidator.Validate( + Dictionary errors = _equalityConstraintValidator.Validate( context.ParsedBody, context.ResourceSchema.EqualityConstraints ); - if (errors.Length == 0) + if (errors.Count == 0) { await next(); } else { - var failureResponse = FailureResponse.ForBadRequest( - "The request could not be processed. See 'errors' for details.", - null, - errors + var failureResponse = FailureResponse.ForDataValidation( + "Data validation failed. See 'validationErrors' for details.", + errors, + null ); _logger.LogDebug( diff --git a/src/core/EdFi.DataManagementService.Core/Response/FailureResponse.cs b/src/core/EdFi.DataManagementService.Core/Response/FailureResponse.cs index d4973d7d2..38fcfd771 100644 --- a/src/core/EdFi.DataManagementService.Core/Response/FailureResponse.cs +++ b/src/core/EdFi.DataManagementService.Core/Response/FailureResponse.cs @@ -24,7 +24,7 @@ public static FailureResponseWithErrors ForDataValidation( ) => new( detail, - type: $"{_badRequestTypePrefix}:data", + type: $"{_badRequestTypePrefix}:data-validation-failed", title: "Data Validation Failed", status: 400, correlationId: null, diff --git a/src/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs b/src/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs index f6b303459..7a11ed270 100644 --- a/src/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs +++ b/src/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Text.Json.Nodes; using EdFi.DataManagementService.Core.Model; +using Json.More; namespace EdFi.DataManagementService.Core.Validation; @@ -17,14 +18,21 @@ internal interface IEqualityConstraintValidator /// /// /// Returns a list of validation failure messages. - string[] Validate(JsonNode? documentBody, IEnumerable equalityConstraints); + Dictionary Validate( + JsonNode? documentBody, + IEnumerable equalityConstraints + ); } internal class EqualityConstraintValidator : IEqualityConstraintValidator { - public string[] Validate(JsonNode? documentBody, IEnumerable equalityConstraints) + public Dictionary Validate( + JsonNode? documentBody, + IEnumerable equalityConstraints + ) { List errors = []; + var validationErrors = new Dictionary(); foreach (var equalityConstraint in equalityConstraints) { var sourcePath = Json.Path.JsonPath.Parse(equalityConstraint.SourceJsonPath.Value); @@ -33,15 +41,36 @@ public string[] Validate(JsonNode? documentBody, IEnumerable var sourcePathResult = sourcePath.Evaluate(documentBody); var targetPathResult = targetPath.Evaluate(documentBody); - Trace.Assert(sourcePathResult.Matches != null, "Evaluation of sourcePathResult.Matches resulted in unexpected null"); - Trace.Assert(targetPathResult.Matches != null, "Evaluation of targetPathResult.Matches resulted in unexpected null"); + Trace.Assert( + sourcePathResult.Matches != null, + "Evaluation of sourcePathResult.Matches resulted in unexpected null" + ); + Trace.Assert( + targetPathResult.Matches != null, + "Evaluation of targetPathResult.Matches resulted in unexpected null" + ); var sourceValues = sourcePathResult.Matches.Select(s => s.Value); var targetValues = targetPathResult.Matches.Select(t => t.Value); if (!AllEqual(sourceValues.Concat(targetValues).ToList())) { - errors.Add($"Constraint failure: document paths {equalityConstraint.SourceJsonPath.Value} and {equalityConstraint.TargetJsonPath.Value} must have the same values"); + string conflictValues = string.Join( + ", ", + sourceValues + .Concat(targetValues) + .Distinct(new JsonNodeEqualityComparer()) + .Select(x => $"'{x}'") + .ToArray() + ); + string targetSegment = targetPath.Segments[^1].ToString().TrimStart('.'); + errors.Add( + $"All values supplied for '{targetSegment}' must match." + + " Review all references (including those higher up in the resource's data)" + + " and align the following conflicting values: " + + conflictValues + ); + validationErrors.Add(sourcePath.ToString(), errors.ToArray()); } bool AllEqual(IList nodes) @@ -50,6 +79,6 @@ bool AllEqual(IList nodes) } } - return errors.ToArray(); + return validationErrors; } } diff --git a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/Constraints/EqualityConstraintValidation.feature b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/Constraints/EqualityConstraintValidation.feature index 7f97bce8a..c8e4be934 100644 --- a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/Constraints/EqualityConstraintValidation.feature +++ b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/Constraints/EqualityConstraintValidation.feature @@ -73,15 +73,17 @@ Feature: Equality Constraint Validation And the response body is """ { - "detail": "The request could not be processed. See 'errors' for details.", - "type": "urn:ed-fi:api:bad-request", - "title": "Bad Request", - "status": 400, - "correlationId": null, - "validationErrors": null, - "errors": [ - "Constraint failure: document paths $.classPeriods[*].classPeriodReference.schoolId and $.schoolReference.schoolId must have the same values" - ] + "detail": "Data validation failed. See 'validationErrors' for details", + "type": "urn:ed-fi:api:bad-request:data-validation-failed", + "title": "Data Validation Failed", + "status": 400, + "correlationId": null, + "validationErrors": { + "$.classPeriods[*].classPeriodReference.schoolId": [ + "All values supplied for 'schoolId' must match. Review all references (including those higher up in the resource's data) and align the following conflicting values: '1', '255901001'" + ] + }, + "errors": null } """ @@ -110,15 +112,17 @@ Feature: Equality Constraint Validation And the response body is """ { - "detail": "The request could not be processed. See 'errors' for details.", - "type": "urn:ed-fi:api:bad-request", - "title": "Bad Request", - "status": 400, - "correlationId": null, - "validationErrors": null, - "errors": [ - "Constraint failure: document paths $.classPeriods[*].classPeriodReference.schoolId and $.courseOfferingReference.schoolId must have the same values" - ] + "detail": "Data validation failed. See 'validationErrors' for details", + "type": "urn:ed-fi:api:bad-request:data-validation-failed", + "title": "Data Validation Failed", + "status": 400, + "correlationId": null, + "validationErrors": { + "$.classPeriods[*].classPeriodReference.schoolId": [ + "All values supplied for 'schoolId' must match. Review all references (including those higher up in the resource's data) and align the following conflicting values: '255901107', '255901001'" + ] + }, + "errors": null } """ @@ -153,15 +157,17 @@ Feature: Equality Constraint Validation And the response body is """ { - "detail": "The request could not be processed. See 'errors' for details.", - "type": "urn:ed-fi:api:bad-request", - "title": "Bad Request", - "status": 400, - "correlationId": null, - "validationErrors": null, - "errors": [ - "Constraint failure: document paths $.classPeriods[*].classPeriodReference.schoolId and $.courseOfferingReference.schoolId must have the same values" - ] + "detail": "Data validation failed. See 'validationErrors' for details", + "type": "urn:ed-fi:api:bad-request:data-validation-failed", + "title": "Data Validation Failed", + "status": 400, + "correlationId": null, + "validationErrors": { + "$.classPeriods[*].classPeriodReference.schoolId": [ + "All values supplied for 'schoolId' must match. Review all references (including those higher up in the resource's data) and align the following conflicting values: '1', '255901001'" + ] + }, + "errors": null } """ diff --git a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/KeyUnification.feature b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/KeyUnification.feature index 254d56f61..1f756f526 100644 --- a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/KeyUnification.feature +++ b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/KeyUnification.feature @@ -69,14 +69,16 @@ Feature: Validation of Natural Key Unification Then the response body is """ { - "validationErrors":null, - "errors":[ - "Constraint failure: document paths $.schoolReference.schoolId and $.sessionReference.schoolId must have the same values" - ], + "validationErrors":{ + "$.schoolReference.schoolId": [ + "All values supplied for 'schoolId' must match. Review all references (including those higher up in the resource's data) and align the following conflicting values: '123', '999'" + ] + }, + "errors": null, "detail":"The request could not be processed. See 'errors' for details.", "type":"urn:ed-fi:api:bad-request", "title":"Bad Request", - "status":400, + "status":400, "correlationId":null } """ diff --git a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/SuperclassReferenceValidation.feature b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/SuperclassReferenceValidation.feature index 7f8148592..78e430316 100644 --- a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/SuperclassReferenceValidation.feature +++ b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/SuperclassReferenceValidation.feature @@ -130,9 +130,7 @@ Feature: SuperclassReferenceValidation of Creation, Update and Deletion of resou "type": "urn:ed-fi:api:data-conflict:unresolved-reference", "title": "Unresolved Reference", "status": 409, - "correlationId": null, - "validationErrors": null, - "errors": null + "correlationId": null } """ diff --git a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/UpdateReferenceValidation.feature b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/UpdateReferenceValidation.feature index 477f02287..72c72b814 100644 --- a/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/UpdateReferenceValidation.feature +++ b/src/tests/EdFi.DataManagementService.Tests.E2E/Features/References/UpdateReferenceValidation.feature @@ -104,9 +104,7 @@ Feature: Update Reference Validation "type": "urn:ed-fi:api:data-conflict:unresolved-reference", "title": "Unresolved Reference", "status": 409, - "correlationId": null, - "validationErrors": null, - "errors": null + "correlationId": null } """ diff --git a/src/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/StepDefinitions.cs b/src/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/StepDefinitions.cs index f39561388..d546eaab3 100644 --- a/src/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/StepDefinitions.cs +++ b/src/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/StepDefinitions.cs @@ -302,11 +302,22 @@ public void ThenTheResponseBodyIs(string expectedBody) _logger.log.Information(responseJson.ToString()); - responseJson.Should().BeEquivalentTo(expectedBodyJson, options => options - .WithoutStrictOrdering() - .IgnoringCyclicReferences() - .Excluding(x => x.Path.EndsWith("correlationId")) - ); + (responseJson as JsonObject)?.Remove("correlationId"); + (expectedBodyJson as JsonObject)?.Remove("correlationId"); + + try + { + responseJson.Should().BeEquivalentTo(expectedBodyJson, options => options + .WithoutStrictOrdering() + .IgnoringCyclicReferences() + .RespectingRuntimeTypes() + ); + } + catch (Exception e) + { + _logger.log.Information(e.Message); + throw; + } } // Use Regex to find all occurrences of {id} in the body