diff --git a/build.sh b/build.sh index 0a5df0a8..b0ef7365 100755 --- a/build.sh +++ b/build.sh @@ -81,6 +81,7 @@ do_java() { --additional-properties licenseName="Apache 2.0" \ --additional-properties artifactDescription="Client for Manticore Search" \ --additional-properties library="jersey3" \ + --additional-properties failOnUnknownProperties=false \ --additional-properties useJakartaEe=true \ --additional-properties prevVersion=$prev_version \ $build_to_branch diff --git a/templates/Java/libraries/jersey3/JSON.mustache b/templates/Java/libraries/jersey3/JSON.mustache index 97cee639..8ed57947 100755 --- a/templates/Java/libraries/jersey3/JSON.mustache +++ b/templates/Java/libraries/jersey3/JSON.mustache @@ -32,7 +32,7 @@ public class JSON implements ContextResolver { mapper = JsonMapper.builder() .serializationInclusion(JsonInclude.Include.NON_NULL) .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}) .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) diff --git a/test/java/api/UnknownResponsePropertyTest.java b/test/java/api/UnknownResponsePropertyTest.java new file mode 100644 index 00000000..6202b56e --- /dev/null +++ b/test/java/api/UnknownResponsePropertyTest.java @@ -0,0 +1,47 @@ +package com.manticoresearch.client.api; + +import com.manticoresearch.client.JSON; +import com.manticoresearch.client.model.SearchResponse; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * A response property the client does not know must not be fatal. + * + * Manticore Search adds response fields between releases, and the compatibility table in the + * README promises that an older client stays PARTIALLY compatible with a newer server. That + * promise is only keepable if an unknown property is ignored rather than thrown on: with + * FAIL_ON_UNKNOWN_PROPERTIES enabled, one new field costs the caller the entire response, not + * just the field. + * + * The schema agrees — `searchResponse`, `aggBucketsResult` and `aggBucket` all declare + * `additionalProperties: true` — and the generated Python client honours it. This test holds the + * Java client to the same contract. + */ +public class UnknownResponsePropertyTest { + + @Test + public void anUnknownTopLevelPropertyIsIgnored() throws Exception { + SearchResponse response = JSON.getDefault().getMapper().readValue( + "{\"took\":3,\"timed_out\":false,\"some_field_from_a_newer_server\":42," + + "\"hits\":{\"total\":1,\"total_relation\":\"eq\",\"hits\":[]}}", + SearchResponse.class); + + assertEquals(3, response.getTook()); + assertNotNull(response.getHits()); + assertEquals(1, response.getHits().getTotal()); + } + + @Test + public void anUnknownNestedPropertyIsIgnored() throws Exception { + SearchResponse response = JSON.getDefault().getMapper().readValue( + "{\"took\":0,\"timed_out\":false," + + "\"hits\":{\"total\":0,\"total_relation\":\"eq\",\"hits\":[]}," + + "\"aggregations\":{\"a\":{\"buckets\":[],\"a_future_field\":\"x\"}}}", + SearchResponse.class); + + assertNotNull(response.getAggregations().get("a")); + } +}