Add support for RESTCONF standard errors

Change-Id: I74c0997bc8e06bc10c97cd610ff70c7a6aa68c8b
diff --git a/apps/restconf/api/BUCK b/apps/restconf/api/BUCK
index b54f53c..39a3872 100644
--- a/apps/restconf/api/BUCK
+++ b/apps/restconf/api/BUCK
@@ -6,6 +6,11 @@
     '//lib:jersey-server',
 ]
 
+TEST_DEPS = [
+    '//lib:TEST_REST',
+]
+
 osgi_jar_with_tests (
     deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
 )
diff --git a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfError.java b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfError.java
new file mode 100644
index 0000000..61c7144
--- /dev/null
+++ b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfError.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.restconf.api;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.List;
+
+/**
+ * Restconf error structured as in "ietf-restconf@2017-01-26.yang".
+ * An entry containing information about one specific error that occurred while
+ * processing a RESTCONF request.
+ */
+public final class RestconfError {
+    private final ErrorType errorType;
+    private final ErrorTag errorTag;
+    private String errorAppTag;
+    private String errorPath;
+    private String errorMessage;
+    private String errorInfo;
+
+    private RestconfError(ErrorType errorType, ErrorTag errorTag) {
+        this.errorType = errorType;
+        this.errorTag = errorTag;
+    }
+
+    /**
+     * The protocol layer where the error occurred.
+     * @return The error type
+     */
+    public ErrorType getErrorType() {
+        return errorType;
+    }
+
+    /**
+     * The enumerated error-tag.
+     * @return The enumerated error-tag
+     */
+    public ErrorTag getErrorTag() {
+        return errorTag;
+    }
+
+    /**
+     * The application-specific error-tag.
+     * @return The application-specific error-tag
+     */
+    public String getErrorAppTag() {
+        return errorAppTag;
+    }
+
+    /**
+     * The YANG instance identifier associated with the error node.
+     * @return A yang instance represented in XPath
+     */
+    public String getErrorPath() {
+        return errorPath;
+    }
+
+    /**
+     * A message describing the error.
+     * @return A message describing the error
+     */
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    /**
+     * A container with zero or more data nodes representing additional error information.
+     * @return A string serialization of datanodes e.g. a stack trace
+     */
+    public String getErrorInfo() {
+        return errorInfo;
+    }
+
+    /**
+     * Convert the restconf error to Json - this is for one error - many may be included in a response.
+     * @return A JSON object containing the details of one error
+     */
+    public ObjectNode toJson() {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode errorTags = (ObjectNode) mapper.createObjectNode();
+        errorTags.put("error-type", errorType.name().toLowerCase());
+        errorTags.put("error-tag", errorTag.text());
+
+        if (errorAppTag != null) {
+            errorTags.put("error-app-tag", errorAppTag);
+        }
+
+        if (errorPath != null) {
+            errorTags.put("error-path", errorPath);
+        }
+
+        if (errorMessage != null) {
+            errorTags.put("error-message", errorMessage);
+        }
+
+        if (errorInfo != null) {
+            errorTags.put("error-info", errorInfo);
+        }
+
+        ObjectNode errorNode = (ObjectNode) mapper.createObjectNode();
+        errorNode.put("error", errorTags);
+        return errorNode;
+    }
+
+    /**
+     * An enumerated set of the protocol layer involved in a RESTCONF request.
+     */
+    public enum ErrorType {
+        //The transport layer
+        TRANSPORT,
+        //The rpc or notification layer
+        RPC,
+        //The protocol operation layer
+        PROTOCOL,
+        //The server application layer
+        APPLICATION
+    }
+
+    /**
+     * An enumerated set of error 'tags' - consistent labels for error causes.
+     * See <a href="https://tools.ietf.org/html/rfc8040#section-7">Section 7 of RFC 8040</a>
+     * for a list of HTTP status codes associated with these
+     * error tags. Developers should ensure that suitable HTTP status codes are used
+     * when raising RESTCONF errors
+     */
+    public enum ErrorTag {
+        /**
+         * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+         */
+        IN_USE("in-use"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST},
+         * 404 {@link javax.ws.rs.core.Response.Status#NOT_FOUND} or
+         * 406 {@link javax.ws.rs.core.Response.Status#NOT_ACCEPTABLE}.
+         */
+        INVALID_VALUE("invalid-value"),
+        /**
+         * Use with Response 413 {@link javax.ws.rs.core.Response.Status#REQUEST_ENTITY_TOO_LARGE}.
+         */
+        REQUEST_TOO_BIG("too-big"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        RESPONSE_TOO_BIG("too-big"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        MISSING_ATTRIBUTE("missing-attribute"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        BAD_ATTRIBUTE("bad-attribute"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        UNKNOWN_ATTRIBUTE("unknown-attribute"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        BAD_ELEMENT("bad-element"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        UNKNOWN_ELEMENT("unknown-element"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        UNKNOWN_NAMESPACE("unknown-namespace"),
+        /**
+         * Use with Response 401 {@link javax.ws.rs.core.Response.Status#UNAUTHORIZED},
+         * or 403 {@link javax.ws.rs.core.Response.Status#FORBIDDEN}.
+         */
+        ACCESS_DENIED("access-denied"),
+        /**
+         * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+         */
+        LOCK_DENIED("lock-denied"),
+        /**
+         * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+         */
+        RESOURCE_DENIED("resource-denied"),
+        /**
+         * Use with Response 500 {@link javax.ws.rs.core.Response.Status#INTERNAL_SERVER_ERROR}.
+         */
+        ROLLBACK_FAILED("rollback-failed"),
+        /**
+         * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+         */
+        DATA_EXISTS("data-exists"),
+        /**
+         * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+         */
+        DATA_MISSING("data-missing"),
+        /**
+         * Use with Response 405 {@link javax.ws.rs.core.Response.Status#METHOD_NOT_ALLOWED},
+         * or 501 {@link javax.ws.rs.core.Response.Status#NOT_IMPLEMENTED}.
+         */
+        OPERATION_NOT_SUPPORTED("operation-not-supported"),
+        /**
+         * Use with Response 412 {@link javax.ws.rs.core.Response.Status#PRECONDITION_FAILED},
+         * or 500 {@link javax.ws.rs.core.Response.Status#INTERNAL_SERVER_ERROR}.
+         */
+        OPERATION_FAILED("operation-failed"),
+        /**
+         * Use with Response 500 {@link javax.ws.rs.core.Response.Status#INTERNAL_SERVER_ERROR}.
+         */
+        PARTIAL_OPERATION("partial-operation"),
+        /**
+         * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+         */
+        MALFORMED_MESSAGE("malformed-message");
+
+        private String text;
+
+        ErrorTag(String text) {
+            this.text = text;
+        }
+
+        /**
+         * Lowercase version of the error tag compliant with the standard.
+         * @return the associated lowercase version
+         */
+        public String text() {
+            return text;
+        }
+    }
+
+    /**
+     * Build a new RestconfError. ErrorTag and ErrorType are mandatory parameters.
+     * @param errorType The error type
+     * @param errorTag The error Tag
+     * @return A build which an be used to create the RestconfError
+     */
+    public static RestconfError.Builder builder(ErrorType errorType, ErrorTag errorTag) {
+        return new Builder(errorType, errorTag);
+    }
+
+    /**
+     * Create the complete JSON representation of the errors for the Response.
+     * Note this can contain many individual RestconfErrors
+     * @param restconfErrors A list of {@link RestconfError}s
+     * @return A JSON node which can be used in a response
+     */
+    public static ObjectNode wrapErrorAsJson(List<RestconfError> restconfErrors) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode errorArray = mapper.createArrayNode();
+        restconfErrors.forEach(error -> errorArray.add(error.toJson()));
+        ObjectNode errorsNode = (ObjectNode) mapper.createObjectNode();
+        errorsNode.put("ietf-restconf:errors", errorArray);
+        return errorsNode;
+    }
+
+    /**
+     * A builder for the Restconf Error. ErrorTag and ErrorType are mandatory parameters.
+     */
+    public static final class Builder {
+        private RestconfError restconfError;
+
+        /**
+         * ErrorTag and ErrorType are mandatory parameters of the error.
+         * @param errorType The error-type
+         * @param errorTag The error-tag
+         */
+        private Builder(ErrorType errorType, ErrorTag errorTag) {
+            restconfError = new RestconfError(errorType, errorTag);
+        }
+
+        /**
+         * Set an error-app-tag on the error.
+         * @param errorAppTag a tag relevant to the error in the application
+         * @return The builder
+         */
+        public Builder errorAppTag(String errorAppTag) {
+            this.restconfError.errorAppTag = errorAppTag;
+            return this;
+        }
+
+        /**
+         * Set an error-path on the error.
+         * @param errorPath A path to the resource that caused the error
+         * @return The builder
+         */
+        public Builder errorPath(String errorPath) {
+            this.restconfError.errorPath = errorPath;
+            return this;
+        }
+
+        /**
+         * Set an error-message on the error.
+         * @param errorMessage an explaination of the error
+         * @return The builder
+         */
+        public Builder errorMessage(String errorMessage) {
+            this.restconfError.errorMessage = errorMessage;
+            return this;
+        }
+
+        /**
+         * Set an error-info on the error.
+         * @param errorInfo Any additional infor about the error
+         * @return The builder
+         */
+        public Builder errorInfo(String errorInfo) {
+            this.restconfError.errorInfo = errorInfo;
+            return this;
+        }
+
+        /**
+         * Build the contents of the builder in to a {@link RestconfError}.
+         * @return A RestconfError
+         */
+        public RestconfError build() {
+            return restconfError;
+        }
+    }
+}
diff --git a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java
index 8f12b7f..459531f 100644
--- a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java
+++ b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java
@@ -15,53 +15,123 @@
  */
 package org.onosproject.restconf.api;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
 
+import java.util.ArrayList;
+import java.util.Optional;
+
 import static javax.ws.rs.core.Response.Status;
 
 /**
- * Exceptions raised during RESTCONF operations. This class extends
- * WebApplicationException. The design intention is to create a place holder
- * for RESTCONF specific errors and to be able to add more functions as the
- * subsystem grows.
+ * Exceptions raised during RESTCONF operations. This class extends WebApplicationException.
+ * To comply with the RESTCONF specification on error handling, the parameters given
+ * when creating this exception will be used to create one
+ * {@link org.onosproject.restconf.api.RestconfError}. Additional
+ * {@link org.onosproject.restconf.api.RestconfError}s may be added subsequently.
+ * This exception should be converted to a Json object using the toRestconfError()
+ * method before being passed back to the caller in a response
  */
 public class RestconfException extends WebApplicationException {
 
     // This is a randomly generated value. A WebApplicationException class is required to define it.
-    private static final long SERIAL_VERSION_UID = 3275970397584007046L;
+    private static final long SERIAL_VERSION_UID = 1275970654684007046L;
+
+    private ArrayList<RestconfError> restconfErrors = new ArrayList<>();
 
     /**
-     * Constructs a new RESTCONF server error exception. The caller raising this
-     * exception may pass in a HTTP error status code and an error message. The
-     * error code will be displayed to the RESTCONF client as part of the
-     * response from the RESTCONF server. The error message is a string which
-     * may be saved in a log file and may be later retrieved by the
-     * getMessage() method.
+     * Constructs a new RESTCONF server error exception based of an existing exception.
+     * The caller raising this exception may pass in a HTTP error status code
+     * and an error message. The error code will be displayed to the RESTCONF
+     * client as part of the response from the RESTCONF server. The error message
+     * is a string which may be saved in a log file and may be later retrieved by
+     * the getMessage() method. The parameters given will be formed in to a
+     * {@link org.onosproject.restconf.api.RestconfError}. Additional errors may be
+     * added after construction.
      *
      * @param message the detailed error message
-     * @param status  HTTP error status
+     * @param throwable The existing exception that caused this response.
+     *                  The message from this exception will be used as error-info
+     * @param errorTag A Restconf Error tag
+     * @param status  HTTP error status. Developers are asked to ensure that the correct one for the error-tag is used
+     * @param errorPath An optional path that gives the item that caused the exception
      * @throws IllegalArgumentException in case the status code is null or is not from
      *                                  javax.ws.rs.core.Response.Status.Family
      *                                  status code family
      */
-    public RestconfException(String message, Status status) {
-        super(message, null, Response.status(status).build());
+    public RestconfException(String message, Throwable throwable,
+                             RestconfError.ErrorTag errorTag, Status status,
+                             Optional<String> errorPath) {
+        super(message, throwable, Response.status(status).build());
+        RestconfError.Builder bldr = RestconfError
+                        .builder(RestconfError.ErrorType.APPLICATION, errorTag)
+                        .errorMessage(message);
+        if (throwable != null) {
+            bldr.errorInfo(throwable.getMessage());
+        }
+        if (errorPath.isPresent()) {
+            bldr.errorPath(errorPath.get());
+        }
+        addToErrors(bldr.build());
     }
 
     /**
-     * Constructs a new RESTCONF server error exception. The caller raising
-     * this exception may pass in the numerical value of a HTTP error
-     * status code, The error code will be displayed to the RESTCONF client
-     * as a response from the RESTCONF server.
+     * Constructs a new RESTCONF server error exception.
+     * The caller raising this exception may pass in a HTTP error status code
+     * and an error message. The error code will be displayed to the RESTCONF
+     * client as part of the response from the RESTCONF server. The error message
+     * is a string which may be saved in a log file and may be later retrieved by
+     * the getMessage() method. The parameters given will be formed in to a
+     * {@link org.onosproject.restconf.api.RestconfError}. Additional errors may be
+     * added after construction.
      *
-     * @param status HTTP error status
-     * @throws IllegalArgumentException in case the status code is not a valid
-     *                                  HTTP status code or if it is not from the
+     * @param message the detailed error message
+     * @param errorTag A Restconf Error tag
+     * @param status  HTTP error status. Developers are asked to ensure that the correct one for the error-tag is used
+     * @param errorPath An optional path that gives the item that caused the exception
+     * @param errorInfo An optional string with more info about the error
+     * @throws IllegalArgumentException in case the status code is null or is not from
      *                                  javax.ws.rs.core.Response.Status.Family
      *                                  status code family
      */
-    public RestconfException(int status) {
-        super((Throwable) null, Response.status(status).build());
+    public RestconfException(String message, RestconfError.ErrorTag errorTag,
+                             Status status, Optional<String> errorPath,
+                             Optional<String> errorInfo) {
+        super(message, null, Response.status(status).build());
+        RestconfError.Builder bldr = RestconfError
+                .builder(RestconfError.ErrorType.APPLICATION, errorTag)
+                .errorMessage(message);
+        if (errorInfo.isPresent()) {
+            bldr.errorInfo(errorInfo.get());
+        }
+        if (errorPath.isPresent()) {
+            bldr.errorPath(errorPath.get());
+        }
+        addToErrors(bldr.build());
+    }
+
+    /**
+     * Allows additional RestconfErrors to be added to the exception.
+     * @param restconfError An additional RestconfError to be added to the response
+     */
+    public void addToErrors(RestconfError restconfError) {
+        restconfErrors.add(restconfError);
+    }
+
+    /**
+     * Convert the RestconfException and all of its RestconfErrors in to a Json object.
+     * @return A json node generated from the RestconfException
+     */
+    public ObjectNode toRestconfErrorJson() {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode errorArray = mapper.createArrayNode();
+        restconfErrors.forEach(error -> errorArray.add(error.toJson()));
+        ObjectNode errorsNode = (ObjectNode) mapper.createObjectNode();
+        errorsNode.put("ietf-restconf:errors", errorArray);
+        return errorsNode;
     }
 }
diff --git a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java
index 9f3ba53..9a12bd6 100644
--- a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java
+++ b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java
@@ -23,7 +23,7 @@
 
 /**
  * Abstraction of RESTCONF Server functionality according to the
- * RESTCONF RFC (no official RFC number yet).
+ * RESTCONF RFC 8040.
  */
 public interface RestconfService {
     /**
diff --git a/apps/restconf/api/src/test/java/org/onosproject/restconf/api/RestconfExceptionTest.java b/apps/restconf/api/src/test/java/org/onosproject/restconf/api/RestconfExceptionTest.java
new file mode 100644
index 0000000..c9a1d04
--- /dev/null
+++ b/apps/restconf/api/src/test/java/org/onosproject/restconf/api/RestconfExceptionTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.restconf.api;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Test;
+
+import javax.ws.rs.core.Response;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the conversion of RestconfErrors in to JSON.
+ */
+public class RestconfExceptionTest {
+
+    private RestconfError error1 = RestconfError
+            .builder(RestconfError.ErrorType.TRANSPORT, RestconfError.ErrorTag.ACCESS_DENIED)
+            .build();
+
+    private RestconfError error2 = RestconfError
+            .builder(RestconfError.ErrorType.TRANSPORT, RestconfError.ErrorTag.BAD_ATTRIBUTE)
+            .build();
+
+    private RestconfError error3 = RestconfError
+            .builder(RestconfError.ErrorType.RPC, RestconfError.ErrorTag.BAD_ELEMENT)
+            .errorAppTag("my-app-tag")
+            .errorMessage("a message about the error")
+            .errorPath("/a/b/c")
+            .errorInfo("info about the error")
+            .build();
+
+    /**
+     * Test a Restconf Exception with many RestconfErrors converted to Json.
+     */
+    @Test
+    public void testToRestconfErrorJson() {
+        IllegalArgumentException ie = new IllegalArgumentException("This is a test");
+        RestconfException e = new RestconfException("Error in system", ie,
+                RestconfError.ErrorTag.DATA_EXISTS, Response.Status.BAD_REQUEST,
+                Optional.of("/some/path"));
+        e.addToErrors(error1);
+        e.addToErrors(error2);
+        e.addToErrors(error3);
+
+        assertEquals("{\"ietf-restconf:errors\":[" +
+                "{\"error\":{" +
+                    "\"error-type\":\"application\"," +
+                    "\"error-tag\":\"data-exists\"," +
+                    "\"error-path\":\"/some/path\"," +
+                    "\"error-message\":\"Error in system\"," +
+                    "\"error-info\":\"This is a test\"}}," +
+                "{\"error\":{" +
+                    "\"error-type\":\"transport\"," +
+                    "\"error-tag\":\"access-denied\"}}," +
+                "{\"error\":{" +
+                    "\"error-type\":\"transport\"," +
+                    "\"error-tag\":\"bad-attribute\"}}," +
+                "{\"error\":{" +
+                    "\"error-type\":\"rpc\"," +
+                    "\"error-tag\":\"bad-element\"," +
+                    "\"error-app-tag\":\"my-app-tag\"," +
+                    "\"error-path\":\"/a/b/c\"," +
+                    "\"error-message\":\"a message about the error\"," +
+                    "\"error-info\":\"info about the error\"}}]}",
+                e.toRestconfErrorJson().toString());
+    }
+
+    @Test
+    public void testWrappingErrorsNotInException() {
+        RestconfError error4 = RestconfError
+                .builder(RestconfError.ErrorType.TRANSPORT, RestconfError.ErrorTag.UNKNOWN_ELEMENT)
+                .build();
+
+        ObjectNode json = RestconfError.wrapErrorAsJson(Arrays.asList(error4, error1, error2, error3));
+
+        assertEquals("{\"ietf-restconf:errors\":[" +
+                    "{\"error\":{" +
+                        "\"error-type\":\"transport\"," +
+                        "\"error-tag\":\"unknown-element\"}}," +
+                    "{\"error\":{" +
+                        "\"error-type\":\"transport\"," +
+                        "\"error-tag\":\"access-denied\"}}," +
+                    "{\"error\":{" +
+                        "\"error-type\":\"transport\"," +
+                        "\"error-tag\":\"bad-attribute\"}}," +
+                    "{\"error\":{" +
+                        "\"error-type\":\"rpc\"," +
+                        "\"error-tag\":\"bad-element\"," +
+                        "\"error-app-tag\":\"my-app-tag\"," +
+                        "\"error-path\":\"/a/b/c\"," +
+                        "\"error-message\":\"a message about the error\"," +
+                        "\"error-info\":\"info about the error\"}}]}",
+                json.toString());
+
+    }
+}
diff --git a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
index 1b6a589..0ad6b25 100644
--- a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
+++ b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
@@ -28,6 +28,7 @@
 import org.onosproject.config.DynamicConfigService;
 import org.onosproject.config.FailedException;
 import org.onosproject.config.Filter;
+import org.onosproject.restconf.api.RestconfError;
 import org.onosproject.restconf.api.RestconfException;
 import org.onosproject.restconf.api.RestconfRpcOutput;
 import org.onosproject.restconf.api.RestconfService;
@@ -46,13 +47,17 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.ws.rs.core.Response;
 import java.net.URI;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import static javax.ws.rs.core.Response.Status.CONFLICT;
 import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
 import static org.onosproject.d.config.ResourceIds.parentOf;
 import static org.onosproject.restconf.utils.RestconfUtils.convertDataNodeToJson;
@@ -119,8 +124,9 @@
             dataNode = dynamicConfigService.readNode(rl.ridForDynConfig(), filter);
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
-            throw new RestconfException("ERROR: DynamicConfigService",
-                                        INTERNAL_SERVER_ERROR);
+            throw new RestconfException("ERROR: DynamicConfigService", e,
+                    RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                    Optional.of(uri.getPath()));
         }
         ObjectNode rootNode = convertDataNodeToJson(rl.ridForYangRuntime(), dataNode);
         return rootNode;
@@ -146,9 +152,16 @@
         try {
             dynamicConfigService.createNode(rl.ridForDynConfig(), dataNode);
         } catch (FailedException e) {
-            log.error("ERROR: DynamicConfigService: ", e);
-            throw new RestconfException("ERROR: DynamicConfigService",
-                                        INTERNAL_SERVER_ERROR);
+            if (e.getMessage().startsWith("Requested node already present")) {
+                throw new RestconfException("Already exists", e,
+                        RestconfError.ErrorTag.DATA_EXISTS, CONFLICT,
+                        Optional.of(uri.getPath()));
+            } else {
+                log.error("ERROR: DynamicConfigService: ", e);
+                throw new RestconfException("ERROR: DynamicConfigService", e,
+                    RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                    Optional.of(uri.getPath()));
+            }
         }
     }
 
@@ -176,8 +189,9 @@
 
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
-            throw new RestconfException("ERROR: DynamicConfigService",
-                                        INTERNAL_SERVER_ERROR);
+            throw new RestconfException("ERROR: DynamicConfigService", e,
+                RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                Optional.of(uri.getPath()));
         }
     }
 
@@ -191,8 +205,9 @@
             }
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
-            throw new RestconfException("ERROR: DynamicConfigService",
-                                        INTERNAL_SERVER_ERROR);
+            throw new RestconfException("ERROR: DynamicConfigService", e,
+                RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                Optional.of(uri.getPath()));
         }
     }
 
@@ -217,8 +232,9 @@
             dynamicConfigService.updateNode(parentOf(rl.ridForDynConfig()), dataNode);
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
-            throw new RestconfException("ERROR: DynamicConfigService",
-                                        INTERNAL_SERVER_ERROR);
+            throw new RestconfException("ERROR: DynamicConfigService", e,
+                RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                Optional.of(uri.getPath()));
         }
     }
 
@@ -240,6 +256,10 @@
                                      ChunkedOutput<String> output)
             throws RestconfException {
         //TODO: to be completed
+        throw new RestconfException("Not implemented",
+                RestconfError.ErrorTag.OPERATION_NOT_SUPPORTED,
+                Response.Status.NOT_IMPLEMENTED,
+                Optional.empty(), Optional.of("subscribeEventStream not yet implemented"));
     }
 
     @Override
@@ -267,12 +287,26 @@
         } catch (InterruptedException e) {
             log.error("ERROR: computeResultQ.take() has been interrupted.");
             log.debug("executeRpc Exception:", e);
-            restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR, null);
+            RestconfError error =
+                    RestconfError.builder(RestconfError.ErrorType.RPC,
+                    RestconfError.ErrorTag.OPERATION_FAILED)
+                        .errorMessage("RPC execution has been interrupted")
+                        .errorPath(uri.getPath())
+                        .build();
+            restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR,
+                    RestconfError.wrapErrorAsJson(Arrays.asList(error)));
             restconfOutput.reason("RPC execution has been interrupted");
         } catch (Exception e) {
             log.error("ERROR: executeRpc: {}", e.getMessage());
             log.debug("executeRpc Exception:", e);
-            restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR, null);
+            RestconfError error =
+                    RestconfError.builder(RestconfError.ErrorType.RPC,
+                            RestconfError.ErrorTag.OPERATION_FAILED)
+                            .errorMessage(e.getMessage())
+                            .errorPath(uri.getPath())
+                            .build();
+            restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR,
+                    RestconfError.wrapErrorAsJson(Arrays.asList(error)));
             restconfOutput.reason(e.getMessage());
         }
 
diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
index cd0b428..41fcf39 100644
--- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
+++ b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
@@ -20,9 +20,9 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.io.IOUtils;
 import org.onlab.osgi.DefaultServiceDirectory;
+import org.onosproject.restconf.api.RestconfError;
 import org.onosproject.restconf.api.RestconfException;
 import org.onosproject.restconf.api.RestconfRpcOutput;
-import org.onosproject.restconf.utils.exceptions.RestconfUtilsException;
 import org.onosproject.yang.model.DataNode;
 import org.onosproject.yang.model.DefaultResourceData;
 import org.onosproject.yang.model.ResourceData;
@@ -43,13 +43,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.util.Optional;
 
-import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
-import static javax.ws.rs.core.Response.Status.EXPECTATION_FAILED;
-import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
-import static javax.ws.rs.core.Response.Status.NO_CONTENT;
-import static javax.ws.rs.core.Response.Status.OK;
-import static javax.ws.rs.core.Response.Status.REQUEST_TIMEOUT;
+import static javax.ws.rs.core.Response.Status.*;
 
 /**
  * Utilities used by the RESTCONF app.
@@ -84,7 +80,9 @@
         try {
             rootNode = (ObjectNode) mapper.readTree(inputStream);
         } catch (IOException e) {
-            throw new RestconfUtilsException("ERROR: InputStream failed to parse");
+            throw new RestconfException("ERROR: InputStream failed to parse",
+                    e, RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                    Optional.empty());
         }
         return rootNode;
     }
@@ -101,7 +99,9 @@
         try {
             inputStream = IOUtils.toInputStream(json);
         } catch (Exception e) {
-            throw new RestconfUtilsException("ERROR: Json Node failed to parse");
+            throw new RestconfException("ERROR: Json Node failed to parse", e,
+                RestconfError.ErrorTag.MALFORMED_MESSAGE, BAD_REQUEST,
+                Optional.empty());
         }
         return inputStream;
     }
@@ -142,13 +142,19 @@
             // CompositeStream --- YangRuntimeService ---> CompositeData.
             CompositeData compositeData = YANG_RUNTIME.decode(compositeStream, context);
             resourceData = compositeData.resourceData();
+        } catch (RestconfException ex) {
+            throw ex;
         } catch (Exception ex) {
             log.error("convertJsonToDataNode failure: {}", ex.getMessage());
             log.debug("convertJsonToDataNode failure", ex);
+            throw new RestconfException("ERROR: JSON cannot be converted to DataNode",
+                    ex, RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                    Optional.of(uri.getPath()));
         }
         if (resourceData == null) {
             throw new RestconfException("ERROR: JSON cannot be converted to DataNode",
-                                        INTERNAL_SERVER_ERROR);
+                RestconfError.ErrorTag.DATA_MISSING, CONFLICT,
+                Optional.of(uri.getPath()), Optional.empty());
         }
         return resourceData;
     }
@@ -193,7 +199,8 @@
         }
         if (rootNode == null) {
             throw new RestconfException("ERROR: InputStream can not be convert to ObjectNode",
-                                        INTERNAL_SERVER_ERROR);
+                    null, RestconfError.ErrorTag.DATA_MISSING, CONFLICT,
+                    Optional.empty());
         }
         return rootNode;
     }
diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/RestconfUtilsException.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/RestconfUtilsException.java
deleted file mode 100644
index b0db098..0000000
--- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/RestconfUtilsException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.onosproject.restconf.utils.exceptions;
-
-/**
- * Represents class of errors related to RESTCONF utilities.
- */
-public class RestconfUtilsException extends RuntimeException {
-
-    /**
-     * Constructs an exception with the specified message.
-     *
-     * @param message the message describing the specific nature of the error
-     */
-    public RestconfUtilsException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs an exception with the specified message and the underlying
-     * cause.
-     *
-     * @param message the message describing the specific nature of the error
-     * @param cause   the underlying cause of this error
-     */
-    public RestconfUtilsException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/package-info.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/package-info.java
deleted file mode 100644
index a0fe88e..0000000
--- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Parse utils custom exceptions.
- */
-package org.onosproject.restconf.utils.exceptions;
\ No newline at end of file
diff --git a/protocols/restconf/server/rpp/BUCK b/protocols/restconf/server/rpp/BUCK
index 8f7bb52..faeeb6f 100644
--- a/protocols/restconf/server/rpp/BUCK
+++ b/protocols/restconf/server/rpp/BUCK
@@ -9,7 +9,14 @@
     '//apps/restconf/api:onos-apps-restconf-api',
 ]
 
