Repository: searchbox-io/Jest (java)

Issue:
Jest BulkResultItem does not contain all Elasticsearch fields
When a successful Bulk request is performed, the Elasticsearch result contains the '_version' field.  However, BulkResultItem does not parse the '_version' or '_shards' field.  See:

"create": {
                "_index": "foo",
                "_type": "FooBar",
                "_id": "12345",
                "_version": 1,
                "_shards": {
                    "total": 1,
                    "successful": 1,
                    "failed": 0
                },
                "status": 201
            } 

Also, when there is a failed request, it would be useful to be able to access the 'error' fields, i.e. 'type' and 'reason' directly rather than having to parse the object from a String:
"create": {
                "_index": "foo",
                "_type": "FooBar",
                "_id": "1234",
                "status": 404,
                "error": {
                    "type": "type_missing_exception",
                    "reason": "type[[FooBar, trying to auto create mapping, but dynamic mapping is disabled]] missing",
                    "index": "foo"
                }
            }

Pull request:
Add additional Elasticsearch result fields to BulkResultItem.
Resolves #385 by adding the additional elasticsearch fields.

Patch:
diff --git a/jest-common/src/main/java/io/searchbox/core/BulkResult.java b/jest-common/src/main/java/io/searchbox/core/BulkResult.java
index 12f82b3e..8a1217d9 100644
--- a/jest-common/src/main/java/io/searchbox/core/BulkResult.java
+++ b/jest-common/src/main/java/io/searchbox/core/BulkResult.java
@@ -3,7 +3,9 @@ package io.searchbox.core;
 import com.google.gson.Gson;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+
 import io.searchbox.client.JestResult;
