Introduce HttpStatusCode 42/111342/1
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 9 Apr 2024 20:35:33 +0000 (22:35 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 9 Apr 2024 21:22:50 +0000 (23:22 +0200)
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 <robert.varga@pantheon.tech>
15 files changed:
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/HttpStatusCode.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagMapping.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/OSGiNorthbound.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTags.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/DefaultRestconfStreamServletFactory.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/RestconfStreamServletFactory.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceMountTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/ErrorTagsTest.java [moved from restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/legacy/ErrorTagsTest.java with 87% similarity]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java

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 (file)
index 0000000..3cc0884
--- /dev/null
@@ -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 <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15">HTTP Status Code</a>. Integer
+ * values used here are assigned through the
+ * <a href="https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml">IANA Status Code Registry</a>.
+ */
+@Beta
+@NonNullByDefault
+public final class HttpStatusCode {
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.1">200 OK</a>.
+     */
+    public static final HttpStatusCode OK = new HttpStatusCode(200, "OK");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.2">201 Created</a>.
+     */
+    public static final HttpStatusCode CREATED = new HttpStatusCode(201, "Created");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.3">202 Accepted</a>.
+     */
+    public static final HttpStatusCode ACCEPTED = new HttpStatusCode(202, "Accepted");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.4">203 Non-Authoritative Information</a>.
+     */
+    public static final HttpStatusCode NON_AUTHORITATIVE_INFORMATION =
+        new HttpStatusCode(203, "Non-Authoritative Information");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.5">204 No Content</a>.
+     */
+    public static final HttpStatusCode NO_CONTENT = new HttpStatusCode(204, "No Content");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.6">205 Reset Content</a>.
+     */
+    public static final HttpStatusCode RESET_CONTENT = new HttpStatusCode(205, "Reset Content");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.7">206 Partial Content</a>.
+     */
+    public static final HttpStatusCode PARTIAL_CONTENT = new HttpStatusCode(206, "Partial Content");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.1">300 Multiple Choices</a>.
+     */
+    public static final HttpStatusCode MULTIPLE_CHOICES = new HttpStatusCode(300, "Multiple Choices");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.2">301 Moved Permanently</a>.
+     */
+    public static final HttpStatusCode MOVED_PERMANENTLY = new HttpStatusCode(301, "Moved Permanently");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3">302 Found</a>.
+     */
+    public static final HttpStatusCode FOUND = new HttpStatusCode(302, "Found");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.4">303 See Other</a>.
+     */
+    public static final HttpStatusCode SEE_OTHER = new HttpStatusCode(303, "See Other");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.5">304 Not Modified</a>.
+     */
+    public static final HttpStatusCode NOT_MODIFIED = new HttpStatusCode(304, "Not Modified");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.6">305 Use Proxy</a>.
+     */
+    public static final HttpStatusCode USE_PROXY = new HttpStatusCode(305, "Use Proxy");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.8">307 Temporary Redirect</a>.
+     */
+    public static final HttpStatusCode TEMPORARY_REDIRECT = new HttpStatusCode(307, "Temporary Redirect");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.9">308 Permanent Redirect</a>.
+     */
+    public static final HttpStatusCode PERMANENT_REDIRECT = new HttpStatusCode(308, "Permanent Redirect");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.1">400 Bad Request</a>.
+     */
+    public static final HttpStatusCode BAD_REQUEST = new HttpStatusCode(400, "Bad Request");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.2">401 Unauthorized</a>.
+     */
+    public static final HttpStatusCode UNAUTHORIZED = new HttpStatusCode(401, "Unauthorized");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.3">402 Payment Required</a>.
+     */
+    public static final HttpStatusCode PAYMENT_REQUIRED = new HttpStatusCode(402, "Payment Required");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.4">403 Forbidden</a>.
+     */
+    public static final HttpStatusCode FORBIDDEN = new HttpStatusCode(403, "Forbidden");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.5">404 Not Found</a>.
+     */
+    public static final HttpStatusCode NOT_FOUND = new HttpStatusCode(404, "Not Found");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.6">405 Method Not Allowed</a>.
+     */
+    public static final HttpStatusCode METHOD_NOT_ALLOWED = new HttpStatusCode(405, "Method Not Allowed");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.7">406 Not Acceptable</a>.
+     */
+    public static final HttpStatusCode NOT_ACCEPTABLE = new HttpStatusCode(406, "Not Acceptable");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.8">407 Proxy Authentication Required</a>.
+     */
+    public static final HttpStatusCode PROXY_AUTHENTICATION_REQUIRED =
+        new HttpStatusCode(407, "Proxy Authentication Required");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.9">408 Request Timeout</a>.
+     */
+    public static final HttpStatusCode REQUEST_TIMEOUT = new HttpStatusCode(408, "Request Timeout");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.10">409 Conflict</a>.
+     */
+    public static final HttpStatusCode CONFLICT = new HttpStatusCode(409, "Conflict");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.11">410 Gone</a>.
+     */
+    public static final HttpStatusCode GONE = new HttpStatusCode(410, "Gone");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.12">411 Length Required</a>.
+     */
+    public static final HttpStatusCode LENGTH_REQUIRED = new HttpStatusCode(411, "Length Required");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.13">412 Precondition Failed</a>.
+     */
+    public static final HttpStatusCode PRECONDITION_FAILED = new HttpStatusCode(412, "Precondition Failed");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.14">413 Content Too Large</a>.
+     */
+    public static final HttpStatusCode CONTENT_TOO_LARGE = new HttpStatusCode(413, "Content Too Large");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.15">414 Content Too Long</a>.
+     */
+    public static final HttpStatusCode URI_TOO_LONG = new HttpStatusCode(414, "URI Too Long");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.16">415 Unsupported Media Type</a>.
+     */
+    public static final HttpStatusCode UNSUPPORTED_MEDIA_TYPE = new HttpStatusCode(415, "Unsupported Media Type");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.17">416 Requested Range Not Satisfiable</a>.
+     */
+    public static final HttpStatusCode REQUESTED_RANGE_NOT_SATISFIABLE =
+        new HttpStatusCode(416, "Requested Range Not Satisfiable");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.18">417 Expectation Failed</a>.
+     */
+    public static final HttpStatusCode EXPECTATION_FAILED = new HttpStatusCode(417, "Expectation Failed");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.19">418 (Unused)</a>.
+     */
+    @Deprecated(forRemoval = true)
+    public static final HttpStatusCode I_M_A_TEAPOT = new HttpStatusCode(418, "I'm a teapot");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.20">421 Misdirected Request</a>.
+     */
+    public static final HttpStatusCode MISDIRECTED_REQUEST = new HttpStatusCode(421, "Misdirected Request");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.21">422 Unprocessable Content</a>.
+     */
+    public static final HttpStatusCode UNPROCESSABLE_CONTENT = new HttpStatusCode(422, "Unprocessable Content");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.22">426 Upgrade Required</a>.
+     */
+    public static final HttpStatusCode UPGRADE_REQUIRED = new HttpStatusCode(426, "Upgrade Required");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc6585#section-3">428 Precondition Required</a>.
+     */
+    public static final HttpStatusCode PRECONDITION_REQUIRED = new HttpStatusCode(428, "Precondition Required");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc6585#section-4">429 Too Many Requests</a>.
+     */
+    public static final HttpStatusCode TOO_MANY_REQUESTS = new HttpStatusCode(429, "Too Many Requests");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc6585#section-5">431 Request Header Fields Too Large</a>.
+     */
+    public static final HttpStatusCode REQUEST_HEADER_FIELDS_TOO_LARGE =
+        new HttpStatusCode(431, "Request Header Fields Too Large");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.1">500 Internal Server Error</a>.
+     */
+    public static final HttpStatusCode INTERNAL_SERVER_ERROR = new HttpStatusCode(500, "Internal Server Error");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.2">501 Not Implemented</a>.
+     */
+    public static final HttpStatusCode NOT_IMPLEMENTED = new HttpStatusCode(501, "Not Implemented");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.3">502 Bad Gateway</a>.
+     */
+    public static final HttpStatusCode BAD_GATEWAY = new HttpStatusCode(502, "Bad Gateway");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.4">503 Service Unavailable</a>.
+     */
+    public static final HttpStatusCode SERVICE_UNAVAILABLE = new HttpStatusCode(503, "Service Unavailable");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.5">504 Gateway Timeout</a>.
+     */
+    public static final HttpStatusCode GATEWAY_TIMEOUT = new HttpStatusCode(504, "Gateway Timeout");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.6">505 HTTP Version Not Supported</a>.
+     */
+    public static final HttpStatusCode HTTP_VERSION_NOT_SUPPORTED =
+        new HttpStatusCode(505, "HTTP Version Not Supported");
+    /**
+     * <a href="https://www.rfc-editor.org/rfc/rfc6585#section-6">511 Network Authentication Required</a>.
+     */
+    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();
+    }
+}
index d0fb42952352af081f8976243e338b0b8d42c663..5a87e8e27e202594f28033c4df401ff85d782012 100644 (file)
@@ -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<DataYangPatchResult> future,
+    private void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> 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<RestconfError> error) {
-                return ErrorTags.statusOf(error.get(0).getErrorTag());
+            private @NonNull HttpStatusCode statusOfFirst(final List<RestconfError> 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 (file)
index 0000000..3e79fe8
--- /dev/null
@@ -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 <a href="https://www.rfc-editor.org/rfc/rfc8040#page-74">RFC8040</a>:
+     * {@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 <a href="https://www.rfc-editor.org/errata/eid5565">Errata 5565</a>:
+     * {@link ErrorTag#DATA_MISSING} is reported as {@code 404 Not Found}. This is more in-line with expectations rooted
+     * in <a href="https://www.rfc-editor.org/rfc/rfc7231#section-6.5.4">HTTP/1.1 specification</a>.
+     */
+    ERRATA_5565(HttpStatusCode.NOT_FOUND);
+
+    private ImmutableMap<ErrorTag, HttpStatusCode> tagToStatus;
+
+    ErrorTagMapping(final HttpStatusCode dataMissing) {
+        tagToStatus = ImmutableMap.<ErrorTag, HttpStatusCode>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;
+    }
+}
index af81a9dbe322abd3606eeb30a39d9f6ee8833776..2e78aba2d0f3d2444efa1db5fa83a2af70124c5f 100644 (file)
@@ -65,10 +65,12 @@ public final class JaxRsNorthbound implements AutoCloseable {
 
                         @Override
                         public Set<Object> 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)
index 3cdef59ba0514d4dbf0fc598a95f33871fba639c..ab99d3e551ceb8a8711517b572d68bb9a2eb9eeb 100644 (file)
@@ -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());
index 77e8b06ec87c5956dcc4f109ff94b88f155b792d..e0545e3ceda74f16ac836b6d0325c3e7f27d16f1 100644 (file)
@@ -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<RestconfDocumentedException> {
     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
index 6e1072378c9d25fe5a3aedaf450fb86d0ed2558b..4c35c3e758f93fed90d6a5d3165a52195469ec20 100644 (file)
@@ -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
- * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-7">RFC8040 section 7</a>, 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<ErrorTag, Status> WELL_KNOWN_ERROR_TAGS = ImmutableMap.<ErrorTag, Status>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;
-    }
 }
index 5c02131a36ed73746f90d7fdc5ffc29b5a2d3ee9..9b9a15ea079eaa6ec8ef2b75a955554b125511e9 100644 (file)
@@ -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<String, ?> 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,
index 0ec164a1a3f5baf8857c1cfd7c1a651658a1f8d0..3f7172a72994e8314f40c65dc03af64a08a84bb1 100644 (file)
@@ -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();
 }
index c9efc3211d20a179059882e45a12393132273c22..74103f7fcaac4406f070d5902d8a60040f96317e 100644 (file)
@@ -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() {
index a7f4a4b6f5b17f5509882b3db44385467b936a4b..ab52d30d94e90ffeecbf894e8a04c5d4fa410583 100644 (file)
@@ -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");
index 43213f3f5b1dcda655e28dbf2664733da1eff976..47fd119b9580a64fbc47e7fbacf052937325e79f 100644 (file)
@@ -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);
     }
 
     /**
index d6c466f64f39626d55e47ab073a53d4d395eccd2..6d63f581b8762cf670a41cf18dedec9f5a3b9da0 100644 (file)
@@ -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);
     }
 
     /**
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 f278a32604bb00959c73cb98670be66289a835a9..a96ce0146f6c955d9ef3254f365c3af961e27335 100644 (file)
@@ -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<Arguments> testStatusOf() {
index 87c171dc5475384c548b5d2b5add43712f998030..097d6307166518e0ddde06cf4b3d9a9c044510c3 100644 (file)
@@ -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);
     }
 
     /**