+TEST_DEPS = [
+    '//lib:TEST_REST',
+    '//utils/osgi:onlab-osgi-tests',
+    '//web/api:onos-rest-tests',
+]
+
 osgi_jar_with_tests (
     deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
     web_context = '/onos/restconf',
 )
diff --git a/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java b/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java
index ba11421..36eb3a2 100644
--- a/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java
+++ b/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java
@@ -20,6 +20,7 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.glassfish.jersey.server.ChunkedOutput;
 import org.onosproject.rest.AbstractWebResource;
+import org.onosproject.restconf.api.RestconfError;
 import org.onosproject.restconf.api.RestconfException;
 import org.onosproject.restconf.api.RestconfRpcOutput;
 import org.onosproject.restconf.api.RestconfService;
@@ -42,6 +43,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.util.Arrays;
 import java.util.concurrent.CompletableFuture;
 
 import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
@@ -68,22 +70,19 @@
     @Context
     UriInfo uriInfo;
 
-    private static final String NOT_EXIST = "Requested data resource does not exist";
-
     private final RestconfService service = get(RestconfService.class);
     private final Logger log = getLogger(getClass());
 
     /**
      * Handles a RESTCONF GET operation against a target data resource. If the
      * operation is successful, the JSON presentation of the resource plus HTTP
-     * status code "200 OK" is returned. Otherwise, HTTP error status code
-     * "400 Bad Request" is returned.
+     * status code "200 OK" is returned. If it is not found then "404 Not Found"
+     * is returned. On internal error "500 Internal Server Error" is returned.
      *
      * @param uriString URI of the data resource.
-     * @return HTTP response
+     * @return HTTP response - 200, 404 or 500
      */
     @GET
