From 4e2e5beb0ab9fa93b74ea1ce97969cf15257630f Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Tue, 9 Apr 2024 22:35:33 +0200 Subject: [PATCH] Introduce HttpStatusCode RESTCONF fall into two categories a success, indicating a 20x status or a failure, indicating a 40x status. RestconfDocumentedException acts as a wrapper for the latter case, with the additional twist of being able to report multiple errors via the 'yang-errors' template. All failure modes in RESTCONF end up being driven by a YANG ErrorTag, hence our server-size response structure really wants to be split along success/failure lines. This patch takes the first step towards that split, defining a restconf.api.HttpStatusCode, which acts as a semantic capture of a HTTP Status Code and use that in error mapping logic. While we are visiting here, we make the status code for data-missing configurable for each northbound instance -- rather than our previous use of a global property. JIRA: NETCONF-1188 Change-Id: I1365256f9fad4ffe66358e6e9da4dfa337a755fd Signed-off-by: Robert Varga --- .../restconf/api/HttpStatusCode.java | 265 ++++++++++++++++++ .../restconf/nb/jaxrs/JaxRsRestconf.java | 33 ++- .../restconf/nb/rfc8040/ErrorTagMapping.java | 78 ++++++ .../restconf/nb/rfc8040/JaxRsNorthbound.java | 6 +- .../restconf/nb/rfc8040/OSGiNorthbound.java | 15 +- .../RestconfDocumentedExceptionMapper.java | 29 +- .../restconf/nb/rfc8040/legacy/ErrorTags.java | 70 +---- .../DefaultRestconfStreamServletFactory.java | 19 +- .../streams/RestconfStreamServletFactory.java | 12 +- .../nb/jaxrs/AbstractRestconfTest.java | 3 +- .../restconf/nb/jaxrs/Netconf799Test.java | 5 +- .../jaxrs/RestconfSchemaServiceMountTest.java | 3 +- .../nb/jaxrs/RestconfSchemaServiceTest.java | 3 +- .../rfc8040/{legacy => }/ErrorTagsTest.java | 7 +- ...RestconfDocumentedExceptionMapperTest.java | 4 +- 15 files changed, 434 insertions(+), 118 deletions(-) create mode 100644 protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/HttpStatusCode.java create mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagMapping.java rename restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/{legacy => }/ErrorTagsTest.java (87%) diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/HttpStatusCode.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/HttpStatusCode.java new file mode 100644 index 0000000000..3cc08840a7 --- /dev/null +++ b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/HttpStatusCode.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.restconf.api; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A simple DTO definitiong an HTTP Status Code. Integer + * values used here are assigned through the + * IANA Status Code Registry. + */ +@Beta +@NonNullByDefault +public final class HttpStatusCode { + /** + * 200 OK. + */ + public static final HttpStatusCode OK = new HttpStatusCode(200, "OK"); + /** + * 201 Created. + */ + public static final HttpStatusCode CREATED = new HttpStatusCode(201, "Created"); + /** + * 202 Accepted. + */ + public static final HttpStatusCode ACCEPTED = new HttpStatusCode(202, "Accepted"); + /** + * 203 Non-Authoritative Information. + */ + public static final HttpStatusCode NON_AUTHORITATIVE_INFORMATION = + new HttpStatusCode(203, "Non-Authoritative Information"); + /** + * 204 No Content. + */ + public static final HttpStatusCode NO_CONTENT = new HttpStatusCode(204, "No Content"); + /** + * 205 Reset Content. + */ + public static final HttpStatusCode RESET_CONTENT = new HttpStatusCode(205, "Reset Content"); + /** + * 206 Partial Content. + */ + public static final HttpStatusCode PARTIAL_CONTENT = new HttpStatusCode(206, "Partial Content"); + /** + * 300 Multiple Choices. + */ + public static final HttpStatusCode MULTIPLE_CHOICES = new HttpStatusCode(300, "Multiple Choices"); + /** + * 301 Moved Permanently. + */ + public static final HttpStatusCode MOVED_PERMANENTLY = new HttpStatusCode(301, "Moved Permanently"); + /** + * 302 Found. + */ + public static final HttpStatusCode FOUND = new HttpStatusCode(302, "Found"); + /** + * 303 See Other. + */ + public static final HttpStatusCode SEE_OTHER = new HttpStatusCode(303, "See Other"); + /** + * 304 Not Modified. + */ + public static final HttpStatusCode NOT_MODIFIED = new HttpStatusCode(304, "Not Modified"); + /** + * 305 Use Proxy. + */ + public static final HttpStatusCode USE_PROXY = new HttpStatusCode(305, "Use Proxy"); + /** + * 307 Temporary Redirect. + */ + public static final HttpStatusCode TEMPORARY_REDIRECT = new HttpStatusCode(307, "Temporary Redirect"); + /** + * 308 Permanent Redirect. + */ + public static final HttpStatusCode PERMANENT_REDIRECT = new HttpStatusCode(308, "Permanent Redirect"); + /** + * 400 Bad Request. + */ + public static final HttpStatusCode BAD_REQUEST = new HttpStatusCode(400, "Bad Request"); + /** + * 401 Unauthorized. + */ + public static final HttpStatusCode UNAUTHORIZED = new HttpStatusCode(401, "Unauthorized"); + /** + * 402 Payment Required. + */ + public static final HttpStatusCode PAYMENT_REQUIRED = new HttpStatusCode(402, "Payment Required"); + /** + * 403 Forbidden. + */ + public static final HttpStatusCode FORBIDDEN = new HttpStatusCode(403, "Forbidden"); + /** + * 404 Not Found. + */ + public static final HttpStatusCode NOT_FOUND = new HttpStatusCode(404, "Not Found"); + /** + * 405 Method Not Allowed. + */ + public static final HttpStatusCode METHOD_NOT_ALLOWED = new HttpStatusCode(405, "Method Not Allowed"); + /** + * 406 Not Acceptable. + */ + public static final HttpStatusCode NOT_ACCEPTABLE = new HttpStatusCode(406, "Not Acceptable"); + /** + * 407 Proxy Authentication Required. + */ + public static final HttpStatusCode PROXY_AUTHENTICATION_REQUIRED = + new HttpStatusCode(407, "Proxy Authentication Required"); + /** + * 408 Request Timeout. + */ + public static final HttpStatusCode REQUEST_TIMEOUT = new HttpStatusCode(408, "Request Timeout"); + /** + * 409 Conflict. + */ + public static final HttpStatusCode CONFLICT = new HttpStatusCode(409, "Conflict"); + /** + * 410 Gone. + */ + public static final HttpStatusCode GONE = new HttpStatusCode(410, "Gone"); + /** + * 411 Length Required. + */ + public static final HttpStatusCode LENGTH_REQUIRED = new HttpStatusCode(411, "Length Required"); + /** + * 412 Precondition Failed. + */ + public static final HttpStatusCode PRECONDITION_FAILED = new HttpStatusCode(412, "Precondition Failed"); + /** + * 413 Content Too Large. + */ + public static final HttpStatusCode CONTENT_TOO_LARGE = new HttpStatusCode(413, "Content Too Large"); + /** + * 414 Content Too Long. + */ + public static final HttpStatusCode URI_TOO_LONG = new HttpStatusCode(414, "URI Too Long"); + /** + * 415 Unsupported Media Type. + */ + public static final HttpStatusCode UNSUPPORTED_MEDIA_TYPE = new HttpStatusCode(415, "Unsupported Media Type"); + /** + * 416 Requested Range Not Satisfiable. + */ + public static final HttpStatusCode REQUESTED_RANGE_NOT_SATISFIABLE = + new HttpStatusCode(416, "Requested Range Not Satisfiable"); + /** + * 417 Expectation Failed. + */ + public static final HttpStatusCode EXPECTATION_FAILED = new HttpStatusCode(417, "Expectation Failed"); + /** + * 418 (Unused). + */ + @Deprecated(forRemoval = true) + public static final HttpStatusCode I_M_A_TEAPOT = new HttpStatusCode(418, "I'm a teapot"); + /** + * 421 Misdirected Request. + */ + public static final HttpStatusCode MISDIRECTED_REQUEST = new HttpStatusCode(421, "Misdirected Request"); + /** + * 422 Unprocessable Content. + */ + public static final HttpStatusCode UNPROCESSABLE_CONTENT = new HttpStatusCode(422, "Unprocessable Content"); + /** + * 426 Upgrade Required. + */ + public static final HttpStatusCode UPGRADE_REQUIRED = new HttpStatusCode(426, "Upgrade Required"); + /** + * 428 Precondition Required. + */ + public static final HttpStatusCode PRECONDITION_REQUIRED = new HttpStatusCode(428, "Precondition Required"); + /** + * 429 Too Many Requests. + */ + public static final HttpStatusCode TOO_MANY_REQUESTS = new HttpStatusCode(429, "Too Many Requests"); + /** + * 431 Request Header Fields Too Large. + */ + public static final HttpStatusCode REQUEST_HEADER_FIELDS_TOO_LARGE = + new HttpStatusCode(431, "Request Header Fields Too Large"); + /** + * 500 Internal Server Error. + */ + public static final HttpStatusCode INTERNAL_SERVER_ERROR = new HttpStatusCode(500, "Internal Server Error"); + /** + * 501 Not Implemented. + */ + public static final HttpStatusCode NOT_IMPLEMENTED = new HttpStatusCode(501, "Not Implemented"); + /** + * 502 Bad Gateway. + */ + public static final HttpStatusCode BAD_GATEWAY = new HttpStatusCode(502, "Bad Gateway"); + /** + * 503 Service Unavailable. + */ + public static final HttpStatusCode SERVICE_UNAVAILABLE = new HttpStatusCode(503, "Service Unavailable"); + /** + * 504 Gateway Timeout. + */ + public static final HttpStatusCode GATEWAY_TIMEOUT = new HttpStatusCode(504, "Gateway Timeout"); + /** + * 505 HTTP Version Not Supported. + */ + public static final HttpStatusCode HTTP_VERSION_NOT_SUPPORTED = + new HttpStatusCode(505, "HTTP Version Not Supported"); + /** + * 511 Network Authentication Required. + */ + public static final HttpStatusCode NETWORK_AUTHENTICATION_REQUIRED = + new HttpStatusCode(511, "Network Authentication Required"); + + private final int code; + private final String phrase; + + public HttpStatusCode(final int code, final @Nullable String phrase) { + if (code < 100 || code > 599) { + throw new IllegalArgumentException("Invalid statusCode " + code); + } + this.code = code; + this.phrase = phrase; + } + + /** + * Returns the HTTP status code, {@code 100-599}. + * + * @return the HTTP status code + */ + public int code() { + return code; + } + + /** + * Returns the phrase or {@code null}. + * + * @return the phrase or {@code null} + */ + public @Nullable String phrase() { + return phrase; + } + + @Override + public int hashCode() { + return code; + } + + @Override + public boolean equals(final @Nullable Object obj) { + return obj == this || obj instanceof HttpStatusCode other && code == other.code; + } + + @Override + public String toString() { + final var sb = new StringBuilder(HttpStatusCode.class.getSimpleName()).append('(').append(code); + if (phrase != null) { + sb.append(' ').append(phrase); + } + return sb.append(')').toString(); + } +} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java index d0fb429523..5a87e8e27e 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java @@ -38,7 +38,6 @@ import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; -import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.ParamConverter; import javax.ws.rs.ext.ParamConverterProvider; @@ -46,14 +45,15 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.restconf.api.ApiPath; import org.opendaylight.restconf.api.FormatParameters; +import org.opendaylight.restconf.api.HttpStatusCode; import org.opendaylight.restconf.api.MediaTypes; import org.opendaylight.restconf.api.QueryParameters; import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.common.errors.RestconfFuture; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.nb.rfc8040.URLConstants; -import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags; import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload; import org.opendaylight.restconf.server.api.ConfigurationMetadata; import org.opendaylight.restconf.server.api.CreateResourceResult; @@ -121,11 +121,19 @@ public final class JaxRsRestconf implements ParamConverterProvider { private final @NonNull RestconfServer server; private final @NonNull ServerRequest emptyRequest; private final @NonNull PrettyPrintParam prettyPrint; + private final @NonNull ErrorTagMapping errorTagMapping; - public JaxRsRestconf(final RestconfServer server, final PrettyPrintParam prettyPrint) { + public JaxRsRestconf(final RestconfServer server, final ErrorTagMapping errorTagMapping, + final PrettyPrintParam prettyPrint) { this.server = requireNonNull(server); + this.errorTagMapping = requireNonNull(errorTagMapping); this.prettyPrint = requireNonNull(prettyPrint); emptyRequest = ServerRequest.of(QueryParameters.of(), prettyPrint); + + LOG.info("RESTCONF data-missing condition is reported as HTTP status {}", switch (errorTagMapping) { + case ERRATA_5565 -> "404 (Errata 5565)"; + case RFC8040 -> "409 (RFC8040)"; + }); } private @NonNull ServerRequest requestOf(final UriInfo uriInfo) { @@ -409,21 +417,22 @@ public final class JaxRsRestconf implements ParamConverterProvider { } } - private static void completeDataYangPATCH(final RestconfFuture future, + private void completeDataYangPATCH(final RestconfFuture future, final AsyncResponse ar) { future.addCallback(new JaxRsRestconfCallback<>(ar) { @Override Response transform(final DataYangPatchResult result) { - final var status = result.status(); - final var builder = Response.status(statusOf(status)) - .entity(new YangPatchStatusBody(status)); + final var patchStatus = result.status(); + final var statusCode = statusOf(patchStatus); + final var builder = Response.status(statusCode.code(), statusCode.phrase()) + .entity(new YangPatchStatusBody(patchStatus)); fillConfigurationMetadata(builder, result); return builder.build(); } - private static Status statusOf(final PatchStatusContext result) { + private HttpStatusCode statusOf(final PatchStatusContext result) { if (result.ok()) { - return Status.OK; + return HttpStatusCode.OK; } final var globalErrors = result.globalErrors(); if (globalErrors != null && !globalErrors.isEmpty()) { @@ -437,11 +446,11 @@ public final class JaxRsRestconf implements ParamConverterProvider { } } } - return Status.INTERNAL_SERVER_ERROR; + return HttpStatusCode.INTERNAL_SERVER_ERROR; } - private static Status statusOfFirst(final List error) { - return ErrorTags.statusOf(error.get(0).getErrorTag()); + private @NonNull HttpStatusCode statusOfFirst(final List error) { + return errorTagMapping.statusOf(error.get(0).getErrorTag()); } }); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagMapping.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagMapping.java new file mode 100644 index 0000000000..3e79fe8438 --- /dev/null +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagMapping.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.restconf.nb.rfc8040; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ImmutableMap; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.restconf.api.HttpStatusCode; +import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags; +import org.opendaylight.yangtools.yang.common.ErrorTag; + +/** + * Mapping of {@link ErrorTag}s to {@link HttpStatusCode}s. + */ +@NonNullByDefault +public enum ErrorTagMapping { + /** + * Mapping specified by RFC8040: + * {@link ErrorTag#DATA_MISSING} is reported as {@code 409 Conflict}. This may be confusing to users, as {@code GET} + * requests to non-existent datastore resources do not report {@code 404 Not Found} as would be expected from any + * other HTTP server. + */ + RFC8040(HttpStatusCode.CONFLICT), + /** + * Mapping proposed by Errata 5565: + * {@link ErrorTag#DATA_MISSING} is reported as {@code 404 Not Found}. This is more in-line with expectations rooted + * in HTTP/1.1 specification. + */ + ERRATA_5565(HttpStatusCode.NOT_FOUND); + + private ImmutableMap tagToStatus; + + ErrorTagMapping(final HttpStatusCode dataMissing) { + tagToStatus = ImmutableMap.builder() + .put(ErrorTag.IN_USE, HttpStatusCode.CONFLICT) + .put(ErrorTag.INVALID_VALUE, HttpStatusCode.BAD_REQUEST) + .put(ErrorTag.TOO_BIG, HttpStatusCode.CONTENT_TOO_LARGE) + .put(ErrorTag.MISSING_ATTRIBUTE, HttpStatusCode.BAD_REQUEST) + .put(ErrorTag.BAD_ATTRIBUTE, HttpStatusCode.BAD_REQUEST) + .put(ErrorTag.UNKNOWN_ATTRIBUTE, HttpStatusCode.BAD_REQUEST) + .put(ErrorTag.MISSING_ELEMENT, HttpStatusCode.BAD_REQUEST) + .put(ErrorTag.BAD_ELEMENT, HttpStatusCode.BAD_REQUEST) + .put(ErrorTag.UNKNOWN_ELEMENT, HttpStatusCode.BAD_REQUEST) + .put(ErrorTag.UNKNOWN_NAMESPACE, HttpStatusCode.BAD_REQUEST) + + .put(ErrorTag.ACCESS_DENIED, HttpStatusCode.FORBIDDEN) + .put(ErrorTag.LOCK_DENIED, HttpStatusCode.CONFLICT) + .put(ErrorTag.RESOURCE_DENIED, HttpStatusCode.CONFLICT) + .put(ErrorTag.ROLLBACK_FAILED, HttpStatusCode.INTERNAL_SERVER_ERROR) + .put(ErrorTag.DATA_EXISTS, HttpStatusCode.CONFLICT) + .put(ErrorTag.DATA_MISSING, dataMissing) + + .put(ErrorTag.OPERATION_NOT_SUPPORTED, HttpStatusCode.NOT_IMPLEMENTED) + .put(ErrorTag.OPERATION_FAILED, HttpStatusCode.INTERNAL_SERVER_ERROR) + .put(ErrorTag.PARTIAL_OPERATION, HttpStatusCode.INTERNAL_SERVER_ERROR) + .put(ErrorTag.MALFORMED_MESSAGE, HttpStatusCode.BAD_REQUEST) + .put(ErrorTags.RESOURCE_DENIED_TRANSPORT, HttpStatusCode.SERVICE_UNAVAILABLE) + .build(); + } + + /** + * Return the HTTP {@link HttpStatusCode} corresponding to specified {@link ErrorTag}. + * + * @param tag Error tag to map + * @return A {@link HttpStatusCode} + * @throws NullPointerException if {@code tag} is null + */ + public HttpStatusCode statusOf(final ErrorTag tag) { + final var known = tagToStatus.get(requireNonNull(tag)); + return known != null ? known : HttpStatusCode.INTERNAL_SERVER_ERROR; + } +} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java index af81a9dbe3..2e78aba2d0 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java @@ -65,10 +65,12 @@ public final class JaxRsNorthbound implements AutoCloseable { @Override public Set getSingletons() { + final var errorTagMapping = servletFactory.errorTagMapping(); + return Set.of( new JsonJaxRsFormattableBodyWriter(), new XmlJaxRsFormattableBodyWriter(), - new RestconfDocumentedExceptionMapper(databindProvider), - new JaxRsRestconf(server, servletFactory.prettyPrint())); + new RestconfDocumentedExceptionMapper(databindProvider, errorTagMapping), + new JaxRsRestconf(server, errorTagMapping, servletFactory.prettyPrint())); } }).build()) .asyncSupported(true) diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java index 3cdef59ba0..ab99d3e551 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java @@ -66,6 +66,16 @@ public final class OSGiNorthbound { name = "default pretty-print", description = "Control the default value of the '" + PrettyPrintParam.uriName + "' query parameter.") boolean pretty$_$print() default false; + + @AttributeDefinition( + name = "Report 404 on data-missing", + description = """ + Control the HTTP status code reporting of conditions corresponding to "data-missing". When this is set + to true, the server will violate RFC8040 and report "404" instead of "409". + + For details and reasoning see https://www.rfc-editor.org/errata/eid5565 and + https://mailarchive.ietf.org/arch/browse/netconf/?gbt=1&index=XcF9r3ek3LvZ4DjF-7_B8kxuiwA""") + boolean data$_$missing$_$is$_$404() default false; } private static final Logger LOG = LoggerFactory.getLogger(OSGiNorthbound.class); @@ -93,6 +103,7 @@ public final class OSGiNorthbound { registry = registryFactory.newInstance(FrameworkUtil.asDictionary(MdsalRestconfStreamRegistry.props(useSSE))); servletProps = DefaultRestconfStreamServletFactory.props(configuration.restconf(), registry.getInstance(), + configuration.data$_$missing$_$is$_$404() ? ErrorTagMapping.ERRATA_5565 : ErrorTagMapping.RFC8040, PrettyPrintParam.of(configuration.pretty$_$print()), useSSE, new StreamsConfiguration(configuration.maximum$_$fragment$_$length(), configuration.idle$_$timeout(), configuration.heartbeat$_$interval()), @@ -113,7 +124,9 @@ public final class OSGiNorthbound { LOG.debug("ListenersBroker restarted with {}", newUseSSE ? "SSE" : "Websockets"); } final var newServletProps = DefaultRestconfStreamServletFactory.props(configuration.restconf(), - registry.getInstance(), PrettyPrintParam.of(configuration.pretty$_$print()), useSSE, + registry.getInstance(), + configuration.data$_$missing$_$is$_$404() ? ErrorTagMapping.ERRATA_5565 : ErrorTagMapping.RFC8040, + PrettyPrintParam.of(configuration.pretty$_$print()), useSSE, new StreamsConfiguration(configuration.maximum$_$fragment$_$length(), configuration.idle$_$timeout(), configuration.heartbeat$_$interval()), configuration.ping$_$executor$_$name$_$prefix(), configuration.max$_$thread$_$count()); diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java index 77e8b06ec8..e0545e3ced 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java @@ -28,7 +28,6 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import javax.xml.stream.XMLOutputFactory; @@ -38,9 +37,10 @@ import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; +import org.opendaylight.restconf.api.HttpStatusCode; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.nb.jaxrs.JaxRsMediaTypes; -import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.server.api.DatabindContext; import org.opendaylight.restconf.server.spi.DatabindProvider; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.errors.Errors; @@ -64,7 +64,6 @@ import org.slf4j.LoggerFactory; public final class RestconfDocumentedExceptionMapper implements ExceptionMapper { private static final Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapper.class); private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_JSON_TYPE; - private static final Status DEFAULT_STATUS_CODE = Status.INTERNAL_SERVER_ERROR; private static final QName ERROR_TYPE_QNAME = qnameOf("error-type"); private static final QName ERROR_TAG_QNAME = qnameOf("error-tag"); private static final QName ERROR_APP_TAG_QNAME = qnameOf("error-app-tag"); @@ -80,6 +79,7 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< } private final DatabindProvider databindProvider; + private final ErrorTagMapping errorTagMapping; @Context private HttpHeaders headers; @@ -89,8 +89,10 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< * * @param databindProvider A {@link DatabindProvider} */ - public RestconfDocumentedExceptionMapper(final DatabindProvider databindProvider) { + public RestconfDocumentedExceptionMapper(final DatabindProvider databindProvider, + final ErrorTagMapping errorTagMapping) { this.databindProvider = requireNonNull(databindProvider); + this.errorTagMapping = requireNonNull(errorTagMapping); } @Override @@ -98,14 +100,7 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< + "we don't to have full stack trace - getMessage(..) method provides finer output.") public Response toResponse(final RestconfDocumentedException exception) { LOG.debug("Starting to map received exception to error response: {}", exception.getMessage()); - final Status responseStatus = getResponseStatusCode(exception); - if (responseStatus != Response.Status.FORBIDDEN - && responseStatus.getFamily() == Response.Status.Family.CLIENT_ERROR - && exception.getErrors().isEmpty()) { - // There should be at least one error entry for 4xx errors except 409 according to RFC8040, but we do not - // have it. Issue a warning with the call trace so we can fix whoever was the originator. - LOG.warn("Input exception has a family of 4xx but does not contain any descriptive errors", exception); - } + final var responseStatus = getResponseStatusCode(exception); final String serializedResponseBody; final MediaType responseMediaType = transformToResponseMediaType(getSupportedMediaType()); @@ -115,7 +110,7 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< serializedResponseBody = serializeExceptionToXml(exception, databindProvider); } - final Response preparedResponse = Response.status(responseStatus) + final Response preparedResponse = Response.status(responseStatus.code(), responseStatus.phrase()) .type(responseMediaType) .entity(serializedResponseBody) .build(); @@ -257,21 +252,21 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< * Deriving of the status code from the thrown exception. At the first step, status code is tried to be read using * {@link RestconfDocumentedException#getStatus()}. If it is {@code null}, status code will be derived from status * codes appended to error entries (the first that will be found). If there are not any error entries, - * {@link RestconfDocumentedExceptionMapper#DEFAULT_STATUS_CODE} will be used. + * {@link HttpStatusCode#INTERNAL_SERVER_ERROR} will be used. * * @param exception Thrown exception. * @return Derived status code. */ - private static Status getResponseStatusCode(final RestconfDocumentedException exception) { + private HttpStatusCode getResponseStatusCode(final RestconfDocumentedException exception) { final var errors = exception.getErrors(); if (errors.isEmpty()) { // if the module, that thrown exception, doesn't specify status code, it is treated as internal // server error - return DEFAULT_STATUS_CODE; + return HttpStatusCode.INTERNAL_SERVER_ERROR; } final var allStatusCodesOfErrorEntries = errors.stream() - .map(restconfError -> ErrorTags.statusOf(restconfError.getErrorTag())) + .map(restconfError -> errorTagMapping.statusOf(restconfError.getErrorTag())) // we would like to preserve iteration order in collected entries - hence usage of LinkedHashSet .collect(Collectors.toCollection(LinkedHashSet::new)); // choosing of the first status code from appended errors, if there are different status codes in error diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTags.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTags.java index 6e1072378c..4c35c3e758 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTags.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTags.java @@ -7,19 +7,12 @@ */ package org.opendaylight.restconf.nb.rfc8040.legacy; -import static java.util.Objects.requireNonNull; - -import com.google.common.collect.ImmutableMap; import javax.ws.rs.core.Response.Status; import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.yangtools.yang.common.ErrorTag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * {@link ErrorTag} mapping to HTTP errors. Aside from the mappings defined by - * RFC8040 section 7, we also define tags which - * map to useful {@link Status} codes. + * Additional {@link ErrorTag}s. */ @NonNullByDefault public final class ErrorTags { @@ -30,68 +23,7 @@ public final class ErrorTags { // FIXME: redefine as SERVICE_UNAVAILABLE? It would be more obvious public static final ErrorTag RESOURCE_DENIED_TRANSPORT = new ErrorTag("resource-denied-transport"); - private static final Logger LOG = LoggerFactory.getLogger(ErrorTags.class); - private static final ImmutableMap WELL_KNOWN_ERROR_TAGS = ImmutableMap.builder() - .put(ErrorTag.IN_USE, Status.CONFLICT) - .put(ErrorTag.INVALID_VALUE, Status.BAD_REQUEST) - .put(ErrorTag.TOO_BIG, Status.REQUEST_ENTITY_TOO_LARGE) - .put(ErrorTag.MISSING_ATTRIBUTE, Status.BAD_REQUEST) - .put(ErrorTag.BAD_ATTRIBUTE, Status.BAD_REQUEST) - .put(ErrorTag.UNKNOWN_ATTRIBUTE, Status.BAD_REQUEST) - .put(ErrorTag.MISSING_ELEMENT, Status.BAD_REQUEST) - .put(ErrorTag.BAD_ELEMENT, Status.BAD_REQUEST) - .put(ErrorTag.UNKNOWN_ELEMENT, Status.BAD_REQUEST) - .put(ErrorTag.UNKNOWN_NAMESPACE, Status.BAD_REQUEST) - - .put(ErrorTag.ACCESS_DENIED, Status.FORBIDDEN) - .put(ErrorTag.LOCK_DENIED, Status.CONFLICT) - .put(ErrorTag.RESOURCE_DENIED, Status.CONFLICT) - .put(ErrorTag.ROLLBACK_FAILED, Status.INTERNAL_SERVER_ERROR) - .put(ErrorTag.DATA_EXISTS, Status.CONFLICT) - .put(ErrorTag.DATA_MISSING, dataMissingHttpStatus()) - - .put(ErrorTag.OPERATION_NOT_SUPPORTED, Status.NOT_IMPLEMENTED) - .put(ErrorTag.OPERATION_FAILED, Status.INTERNAL_SERVER_ERROR) - .put(ErrorTag.PARTIAL_OPERATION, Status.INTERNAL_SERVER_ERROR) - .put(ErrorTag.MALFORMED_MESSAGE, Status.BAD_REQUEST) - .put(ErrorTags.RESOURCE_DENIED_TRANSPORT, Status.SERVICE_UNAVAILABLE) - .build(); - private ErrorTags() { // Hidden on purpose } - - /** - * Return the HTTP {@link Status} corresponding to specified {@link ErrorTag}. - * - * @param tag Error tag to map - * @return HTTP Status - * @throws NullPointerException if {@code tag} is null - */ - public static Status statusOf(final ErrorTag tag) { - final var known = WELL_KNOWN_ERROR_TAGS.get(requireNonNull(tag)); - return known != null ? known : Status.INTERNAL_SERVER_ERROR; - } - - private static Status dataMissingHttpStatus() { - // Control over the HTTP status reported on "data-missing" conditions. This defaults to disabled, - // HTTP status 409 as specified by RFC8040 (and all previous drafts). See the discussion in: - // https://www.rfc-editor.org/errata/eid5565 - // https://mailarchive.ietf.org/arch/msg/netconf/hkVDdHK4xA74NgvXzWP0zObMiyY/ - final var propName = "org.opendaylight.restconf.eid5565"; - final var propValue = System.getProperty(propName, "disabled"); - switch (propValue) { - case "enabled": - // RFC7231 interpretation: 404 Not Found - LOG.info("RESTCONF data-missing condition is reported as HTTP status 404 (Errata 5565)"); - return Status.NOT_FOUND; - case "disabled": - break; - default: - LOG.warn("Unhandled {} value \"{}\", assuming disabled", propName, propValue); - } - - // RFC8040 specification: 409 Conflict - return Status.CONFLICT; - } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java index 5c02131a36..9b9a15ea07 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java @@ -16,6 +16,7 @@ import javax.ws.rs.core.Application; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.aaa.web.servlet.ServletSupport; import org.opendaylight.restconf.api.query.PrettyPrintParam; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.server.spi.RestconfStream; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -45,8 +46,10 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream private static final String PROP_STREAMS_CONFIGURATION = ".streamsConfiguration"; private static final String PROP_RESTCONF = ".restconf"; private static final String PROP_PRETTY_PRINT = ".prettyPrint"; + private static final String PROP_ERROR_TAG_MAPPING = ".errorTagMapping"; private final @NonNull String restconf; + private final @NonNull ErrorTagMapping errorTagMapping; private final @NonNull PrettyPrintParam prettyPrint; private final RestconfStream.Registry streamRegistry; private final ServletSupport servletSupport; @@ -57,8 +60,8 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream public DefaultRestconfStreamServletFactory(final ServletSupport servletSupport, final String restconf, final RestconfStream.Registry streamRegistry, final StreamsConfiguration streamsConfiguration, - final PrettyPrintParam prettyPrint, final String namePrefix, final int corePoolSize, - final boolean useWebsockets) { + final ErrorTagMapping errorTagMapping, final PrettyPrintParam prettyPrint, final String namePrefix, + final int corePoolSize, final boolean useWebsockets) { this.servletSupport = requireNonNull(servletSupport); this.restconf = requireNonNull(restconf); if (restconf.endsWith("/")) { @@ -66,6 +69,7 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream } this.streamRegistry = requireNonNull(streamRegistry); this.streamsConfiguration = requireNonNull(streamsConfiguration); + this.errorTagMapping = requireNonNull(errorTagMapping); this.prettyPrint = requireNonNull(prettyPrint); pingExecutor = new DefaultPingExecutor(namePrefix, corePoolSize); this.useWebsockets = useWebsockets; @@ -82,6 +86,7 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream this(servletSupport, (String) props.get(PROP_RESTCONF), (RestconfStream.Registry) props.get(PROP_STREAM_REGISTRY), (StreamsConfiguration) props.get(PROP_STREAMS_CONFIGURATION), + (ErrorTagMapping) props.get(PROP_ERROR_TAG_MAPPING), (PrettyPrintParam) props.get(PROP_PRETTY_PRINT), (String) props.get(PROP_NAME_PREFIX), (int) requireNonNull(props.get(PROP_CORE_POOL_SIZE)), (boolean) requireNonNull(props.get(PROP_USE_WEBSOCKETS))); @@ -109,6 +114,11 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream return prettyPrint; } + @Override + public ErrorTagMapping errorTagMapping() { + return errorTagMapping; + } + @Override @Deactivate public void close() { @@ -116,11 +126,12 @@ public final class DefaultRestconfStreamServletFactory implements RestconfStream } public static Map props(final String restconf, final RestconfStream.Registry streamRegistry, - final PrettyPrintParam prettyPrint, final boolean useSSE, final StreamsConfiguration streamsConfiguration, - final String namePrefix, final int corePoolSize) { + final ErrorTagMapping errorTagMapping, final PrettyPrintParam prettyPrint, final boolean useSSE, + final StreamsConfiguration streamsConfiguration, final String namePrefix, final int corePoolSize) { return Map.of( PROP_RESTCONF, restconf, PROP_STREAM_REGISTRY, streamRegistry, + PROP_ERROR_TAG_MAPPING, errorTagMapping, PROP_PRETTY_PRINT, prettyPrint, PROP_USE_WEBSOCKETS, !useSSE, PROP_STREAMS_CONFIGURATION, streamsConfiguration, diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java index 0ec164a1a3..3f7172a729 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java @@ -8,8 +8,9 @@ package org.opendaylight.restconf.nb.rfc8040.streams; import javax.servlet.http.HttpServlet; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.restconf.api.query.PrettyPrintParam; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.server.spi.RestconfStream; /** @@ -18,6 +19,7 @@ import org.opendaylight.restconf.server.spi.RestconfStream; * @deprecated This interface exists only to support SSE/Websocket delivery. It will be removed when support for * WebSockets is removed. */ +@NonNullByDefault @Deprecated(since = "7.0.0", forRemoval = true) public interface RestconfStreamServletFactory { /** @@ -25,9 +27,11 @@ public interface RestconfStreamServletFactory { * * @return the value of {@code {+restconf}} macro */ - @NonNull String restconf(); + String restconf(); - @NonNull HttpServlet newStreamServlet(); + HttpServlet newStreamServlet(); - @NonNull PrettyPrintParam prettyPrint(); + PrettyPrintParam prettyPrint(); + + ErrorTagMapping errorTagMapping(); } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java index c9efc3211d..74103f7fca 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java @@ -43,6 +43,7 @@ import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload; import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; @@ -75,7 +76,7 @@ abstract class AbstractRestconfTest extends AbstractJukeboxTest { restconf = new JaxRsRestconf( new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(modelContext())), dataBroker, rpcService, actionService, mountPointService), - PrettyPrintParam.FALSE); + ErrorTagMapping.RFC8040, PrettyPrintParam.FALSE); } EffectiveModelContext modelContext() { diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java index a7f4a4b6f5..ab52d30d94 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java @@ -34,6 +34,7 @@ import org.opendaylight.restconf.api.ApiPath; import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest; import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; import org.opendaylight.yangtools.yang.common.QName; @@ -71,7 +72,7 @@ class Netconf799Test extends AbstractInstanceIdentifierTest { final var restconf = new JaxRsRestconf( new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), dataBroker, rpcService, actionService, mountPointService), - PrettyPrintParam.FALSE); + ErrorTagMapping.RFC8040, PrettyPrintParam.FALSE); doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters(); doReturn(true).when(asyncResponse).resume(captor.capture()); restconf.postDataJSON(ApiPath.parse("instance-identifier-module:cont/cont1/reset"), @@ -97,7 +98,7 @@ class Netconf799Test extends AbstractInstanceIdentifierTest { final var restconf = new JaxRsRestconf( new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), dataBroker, rpcService, actionService, mountPointService), - PrettyPrintParam.FALSE); + ErrorTagMapping.RFC8040, PrettyPrintParam.FALSE); doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters(); final var apiPath = ApiPath.parse("instance-identifier-module:cont/cont1/reset"); diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java index 43213f3f5b..47fd119b95 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java @@ -26,6 +26,7 @@ import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl; import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; import org.opendaylight.restconf.api.ApiPath; import org.opendaylight.restconf.api.query.PrettyPrintParam; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags; import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; @@ -86,7 +87,7 @@ public class RestconfSchemaServiceMountTest { new MdsalRestconfServer(new MdsalDatabindProvider( new FixedDOMSchemaService(SCHEMA_CONTEXT_WITH_MOUNT_POINTS)), dataBroker, rpcService, actionService, mountPointService), - PrettyPrintParam.FALSE); + ErrorTagMapping.RFC8040, PrettyPrintParam.FALSE); } /** diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java index d6c466f64f..6d63f581b8 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java @@ -28,6 +28,7 @@ import org.opendaylight.mdsal.dom.api.DOMRpcService; import org.opendaylight.mdsal.dom.api.DOMSchemaService.YangTextSourceExtension; import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; import org.opendaylight.restconf.api.query.PrettyPrintParam; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider; import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer; import org.opendaylight.yangtools.yang.common.ErrorTag; @@ -71,7 +72,7 @@ public class RestconfSchemaServiceTest { new MdsalRestconfServer(new MdsalDatabindProvider( new FixedDOMSchemaService(() -> MODEL_CONTEXT, sourceProvider)), dataBroker, rpcService, actionService, mountPointService), - PrettyPrintParam.FALSE); + ErrorTagMapping.RFC8040, PrettyPrintParam.FALSE); } /** diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTagsTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagsTest.java similarity index 87% rename from restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTagsTest.java rename to restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagsTest.java index f278a32604..a96ce0146f 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTagsTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagsTest.java @@ -5,11 +5,12 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.restconf.nb.rfc8040.legacy; +package org.opendaylight.restconf.nb.rfc8040; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; +import org.eclipse.jdt.annotation.NonNull; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -18,8 +19,8 @@ import org.opendaylight.yangtools.yang.common.ErrorTag; class ErrorTagsTest { @ParameterizedTest(name = "{0} => {1}") @MethodSource - void testStatusOf(final String tagName, final int status) { - assertEquals(status, ErrorTags.statusOf(new ErrorTag(tagName)).getStatusCode()); + void testStatusOf(final @NonNull String tagName, final int status) { + assertEquals(status, ErrorTagMapping.RFC8040.statusOf(new ErrorTag(tagName)).code()); } static List testStatusOf() { diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java index 87c171dc54..097d630716 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java @@ -30,6 +30,7 @@ import org.junit.runners.Parameterized.Parameters; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.nb.jaxrs.JaxRsMediaTypes; +import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping; import org.opendaylight.restconf.server.api.DatabindContext; import org.opendaylight.yangtools.yang.common.ErrorTag; import org.opendaylight.yangtools.yang.common.ErrorType; @@ -51,7 +52,8 @@ public class RestconfDocumentedExceptionMapperTest { final var schemaContext = YangParserTestUtils.parseYangResources( RestconfDocumentedExceptionMapperTest.class, "/restconf/impl/ietf-restconf@2017-01-26.yang", "/instanceidentifier/yang/instance-identifier-patch-module.yang"); - exceptionMapper = new RestconfDocumentedExceptionMapper(() -> DatabindContext.ofModel(schemaContext)); + exceptionMapper = new RestconfDocumentedExceptionMapper(() -> DatabindContext.ofModel(schemaContext), + ErrorTagMapping.RFC8040); } /** -- 2.36.6