{"record":{"id":"c2c4f8058d6dbf99","repo":"quickwit-oss/quickwit","slug":"concatenate-type-must-have-at-least-one-sub-field","errorCode":null,"errorMessage":"concatenate type must have at least one sub-field","messagePattern":"concatenate type must have at least one sub-field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-doc-mapper/src/doc_mapper/field_mapping_entry.rs","lineNumber":740,"sourceCode":"    quickwit_field_type: QuickwitFieldType,\n    json: JsonValue,\n) -> anyhow::Result<FieldMappingType> {\n    let (typ, cardinality) = match quickwit_field_type {\n        QuickwitFieldType::Simple(typ) => (typ, Cardinality::SingleValued),\n        QuickwitFieldType::Array(typ) => (typ, Cardinality::MultiValued),\n        QuickwitFieldType::Object => {\n            let object_options: QuickwitObjectOptions = serde_json::from_value(json)?;\n            if object_options.field_mappings.is_empty() {\n                anyhow::bail!(\"object type must have at least one field mapping\");\n            }\n            return Ok(FieldMappingType::Object(object_options));\n        }\n        QuickwitFieldType::Concatenate => {\n            let concatenate_options: QuickwitConcatenateOptions = serde_json::from_value(json)?;\n            if concatenate_options.concatenate_fields.is_empty()\n                && !concatenate_options.include_dynamic_fields\n            {\n                anyhow::bail!(\"concatenate type must have at least one sub-field\");\n            }\n            return Ok(FieldMappingType::Concatenate(concatenate_options));\n        }\n    };\n    match typ {\n        Type::Str => {\n            let text_options: QuickwitTextOptions = serde_json::from_value(json)?;\n            Ok(FieldMappingType::Text(text_options, cardinality))\n        }\n        Type::U64 => {\n            let numeric_options: QuickwitNumericOptions = serde_json::from_value(json)?;\n            Ok(FieldMappingType::U64(numeric_options, cardinality))\n        }\n        Type::I64 => {\n            let numeric_options: QuickwitNumericOptions = serde_json::from_value(json)?;\n            Ok(FieldMappingType::I64(numeric_options, cardinality))\n        }\n        Type::F64 => {","sourceCodeStart":722,"sourceCodeEnd":758,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-doc-mapper/src/doc_mapper/field_mapping_entry.rs#L722-L758","documentation":"A `concatenate` field builds a combined searchable text from sub-fields. It must list at least one `concatenate_fields`, unless `include_dynamic_fields` is true (in which case dynamic fields provide the input). An empty list with dynamic fields disabled leaves the concatenate field with no source data, so deserialization bails.","triggerScenarios":"A mapping like `{name: \"all\", type: \"concatenate\", concatenate_fields: [], include_dynamic_fields: false}` — FieldMappingType::Concatenate deserialization bails.","commonSituations":"Setting up a 'search everything' concatenate field but forgetting to list fields; auto-generated mappings with empty lists when the intended default was include_dynamic_fields=true; field names removed from the list leaving it empty.","solutions":["Populate `concatenate_fields` with at least one text field name.","Set `include_dynamic_fields: true` if you intend the concatenate field to cover dynamic fields.","If neither applies, remove the concatenate field from the mapping."],"exampleFix":"// before\n{name: \"all\", type: \"concatenate\", concatenate_fields: [], include_dynamic_fields: false}\n// after\n{name: \"all\", type: \"concatenate\", concatenate_fields: [\"title\", \"body\"], include_dynamic_fields: false}","handlingStrategy":"validation","validationCode":"fn concatenate_has_sources(f: &serde_json::Value) -> bool {\n    if f.get(\"type\").and_then(|t| t.as_str()) == Some(\"concatenate\") {\n        let has_fields = f.get(\"concatenate_fields\")\n            .and_then(|m| m.as_array())\n            .map_or(false, |a| !a.is_empty());\n        let dynamic = f.get(\"include_dynamic_fields\")\n            .and_then(|v| v.as_bool()) == Some(true);\n        has_fields || dynamic\n    } else { true }\n}","typeGuard":null,"tryCatchPattern":"match create_index_result {\n    Err(e) if e.to_string().contains(\"concatenate type must have at least one sub-field\") => {\n        eprintln!(\"List concatenate_fields or enable include_dynamic_fields\");\n    }\n    other => other?,\n}","preventionTips":["Explicitly set include_dynamic_fields when you rely on dynamic fields for a concatenate field.","Re-check concatenate_fields lists after removing source fields from the mapping.","Lint mappings for empty concatenate_fields arrays."],"tags":["schema","mapping","validation","rust"],"backgroundTag":"empty-required-field","analyzedSha":"a39730c5cdcd1a4fe798403737ae293999ea21f8","analyzedAt":"2026-09-08T13:19:37.784Z","contentChangedAt":"2026-09-08T13:19:37.784Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}