-    @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     @Path("data/{identifier : .+}")
     public Response handleGetRequest(@PathParam("identifier") String uriString) {
@@ -94,13 +93,27 @@
         try {
             ObjectNode node = service.runGetOperationOnDataResource(uri);
             if (node == null) {
-                return Response.status(NOT_FOUND).entity(NOT_EXIST).build();
+                RestconfError error =
+                        RestconfError.builder(RestconfError.ErrorType.PROTOCOL,
+                                RestconfError.ErrorTag.INVALID_VALUE)
+                        .errorMessage("Resource not found")
+                        .errorPath(uriString)
+                        .errorAppTag("handleGetRequest")
+                        .build();
+                return Response.status(NOT_FOUND)
+                        .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
             }
             return ok(node).build();
         } catch (RestconfException e) {
             log.error("ERROR: handleGetRequest: {}", e.getMessage());
             log.debug("Exception in handleGetRequest:", e);
-            return e.getResponse();
+            return Response.status(e.getResponse().getStatus()).entity(e.toRestconfErrorJson()).build();
+        } catch (Exception e) {
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
+                    .errorMessage(e.getMessage()).errorAppTag("handlePostRequest").build();
+            return Response.status(INTERNAL_SERVER_ERROR)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         }
     }
 
@@ -186,14 +199,23 @@
             return Response.created(uriInfo.getRequestUri()).build();
         } catch (JsonProcessingException e) {
             log.error("ERROR: handlePostRequest ", e);
-            return Response.status(BAD_REQUEST).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
+                    .errorMessage(e.getMessage()).errorAppTag("handlePostRequest").build();
+            return Response.status(BAD_REQUEST)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         } catch (RestconfException e) {
             log.error("ERROR: handlePostRequest: {}", e.getMessage());
             log.debug("Exception in handlePostRequest:", e);
-            return e.getResponse();
+            return Response.status(e.getResponse().getStatus())
+                    .entity(e.toRestconfErrorJson()).build();
         } catch (IOException ex) {
             log.error("ERROR: handlePostRequest ", ex);
-            return Response.status(INTERNAL_SERVER_ERROR).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
+                    .errorMessage(ex.getMessage()).errorAppTag("handlePostRequest").build();
+            return Response.status(INTERNAL_SERVER_ERROR)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         }
     }
 
