Skip to content

Commit 4af7642

Browse files
authored
Merge pull request #1530 from marklogic/feature/eval-error-fix
DEVEXP-191 Ensuring eval errors are returned as JSON
2 parents e417321 + 534e2e1 commit 4af7642

File tree

2 files changed

+58
-15
lines changed

2 files changed

+58
-15
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3851,28 +3851,24 @@ private <U extends OkHttpResultIterator> U postIteratedResourceImpl(
38513851
requestBldr = addTransactionScopedCookies(requestBldr, transaction);
38523852
requestBldr = addTelemetryAgentId(requestBldr);
38533853
requestBldr = addTrailerHeadersIfNecessary(requestBldr, path);
3854+
requestBldr = setErrorFormatIfNecessary(requestBldr, path);
38543855

3855-
Consumer<Boolean> resendableConsumer = new Consumer<Boolean>() {
3856-
public void accept(Boolean resendable) {
3856+
Consumer<Boolean> resendableConsumer = resendable -> {
38573857
if (!isResendable) {
38583858
checkFirstRequest();
38593859
throw new ResourceNotResendableException(
38603860
"Cannot retry request for " + path);
38613861
}
3862-
}
3863-
};
3864-
Function<Request.Builder, Response> doPostFunction = new Function<Request.Builder, Response>() {
3865-
public Response apply(Request.Builder funcBuilder) {
3866-
return doPost(reqlog, funcBuilder.header(HEADER_ACCEPT, multipartMixedWithBoundary()),
3867-
inputBase.sendContent());
3868-
}
38693862
};
3870-
Response response = sendRequestWithRetry(requestBldr, (transaction == null), doPostFunction, resendableConsumer);
3871-
int status = response.code();
38723863

3873-
checkStatus(response, status, "apply", "resource", path,
3874-
ResponseStatus.OK_OR_CREATED_OR_NO_CONTENT);
3864+
Function<Request.Builder, Response> doPostFunction = requestBuilder -> doPost(
3865+
reqlog,
3866+
requestBuilder.header(HEADER_ACCEPT, multipartMixedWithBoundary()),
3867+
inputBase.sendContent()
3868+
);
38753869

3870+
Response response = sendRequestWithRetry(requestBldr, (transaction == null), doPostFunction, resendableConsumer);
3871+
checkStatus(response, response.code(), "apply", "resource", path, ResponseStatus.OK_OR_CREATED_OR_NO_CONTENT);
38763872
return makeResults(constructor, reqlog, "apply", "resource", response);
38773873
}
38783874

@@ -4198,6 +4194,18 @@ private Request.Builder addTrailerHeadersIfNecessary(Request.Builder requestBldr
41984194
return requestBldr;
41994195
}
42004196

4197+
private Request.Builder setErrorFormatIfNecessary(Request.Builder requestBuilder, String path) {
4198+
// Slightly dirty hack; per https://docs.marklogic.com/guide/rest-dev/intro#id_34966, the X-Error-Accept header
4199+
// should be used to specify the error format. A REST API server defaults to 'json', though the App-Services app
4200+
// server defaults to 'compatible'. If the error format is 'compatible', a block of HTML is sent back which
4201+
// causes an error that prevents the user from seeing the actual error from the server. So for all eval calls,
4202+
// X-Error-Accept is used to request any errors back as JSON so that they can be handled correctly.
4203+
if ("eval".equals(path)) {
4204+
requestBuilder.addHeader(HEADER_ERROR_FORMAT, "application/json");
4205+
}
4206+
return requestBuilder;
4207+
}
4208+
42014209
private <W extends AbstractWriteHandle> boolean addParts(
42024210
MultipartBody.Builder multiPart, RequestLogger reqlog, W[] input)
42034211
{

marklogic-client-api/src/test/java/com/marklogic/client/test/EvalTest.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.ObjectMapper;
2121
import com.fasterxml.jackson.databind.node.NullNode;
22+
import com.fasterxml.jackson.databind.node.ObjectNode;
2223
import com.marklogic.client.DatabaseClient;
2324
import com.marklogic.client.FailedRequestException;
2425
import com.marklogic.client.Transaction;
@@ -32,6 +33,8 @@
3233
import com.marklogic.client.io.*;
3334
import com.marklogic.client.query.DeleteQueryDefinition;
3435
import com.marklogic.client.query.QueryManager;
36+
import com.marklogic.mgmt.ManageClient;
37+
import com.marklogic.mgmt.resource.appservers.ServerManager;
3538
import org.junit.jupiter.api.AfterAll;
3639
import org.junit.jupiter.api.BeforeAll;
3740
import org.junit.jupiter.api.Test;
@@ -61,15 +64,47 @@ public class EvalTest {
6164
private static DatabaseClient restAdminClient = Common.connectRestAdmin();
6265

6366
@BeforeAll
64-
public static void beforeClass() {
67+
public static void beforeAll() {
6568
libMgr = restAdminClient.newServerConfigManager().newExtensionLibrariesManager();
6669
Common.connectEval();
6770

6871
septFirst.set(2014, Calendar.SEPTEMBER, 1, 0, 0, 0);
6972
septFirst.set(Calendar.MILLISECOND, 0);
73+
74+
// In order to verify that X-Error-Accept is used to request back errors as JSON, the app server used by this test
75+
// must first be modified to default to "compatible" which results in a block of HTML being sent back, which will
76+
// cause an error to be returned when the client tries to process it as JSON.
77+
ObjectNode payload = Common.newServerPayload().put("default-error-format", "compatible");
78+
new ServerManager(Common.newManageClient()).save(payload.toString());
7079
}
80+
7181
@AfterAll
72-
public static void afterClass() {
82+
public static void afterAll() {
83+
// Reset the app server back to the default of "json" as the error format
84+
ObjectNode payload = Common.newServerPayload().put("default-error-format", "json");
85+
new ServerManager(Common.newManageClient()).save(payload.toString());
86+
}
87+
88+
@Test
89+
void invalidJavascript() {
90+
FailedRequestException ex = assertThrows(FailedRequestException.class, () ->
91+
Common.evalClient.newServerEval().javascript("console.log('This fails").evalAs(String.class));
92+
93+
String message = ex.getServerMessage();
94+
assertTrue(message.contains("Invalid or unexpected token"), "The error message from the server is expected " +
95+
"to contain the actual error, which in this case is due to bad syntax. In order for this to happen, the " +
96+
"Java Client should send the X-Error-Accept header per the docs at " +
97+
"https://docs.marklogic.com/guide/rest-dev/intro#id_34966; actual error: " + message);
98+
}
99+
100+
@Test
101+
void invalidXQuery() {
102+
FailedRequestException ex = assertThrows(FailedRequestException.class, () ->
103+
Common.evalClient.newServerEval().xquery("let $var := this fails").evalAs(String.class));
104+
105+
String message = ex.getServerMessage();
106+
assertTrue(message.contains("Unexpected token syntax error"), "The server error message should contain the " +
107+
"actual error; actual message: " + message);
73108
}
74109

75110
@Test

0 commit comments

Comments
 (0)