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:
Andy Wilkinson
2015-07-13 11:44:06 +01:00
parent cdcddcf91b
commit 88e26ba88b
7 changed files with 111 additions and 13 deletions

View File

@@ -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 + "'");
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}
});
}

View File

@@ -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);
}
}

View File

@@ -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>();

View File

@@ -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\":{}}"));

View File

@@ -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);