@@ -230,14 +252,23 @@
             return ok(node).build();
         } catch (JsonProcessingException e) {
             log.error("ERROR:  handleRpcRequest", e);
-            return Response.status(BAD_REQUEST).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
+                    .errorMessage(e.getMessage()).errorAppTag("handleRpcRequest").build();
+            return Response.status(BAD_REQUEST)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         } catch (RestconfException e) {
             log.error("ERROR: handleRpcRequest: {}", e.getMessage());
             log.debug("Exception in handleRpcRequest:", e);
-            return e.getResponse();
+            return Response.status(e.getResponse().getStatus())
+                    .entity(e.toRestconfErrorJson()).build();
         } catch (Exception e) {
             log.error("ERROR: handleRpcRequest ", e);
-            return Response.status(INTERNAL_SERVER_ERROR).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
+                    .errorMessage(e.getMessage()).errorAppTag("handleRpcRequest").build();
+            return Response.status(INTERNAL_SERVER_ERROR)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         }
     }
 
@@ -272,14 +303,23 @@
             return Response.created(uriInfo.getRequestUri()).build();
         } catch (JsonProcessingException e) {
             log.error("ERROR: handlePutRequest ", e);
-            return Response.status(BAD_REQUEST).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
+                    .errorMessage(e.getMessage()).errorAppTag("handlePutRequest").build();
+            return Response.status(BAD_REQUEST)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         } catch (RestconfException e) {
             log.error("ERROR: handlePutRequest: {}", e.getMessage());
             log.debug("Exception in handlePutRequest:", e);
-            return e.getResponse();
+            return Response.status(e.getResponse().getStatus())
+                    .entity(e.toRestconfErrorJson()).build();
         } catch (IOException ex) {
             log.error("ERROR: handlePutRequest ", ex);
-            return Response.status(INTERNAL_SERVER_ERROR).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
+                    .errorMessage(ex.getMessage()).errorAppTag("handlePutRequest").build();
+            return Response.status(INTERNAL_SERVER_ERROR)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         }
     }
 
