From e7ca66848c1c354dca1b0a47e7e9d2cb7c1dc62b Mon Sep 17 00:00:00 2001 From: dshvaika Date: Wed, 14 Jan 2026 15:56:10 +0200 Subject: [PATCH] Improved errors handling for Geofencing CF --- .../state/geofencing/GeofencingZoneState.java | 5 ++- .../GeofencingValueArgumentEntryTest.java | 4 +- .../thingsboard/common/util/JacksonUtil.java | 7 +++- .../geo/PerimeterDefinitionDeserializer.java | 6 ++- .../PerimeterDefinitionDeserializerTest.java | 42 +++++++++++++++++++ 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java index 9fe51bbf22..cd61509c18 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java @@ -51,9 +51,10 @@ public class GeofencingZoneState { this.ts = attributeKvEntry.getLastUpdateTs(); this.version = attributeKvEntry.getVersion(); if (entry.getValueAsString() == null) { - throw new IllegalArgumentException("Perimeter attribute key '" + entry.getKey() + "' not found for Zone with id: " + zoneId); + throw new IllegalArgumentException("Perimeter attribute '" + entry.getKey() + "' not found for Zone with id: " + zoneId); } - this.perimeterDefinition = JacksonUtil.fromString(entry.getValueAsString(), PerimeterDefinition.class); + this.perimeterDefinition = JacksonUtil.fromString(entry.getValueAsString(), PerimeterDefinition.class, + "Invalid perimeter definition format for Zone with id: " + zoneId + ". Failed to parse attribute '" + entry.getKey() + "'"); } public GeofencingZoneState(GeofencingZoneProto proto) { diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java index 2e550b8b74..d7bc39f6e8 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java @@ -177,7 +177,7 @@ public class GeofencingValueArgumentEntryTest { BaseAttributeKvEntry invalidZoneEntry = new BaseAttributeKvEntry(new StringDataEntry("zone", "someString"), 363L, 155L); assertThatThrownBy(() -> new GeofencingArgumentEntry(Map.of(ZONE_1_ID, invalidZoneEntry))) .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage("The given string value cannot be transformed to Json object: someString"); + .hasMessage("Invalid perimeter definition format for Zone with id: " + ZONE_1_ID + ". Failed to parse attribute 'zone'"); } @Test @@ -185,7 +185,7 @@ public class GeofencingValueArgumentEntryTest { BaseAttributeKvEntry invalidZoneEntry = new BaseAttributeKvEntry(new JsonDataEntry("zone", "\"{}\""), 363L, 155L); assertThatThrownBy(() -> new GeofencingArgumentEntry(Map.of(ZONE_1_ID, invalidZoneEntry))) .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage("The given string value cannot be transformed to Json object: \"{}\""); + .hasMessage("Invalid perimeter definition format for Zone with id: " + ZONE_1_ID + ". Failed to parse attribute 'zone'"); } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index 9913210c1a..2cf758885f 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -115,10 +115,15 @@ public class JacksonUtil { @Contract("null, _ -> null") // so that IDE doesn't show NPE warning when input is not null public static T fromString(String string, Class clazz) { + return fromString(string, clazz, "The given string value cannot be transformed to Json object: " + string); + } + + @Contract("null, _, _ -> null") // so that IDE doesn't show NPE warning when input is not null + public static T fromString(String string, Class clazz, String errorMsg) { try { return string != null ? OBJECT_MAPPER.readValue(string, clazz) : null; } catch (IOException e) { - throw new IllegalArgumentException("The given string value cannot be transformed to Json object: " + string, e); + throw new IllegalArgumentException(errorMsg, e); } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterDefinitionDeserializer.java b/common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterDefinitionDeserializer.java index 9ddddbdf9e..1c22d6a007 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterDefinitionDeserializer.java +++ b/common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterDefinitionDeserializer.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -32,6 +33,9 @@ public class PerimeterDefinitionDeserializer extends JsonDeserializer JacksonUtil.fromString(badJson, PerimeterDefinition.class, customError)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(customError) + .hasCauseInstanceOf(JsonMappingException.class) + .rootCause() + .hasMessageContaining("CirclePerimeterDefinition missing required fields"); + } + + @Test + void shouldThrowWhenJsonIsGarbage() { + String garbageJson = "\"NotAnObjectOrArray\""; + String customError = "Garbage check"; + + assertThatThrownBy(() -> JacksonUtil.fromString(garbageJson, PerimeterDefinition.class, customError)) + .isInstanceOf(IllegalArgumentException.class) + .hasCauseInstanceOf(JsonMappingException.class) + .rootCause() + .hasMessageContaining("Unknown JSON format"); + } + + @Test + void shouldReturnNullWhenInputIsNull() { + //noinspection ConstantConditions + PerimeterDefinition result = JacksonUtil.fromString(null, PerimeterDefinition.class, "Error"); + assertThat(result).isNull(); + } + }