From 503a6b5ac89fb0867308a10d0219e387c713318c Mon Sep 17 00:00:00 2001 From: Augment Agent Date: Fri, 27 Feb 2026 01:07:46 +0000 Subject: [PATCH] Strip _doc wrapper from effective_mapping field in API responses The effective_mapping field in the get data stream API response and simulate ingest API response was incorrectly including a _doc wrapper at the top level. This fix strips the _doc type placeholder from the effective mapping output to match the API specification. Changes: - SimulateIndexResponse: Strip _doc wrapper in innerToXContent() - GetDataStreamMappingsAction: Strip _doc wrapper in DataStreamMappingsResponse.toXContent() - UpdateDataStreamMappingsAction: Strip _doc wrapper in DataStreamMappingsResponse.toXContent() - Updated YAML REST test to expect mappings without _doc wrapper The fix uses the same pattern as Template.reduceMapping() to check if the mapping contains only a single _doc key and unwraps it if present. Fixes issue where effective_mapping output had incorrect structure. --- .../test/ingest/80_ingest_simulate.yml | 8 ++++---- .../datastreams/GetDataStreamMappingsAction.java | 13 +++++++++++-- .../UpdateDataStreamMappingsAction.java | 13 +++++++++++-- .../action/ingest/SimulateIndexResponse.java | 14 ++++++++++---- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml b/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml index 664a76ef6778c..f378e953e3c5e 100644 --- a/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml +++ b/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml @@ -2306,7 +2306,7 @@ setup: } - length: { docs: 1 } - match: { docs.0.doc._index: "second-index" } - - not_exists: docs.0.doc.effective_mapping._doc.properties.foo - - match: { docs.0.doc.effective_mapping._doc.properties.@timestamp.type: "date" } - - match: { docs.0.doc.effective_mapping._doc.properties.bar.type: "text" } - - match: { docs.0.doc.effective_mapping._doc.properties.baz.type: "keyword" } + - not_exists: docs.0.doc.effective_mapping.properties.foo + - match: { docs.0.doc.effective_mapping.properties.@timestamp.type: "date" } + - match: { docs.0.doc.effective_mapping.properties.bar.type: "text" } + - match: { docs.0.doc.effective_mapping.properties.baz.type: "keyword" } diff --git a/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamMappingsAction.java b/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamMappingsAction.java index 5d81d44b7df70..ecfd78b3772ee 100644 --- a/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamMappingsAction.java +++ b/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamMappingsAction.java @@ -21,6 +21,7 @@ import org.elasticsearch.common.xcontent.ChunkedToXContentObject; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.tasks.CancellableTask; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; @@ -131,8 +132,16 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws true, XContentType.JSON ).v2(); - builder.field("effective_mappings"); - builder.map(uncompressedEffectiveMappings); + // Strip the _doc wrapper if present + if (uncompressedEffectiveMappings.size() == 1 && uncompressedEffectiveMappings.containsKey(MapperService.SINGLE_MAPPING_NAME)) { + @SuppressWarnings("unchecked") + Map unwrapped = (Map) uncompressedEffectiveMappings.get(MapperService.SINGLE_MAPPING_NAME); + builder.field("effective_mappings"); + builder.map(unwrapped); + } else { + builder.field("effective_mappings"); + builder.map(uncompressedEffectiveMappings); + } builder.endObject(); return builder; } diff --git a/server/src/main/java/org/elasticsearch/action/datastreams/UpdateDataStreamMappingsAction.java b/server/src/main/java/org/elasticsearch/action/datastreams/UpdateDataStreamMappingsAction.java index 561e59f3eed36..b71151a2fb6d6 100644 --- a/server/src/main/java/org/elasticsearch/action/datastreams/UpdateDataStreamMappingsAction.java +++ b/server/src/main/java/org/elasticsearch/action/datastreams/UpdateDataStreamMappingsAction.java @@ -23,6 +23,7 @@ import org.elasticsearch.common.xcontent.ChunkedToXContentObject; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.tasks.CancellableTask; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; @@ -214,8 +215,16 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws XContentType.JSON ).v2(); if (uncompressedEffectiveMappings.isEmpty() == false) { - builder.field("effective_mappings"); - builder.map(uncompressedEffectiveMappings); + // Strip the _doc wrapper if present + if (uncompressedEffectiveMappings.size() == 1 && uncompressedEffectiveMappings.containsKey(MapperService.SINGLE_MAPPING_NAME)) { + @SuppressWarnings("unchecked") + Map unwrapped = (Map) uncompressedEffectiveMappings.get(MapperService.SINGLE_MAPPING_NAME); + builder.field("effective_mappings"); + builder.map(unwrapped); + } else { + builder.field("effective_mappings"); + builder.map(uncompressedEffectiveMappings); + } } builder.endObject(); return builder; diff --git a/server/src/main/java/org/elasticsearch/action/ingest/SimulateIndexResponse.java b/server/src/main/java/org/elasticsearch/action/ingest/SimulateIndexResponse.java index d8b2926dc18c2..854e64b48ffe4 100644 --- a/server/src/main/java/org/elasticsearch/action/ingest/SimulateIndexResponse.java +++ b/server/src/main/java/org/elasticsearch/action/ingest/SimulateIndexResponse.java @@ -20,6 +20,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.core.Nullable; +import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xcontent.XContentBuilder; @@ -131,10 +132,15 @@ public XContentBuilder innerToXContent(XContentBuilder builder, Params params) t if (effectiveMapping == null) { builder.field("effective_mapping", Map.of()); } else { - builder.field( - "effective_mapping", - XContentHelper.convertToMap(effectiveMapping.uncompressed(), true, builder.contentType()).v2() - ); + Map mapping = XContentHelper.convertToMap(effectiveMapping.uncompressed(), true, builder.contentType()).v2(); + // Strip the _doc wrapper if present + if (mapping.size() == 1 && mapping.containsKey(MapperService.SINGLE_MAPPING_NAME)) { + @SuppressWarnings("unchecked") + Map unwrapped = (Map) mapping.get(MapperService.SINGLE_MAPPING_NAME); + builder.field("effective_mapping", unwrapped); + } else { + builder.field("effective_mapping", mapping); + } } return builder; }