Improve diagnostics when no type is provided for absent optional field
Previously, if an optional field was being documented and that field was not present in the payload a failure would occur with the message "The payload does not contain a field with the path 'the.field.path'". This isn't very helpful as it doesn't explain why the field was being looked for (to resolve its type). This commit improves the diagnostics to improve the message to explain that a field's type could not be determined as it didn't exist in the payload and to suggest the use of FieldDescriptor.type(FieldType) to provide a type. Closes gh-83
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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;
|
||||
|
||||
/**
|
||||
* A {@code FieldDoesNotExistException} is thrown when a requested field does not exist in
|
||||
* a payload.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class FieldDoesNotExistException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new {@code FieldDoesNotExistException} that indicates that the field with
|
||||
* the given {@code fieldPath} does not exist.
|
||||
*
|
||||
* @param fieldPath the path of the field that does not exist
|
||||
*/
|
||||
public FieldDoesNotExistException(FieldPath fieldPath) {
|
||||
super("The payload does not contain a field with the path '" + fieldPath + "'");
|
||||
}
|
||||
}
|
||||
@@ -55,8 +55,7 @@ final class FieldProcessor {
|
||||
|
||||
});
|
||||
if (matches.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"The payload does not contain a field with the path '" + path + "'");
|
||||
throw new FieldDoesNotExistException(path);
|
||||
}
|
||||
if (path.isPrecise()) {
|
||||
return matches.get(0);
|
||||
|
||||
@@ -78,15 +78,32 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
||||
for (Entry<String, FieldDescriptor> entry : FieldSnippetResultHandler.this.descriptorsByPath
|
||||
.entrySet()) {
|
||||
FieldDescriptor descriptor = entry.getValue();
|
||||
FieldType type = descriptor.getType() != null ? descriptor.getType()
|
||||
: FieldSnippetResultHandler.this.fieldTypeResolver
|
||||
.resolveFieldType(descriptor.getPath(), payload);
|
||||
FieldType type = getFieldType(descriptor, payload);
|
||||
tableWriter.row(entry.getKey().toString(), type.toString(), entry
|
||||
.getValue().getDescription());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private FieldType getFieldType(FieldDescriptor descriptor, Object payload) {
|
||||
if (descriptor.getType() != null) {
|
||||
return descriptor.getType();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
return FieldSnippetResultHandler.this.fieldTypeResolver
|
||||
.resolveFieldType(descriptor.getPath(), payload);
|
||||
}
|
||||
catch (FieldDoesNotExistException ex) {
|
||||
String message = "Cannot determine the type of the field '"
|
||||
+ descriptor.getPath() + "' as it is not present in the"
|
||||
+ " payload. Please provide a type using"
|
||||
+ " FieldDescriptor.type(FieldType).";
|
||||
throw new FieldTypeRequiredException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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;
|
||||
|
||||
/**
|
||||
* A {@code FieldTypeRequiredException} is thrown when a field's type cannot be determined
|
||||
* automatically and, therefore, must be explicitly provided.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class FieldTypeRequiredException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new {@code FieldTypeRequiredException} indicating that a type is required
|
||||
* for the reason described in the given {@code message}.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
public FieldTypeRequiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -129,27 +129,27 @@ public class FieldProcessorTests {
|
||||
Arrays.asList(4))));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentTopLevelField() {
|
||||
this.fieldProcessor
|
||||
.extract(FieldPath.compile("a"), new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentNestedField() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("a", new HashMap<String, Object>());
|
||||
this.fieldProcessor.extract(FieldPath.compile("a.b"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentNestedFieldWhenParentIsNotAMap() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("a", 5);
|
||||
this.fieldProcessor.extract(FieldPath.compile("a.b"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentFieldWhenParentIsAnArray() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
HashMap<String, Object> alpha = new HashMap<String, Object>();
|
||||
@@ -158,20 +158,20 @@ public class FieldProcessorTests {
|
||||
this.fieldProcessor.extract(FieldPath.compile("a.b.c"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentArrayField() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
this.fieldProcessor.extract(FieldPath.compile("a[]"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentArrayFieldAsTypeDoesNotMatch() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("a", 5);
|
||||
this.fieldProcessor.extract(FieldPath.compile("a[]"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentFieldBeneathAnArray() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
HashMap<String, Object> alpha = new HashMap<String, Object>();
|
||||
|
||||
@@ -93,7 +93,7 @@ public class FieldTypeResolverTests {
|
||||
|
||||
@Test
|
||||
public void nonExistentFieldProducesIllegalArgumentException() throws IOException {
|
||||
this.thrownException.expect(IllegalArgumentException.class);
|
||||
this.thrownException.expect(FieldDoesNotExistException.class);
|
||||
this.thrownException
|
||||
.expectMessage("The payload does not contain a field with the path 'a.b'");
|
||||
this.fieldTypeResolver.resolveFieldType("a.b", createPayload("{\"a\":{}}"));
|
||||
|
||||
@@ -141,6 +141,14 @@ public class PayloadDocumentationTests {
|
||||
result(get("/foo").content("{}")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException {
|
||||
this.thrown.expect(FieldTypeRequiredException.class);
|
||||
documentRequestFields("missing-optional-request-field-with-no-type",
|
||||
fieldWithPath("a.b").description("one").optional()).handle(
|
||||
result(get("/foo").content("{ }")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedRequestFieldAndMissingRequestField() throws IOException {
|
||||
this.thrown.expect(SnippetGenerationException.class);
|
||||
|
||||
Reference in New Issue
Block a user