Skip to content

Commit 3802568

Browse files
committed
Add support for default tags in tagged unions
1 parent 4c60424 commit 3802568

File tree

9 files changed

+144
-10
lines changed

9 files changed

+144
-10
lines changed

java-client/src/main/java/co/elastic/clients/json/JsonpDeserializerBase.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,15 @@ public Integer deserialize(JsonParser parser, JsonpMapper mapper, Event event) {
125125

126126
static final JsonpDeserializer<Boolean> BOOLEAN =
127127
new JsonpDeserializerBase<Boolean>(
128-
EnumSet.of(Event.VALUE_FALSE, Event.VALUE_TRUE)
128+
EnumSet.of(Event.VALUE_FALSE, Event.VALUE_TRUE, Event.VALUE_STRING)
129129
) {
130130
@Override
131131
public Boolean deserialize(JsonParser parser, JsonpMapper mapper, Event event) {
132-
return event == Event.VALUE_TRUE;
132+
if (event == Event.VALUE_STRING) {
133+
return Boolean.parseBoolean(parser.getString());
134+
} else {
135+
return event == Event.VALUE_TRUE;
136+
}
133137
}
134138
};
135139

java-client/src/main/java/co/elastic/clients/json/JsonpUtils.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,18 @@ public static <T> void serialize(T value, JsonGenerator generator, @Nullable Jso
136136
* Returns a pair containing that value and a parser that should be used to actually parse the object
137137
* (the object has been consumed from the original one).
138138
*/
139-
public static Map.Entry<String, JsonParser> lookAheadFieldValue(String name, JsonParser parser, JsonpMapper mapper) {
139+
public static Map.Entry<String, JsonParser> lookAheadFieldValue(
140+
String name, String defaultValue, JsonParser parser, JsonpMapper mapper
141+
) {
140142
// FIXME: need a buffering parser wrapper so that we don't roundtrip through a JsonObject and a String
141143
// FIXME: resulting parser should return locations that are offset with the original parser's location
142144
JsonObject object = parser.getObject();
143145
String result = object.getString(name, null);
144146

147+
if (result == null) {
148+
result = defaultValue;
149+
}
150+
145151
if (result == null) {
146152
throw new JsonParsingException("Property '" + name + "' not found", parser.getLocation());
147153
}

java-client/src/main/java/co/elastic/clients/json/ObjectDeserializer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public void deserialize(JsonParser parser, JsonpMapper mapper, String fieldName,
102102
protected final Map<String, FieldDeserializer<ObjectType>> fieldDeserializers;
103103
private FieldDeserializer<ObjectType> singleKey;
104104
private String typeProperty;
105+
private String defaultType;
105106
private FieldDeserializer<ObjectType> shortcutProperty;
106107
private QuadConsumer<ObjectType, String, JsonParser, JsonpMapper> unknownFieldHandler;
107108

@@ -180,7 +181,7 @@ public ObjectType deserialize(ObjectType value, JsonParser parser, JsonpMapper m
180181
// Union variant: find the property to find the proper deserializer
181182
// We cannot start with a key name here.
182183
JsonpUtils.expectEvent(parser, Event.START_OBJECT, event);
183-
Map.Entry<String, JsonParser> unionInfo = JsonpUtils.lookAheadFieldValue(typeProperty, parser, mapper);
184+
Map.Entry<String, JsonParser> unionInfo = JsonpUtils.lookAheadFieldValue(typeProperty, defaultType, parser, mapper);
184185
String variant = unionInfo.getKey();
185186
JsonParser innerParser = unionInfo.getValue();
186187

@@ -261,8 +262,9 @@ public <FieldType> void setKey(BiConsumer<ObjectType, FieldType> setter, JsonpDe
261262
this.singleKey = new FieldObjectDeserializer<>(setter, deserializer, null);
262263
}
263264

264-
public void setTypeProperty(String name) {
265+
public void setTypeProperty(String name, String defaultType) {
265266
this.typeProperty = name;
267+
this.defaultType = defaultType;
266268
}
267269

268270
//----- Primitive types

java-client/src/main/java/co/elastic/clients/transport/rest_client/RestClientTransport.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,5 @@ private void checkProductHeader(Response clientResp, Endpoint<?, ?, ?> endpoint)
348348
new ResponseException(clientResp)
349349
);
350350
}
351-
352351
}
353352
}

java-client/src/test/java/co/elastic/clients/elasticsearch/end_to_end/RequestTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
2525
import co.elastic.clients.elasticsearch._types.Refresh;
2626
import co.elastic.clients.elasticsearch._types.aggregations.HistogramAggregate;
27+
import co.elastic.clients.elasticsearch._types.mapping.Property;
2728
import co.elastic.clients.elasticsearch.cat.NodesResponse;
2829
import co.elastic.clients.elasticsearch.core.BulkResponse;
2930
import co.elastic.clients.elasticsearch.core.ClearScrollResponse;
@@ -37,6 +38,8 @@
3738
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
3839
import co.elastic.clients.elasticsearch.indices.DiskUsageResponse;
3940
import co.elastic.clients.elasticsearch.indices.GetIndexResponse;
41+
import co.elastic.clients.elasticsearch.indices.GetIndicesSettingsResponse;
42+
import co.elastic.clients.elasticsearch.indices.GetMappingResponse;
4043
import co.elastic.clients.elasticsearch.indices.IndexState;
4144
import co.elastic.clients.elasticsearch.model.ModelTestCase;
4245
import co.elastic.clients.json.JsonpMapper;
@@ -57,6 +60,7 @@
5760

5861
import java.io.IOException;
5962
import java.time.Duration;
63+
import java.util.Collections;
6064
import java.util.Map;
6165
import java.util.concurrent.CompletableFuture;
6266
import java.util.concurrent.ExecutionException;
@@ -345,6 +349,49 @@ public void testSearchAggregation() throws IOException {
345349

346350
}
347351

352+
@Test
353+
public void testGetMapping() throws Exception {
354+
// See also VariantsTest.testNestedTaggedUnionWithDefaultTag()
355+
// and https://github.com/elastic/elasticsearch-java/issues/45
356+
String index = "testindex";
357+
358+
Map<String, Property> fields = Collections.singletonMap("keyword", Property.of(p -> p.keyword(k -> k.ignoreAbove(256))));
359+
Property text = Property.of(p -> p.text(t -> t.fields(fields)));
360+
361+
client.indices().create(c -> c
362+
.index(index)
363+
.mappings(m -> m.properties(ps -> ps
364+
.put("id", text)
365+
.put("name", p -> p
366+
.object(o -> o.properties(fs -> fs
367+
.put("first", text)
368+
.put("last", text)
369+
))
370+
)
371+
))
372+
);
373+
374+
GetMappingResponse mr = client.indices().getMapping(mrb -> mrb.index(index));
375+
376+
assertNotNull(mr.result().get(index));
377+
assertNotNull(mr.result().get(index).mappings().properties().get("name").object());
378+
}
379+
380+
@Test
381+
public void testDefaultIndexSettings() throws IOException {
382+
//https://github.com/elastic/elasticsearch-java/issues/46
383+
384+
String index = "index-settings";
385+
client.index(_1 -> _1.index(index).document(new Product(5)).refresh(Refresh.True));
386+
387+
GetIndicesSettingsResponse settings;
388+
settings = client.indices().getSettings(b -> b.index(index).includeDefaults(true));
389+
assertNotNull(settings.get(index).defaults());
390+
391+
settings = client.indices().getSettings(b -> b.index(index));
392+
assertNull(settings.get(index).defaults());
393+
}
394+
348395
@Test
349396
public void testValueBodyResponse() throws Exception {
350397
DiskUsageResponse resp = client.indices().diskUsage(b -> b

java-client/src/test/java/co/elastic/clients/elasticsearch/experiments/containers/SomeUnion.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,6 @@ protected static void setupSomeUnionDeserializer(ObjectDeserializer<Builder> op)
147147
op.add(SomeUnion.Builder::variantA, UVariantA._DESERIALIZER, "variant_a");
148148
op.add(SomeUnion.Builder::variantB, UVariantB._DESERIALIZER, "variant_b");
149149

150-
op.setTypeProperty("type");
150+
op.setTypeProperty("type", null);
151151
}
152152
}

java-client/src/test/java/co/elastic/clients/elasticsearch/model/BehaviorsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@ public void testAdditionalPropertyOnContainer() {
8787
{
8888
SortOptions so = SortOptionsBuilders.geoDistance()
8989
.field("foo")
90-
.value(GeoLocation.of(_b -> _b.text("someWKT")))
90+
.location(GeoLocation.of(_b -> _b.text("someWKT")))
9191
.build()
9292
._toSortOptions();
9393

9494
so = checkJsonRoundtrip(so, "{\"_geo_distance\":{\"foo\":[\"someWKT\"]}}");
9595
assertEquals(SortOptions.Kind.GeoDistance, so._kind());
9696
assertEquals("foo", so.geoDistance().field());
97-
assertEquals("someWKT", so.geoDistance().value().get(0).text());
97+
assertEquals("someWKT", so.geoDistance().location().get(0).text());
9898
}
9999

100100
{

java-client/src/test/java/co/elastic/clients/elasticsearch/model/UnionTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@
2020
package co.elastic.clients.elasticsearch.model;
2121

2222
import co.elastic.clients.elasticsearch._types.Script;
23+
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
24+
import co.elastic.clients.elasticsearch.indices.GetIndicesSettingsResponse;
25+
import co.elastic.clients.elasticsearch.indices.GetMappingResponse;
2326
import org.junit.Test;
2427

2528

2629
public class UnionTests extends ModelTestCase {
2730

2831
@Test
2932
public void testScriptDeserializer() {
30-
33+
// A union discriminated by its field names (source -> inline, id -> stored)
3134
{
3235
Script s = Script.of(_1 -> _1
3336
.inline(_2 -> _2

java-client/src/test/java/co/elastic/clients/elasticsearch/model/VariantsTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
package co.elastic.clients.elasticsearch.model;
2121

2222
import co.elastic.clients.elasticsearch._types.mapping.Property;
23+
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
2324
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
2425
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
26+
import co.elastic.clients.elasticsearch.indices.GetMappingResponse;
2527
import org.junit.Test;
2628

2729
public class VariantsTest extends ModelTestCase {
@@ -105,4 +107,75 @@ public void testBuilders() {
105107

106108
assertEquals("{\"exists\":{\"field\":\"foo\"}}", toJson(q));
107109
}
110+
111+
112+
@Test
113+
public void testNestedTaggedUnionWithDefaultTag() {
114+
// https://github.com/elastic/elasticsearch-java/issues/45
115+
116+
// Object fields don't really exist in ES and are based on a naming convention where field names
117+
// are dot-separated paths. The hierarchy is rebuilt from these names and ES doesn't send back
118+
// "type": "object" for object properties.
119+
// See https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html
120+
//
121+
// Mappings are therefore a hierarchy of internally-tagged unions based on the "type" property
122+
// with a default "object" tag value if the "type" property is missing.
123+
124+
String json =
125+
"{\n" +
126+
" \"testindex\" : {\n" +
127+
" \"mappings\" : {\n" +
128+
" \"properties\" : {\n" +
129+
" \"id\" : {\n" +
130+
" \"type\" : \"text\",\n" +
131+
" \"fields\" : {\n" +
132+
" \"keyword\" : {\n" +
133+
" \"type\" : \"keyword\",\n" +
134+
" \"ignore_above\" : 256\n" +
135+
" }\n" +
136+
" }\n" +
137+
" },\n" +
138+
" \"name\" : {\n" +
139+
" \"properties\" : {\n" +
140+
" \"first\" : {\n" +
141+
" \"type\" : \"text\",\n" +
142+
" \"fields\" : {\n" +
143+
" \"keyword\" : {\n" +
144+
" \"type\" : \"keyword\",\n" +
145+
" \"ignore_above\" : 256\n" +
146+
" }\n" +
147+
" }\n" +
148+
" },\n" +
149+
" \"last\" : {\n" +
150+
" \"type\" : \"text\",\n" +
151+
" \"fields\" : {\n" +
152+
" \"keyword\" : {\n" +
153+
" \"type\" : \"keyword\",\n" +
154+
" \"ignore_above\" : 256\n" +
155+
" }\n" +
156+
" }\n" +
157+
" }\n" +
158+
" }\n" +
159+
" }\n" +
160+
" }\n" +
161+
" }\n" +
162+
" }\n" +
163+
"}";
164+
165+
GetMappingResponse response = fromJson(json, GetMappingResponse.class);
166+
167+
TypeMapping mappings = response.get("testindex").mappings();
168+
assertTrue(mappings.properties().get("name").isObject());
169+
170+
assertEquals(256, mappings
171+
.properties().get("name").object()
172+
.properties().get("first").text()
173+
.fields().get("keyword").keyword().
174+
ignoreAbove().longValue()
175+
);
176+
177+
assertTrue(mappings.properties().get("id").isText());
178+
179+
assertEquals(256, mappings.properties().get("id").text().fields().get("keyword").keyword().ignoreAbove().longValue());
180+
}
108181
}

0 commit comments

Comments
 (0)