Skip to content

Commit ddebeae

Browse files
authoredJan 27, 2024
[#3609] Improve error handling of custom mapper
The logic for invoking a custom mapper for upstream commands has been adapted to consider the (failure) status code returned by the mapper when generating a corresponding ServiceInvocationException to be sent back in the reply to the downstream sender. fixes #3609
1 parent e9a92f3 commit ddebeae

File tree

2 files changed

+58
-6
lines changed

2 files changed

+58
-6
lines changed
 

‎adapters/mqtt-base/src/main/java/org/eclipse/hono/adapter/mqtt/impl/HttpBasedMessageMapping.java

+19-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.eclipse.hono.adapter.mqtt.MqttProtocolAdapterProperties;
2727
import org.eclipse.hono.client.ServerErrorException;
2828
import org.eclipse.hono.client.command.Command;
29+
import org.eclipse.hono.client.util.StatusCodeMapper;
2930
import org.eclipse.hono.util.MessageHelper;
3031
import org.eclipse.hono.util.RegistrationAssertion;
3132
import org.eclipse.hono.util.Strings;
@@ -199,7 +200,8 @@ private void mapUpstreamMessageRequest(
199200
command.getDeviceId(),
200201
mapperEndpoint.getHost(), mapperEndpoint.getPort(), mapperEndpoint.getUri(),
201202
httpResponseAsyncResult.cause());
202-
result.fail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, httpResponseAsyncResult.cause()));
203+
final Throwable exception = mapException(command.getTenant(), httpResponseAsyncResult, null);
204+
result.fail(exception);
203205
} else {
204206
final HttpResponse<Buffer> httpResponse = httpResponseAsyncResult.result();
205207
if (httpResponse.statusCode() == HttpURLConnection.HTTP_OK) {
@@ -208,8 +210,9 @@ private void mapUpstreamMessageRequest(
208210
LOG.debug("mapping service [host: {}, port: {}, URI: {}] returned unexpected status code: {}",
209211
mapperEndpoint.getHost(), mapperEndpoint.getPort(), mapperEndpoint.getUri(),
210212
httpResponse.statusCode());
211-
result.fail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE,
212-
"could not invoke configured mapping service"));
213+
final Throwable exception = mapException(command.getTenant(), httpResponseAsyncResult,
214+
"could not invoke configured mapping service");
215+
result.fail(exception);
213216
}
214217
}
215218
resultHandler.handle(result.future());
@@ -279,4 +282,17 @@ private void mapDownstreamMessageRequest(
279282
resultHandler.handle(result.future());
280283
});
281284
}
285+
286+
private Throwable mapException(final String tenantId, final AsyncResult<HttpResponse<Buffer>> httpResponseAsyncResult, final String message) {
287+
final String detailMessage = Optional.ofNullable(message)
288+
.orElse(Optional.ofNullable(httpResponseAsyncResult.cause()).map(Throwable::getMessage).orElse(null));
289+
final Optional<HttpResponse<Buffer>> httpResponse = Optional.ofNullable(httpResponseAsyncResult.result());
290+
final int statusCode = httpResponse.map(HttpResponse::statusCode).orElse(HttpURLConnection.HTTP_UNAVAILABLE);
291+
return StatusCodeMapper.from(
292+
tenantId,
293+
statusCode,
294+
detailMessage,
295+
httpResponseAsyncResult.cause()
296+
);
297+
}
282298
}

‎adapters/mqtt-base/src/test/java/org/eclipse/hono/adapter/mqtt/impl/HttpBasedMessageMappingTest.java

+39-3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.eclipse.hono.adapter.MapperEndpoint;
3535
import org.eclipse.hono.adapter.mqtt.MqttContext;
3636
import org.eclipse.hono.adapter.mqtt.MqttProtocolAdapterProperties;
37+
import org.eclipse.hono.client.ClientErrorException;
3738
import org.eclipse.hono.client.ServerErrorException;
3839
import org.eclipse.hono.client.command.Command;
3940
import org.eclipse.hono.service.auth.DeviceUser;
@@ -353,7 +354,7 @@ public void testMapCommandSucceeds(final VertxTestContext ctx) {
353354
}
354355

355356
/**
356-
* Verifies that the upstream mapper returns a failed future with a ServerErrorException if the upstream mapper has been configured
357+
* Verifies that the upstream mapper returns a failed future with a ClientErrorException if the upstream mapper has been configured
357358
* for an adapter but the remote service returns a 403 status code indicating that the device payload cannot be mapped.
358359
*
359360
* @param ctx The Vert.x test context.
@@ -379,8 +380,8 @@ public void testMappingCommandFailsForWhenPayloadCannotMapped(final VertxTestCon
379380
messageMapping.mapUpstreamMessage(assertion, command)
380381
.onComplete(ctx.failing(t -> {
381382
ctx.verify(() -> {
382-
assertThat(t).isInstanceOf(ServerErrorException.class);
383-
assertThat((((ServerErrorException) t).getErrorCode())).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
383+
assertThat(t).isInstanceOf(ClientErrorException.class);
384+
assertThat((((ClientErrorException) t).getErrorCode())).isEqualTo(HttpURLConnection.HTTP_FORBIDDEN);
384385
});
385386
ctx.completeNow();
386387
}));
@@ -389,4 +390,39 @@ public void testMappingCommandFailsForWhenPayloadCannotMapped(final VertxTestCon
389390
verify(httpRequest).sendBuffer(any(Buffer.class), handleCaptor.capture());
390391
handleCaptor.getValue().handle(Future.succeededFuture(httpResponse));
391392
}
393+
394+
/**
395+
* Verifies that the upstream mapper returns a failed future with a ServerErrorException if the upstream mapper has been configured
396+
* for an adapter but the remote service cannot be reached should return a 503.
397+
*
398+
* @param ctx The Vert.x test context.
399+
*/
400+
@Test
401+
@SuppressWarnings("unchecked")
402+
public void testMappingCommandFailsForWhenMapperCannotBeReached(final VertxTestContext ctx) {
403+
404+
config.setMapperEndpoints(Map.of("mapper", MapperEndpoint.from("host", 1234, "/uri", false)));
405+
final HttpRequest<Buffer> httpRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
406+
407+
final Buffer payload = Buffer.buffer("payload");
408+
409+
when(mapperWebClient.post(anyInt(), anyString(), anyString())).thenReturn(httpRequest);
410+
411+
final Command command = mock(Command.class);
412+
when(command.getPayload()).thenReturn(payload);
413+
414+
final RegistrationAssertion assertion = new RegistrationAssertion("gateway").setUpstreamMessageMapper("mapper");
415+
messageMapping.mapUpstreamMessage(assertion, command)
416+
.onComplete(ctx.failing(t -> {
417+
ctx.verify(() -> {
418+
assertThat(t).isInstanceOf(ServerErrorException.class);
419+
assertThat((((ServerErrorException) t).getErrorCode())).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
420+
});
421+
ctx.completeNow();
422+
}));
423+
424+
final ArgumentCaptor<Handler<AsyncResult<HttpResponse<Buffer>>>> handleCaptor = VertxMockSupport.argumentCaptorHandler();
425+
verify(httpRequest).sendBuffer(any(Buffer.class), handleCaptor.capture());
426+
handleCaptor.getValue().handle(Future.failedFuture(new RuntimeException("something went wrong")));
427+
}
392428
}

0 commit comments

Comments
 (0)
Please sign in to comment.