From 2b5eab309ce53098235bace0e52a9331284ad2f9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Apr 2021 14:18:47 +0100 Subject: [PATCH] Improve error message when extracting a sometimes absent sub-section Fixes gh-715 --- .../FieldPathPayloadSubsectionExtractor.java | 10 ++++++--- .../restdocs/payload/JsonFieldPaths.java | 7 ++++++- ...ldPathPayloadSubsectionExtractorTests.java | 21 ++++++++++++++++++- .../restdocs/payload/JsonFieldPathsTests.java | 13 +++++++++++- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java index 7a0b5070..69d48063 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.ObjectMapper; @@ -93,7 +94,9 @@ public class FieldPathPayloadSubsectionExtractor List extractedList = (List) value; JsonContentHandler contentHandler = new JsonContentHandler(payload, descriptorsByPath.values()); Set uncommonPaths = JsonFieldPaths.from(extractedList).getUncommon().stream() - .map((path) -> JsonFieldPath.compile(this.fieldPath + "." + path)).filter((path) -> { + .map((path) -> JsonFieldPath + .compile((path.equals("")) ? this.fieldPath : this.fieldPath + "." + path)) + .filter((path) -> { FieldDescriptor descriptorForPath = descriptorsByPath.getOrDefault(path, new FieldDescriptor(path.toString())); return contentHandler.isMissing(descriptorForPath); @@ -105,7 +108,8 @@ public class FieldPathPayloadSubsectionExtractor String message = this.fieldPath + " identifies multiple sections of " + "the payload and they do not have a common structure. The " + "following non-optional uncommon paths were found: "; - message += uncommonPaths; + message += uncommonPaths.stream().map(JsonFieldPath::toString) + .collect(Collectors.toCollection(TreeSet::new)); throw new PayloadHandlingException(message); } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPaths.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPaths.java index 9ae350f9..87c64333 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPaths.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPaths.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; + /** * {@code JsonFieldPaths} provides support for extracting fields paths from JSON * structures and identifying uncommon paths. @@ -69,6 +71,9 @@ final class JsonFieldPaths { else if (object instanceof Map) { from(paths, parent, (Map) object); } + else if (object.equals(ExtractedField.ABSENT)) { + paths.add(parent); + } } private static void from(Set paths, String parent, List items) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java index b50efb97..3bd66768 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -107,6 +107,25 @@ public class FieldPathPayloadSubsectionExtractorTests { "{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON); } + @Test + public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() + throws JsonParseException, JsonMappingException, IOException { + this.thrown.expect(PayloadHandlingException.class); + this.thrown.expectMessage("The following non-optional uncommon paths were found: [*.d, *.d.e, *.d.f]"); + new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection( + "{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON); + } + + @Test + public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhereAllSubsectionFieldsAreOptional() + throws IOException { + this.thrown.expect(PayloadHandlingException.class); + this.thrown.expectMessage("The following non-optional uncommon paths were found: [*.d]"); + new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection( + "{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON, + Arrays.asList(new FieldDescriptor("e").optional(), new FieldDescriptor("f").optional())); + } + @Test @SuppressWarnings("unchecked") public void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMultiElementArrayInAJsonMap() diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java index 3558a61c..9f18ddc2 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import java.util.Arrays; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; +import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -74,6 +76,15 @@ public class JsonFieldPathsTests { .getUncommon()).containsExactly("b.c", "b.d"); } + @Test + public void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsUncommon() { + assertThat( + JsonFieldPaths + .from(Arrays.asList(ExtractedField.ABSENT, ("{\"a\": 1, \"b\": {\"c\": 1}}"), + json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}"))) + .getUncommon()).containsExactly("", "a", "b", "b.c", "b.d"); + } + @Test public void missingEntryBeneathArrayIsIdentifiedAsUncommon() { assertThat(JsonFieldPaths