+
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 
@@ -79,14 +81,30 @@ public class BulkResult extends JestResult {
          * Can be null if the item completed without errors.
          */
         public final String error;
+        /**
+         * Can be null if the error was not a JSON object or if the item completed without errors
+         */
+        public final String errorType;
+        /**
+         * Can be null if the error was not a JSON object or if the item completed without errors
+         */
+        public final String errorReason;
+        /**
+         * Can be null when item completed with errors.
+         */
+        public final Integer version;
 
-        public BulkResultItem(String operation, String index, String type, String id, int status, String error) {
+    public BulkResultItem(String operation, String index, String type, String id, int status,
+        String error, Integer version, String errorType, String errorReason) {
             this.operation = operation;
             this.index = index;
             this.type = type;
             this.id = id;
             this.status = status;
             this.error = error;
+            this.errorType = errorType;
+            this.errorReason = errorReason;
+            this.version = version;
         }
 
         public BulkResultItem(String operation, JsonObject values) {
@@ -96,6 +114,17 @@ public class BulkResult extends JestResult {
             this.id = values.has("_id") && !values.get("_id").isJsonNull() ? values.get("_id").getAsString() : null;
             this.status = values.get("status").getAsInt();
             this.error = values.has("error") ? values.get("error").toString() : null;
+
+            if (values.has("error") && values.get("error").isJsonObject()) {
+                final JsonObject errorObject = values.get("error").getAsJsonObject();
+                this.errorType = errorObject.has("type") ? errorObject.get("type").getAsString() : null;
+                this.errorReason = errorObject.has("reason") ? errorObject.get("reason").getAsString() : null;
+            } else {
+                this.errorType = null;
+                this.errorReason = null;
+            }
+
+            this.version = values.has("_version") ? values.get("_version").getAsInt() : null;
         }
 
         @Override
@@ -113,6 +142,9 @@ public class BulkResult extends JestResult {
                     .append(type, that.type)
                     .append(id, that.id)
                     .append(error, that.error)
+                    .append(errorType, that.errorType)
+                    .append(errorReason, that.errorReason)
+                    .append(version, that.version)
                     .isEquals();
         }
 
@@ -125,6 +157,9 @@ public class BulkResult extends JestResult {
                     .append(id)
                     .append(status)
                     .append(error)
+                    .append(errorType)
+                    .append(errorReason)
+                    .append(version)
                     .toHashCode();
         }
     }


Test patch:
diff --git a/jest-common/src/test/java/io/searchbox/core/BulkResultTest.java b/jest-common/src/test/java/io/searchbox/core/BulkResultTest.java
index 86645dcf..7e42372a 100644
--- a/jest-common/src/test/java/io/searchbox/core/BulkResultTest.java
+++ b/jest-common/src/test/java/io/searchbox/core/BulkResultTest.java
@@ -1,14 +1,16 @@
 package io.searchbox.core;
 
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+
 import org.junit.Test;
 
 import java.util.Map;
 
-import static org.junit.Assert.assertEquals;
-
 public class BulkResultTest {
 
     static String indexFailedResult = "{\n" +
@@ -27,6 +29,48 @@ public class BulkResultTest {
             "    ]\n" +
             "}";
 
+    static String indexSuccessResult = "{\n" +
+            "    \"took\": 36,\n" +
+            "    \"errors\": false,\n" +
+            "    \"items\": [\n" +
+            "        {\n" +
+            "            \"index\": {\n" +
+            "                \"_index\": \"foo\",\n" +
+            "                \"_type\": \"FooBar\",\n" +
+            "                \"_id\": \"12345\",\n" +
+            "                \"status\": 201,\n" +
+            "                \"_version\": 3,\n" +
+            "                \"_shards\": {\n" +
+            "                    \"total\": 1,\n" +
+            "                    \"successful\": 1,\n" +
+            "                    \"failed\": 0\n" +
+            "                }\n" +
+            "            }\n" +
+            "        }\n" +
+            "    ]\n" +
+            "}";
+
+  static String indexFailedResultObject = "{\n" +
+      "    \"took\": 10,\n" +
+      "    \"errors\": true,\n" +
+      "    \"items\": [\n" +
+      "        {\n" +
+      "            \"index\": {\n" +
+      "                \"_index\": \"index-name\",\n" +
+      "                \"_type\": \"type-name\",\n" +
+      "                \"_id\": null,\n" +
+      "                \"status\": 400,\n" +
+      "                \"error\": {\n" +
+      "                    \"type\": \"type_missing_exception\",\n" +
+      "                    \"reason\": \"Reason is missing type\",\n" +
+      "                    \"index\": \"foo\"\n" +
+      "                }\n" +
+      "            }\n" +
+      "        }\n" +
+      "    ]\n" +
+      "}";
+
+    @SuppressWarnings("unchecked")
     @Test
     public void bulkResultWithFailures() {
         BulkResult bulkResult = new BulkResult(new GsonBuilder().serializeNulls().create());
@@ -36,5 +80,42 @@ public class BulkResultTest {
 
         assertEquals(1, bulkResult.getItems().size());
         assertEquals(1, bulkResult.getFailedItems().size());
+        assertNull(bulkResult.getItems().get(0).version);
+
+        assertEquals(
+            "\"MapperParsingException[mapping [type-name]]; nested: MapperParsingException[No type specified for property [field-name]]; \"",
+            bulkResult.getItems().get(0).error);
+        assertNull(bulkResult.getItems().get(0).errorType);
+        assertNull(bulkResult.getItems().get(0).errorReason);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void bulkResultWithSuccess() {
+        BulkResult bulkResult = new BulkResult(new GsonBuilder().serializeNulls().create());
+        bulkResult.setJsonString(indexSuccessResult);
+        bulkResult.setJsonMap(new Gson().fromJson(indexSuccessResult, Map.class));
+        bulkResult.setSucceeded(true);
+
+        assertEquals(1, bulkResult.getItems().size());
+        assertEquals(0, bulkResult.getFailedItems().size());
+        assertEquals(201, bulkResult.getItems().get(0).status);
+        assertEquals(Integer.valueOf(3), bulkResult.getItems().get(0).version);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void bulkResultWithFailuresObject() {
+        BulkResult bulkResult = new BulkResult(new GsonBuilder().serializeNulls().create());
+        bulkResult.setJsonString(indexFailedResultObject);
+        bulkResult.setJsonMap(new Gson().fromJson(indexFailedResultObject, Map.class));
+        bulkResult.setSucceeded(false);
+
+        assertEquals(1, bulkResult.getItems().size());
+        assertEquals(1, bulkResult.getFailedItems().size());
+        assertNull(bulkResult.getItems().get(0).version);
+
+        assertEquals("type_missing_exception", bulkResult.getItems().get(0).errorType);
+        assertEquals("Reason is missing type", bulkResult.getItems().get(0).errorReason);
     }
 }