Browse Source

Improved errors handling for Geofencing CF

pull/14808/head
dshvaika 7 months ago
parent
commit
e7ca66848c
  1. 5
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java
  2. 4
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java
  3. 7
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  4. 6
      common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterDefinitionDeserializer.java
  5. 42
      common/util/src/test/java/org/thingsboard/common/util/geo/PerimeterDefinitionDeserializerTest.java

5
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) {

4
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'");
}
}

7
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> T fromString(String string, Class<T> 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> T fromString(String string, Class<T> 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);
}
}

6
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<PerimeterD
JsonNode node = codec.readTree(p);
if (node.isObject()) {
if (!node.has("latitude") || !node.has("longitude") || !node.has("radius")) {
throw JsonMappingException.from(p, "CirclePerimeterDefinition missing required fields. Received: " + node);
}
double latitude = node.get("latitude").asDouble();
double longitude = node.get("longitude").asDouble();
double radius = node.get("radius").asDouble();
@ -42,7 +46,7 @@ public class PerimeterDefinitionDeserializer extends JsonDeserializer<PerimeterD
String polygonStrDefinition = mapper.writeValueAsString(node);
return new PolygonPerimeterDefinition(polygonStrDefinition);
}
throw new IOException("Failed to deserialize PerimeterDefinition from node: " + node);
throw JsonMappingException.from(p, "Unknown JSON format for PerimeterDefinition. Expected OBJECT (Circle) or ARRAY (Polygon), but found: " + node.getNodeType());
}
}

42
common/util/src/test/java/org/thingsboard/common/util/geo/PerimeterDefinitionDeserializerTest.java

@ -15,10 +15,12 @@
*/
package org.thingsboard.common.util.geo;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.junit.jupiter.api.Test;
import org.thingsboard.common.util.JacksonUtil;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class PerimeterDefinitionDeserializerTest {
@ -47,4 +49,44 @@ public class PerimeterDefinitionDeserializerTest {
PolygonPerimeterDefinition poly = (PolygonPerimeterDefinition) def;
assertThat(poly.getPolygonDefinition()).isEqualTo(json);
}
@Test
void shouldThrowWhenCircleFieldIsMissing() {
// Missing "radius"
String badJson = """
{
"latitude": 48.8566,
"longitude": 2.3522
}
""";
String customError = "Custom error context";
assertThatThrownBy(() -> 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();
}
}

Loading…
Cancel
Save