@@ -294,7 +334,6 @@
      * @return HTTP response
      */
     @DELETE
-    @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     @Path("data/{identifier : .+}")
     public Response handleDeleteRequest(@PathParam("identifier") String uriString) {
@@ -308,7 +347,8 @@
         } catch (RestconfException e) {
             log.error("ERROR: handleDeleteRequest: {}", e.getMessage());
             log.debug("Exception in handleDeleteRequest:", e);
-            return e.getResponse();
+            return Response.status(e.getResponse().getStatus())
+                    .entity(e.toRestconfErrorJson()).build();
         }
     }
 
@@ -340,14 +380,23 @@
             return Response.ok().build();
         } catch (JsonProcessingException e) {
             log.error("ERROR: handlePatchRequest ", e);
-            return Response.status(BAD_REQUEST).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.MALFORMED_MESSAGE)
+                    .errorMessage(e.getMessage()).errorAppTag("handlePatchRequest").build();
+            return Response.status(BAD_REQUEST)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         } catch (RestconfException e) {
             log.error("ERROR: handlePatchRequest: {}", e.getMessage());
             log.debug("Exception in handlePatchRequest:", e);
-            return e.getResponse();
+            return Response.status(e.getResponse().getStatus())
+                    .entity(e.toRestconfErrorJson()).build();
         } catch (IOException ex) {
             log.error("ERROR: handlePatchRequest ", ex);
-            return Response.status(INTERNAL_SERVER_ERROR).build();
+            RestconfError error = RestconfError
+                    .builder(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED)
+                    .errorMessage(ex.getMessage()).errorAppTag("handlePatchRequest").build();
+            return Response.status(INTERNAL_SERVER_ERROR)
+                    .entity(RestconfError.wrapErrorAsJson(Arrays.asList(error))).build();
         }
     }
 
