import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import javax.ws.rs.core.Response.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.common.ErrorTags;
private static final long serialVersionUID = 3L;
private final List<RestconfError> errors;
- private final Status status;
// FIXME: this field should be non-null
private final transient @Nullable EffectiveModelContext modelContext;
// FIXME: We override getMessage so supplied message is lost for any public access
// this was lost also in original code.
super(cause);
- if (!errors.isEmpty()) {
- this.errors = List.copyOf(errors);
- } else {
+ if (errors.isEmpty()) {
this.errors = List.of(new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message));
+ } else {
+ this.errors = List.copyOf(errors);
}
- status = null;
modelContext = null;
}
this(message, cause, convertToRestconfErrors(rpcErrors));
}
- /**
- * Constructs an instance with an HTTP status and no error information.
- *
- * @param status
- * the HTTP status.
- */
- public RestconfDocumentedException(final Status status) {
- errors = List.of();
- modelContext = null;
- this.status = requireNonNull(status, "Status can't be null");
- }
-
public RestconfDocumentedException(final Throwable cause, final RestconfError error) {
super(cause);
- status = ErrorTags.statusOf(error.getErrorTag());
errors = List.of(error);
modelContext = null;
}
public RestconfDocumentedException(final Throwable cause, final RestconfError error,
final EffectiveModelContext modelContext) {
super(cause);
- status = ErrorTags.statusOf(error.getErrorTag());
errors = List.of(error);
this.modelContext = requireNonNull(modelContext);
}
public RestconfDocumentedException(final Throwable cause, final List<RestconfError> errors) {
super(cause);
- status = ErrorTags.statusOf(errors.get(0).getErrorTag());
+ if (errors.isEmpty()) {
+ throw new IllegalArgumentException("At least one error is required");
+ }
this.errors = List.copyOf(errors);
modelContext = null;
}
public List<RestconfError> getErrors() {
return errors;
}
-
- public Status getStatus() {
- return status;
- }
}
@RunWith(Parameterized.class)
public class RestconfDocumentedExceptionMapperTest {
-
- private static final String EMPTY_XML = "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"></errors>";
- private static final String EMPTY_JSON = "{}";
private static final QNameModule MONITORING_MODULE_INFO = QNameModule.create(
XMLNamespace.of("instance:identifier:patch:module"), Revision.of("2015-11-21"));
.build())));
return Arrays.asList(new Object[][] {
- {
- "Mapping of the exception without any errors and XML output derived from content type",
- new RestconfDocumentedException(Status.BAD_REQUEST),
- mockHttpHeaders(MediaType.APPLICATION_XML_TYPE, List.of()),
- Response.status(Status.BAD_REQUEST)
- .type(MediaTypes.APPLICATION_YANG_DATA_XML_TYPE)
- .entity(EMPTY_XML)
- .build()
- },
- {
- "Mapping of the exception without any errors and JSON output derived from unsupported content type",
- new RestconfDocumentedException(Status.INTERNAL_SERVER_ERROR),
- mockHttpHeaders(MediaType.APPLICATION_FORM_URLENCODED_TYPE, List.of()),
- Response.status(Status.INTERNAL_SERVER_ERROR)
- .type(MediaTypes.APPLICATION_YANG_DATA_JSON_TYPE)
- .entity(EMPTY_JSON)
- .build()
- },
- {
- "Mapping of the exception without any errors and JSON output derived from missing content type "
- + "and accepted media types",
- new RestconfDocumentedException(Status.NOT_IMPLEMENTED),
- mockHttpHeaders(null, List.of()),
- Response.status(Status.NOT_IMPLEMENTED)
- .type(MediaTypes.APPLICATION_YANG_DATA_JSON_TYPE)
- .entity(EMPTY_JSON)
- .build()
- },
- {
- "Mapping of the exception without any errors and JSON output derived from expected types - both JSON"
- + "and XML types are accepted, but server should prefer JSON format",
- new RestconfDocumentedException(Status.INTERNAL_SERVER_ERROR),
- mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(
- MediaType.APPLICATION_FORM_URLENCODED_TYPE, MediaType.APPLICATION_XML_TYPE,
- MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_OCTET_STREAM_TYPE)),
- Response.status(Status.INTERNAL_SERVER_ERROR)
- .type(MediaTypes.APPLICATION_YANG_DATA_JSON_TYPE)
- .entity(EMPTY_JSON)
- .build()
- },
- {
- "Mapping of the exception without any errors and JSON output derived from expected types - there"
- + "is only a wildcard type that should be mapped to default type",
- new RestconfDocumentedException(Status.NOT_FOUND),
- mockHttpHeaders(null, List.of(MediaType.WILDCARD_TYPE)),
- Response.status(Status.NOT_FOUND)
- .type(MediaTypes.APPLICATION_YANG_DATA_JSON_TYPE)
- .entity(EMPTY_JSON)
- .build()
- },
- {
- "Mapping of the exception without any errors and XML output derived from expected types - "
- + "we should choose the most specific and supported type",
- new RestconfDocumentedException(Status.NOT_FOUND),
- mockHttpHeaders(null, List.of(MediaType.valueOf("*/yang-data+json"),
- MediaType.valueOf("application/yang-data+xml"), MediaType.WILDCARD_TYPE)),
- Response.status(Status.NOT_FOUND)
- .type(MediaTypes.APPLICATION_YANG_DATA_XML_TYPE)
- .entity(EMPTY_XML)
- .build()
- },
- {
- "Mapping of the exception without any errors and XML output derived from expected types - "
- + "we should choose the most specific and supported type",
- new RestconfDocumentedException(Status.NOT_FOUND),
- mockHttpHeaders(null, List.of(MediaType.valueOf("*/unsupported"),
- MediaType.valueOf("application/*"), MediaType.WILDCARD_TYPE)),
- Response.status(Status.NOT_FOUND)
- .type(MediaTypes.APPLICATION_YANG_DATA_JSON_TYPE)
- .entity(EMPTY_JSON)
- .build()
- },
{
"Mapping of the exception with one error entry but null status code. This status code should"
+ "be derived from single error entry; JSON output",
mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(MediaTypes.APPLICATION_YANG_PATCH_JSON_TYPE)),
Response.status(Status.INTERNAL_SERVER_ERROR)
.type(MediaTypes.APPLICATION_YANG_DATA_JSON_TYPE)
- .entity("{\n"
- + " \"errors\": {\n"
- + " \"error\": [\n"
- + " {\n"
- + " \"error-tag\": \"operation-failed\",\n"
- + " \"error-message\": \"Sample error message\",\n"
- + " \"error-type\": \"application\"\n"
- + " }\n"
- + " ]\n"
- + " }\n"
- + "}")
+ .entity("""
+ {
+ "errors": {
+ "error": [
+ {
+ "error-tag": "operation-failed",
+ "error-message": "Sample error message",
+ "error-type": "application"
+ }
+ ]
+ }
+ }""")
.build()
},
{
mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(MediaTypes.APPLICATION_YANG_PATCH_XML_TYPE)),
Response.status(Status.BAD_REQUEST)
.type(MediaTypes.APPLICATION_YANG_DATA_XML_TYPE)
- .entity("<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n"
- + "<error>\n"
- + "<error-message>message 1</error-message>\n"
- + "<error-tag>bad-attribute</error-tag>\n"
- + "<error-type>application</error-type>\n"
- + "</error>\n"
- + "<error>\n"
- + "<error-message>message 2</error-message>\n"
- + "<error-tag>operation-failed</error-tag>\n"
- + "<error-type>application</error-type>\n"
- + "</error>\n"
- + "</errors>")
+ .entity("""
+ <errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
+ <error>
+ <error-message>message 1</error-message>
+ <error-tag>bad-attribute</error-tag>
+ <error-type>application</error-type>
+ </error>
+ <error>
+ <error-message>message 2</error-message>
+ <error-tag>operation-failed</error-tag>
+ <error-type>application</error-type>
+ </error>
+ </errors>""")
.build()
},
{
MediaType.APPLICATION_JSON_TYPE)),
Response.status(Status.BAD_REQUEST)
.type(MediaTypes.APPLICATION_YANG_DATA_JSON_TYPE)
- .entity("{\n"
- + " \"errors\": {\n"
- + " \"error\": [\n"
- + " {\n"
- + " \"error-tag\": \"bad-attribute\",\n"
- + " \"error-app-tag\": \"app tag #1\",\n"
- + " \"error-message\": \"message 1\",\n"
- + " \"error-type\": \"application\"\n"
- + " },\n"
- + " {\n"
- + " \"error-tag\": \"operation-failed\",\n"
- + " \"error-app-tag\": \"app tag #2\",\n"
- + " \"error-info\": \"my info\",\n"
- + " \"error-message\": \"message 2\",\n"
- + " \"error-type\": \"application\"\n"
- + " },\n"
- + " {\n"
- + " \"error-tag\": \"data-missing\",\n"
- + " \"error-app-tag\": \" app tag #3\",\n"
- + " \"error-info\": \"my error info\",\n"
- + " \"error-message\": \"message 3\",\n"
- + " \"error-path\": \"/instance-identifier-patch-module:patch-cont/"
- + "my-list1[name='sample']/my-leaf12\",\n"
- + " \"error-type\": \"rpc\"\n"
- + " }\n"
- + " ]\n"
- + " }\n"
- + "}")
+ .entity("""
+ {
+ "errors": {
+ "error": [
+ {
+ "error-tag": "bad-attribute",
+ "error-app-tag": "app tag #1",
+ "error-message": "message 1",
+ "error-type": "application"
+ },
+ {
+ "error-tag": "operation-failed",
+ "error-app-tag": "app tag #2",
+ "error-info": "my info",
+ "error-message": "message 2",
+ "error-type": "application"
+ },
+ {
+ "error-tag": "data-missing",
+ "error-app-tag": " app tag #3",
+ "error-info": "my error info",
+ "error-message": "message 3",
+ "error-path": "/instance-identifier-patch-module:patch-cont/\
+ my-list1[name='sample']/my-leaf12",
+ "error-type": "rpc"
+ }
+ ]
+ }
+ }""")
.build()
},
{
List.of(MediaTypes.APPLICATION_YANG_DATA_XML_TYPE)),
Response.status(Status.BAD_REQUEST)
.type(MediaTypes.APPLICATION_YANG_DATA_XML_TYPE)
- .entity("<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n"
- + "<error>\n"
- + "<error-type>application</error-type>\n"
- + "<error-message>message 1</error-message>\n"
- + "<error-tag>bad-attribute</error-tag>\n"
- + "<error-app-tag>app tag #1</error-app-tag>\n"
- + "</error>\n"
- + "<error>\n"
- + "<error-type>application</error-type>\n"
- + "<error-message>message 2</error-message>\n"
- + "<error-tag>operation-failed</error-tag>\n"
- + "<error-app-tag>app tag #2</error-app-tag>\n"
- + "<error-info>my info</error-info></error>\n"
- + "<error>\n"
- + "<error-type>rpc</error-type>\n"
- + "<error-path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont/"
- + "a:my-list1[a:name='sample']/a:my-leaf12</error-path>\n"
- + "<error-message>message 3</error-message>\n"
- + "<error-tag>data-missing</error-tag>\n"
- + "<error-app-tag> app tag #3</error-app-tag>\n"
- + "<error-info>my error info</error-info>\n"
- + "</error>\n"
- + "</errors>")
+ .entity("""
+ <errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
+ <error>
+ <error-type>application</error-type>
+ <error-message>message 1</error-message>
+ <error-tag>bad-attribute</error-tag>
+ <error-app-tag>app tag #1</error-app-tag>
+ </error>
+ <error>
+ <error-type>application</error-type>
+ <error-message>message 2</error-message>
+ <error-tag>operation-failed</error-tag>
+ <error-app-tag>app tag #2</error-app-tag>
+ <error-info>my info</error-info></error>
+ <error>
+ <error-type>rpc</error-type>
+ <error-path xmlns:a="instance:identifier:patch:module">/a:patch-cont/\
+ a:my-list1[a:name='sample']/a:my-leaf12</error-path>
+ <error-message>message 3</error-message>
+ <error-tag>data-missing</error-tag>
+ <error-app-tag> app tag #3</error-app-tag>
+ <error-info>my error info</error-info>
+ </error>
+ </errors>""")
.build()
}
});