Add parsing method for ElasticsearchException (#22143)
ElasticsearchException is used in various response objects like IndexResponse, DeleteResponse or BulkItemResponse.Failure. It would be helpful to the High Level Rest Client to be able to parse these exceptions back. This commit adds the fromXContent() method to the ElasticsearchException object. This method does not return the original (wrapped or unwrapped) exception but always returns a ElasticsearchException that serves as a simple POJO for all types of exceptions. The parsed ElasticsearchException's message will be composed of the original exception type (ex: illegal_argument_exception) concatenated with the original reason to help users/clients to known and handle the error.
This commit is contained in:
parent
a511fb9ce6
commit
135f60502c
|
@ -27,6 +27,7 @@ import org.elasticsearch.common.io.stream.Writeable;
|
|||
import org.elasticsearch.common.logging.LoggerMessageFormat;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.Index;
|
||||
import org.elasticsearch.index.shard.ShardId;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
@ -43,6 +44,8 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
|
||||
|
||||
/**
|
||||
* A base class for all elasticsearch exceptions.
|
||||
|
@ -71,6 +74,14 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
private static final String RESOURCE_HEADER_TYPE_KEY = "es.resource.type";
|
||||
private static final String RESOURCE_HEADER_ID_KEY = "es.resource.id";
|
||||
|
||||
private static final String TYPE = "type";
|
||||
private static final String REASON = "reason";
|
||||
private static final String CAUSED_BY = "caused_by";
|
||||
private static final String STACK_TRACE = "stack_trace";
|
||||
private static final String HEADER = "header";
|
||||
private static final String ERROR = "error";
|
||||
private static final String ROOT_CAUSE = "root_cause";
|
||||
|
||||
private static final Map<Integer, FunctionThatThrowsIOException<StreamInput, ? extends ElasticsearchException>> ID_TO_SUPPLIER;
|
||||
private static final Map<Class<? extends ElasticsearchException>, ElasticsearchExceptionHandle> CLASS_TO_ELASTICSEARCH_EXCEPTION_HANDLE;
|
||||
private final Map<String, List<String>> headers = new HashMap<>();
|
||||
|
@ -247,8 +258,8 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
if (ex != this) {
|
||||
toXContent(builder, params, this);
|
||||
} else {
|
||||
builder.field("type", getExceptionName());
|
||||
builder.field("reason", getMessage());
|
||||
builder.field(TYPE, getExceptionName());
|
||||
builder.field(REASON, getMessage());
|
||||
for (String key : headers.keySet()) {
|
||||
if (key.startsWith("es.")) {
|
||||
List<String> values = headers.get(key);
|
||||
|
@ -258,7 +269,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
innerToXContent(builder, params);
|
||||
renderHeader(builder, params);
|
||||
if (params.paramAsBoolean(REST_EXCEPTION_SKIP_STACK_TRACE, REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT) == false) {
|
||||
builder.field("stack_trace", ExceptionsHelper.stackTrace(this));
|
||||
builder.field(STACK_TRACE, ExceptionsHelper.stackTrace(this));
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
|
@ -277,7 +288,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
protected void causeToXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
final Throwable cause = getCause();
|
||||
if (cause != null && params.paramAsBoolean(REST_EXCEPTION_SKIP_CAUSE, REST_EXCEPTION_SKIP_CAUSE_DEFAULT) == false) {
|
||||
builder.field("caused_by");
|
||||
builder.field(CAUSED_BY);
|
||||
builder.startObject();
|
||||
toXContent(builder, params, cause);
|
||||
builder.endObject();
|
||||
|
@ -291,7 +302,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
continue;
|
||||
}
|
||||
if (hasHeader == false) {
|
||||
builder.startObject("header");
|
||||
builder.startObject(HEADER);
|
||||
hasHeader = true;
|
||||
}
|
||||
List<String> values = headers.get(key);
|
||||
|
@ -324,20 +335,74 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
if (ex instanceof ElasticsearchException) {
|
||||
((ElasticsearchException) ex).toXContent(builder, params);
|
||||
} else {
|
||||
builder.field("type", getExceptionName(ex));
|
||||
builder.field("reason", ex.getMessage());
|
||||
builder.field(TYPE, getExceptionName(ex));
|
||||
builder.field(REASON, ex.getMessage());
|
||||
if (ex.getCause() != null) {
|
||||
builder.field("caused_by");
|
||||
builder.field(CAUSED_BY);
|
||||
builder.startObject();
|
||||
toXContent(builder, params, ex.getCause());
|
||||
builder.endObject();
|
||||
}
|
||||
if (params.paramAsBoolean(REST_EXCEPTION_SKIP_STACK_TRACE, REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT) == false) {
|
||||
builder.field("stack_trace", ExceptionsHelper.stackTrace(ex));
|
||||
builder.field(STACK_TRACE, ExceptionsHelper.stackTrace(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a {@link ElasticsearchException} from a {@link XContentParser}. This does not
|
||||
* return the original exception type (ie NodeClosedException for example) but just wraps
|
||||
* the type, the reason and the cause of the exception. It also recursively parses the
|
||||
* tree structure of the cause, returning it as a tree structure of {@link ElasticsearchException}
|
||||
* instances.
|
||||
*/
|
||||
public static ElasticsearchException fromXContent(XContentParser parser) throws IOException {
|
||||
XContentParser.Token token = ensureFieldName(parser.nextToken(), parser::getTokenLocation);
|
||||
|
||||
String type = null, reason = null, stack = null;
|
||||
ElasticsearchException cause = null;
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
|
||||
do {
|
||||
String currentFieldName = parser.currentName();
|
||||
token = parser.nextToken();
|
||||
if (token.isValue()) {
|
||||
if (TYPE.equals(currentFieldName)) {
|
||||
type = parser.text();
|
||||
} else if (REASON.equals(currentFieldName)) {
|
||||
reason = parser.text();
|
||||
} else if (STACK_TRACE.equals(currentFieldName)) {
|
||||
stack = parser.text();
|
||||
} else {
|
||||
// Everything else is considered as a header
|
||||
headers.put(currentFieldName, parser.text());
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
if (CAUSED_BY.equals(currentFieldName)) {
|
||||
cause = fromXContent(parser);
|
||||
} else if (HEADER.equals(currentFieldName)) {
|
||||
headers.putAll(parser.map());
|
||||
} else {
|
||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
||||
}
|
||||
}
|
||||
} while ((token = parser.nextToken()) == XContentParser.Token.FIELD_NAME);
|
||||
|
||||
StringBuilder message = new StringBuilder("Elasticsearch exception [");
|
||||
message.append(TYPE).append('=').append(type).append(", ");
|
||||
message.append(REASON).append('=').append(reason);
|
||||
if (stack != null) {
|
||||
message.append(", ").append(STACK_TRACE).append('=').append(stack);
|
||||
}
|
||||
message.append(']');
|
||||
|
||||
ElasticsearchException e = new ElasticsearchException(message.toString(), cause);
|
||||
for (Map.Entry<String, Object> header : headers.entrySet()) {
|
||||
e.addHeader(header.getKey(), String.valueOf(header.getValue()));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root cause of this exception or multiple if different shards caused different exceptions
|
||||
*/
|
||||
|
@ -809,9 +874,9 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
}
|
||||
|
||||
public static void renderException(XContentBuilder builder, Params params, Exception e) throws IOException {
|
||||
builder.startObject("error");
|
||||
builder.startObject(ERROR);
|
||||
final ElasticsearchException[] rootCauses = ElasticsearchException.guessRootCauses(e);
|
||||
builder.field("root_cause");
|
||||
builder.field(ROOT_CAUSE);
|
||||
builder.startArray();
|
||||
for (ElasticsearchException rootCause : rootCauses) {
|
||||
builder.startObject();
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser.Token;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A set of static methods to get {@link Token} from {@link XContentParser}
|
||||
* while checking for their types and throw {@link ParsingException} if needed.
|
||||
*/
|
||||
public final class XContentParserUtils {
|
||||
|
||||
private XContentParserUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that current token is of type {@link XContentParser.Token#FIELD_NAME}
|
||||
*
|
||||
* @return the token
|
||||
* @throws ParsingException if the token is not of type {@link XContentParser.Token#FIELD_NAME}
|
||||
*/
|
||||
public static Token ensureFieldName(Token token, Supplier<XContentLocation> location) throws IOException {
|
||||
return ensureType(Token.FIELD_NAME, token, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that current token is of type {@link XContentParser.Token#FIELD_NAME} and the the field name is equal to the provided one
|
||||
*
|
||||
* @return the token
|
||||
* @throws ParsingException if the token is not of type {@link XContentParser.Token#FIELD_NAME} or is not equal to the given
|
||||
* field name
|
||||
*/
|
||||
public static Token ensureFieldName(XContentParser parser, Token token, String fieldName) throws IOException {
|
||||
Token t = ensureType(Token.FIELD_NAME, token, parser::getTokenLocation);
|
||||
|
||||
String current = parser.currentName() != null ? parser.currentName() : "<null>";
|
||||
if (current.equals(fieldName) == false) {
|
||||
String message = "Failed to parse object: expecting field with name [%s] but found [%s]";
|
||||
throw new ParsingException(parser.getTokenLocation(), String.format(Locale.ROOT, message, fieldName, current));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ParsingException with a "unknown field found" reason
|
||||
*/
|
||||
public static void throwUnknownField(String field, XContentLocation location) {
|
||||
String message = "Failed to parse object: unknown field [%s] found";
|
||||
throw new ParsingException(location, String.format(Locale.ROOT, message, field));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that current token is of the expected type
|
||||
*
|
||||
* @return the token
|
||||
* @throws ParsingException if the token is not equal to the expected type
|
||||
*/
|
||||
private static Token ensureType(Token expected, Token current, Supplier<XContentLocation> location) {
|
||||
if (current != expected) {
|
||||
String message = "Failed to parse object: expecting token of type [%s] but found [%s]";
|
||||
throw new ParsingException(location.get(), String.format(Locale.ROOT, message, expected, current));
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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.elasticsearch;
|
||||
|
||||
import org.elasticsearch.action.RoutingMissingException;
|
||||
import org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException;
|
||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.discovery.DiscoverySettings;
|
||||
import org.elasticsearch.index.shard.IndexShardRecoveringException;
|
||||
import org.elasticsearch.index.shard.ShardId;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.hasItem;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
public class ElasticsearchExceptionTests extends ESTestCase {
|
||||
|
||||
public void testToXContent() throws IOException {
|
||||
ElasticsearchException e = new ElasticsearchException("test");
|
||||
assertExceptionAsJson(e, false, equalTo("{\"type\":\"exception\",\"reason\":\"test\"}"));
|
||||
|
||||
e = new IndexShardRecoveringException(new ShardId("_test", "_0", 5));
|
||||
assertExceptionAsJson(e, false, equalTo("{\"type\":\"index_shard_recovering_exception\"," +
|
||||
"\"reason\":\"CurrentState[RECOVERING] Already recovering\",\"index_uuid\":\"_0\",\"shard\":\"5\",\"index\":\"_test\"}"));
|
||||
|
||||
e = new BroadcastShardOperationFailedException(new ShardId("_index", "_uuid", 12), "foo", new IllegalStateException("bar"));
|
||||
assertExceptionAsJson(e, false, equalTo("{\"type\":\"illegal_state_exception\",\"reason\":\"bar\"}"));
|
||||
|
||||
e = new ElasticsearchException(new IllegalArgumentException("foo"));
|
||||
assertExceptionAsJson(e, false, equalTo("{\"type\":\"exception\",\"reason\":\"java.lang.IllegalArgumentException: foo\"," +
|
||||
"\"caused_by\":{\"type\":\"illegal_argument_exception\",\"reason\":\"foo\"}}"));
|
||||
|
||||
e = new ElasticsearchException("foo", new IllegalStateException("bar"));
|
||||
assertExceptionAsJson(e, false, equalTo("{\"type\":\"exception\",\"reason\":\"foo\"," +
|
||||
"\"caused_by\":{\"type\":\"illegal_state_exception\",\"reason\":\"bar\"}}"));
|
||||
|
||||
// Test the same exception but with the "rest.exception.stacktrace.skip" parameter disabled: the stack_trace must be present
|
||||
// in the JSON. Since the stack can be large, it only checks the beginning of the JSON.
|
||||
assertExceptionAsJson(e, true, startsWith("{\"type\":\"exception\",\"reason\":\"foo\"," +
|
||||
"\"caused_by\":{\"type\":\"illegal_state_exception\",\"reason\":\"bar\"," +
|
||||
"\"stack_trace\":\"java.lang.IllegalStateException: bar"));
|
||||
}
|
||||
|
||||
public void testToXContentWithHeaders() throws IOException {
|
||||
ElasticsearchException e = new ElasticsearchException("foo",
|
||||
new ElasticsearchException("bar",
|
||||
new ElasticsearchException("baz",
|
||||
new ClusterBlockException(singleton(DiscoverySettings.NO_MASTER_BLOCK_WRITES)))));
|
||||
e.addHeader("foo_0", "0");
|
||||
e.addHeader("foo_1", "1");
|
||||
e.addHeader("es.header_foo_0", "foo_0");
|
||||
e.addHeader("es.header_foo_1", "foo_1");
|
||||
|
||||
final String expectedJson = "{"
|
||||
+ "\"type\":\"exception\","
|
||||
+ "\"reason\":\"foo\","
|
||||
+ "\"header_foo_0\":\"foo_0\","
|
||||
+ "\"header_foo_1\":\"foo_1\","
|
||||
+ "\"caused_by\":{"
|
||||
+ "\"type\":\"exception\","
|
||||
+ "\"reason\":\"bar\","
|
||||
+ "\"caused_by\":{"
|
||||
+ "\"type\":\"exception\","
|
||||
+ "\"reason\":\"baz\","
|
||||
+ "\"caused_by\":{"
|
||||
+ "\"type\":\"cluster_block_exception\","
|
||||
+ "\"reason\":\"blocked by: [SERVICE_UNAVAILABLE/2/no master];\""
|
||||
+ "}"
|
||||
+ "}"
|
||||
+ "},"
|
||||
+ "\"header\":{"
|
||||
+ "\"foo_0\":\"0\","
|
||||
+ "\"foo_1\":\"1\""
|
||||
+ "}"
|
||||
+ "}";
|
||||
|
||||
assertExceptionAsJson(e, false, equalTo(expectedJson));
|
||||
|
||||
ElasticsearchException parsed;
|
||||
try (XContentParser parser = XContentType.JSON.xContent().createParser(expectedJson)) {
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
parsed = ElasticsearchException.fromXContent(parser);
|
||||
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
|
||||
assertNotNull(parsed);
|
||||
assertEquals(parsed.getMessage(), "Elasticsearch exception [type=exception, reason=foo]");
|
||||
assertThat(parsed.getHeaderKeys(), hasSize(4));
|
||||
assertEquals(parsed.getHeader("header_foo_0").get(0), "foo_0");
|
||||
assertEquals(parsed.getHeader("header_foo_1").get(0), "foo_1");
|
||||
assertEquals(parsed.getHeader("foo_0").get(0), "0");
|
||||
assertEquals(parsed.getHeader("foo_1").get(0), "1");
|
||||
|
||||
ElasticsearchException cause = (ElasticsearchException) parsed.getCause();
|
||||
assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=bar]");
|
||||
|
||||
cause = (ElasticsearchException) cause.getCause();
|
||||
assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=baz]");
|
||||
|
||||
cause = (ElasticsearchException) cause.getCause();
|
||||
assertEquals(cause.getMessage(),
|
||||
"Elasticsearch exception [type=cluster_block_exception, reason=blocked by: [SERVICE_UNAVAILABLE/2/no master];]");
|
||||
}
|
||||
|
||||
public void testFromXContent() throws IOException {
|
||||
final XContent xContent = randomFrom(XContentType.values()).xContent();
|
||||
XContentBuilder builder = XContentBuilder.builder(xContent)
|
||||
.startObject()
|
||||
.field("type", "foo")
|
||||
.field("reason", "something went wrong")
|
||||
.field("stack_trace", "...")
|
||||
.endObject();
|
||||
|
||||
ElasticsearchException parsed;
|
||||
try (XContentParser parser = xContent.createParser(builder.bytes())) {
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
parsed = ElasticsearchException.fromXContent(parser);
|
||||
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
|
||||
assertNotNull(parsed);
|
||||
assertEquals(parsed.getMessage(), "Elasticsearch exception [type=foo, reason=something went wrong, stack_trace=...]");
|
||||
}
|
||||
|
||||
public void testFromXContentWithCause() throws IOException {
|
||||
ElasticsearchException e = new ElasticsearchException("foo",
|
||||
new ElasticsearchException("bar",
|
||||
new ElasticsearchException("baz",
|
||||
new RoutingMissingException("_test", "_type", "_id"))));
|
||||
|
||||
final XContent xContent = randomFrom(XContentType.values()).xContent();
|
||||
XContentBuilder builder = XContentBuilder.builder(xContent).startObject().value(e).endObject();
|
||||
|
||||
ElasticsearchException parsed;
|
||||
try (XContentParser parser = xContent.createParser(builder.bytes())) {
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
parsed = ElasticsearchException.fromXContent(parser);
|
||||
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
|
||||
assertNotNull(parsed);
|
||||
assertEquals(parsed.getMessage(), "Elasticsearch exception [type=exception, reason=foo]");
|
||||
|
||||
ElasticsearchException cause = (ElasticsearchException) parsed.getCause();
|
||||
assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=bar]");
|
||||
|
||||
cause = (ElasticsearchException) cause.getCause();
|
||||
assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=baz]");
|
||||
|
||||
cause = (ElasticsearchException) cause.getCause();
|
||||
assertEquals(cause.getMessage(),
|
||||
"Elasticsearch exception [type=routing_missing_exception, reason=routing is required for [_test]/[_type]/[_id]]");
|
||||
assertThat(cause.getHeaderKeys(), hasSize(2));
|
||||
assertThat(cause.getHeader("index"), hasItem("_test"));
|
||||
assertThat(cause.getHeader("index_uuid"), hasItem("_na_"));
|
||||
}
|
||||
|
||||
public void testFromXContentWithHeaders() throws IOException {
|
||||
RoutingMissingException routing = new RoutingMissingException("_test", "_type", "_id");
|
||||
ElasticsearchException baz = new ElasticsearchException("baz", routing);
|
||||
baz.addHeader("baz_0", "baz0");
|
||||
baz.addHeader("es.baz_1", "baz1");
|
||||
baz.addHeader("baz_2", "baz2");
|
||||
baz.addHeader("es.baz_3", "baz3");
|
||||
ElasticsearchException bar = new ElasticsearchException("bar", baz);
|
||||
bar.addHeader("es.bar_0", "bar0");
|
||||
bar.addHeader("bar_1", "bar1");
|
||||
bar.addHeader("es.bar_2", "bar2");
|
||||
ElasticsearchException foo = new ElasticsearchException("foo", bar);
|
||||
foo.addHeader("es.foo_0", "foo0");
|
||||
foo.addHeader("foo_1", "foo1");
|
||||
|
||||
final XContent xContent = randomFrom(XContentType.values()).xContent();
|
||||
XContentBuilder builder = XContentBuilder.builder(xContent).startObject().value(foo).endObject();
|
||||
|
||||
ElasticsearchException parsed;
|
||||
try (XContentParser parser = xContent.createParser(builder.bytes())) {
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
parsed = ElasticsearchException.fromXContent(parser);
|
||||
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
|
||||
assertNotNull(parsed);
|
||||
assertEquals(parsed.getMessage(), "Elasticsearch exception [type=exception, reason=foo]");
|
||||
assertThat(parsed.getHeaderKeys(), hasSize(2));
|
||||
assertThat(parsed.getHeader("foo_0"), hasItem("foo0"));
|
||||
assertThat(parsed.getHeader("foo_1"), hasItem("foo1"));
|
||||
|
||||
ElasticsearchException cause = (ElasticsearchException) parsed.getCause();
|
||||
assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=bar]");
|
||||
assertThat(cause.getHeaderKeys(), hasSize(3));
|
||||
assertThat(cause.getHeader("bar_0"), hasItem("bar0"));
|
||||
assertThat(cause.getHeader("bar_1"), hasItem("bar1"));
|
||||
assertThat(cause.getHeader("bar_2"), hasItem("bar2"));
|
||||
|
||||
cause = (ElasticsearchException) cause.getCause();
|
||||
assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=baz]");
|
||||
assertThat(cause.getHeaderKeys(), hasSize(4));
|
||||
assertThat(cause.getHeader("baz_0"), hasItem("baz0"));
|
||||
assertThat(cause.getHeader("baz_1"), hasItem("baz1"));
|
||||
assertThat(cause.getHeader("baz_2"), hasItem("baz2"));
|
||||
assertThat(cause.getHeader("baz_3"), hasItem("baz3"));
|
||||
|
||||
cause = (ElasticsearchException) cause.getCause();
|
||||
assertEquals(cause.getMessage(),
|
||||
"Elasticsearch exception [type=routing_missing_exception, reason=routing is required for [_test]/[_type]/[_id]]");
|
||||
assertThat(cause.getHeaderKeys(), hasSize(2));
|
||||
assertThat(cause.getHeader("index"), hasItem("_test"));
|
||||
assertThat(cause.getHeader("index_uuid"), hasItem("_na_"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link ToXContent} using a JSON XContentBuilder and check the resulting string with the given {@link Matcher}.
|
||||
*
|
||||
* By default, the stack trace of the exception is not rendered. The parameter `errorTrace` forces the stack trace to
|
||||
* be rendered like the REST API does when the "error_trace" parameter is set to true.
|
||||
*/
|
||||
private static void assertExceptionAsJson(ElasticsearchException e, boolean errorTrace, Matcher<String> expected)
|
||||
throws IOException {
|
||||
ToXContent.Params params = ToXContent.EMPTY_PARAMS;
|
||||
if (errorTrace) {
|
||||
params = new ToXContent.MapParams(Collections.singletonMap(ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE, "false"));
|
||||
}
|
||||
try (XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent())) {
|
||||
builder.startObject();
|
||||
e.toXContent(builder, params);
|
||||
builder.endObject();
|
||||
assertThat(builder.bytes().utf8ToString(), expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class XContentParserUtilsTests extends ESTestCase {
|
||||
|
||||
private XContentType xContentType;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
xContentType = randomFrom(XContentType.values());
|
||||
}
|
||||
|
||||
public void testEnsureFieldName() throws IOException {
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> {
|
||||
XContentParser parser = createParser(createBuilder().startObject().endObject().bytes());
|
||||
// Parser current token is null
|
||||
assertNull(parser.currentToken());
|
||||
XContentParserUtils.ensureFieldName(parser.currentToken(), parser::getTokenLocation);
|
||||
});
|
||||
assertThat(e.getMessage(), equalTo("Failed to parse object: expecting token of type [FIELD_NAME] but found [null]"));
|
||||
|
||||
e = expectThrows(ParsingException.class, () -> {
|
||||
XContentParser parser = createParser(createBuilder().startObject().field("foo", "bar").endObject().bytes());
|
||||
// Parser next token is a start object
|
||||
XContentParserUtils.ensureFieldName(parser.nextToken(), parser::getTokenLocation);
|
||||
});
|
||||
assertThat(e.getMessage(), equalTo("Failed to parse object: expecting token of type [FIELD_NAME] but found [START_OBJECT]"));
|
||||
|
||||
e = expectThrows(ParsingException.class, () -> {
|
||||
XContentParser parser = createParser(createBuilder().startObject().field("foo", "bar").endObject().bytes());
|
||||
// Moves to start object
|
||||
assertThat(parser.nextToken(), is(XContentParser.Token.START_OBJECT));
|
||||
// Expected field name is "foo", not "test"
|
||||
XContentParserUtils.ensureFieldName(parser, parser.nextToken(), "test");
|
||||
});
|
||||
assertThat(e.getMessage(), equalTo("Failed to parse object: expecting field with name [test] but found [foo]"));
|
||||
|
||||
// Everything is fine
|
||||
final String randomFieldName = randomAsciiOfLength(5);
|
||||
XContentParser parser = createParser(createBuilder().startObject().field(randomFieldName, 0).endObject().bytes());
|
||||
assertThat(parser.nextToken(), is(XContentParser.Token.START_OBJECT));
|
||||
XContentParserUtils.ensureFieldName(parser, parser.nextToken(), randomFieldName);
|
||||
}
|
||||
|
||||
private XContentBuilder createBuilder() throws IOException {
|
||||
return XContentBuilder.builder(xContentType.xContent());
|
||||
}
|
||||
|
||||
private XContentParser createParser(BytesReference bytes) throws IOException {
|
||||
return xContentType.xContent().createParser(bytes);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue