Improve beneathPath to work with multiple matches with common structure

Closes gh-473
This commit is contained in:
Andy Wilkinson
2018-11-22 11:00:23 +00:00
parent 5b493977cc
commit 9e1ba8a922
5 changed files with 263 additions and 31 deletions

View File

@@ -17,12 +17,13 @@
package org.springframework.restdocs.payload;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldPath.PathType;
import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField;
/**
@@ -44,7 +45,7 @@ public class FieldPathPayloadSubsectionExtractor
/**
* Creates a new {@code FieldPathPayloadSubsectionExtractor} that will extract the
* subsection of the JSON payload found at the given {@code fieldPath}. The
* subsection of the JSON payload beneath the given {@code fieldPath}. The
* {@code fieldPath} prefixed with {@code beneath-} with be used as the subsection ID.
* @param fieldPath the path of the field
*/
@@ -54,8 +55,8 @@ public class FieldPathPayloadSubsectionExtractor
/**
* Creates a new {@code FieldPathPayloadSubsectionExtractor} that will extract the
* subsection of the JSON payload found at the given {@code fieldPath} and that will
* us the given {@code subsectionId} to identify the subsection.
* subsection of the JSON payload beneath the given {@code fieldPath} and that will
* use the given {@code subsectionId} to identify the subsection.
* @param fieldPath the path of the field
* @param subsectionId the ID of the subsection
*/
@@ -70,14 +71,23 @@ public class FieldPathPayloadSubsectionExtractor
ExtractedField extractedField = new JsonFieldProcessor().extract(
this.fieldPath, objectMapper.readValue(payload, Object.class));
Object value = extractedField.getValue();
if (value instanceof List && extractedField.getType() == PathType.MULTI) {
if (value instanceof List) {
List<?> extractedList = (List<?>) value;
if (extractedList.size() == 1) {
Set<String> uncommonPaths = JsonFieldPaths.from(extractedList)
.getUncommon();
if (uncommonPaths.isEmpty()) {
value = extractedList.get(0);
}
else {
throw new PayloadHandlingException(this.fieldPath
+ " does not uniquely identify a subsection of the payload");
String message = this.fieldPath + " identifies multiple sections of "
+ "the payload and they do not have a common structure. The "
+ "following uncommon paths were found: ";
List<String> prefixedPaths = new ArrayList<>();
for (String uncommonPath : uncommonPaths) {
prefixedPaths.add(this.fieldPath + "." + uncommonPath);
}
message += prefixedPaths;
throw new PayloadHandlingException(message);
}
}
return objectMapper.writeValueAsBytes(value);

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2014-2018 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.payload;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* {@code JsonFieldPaths} provides support for extracting fields paths from JSON
* structures and identifying uncommon paths.
*
* @author Andy Wilkinson
*/
final class JsonFieldPaths {
private final Set<String> uncommonFieldPaths;
private JsonFieldPaths(Set<String> uncommonFieldPaths) {
this.uncommonFieldPaths = uncommonFieldPaths;
}
Set<String> getUncommon() {
return this.uncommonFieldPaths;
}
static JsonFieldPaths from(Collection<?> items) {
Set<Set<String>> itemsFieldPaths = new HashSet<>();
Set<String> allFieldPaths = new HashSet<>();
for (Object item : items) {
Set<String> paths = new LinkedHashSet<>();
from(paths, "", item);
itemsFieldPaths.add(paths);
allFieldPaths.addAll(paths);
}
Set<String> uncommonFieldPaths = new HashSet<>();
for (Set<String> itemFieldPaths : itemsFieldPaths) {
Set<String> uncommonForItem = new HashSet<>(allFieldPaths);
uncommonForItem.removeAll(itemFieldPaths);
uncommonFieldPaths.addAll(uncommonForItem);
}
return new JsonFieldPaths(uncommonFieldPaths);
}
private static void from(Set<String> paths, String parent, Object object) {
if (object instanceof List) {
String path = append(parent, "[]");
paths.add(path);
from(paths, path, (List<?>) object);
}
else if (object instanceof Map) {
from(paths, parent, (Map<?, ?>) object);
}
}
private static void from(Set<String> paths, String parent, List<?> items) {
for (Object item : items) {
from(paths, parent, item);
}
}
private static void from(Set<String> paths, String parent, Map<?, ?> map) {
for (Entry<?, ?> entry : map.entrySet()) {
String path = append(parent, entry.getKey());
paths.add(path);
from(paths, path, entry.getValue());
}
}
private static String append(String path, Object suffix) {
return (path.length() == 0) ? ("" + suffix) : (path + "." + suffix);
}
}