diff --git a/protocols/restconf/server/rpp/src/test/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResourceTest.java b/protocols/restconf/server/rpp/src/test/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResourceTest.java
new file mode 100644
index 0000000..603bbf6
--- /dev/null
+++ b/protocols/restconf/server/rpp/src/test/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResourceTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.protocol.restconf.server.rpp;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.easymock.EasyMock;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.rest.resources.ResourceTest;
+import org.onosproject.restconf.api.RestconfError;
+import org.onosproject.restconf.api.RestconfException;
+import org.onosproject.restconf.api.RestconfService;
+
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import java.io.ByteArrayInputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static javax.ws.rs.core.Response.Status.CONFLICT;
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Test the RestconfWebResource.
+ */
+public class RestconfWebResourceTest extends ResourceTest {
+
+    public static final String DATA_IETF_SYSTEM_SYSTEM = "data/ietf-system:system";
+
+    private static final Pattern RESTCONF_ERROR_REGEXP =
+            Pattern.compile("(\\{\"ietf-restconf:errors\":\\[)\\R?"
+                    + "((\\{\"error\":)\\R?"
+                    + "(\\{\"error-type\":\")((protocol)|(transport)|(rpc)|(application))(\",)\\R?"
+                    + "(\"error-tag\":\")[a-z\\-]*(\",)\\R?"
+                    + "((\"error-app-tag\":\").*(\",))?\\R?"
+                    + "((\"error-path\":\").*(\",))?\\R?"
+                    + "((\"error-message\":\").*(\"))?(\\}\\},?))*(\\]\\})", Pattern.DOTALL);
+
+    private RestconfService restconfService = createMock(RestconfService.class);
+
+    public RestconfWebResourceTest() {
+        super(ResourceConfig.forApplicationClass(RestconfProtocolProxy.class));
+    }
+
+    @Before
+    public void setup() {
+        ServiceDirectory testDirectory = new TestServiceDirectory()
+                .add(RestconfService.class, restconfService);
+        setServiceDirectory(testDirectory);
+    }
+
+    /**
+     * Test handleGetRequest when an Json object is returned.
+     */
+    @Test
+    public void testHandleGetRequest() {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode node = mapper.createObjectNode();
+        expect(restconfService
+                .runGetOperationOnDataResource(URI.create(getBaseUri() + DATA_IETF_SYSTEM_SYSTEM)))
+                .andReturn(node).anyTimes();
+        replay(restconfService);
+
+        WebTarget wt = target();
+        String response = wt.path("/" + DATA_IETF_SYSTEM_SYSTEM).request().get(String.class);
+        assertNotNull(response);
+    }
+
+    /**
+     * Test handleGetRequest when nothing is returned.
+     */
+    @Test
+    public void testHandleGetRequestNotFound() {
+        expect(restconfService
+                .runGetOperationOnDataResource(URI.create(getBaseUri() + DATA_IETF_SYSTEM_SYSTEM)))
+                .andReturn(null).anyTimes();
+        replay(restconfService);
+
+        WebTarget wt = target();
+        try {
+            String response = wt.path("/" + DATA_IETF_SYSTEM_SYSTEM).request().get(String.class);
+            fail("Expecting fail as response is none");
+        } catch (NotFoundException e) {
+            assertNotNull(e.getResponse());
+            assertRestconfErrorJson(e.getResponse());
+        }
+    }
+
+    /**
+     * Test handleGetRequest when an RestconfException is thrown.
+     */
+    @Test
+    public void testHandleGetRequestRestconfException() {
+        expect(restconfService
+                .runGetOperationOnDataResource(URI.create(getBaseUri() + DATA_IETF_SYSTEM_SYSTEM)))
+                .andThrow(new RestconfException("Suitable error message",
+                        RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+                        Optional.of("/" + DATA_IETF_SYSTEM_SYSTEM),
+                        Optional.of("More info about the error")))
+                .anyTimes();
+        replay(restconfService);
+
+        WebTarget wt = target();
+        try {
+            String response = wt.path("/" + DATA_IETF_SYSTEM_SYSTEM).request().get(String.class);
+            fail("Expecting fail as response is RestconfException");
+        } catch (InternalServerErrorException e) {
+            assertNotNull(e.getResponse());
+            assertRestconfErrorJson(e.getResponse());
+        }
+    }
+
+    /**
+     * Test handleGetRequest when an Exception is thrown.
+     */
+    @Test
+    public void testHandleGetRequestIoException() {
+        expect(restconfService
+                .runGetOperationOnDataResource(URI.create(getBaseUri() + DATA_IETF_SYSTEM_SYSTEM)))
+                .andThrow(new IllegalArgumentException("A test exception"))
+                .anyTimes();
+        replay(restconfService);
+
+        WebTarget wt = target();
+        try {
+            String response = wt.path("/" + DATA_IETF_SYSTEM_SYSTEM).request().get(String.class);
+            fail("Expecting fail as response is IllegalArgumentException");
+        } catch (InternalServerErrorException e) {
+            assertNotNull(e.getResponse());
+            assertRestconfErrorJson(e.getResponse());
+        }
+    }
+
+    /**
+     * Test handlePostRequest with no exception.
+     */
+    @Test
+    public void testHandlePostRequest() {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode ietfSystemSubNode = mapper.createObjectNode();
+        ietfSystemSubNode.put("contact", "Open Networking Foundation");
+        ietfSystemSubNode.put("hostname", "host1");
+        ietfSystemSubNode.put("location", "The moon");
+
+        ObjectNode ietfSystemNode = mapper.createObjectNode();
+        ietfSystemNode.put("ietf-system:system", ietfSystemSubNode);
+
+        WebTarget wt = target();
+        Response response = wt.path("/" + DATA_IETF_SYSTEM_SYSTEM)
+                .request()
+                .post(Entity.json(ietfSystemNode.toString()));
+        assertEquals(201, response.getStatus());
+    }
+
+    /**
+     * Test handlePostRequest with 'already exists' exception.
+     */
+    @Test
+    public void testHandlePostRequestAlreadyExists() {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode ietfSystemSubNode = mapper.createObjectNode();
+        ietfSystemSubNode.put("contact", "Open Networking Foundation");
+        ietfSystemSubNode.put("hostname", "host1");
+        ietfSystemSubNode.put("location", "The moon");
+
+        ObjectNode ietfSystemNode = mapper.createObjectNode();
+        ietfSystemNode.put("ietf-system:system", ietfSystemSubNode);
+
+        restconfService.runPostOperationOnDataResource(
+                EasyMock.<URI>anyObject(), EasyMock.<ObjectNode>anyObject());
+        expectLastCall().andThrow(new RestconfException("Requested node already present", null,
+                RestconfError.ErrorTag.DATA_EXISTS, CONFLICT,
+                Optional.of("/" + DATA_IETF_SYSTEM_SYSTEM)));
+        replay(restconfService);
+
+        WebTarget wt = target();
+        Response response = wt.path("/" + DATA_IETF_SYSTEM_SYSTEM)
+                .request()
+                .post(Entity.json(ietfSystemNode.toString()));
+        assertEquals(409, response.getStatus());
+    }
+
+    private static void assertRestconfErrorJson(Response errorResponse) {
+        ByteArrayInputStream in = (ByteArrayInputStream) errorResponse.getEntity();
+        int n = in.available();
+        byte[] bytes = new byte[n];
+        in.read(bytes, 0, n);
+
+        Matcher m = RESTCONF_ERROR_REGEXP.matcher(new String(bytes, StandardCharsets.UTF_8));
+        assertTrue(m.matches());
+    }
+}