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)}
|
||||
*/
|
||||
public static ElasticsearchException failureFromXContent(XContentParser parser) throws IOException {
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
ensureFieldName(parser, token, ERROR);
|
||||
|
||||
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.util.Supplier;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.logging.ESLoggerFactory;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
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_DEFAULT;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||
|
||||
|
||||
public class BytesRestResponse extends RestResponse {
|
||||
|
||||
public static final String TEXT_CONTENT_TYPE = "text/plain; charset=UTF-8";
|
||||
|
||||
private static final String STATUS = "status";
|
||||
|
||||
private final RestStatus status;
|
||||
private final BytesReference content;
|
||||
private final String contentType;
|
||||
|
@ -136,8 +141,44 @@ public class BytesRestResponse extends RestResponse {
|
|||
|
||||
XContentBuilder builder = channel.newErrorBuilder().startObject();
|
||||
ElasticsearchException.generateFailureXContent(builder, params, e, channel.detailedErrorsEnabled());
|
||||
builder.field("status", status.getStatus());
|
||||
builder.field(STATUS, status.getStatus());
|
||||
builder.endObject();
|
||||
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;
|
||||
try (XContentParser parser = createParser(xContent, failureBytes)) {
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||
parsedFailure = ElasticsearchException.failureFromXContent(parser);
|
||||
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||
assertNull(parser.nextToken());
|
||||
|
@ -629,6 +630,7 @@ public class ElasticsearchExceptionTests extends ESTestCase {
|
|||
ElasticsearchException parsedFailure;
|
||||
try (XContentParser parser = createParser(xContent, failureBytes)) {
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||
parsedFailure = ElasticsearchException.failureFromXContent(parser);
|
||||
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||
assertNull(parser.nextToken());
|
||||
|
@ -670,14 +672,14 @@ public class ElasticsearchExceptionTests extends ESTestCase {
|
|||
}, expectedJson);
|
||||
}
|
||||
|
||||
private static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) {
|
||||
public static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) {
|
||||
do {
|
||||
if (expected == null) {
|
||||
assertNull(actual);
|
||||
} else {
|
||||
assertNotNull(actual);
|
||||
}
|
||||
|
||||
do {
|
||||
assertEquals(expected.getMessage(), actual.getMessage());
|
||||
assertEquals(expected.getHeaders(), actual.getHeaders());
|
||||
assertEquals(expected.getMetadata(), actual.getMetadata());
|
||||
|
|
|
@ -20,12 +20,19 @@
|
|||
package org.elasticsearch.rest;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.ResourceAlreadyExistsException;
|
||||
import org.elasticsearch.ResourceNotFoundException;
|
||||
import org.elasticsearch.action.search.SearchPhaseExecutionException;
|
||||
import org.elasticsearch.action.search.ShardSearchFailure;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
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.search.SearchShardTarget;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
@ -37,6 +44,7 @@ import java.io.IOException;
|
|||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.ElasticsearchExceptionTests.assertDeepEquals;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
@ -210,6 +218,144 @@ public class BytesRestResponseTests extends ESTestCase {
|
|||
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 {
|
||||
|
||||
WithHeadersException() {
|
||||
|
|
Loading…
Reference in New Issue