Add parsing method to BytesRestResponse's error (#22873)
This commit adds a BytesRestResponse.errorFromXContent() method to parse the error returned by BytesRestResponse. It returns a ElasticsearchStatusException instance.
This commit is contained in:
parent
40e1c136de
commit
c74679b6b9
|
@ -565,7 +565,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
||||||
* Parses the output of {@link #generateFailureXContent(XContentBuilder, Params, Exception, boolean)}
|
* Parses the output of {@link #generateFailureXContent(XContentBuilder, Params, Exception, boolean)}
|
||||||
*/
|
*/
|
||||||
public static ElasticsearchException failureFromXContent(XContentParser parser) throws IOException {
|
public static ElasticsearchException failureFromXContent(XContentParser parser) throws IOException {
|
||||||
XContentParser.Token token = parser.nextToken();
|
XContentParser.Token token = parser.currentToken();
|
||||||
ensureFieldName(parser, token, ERROR);
|
ensureFieldName(parser, token, ERROR);
|
||||||
|
|
||||||
token = parser.nextToken();
|
token = parser.nextToken();
|
||||||
|
|
|
@ -23,24 +23,29 @@ import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
import org.apache.logging.log4j.util.Supplier;
|
import org.apache.logging.log4j.util.Supplier;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
|
import org.elasticsearch.ElasticsearchStatusException;
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
import org.elasticsearch.common.bytes.BytesArray;
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.logging.ESLoggerFactory;
|
import org.elasticsearch.common.logging.ESLoggerFactory;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE;
|
import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE;
|
||||||
import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT;
|
import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT;
|
||||||
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
|
|
||||||
|
|
||||||
public class BytesRestResponse extends RestResponse {
|
public class BytesRestResponse extends RestResponse {
|
||||||
|
|
||||||
public static final String TEXT_CONTENT_TYPE = "text/plain; charset=UTF-8";
|
public static final String TEXT_CONTENT_TYPE = "text/plain; charset=UTF-8";
|
||||||
|
|
||||||
|
private static final String STATUS = "status";
|
||||||
|
|
||||||
private final RestStatus status;
|
private final RestStatus status;
|
||||||
private final BytesReference content;
|
private final BytesReference content;
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
|
@ -136,8 +141,44 @@ public class BytesRestResponse extends RestResponse {
|
||||||
|
|
||||||
XContentBuilder builder = channel.newErrorBuilder().startObject();
|
XContentBuilder builder = channel.newErrorBuilder().startObject();
|
||||||
ElasticsearchException.generateFailureXContent(builder, params, e, channel.detailedErrorsEnabled());
|
ElasticsearchException.generateFailureXContent(builder, params, e, channel.detailedErrorsEnabled());
|
||||||
builder.field("status", status.getStatus());
|
builder.field(STATUS, status.getStatus());
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ElasticsearchStatusException errorFromXContent(XContentParser parser) throws IOException {
|
||||||
|
XContentParser.Token token = parser.nextToken();
|
||||||
|
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
|
||||||
|
|
||||||
|
ElasticsearchException exception = null;
|
||||||
|
RestStatus status = null;
|
||||||
|
|
||||||
|
String currentFieldName = null;
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
currentFieldName = parser.currentName();
|
||||||
|
}
|
||||||
|
if (STATUS.equals(currentFieldName)) {
|
||||||
|
if (token != XContentParser.Token.FIELD_NAME) {
|
||||||
|
ensureExpectedToken(XContentParser.Token.VALUE_NUMBER, token, parser::getTokenLocation);
|
||||||
|
status = RestStatus.fromCode(parser.intValue());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exception = ElasticsearchException.failureFromXContent(parser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception == null) {
|
||||||
|
throw new IllegalStateException("Failed to parse elasticsearch status exception: no exception was found");
|
||||||
|
}
|
||||||
|
|
||||||
|
ElasticsearchStatusException result = new ElasticsearchStatusException(exception.getMessage(), status, exception.getCause());
|
||||||
|
for (String header : exception.getHeaderKeys()) {
|
||||||
|
result.addHeader(header, exception.getHeader(header));
|
||||||
|
}
|
||||||
|
for (String metadata : exception.getMetadataKeys()) {
|
||||||
|
result.addMetadata(metadata, exception.getMetadata(metadata));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -604,6 +604,7 @@ public class ElasticsearchExceptionTests extends ESTestCase {
|
||||||
ElasticsearchException parsedFailure;
|
ElasticsearchException parsedFailure;
|
||||||
try (XContentParser parser = createParser(xContent, failureBytes)) {
|
try (XContentParser parser = createParser(xContent, failureBytes)) {
|
||||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
parsedFailure = ElasticsearchException.failureFromXContent(parser);
|
parsedFailure = ElasticsearchException.failureFromXContent(parser);
|
||||||
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
assertNull(parser.nextToken());
|
assertNull(parser.nextToken());
|
||||||
|
@ -629,6 +630,7 @@ public class ElasticsearchExceptionTests extends ESTestCase {
|
||||||
ElasticsearchException parsedFailure;
|
ElasticsearchException parsedFailure;
|
||||||
try (XContentParser parser = createParser(xContent, failureBytes)) {
|
try (XContentParser parser = createParser(xContent, failureBytes)) {
|
||||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
parsedFailure = ElasticsearchException.failureFromXContent(parser);
|
parsedFailure = ElasticsearchException.failureFromXContent(parser);
|
||||||
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
assertNull(parser.nextToken());
|
assertNull(parser.nextToken());
|
||||||
|
@ -670,14 +672,14 @@ public class ElasticsearchExceptionTests extends ESTestCase {
|
||||||
}, expectedJson);
|
}, expectedJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) {
|
public static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) {
|
||||||
if (expected == null) {
|
|
||||||
assertNull(actual);
|
|
||||||
} else {
|
|
||||||
assertNotNull(actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
if (expected == null) {
|
||||||
|
assertNull(actual);
|
||||||
|
} else {
|
||||||
|
assertNotNull(actual);
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(expected.getMessage(), actual.getMessage());
|
assertEquals(expected.getMessage(), actual.getMessage());
|
||||||
assertEquals(expected.getHeaders(), actual.getHeaders());
|
assertEquals(expected.getHeaders(), actual.getHeaders());
|
||||||
assertEquals(expected.getMetadata(), actual.getMetadata());
|
assertEquals(expected.getMetadata(), actual.getMetadata());
|
||||||
|
|
|
@ -20,12 +20,19 @@
|
||||||
package org.elasticsearch.rest;
|
package org.elasticsearch.rest;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
|
import org.elasticsearch.ElasticsearchStatusException;
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
|
import org.elasticsearch.ResourceAlreadyExistsException;
|
||||||
|
import org.elasticsearch.ResourceNotFoundException;
|
||||||
import org.elasticsearch.action.search.SearchPhaseExecutionException;
|
import org.elasticsearch.action.search.SearchPhaseExecutionException;
|
||||||
import org.elasticsearch.action.search.ShardSearchFailure;
|
import org.elasticsearch.action.search.ShardSearchFailure;
|
||||||
import org.elasticsearch.common.ParsingException;
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.transport.TransportAddress;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.index.Index;
|
import org.elasticsearch.index.Index;
|
||||||
import org.elasticsearch.search.SearchShardTarget;
|
import org.elasticsearch.search.SearchShardTarget;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -37,6 +44,7 @@ import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.elasticsearch.ElasticsearchExceptionTests.assertDeepEquals;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
@ -210,6 +218,144 @@ public class BytesRestResponseTests extends ESTestCase {
|
||||||
assertThat(content, containsString("\"status\":" + 500));
|
assertThat(content, containsString("\"status\":" + 500));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testErrorToAndFromXContent() throws IOException {
|
||||||
|
final boolean detailed = randomBoolean();
|
||||||
|
|
||||||
|
Exception original;
|
||||||
|
ElasticsearchException cause = null;
|
||||||
|
String reason;
|
||||||
|
String type = "exception";
|
||||||
|
RestStatus status = RestStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
boolean addHeadersOrMetadata = false;
|
||||||
|
|
||||||
|
switch (randomIntBetween(0, 5)) {
|
||||||
|
case 0:
|
||||||
|
original = new ElasticsearchException("ElasticsearchException without cause");
|
||||||
|
if (detailed) {
|
||||||
|
addHeadersOrMetadata = randomBoolean();
|
||||||
|
reason = "ElasticsearchException without cause";
|
||||||
|
} else {
|
||||||
|
reason = "ElasticsearchException[ElasticsearchException without cause]";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
original = new ElasticsearchException("ElasticsearchException with a cause", new FileNotFoundException("missing"));
|
||||||
|
if (detailed) {
|
||||||
|
addHeadersOrMetadata = randomBoolean();
|
||||||
|
type = "exception";
|
||||||
|
reason = "ElasticsearchException with a cause";
|
||||||
|
cause = new ElasticsearchException("Elasticsearch exception [type=file_not_found_exception, reason=missing]");
|
||||||
|
} else {
|
||||||
|
reason = "ElasticsearchException[ElasticsearchException with a cause]";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
original = new ResourceNotFoundException("ElasticsearchException with custom status");
|
||||||
|
status = RestStatus.NOT_FOUND;
|
||||||
|
if (detailed) {
|
||||||
|
addHeadersOrMetadata = randomBoolean();
|
||||||
|
type = "resource_not_found_exception";
|
||||||
|
reason = "ElasticsearchException with custom status";
|
||||||
|
} else {
|
||||||
|
reason = "ResourceNotFoundException[ElasticsearchException with custom status]";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
TransportAddress address = buildNewFakeTransportAddress();
|
||||||
|
original = new RemoteTransportException("remote", address, "action",
|
||||||
|
new ResourceAlreadyExistsException("ElasticsearchWrapperException with a cause that has a custom status"));
|
||||||
|
status = RestStatus.BAD_REQUEST;
|
||||||
|
if (detailed) {
|
||||||
|
type = "resource_already_exists_exception";
|
||||||
|
reason = "ElasticsearchWrapperException with a cause that has a custom status";
|
||||||
|
} else {
|
||||||
|
reason = "RemoteTransportException[[remote][" + address.toString() + "][action]]";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
original = new RemoteTransportException("ElasticsearchWrapperException with a cause that has a special treatment",
|
||||||
|
new IllegalArgumentException("wrong"));
|
||||||
|
status = RestStatus.BAD_REQUEST;
|
||||||
|
if (detailed) {
|
||||||
|
type = "illegal_argument_exception";
|
||||||
|
reason = "wrong";
|
||||||
|
} else {
|
||||||
|
reason = "RemoteTransportException[[ElasticsearchWrapperException with a cause that has a special treatment]]";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
status = randomFrom(RestStatus.values());
|
||||||
|
original = new ElasticsearchStatusException("ElasticsearchStatusException with random status", status);
|
||||||
|
if (detailed) {
|
||||||
|
addHeadersOrMetadata = randomBoolean();
|
||||||
|
type = "status_exception";
|
||||||
|
reason = "ElasticsearchStatusException with random status";
|
||||||
|
} else {
|
||||||
|
reason = "ElasticsearchStatusException[ElasticsearchStatusException with random status]";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Failed to generate random exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = "Elasticsearch exception [type=" + type + ", reason=" + reason + "]";
|
||||||
|
ElasticsearchStatusException expected = new ElasticsearchStatusException(message, status, cause);
|
||||||
|
|
||||||
|
if (addHeadersOrMetadata) {
|
||||||
|
ElasticsearchException originalException = ((ElasticsearchException) original);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
originalException.addHeader("foo", "bar", "baz");
|
||||||
|
expected.addHeader("foo", "bar", "baz");
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
originalException.addMetadata("es.metadata_0", "0");
|
||||||
|
expected.addMetadata("es.metadata_0", "0");
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
String resourceType = randomAsciiOfLength(5);
|
||||||
|
String resourceId = randomAsciiOfLength(5);
|
||||||
|
originalException.setResources(resourceType, resourceId);
|
||||||
|
expected.setResources(resourceType, resourceId);
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
originalException.setIndex("_index");
|
||||||
|
expected.setIndex("_index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
|
||||||
|
Map<String, String> params = Collections.singletonMap("format", xContentType.mediaType());
|
||||||
|
RestRequest request = new FakeRestRequest.Builder(xContentRegistry()).withParams(params).build();
|
||||||
|
RestChannel channel = detailed ? new DetailedExceptionRestChannel(request) : new SimpleExceptionRestChannel(request);
|
||||||
|
|
||||||
|
BytesRestResponse response = new BytesRestResponse(channel, original);
|
||||||
|
|
||||||
|
ElasticsearchException parsedError;
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), response.content())) {
|
||||||
|
parsedError = BytesRestResponse.errorFromXContent(parser);
|
||||||
|
assertNull(parser.nextToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(expected.status(), parsedError.status());
|
||||||
|
assertDeepEquals(expected, parsedError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoErrorFromXContent() throws IOException {
|
||||||
|
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
|
||||||
|
try (XContentBuilder builder = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())) {
|
||||||
|
builder.startObject();
|
||||||
|
builder.field("status", randomFrom(RestStatus.values()));
|
||||||
|
builder.endObject();
|
||||||
|
|
||||||
|
try (XContentParser parser = createParser(builder.contentType().xContent(), builder.bytes())) {
|
||||||
|
BytesRestResponse.errorFromXContent(parser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertEquals("Unable to parse elasticsearch status exception", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
public static class WithHeadersException extends ElasticsearchException {
|
public static class WithHeadersException extends ElasticsearchException {
|
||||||
|
|
||||||
WithHeadersException() {
|
WithHeadersException() {
|
||||||
|
|
Loading…
Reference in New Issue