Repository: elastic/ecs

Issue:
Marks items as arrays if required.
This is to aid code generation for languages that support array constructs.

Note: This field is optional, the default value is false. Lack of field presence implies a value of false.


Discussion:

Resolving patch:
diff --git a/scripts/generators/asciidoc_fields.py b/scripts/generators/asciidoc_fields.py
--- a/scripts/generators/asciidoc_fields.py
+++ b/scripts/generators/asciidoc_fields.py
@@ -95,10 +95,15 @@ def render_field_details_row(field):
         for mf in field['multi_fields']:
             field_type_with_mf += "* {} (type: {})\n\n".format(mf['flat_name'], mf['type'])
 
+    field_normalization = ''
+    if 'array' in field['normalize']:
+        field_normalization = "\nNote: this field should contain an array of values.\n\n"
+
     text = field_details_row().format(
         field_flat_name=field['flat_name'],
         field_description=render_asciidoc_paragraphs(field['description']),
         field_example=example,
+        field_normalization=field_normalization,
         field_level=field['level'],
         field_type=field_type_with_mf,
     )
@@ -235,6 +240,8 @@ def field_details_row():
 
 type: {field_type}
 
+{field_normalization}
+
 {field_example}
 
 | {field_level}
diff --git a/scripts/generators/csv_generator.py b/scripts/generators/csv_generator.py
--- a/scripts/generators/csv_generator.py
+++ b/scripts/generators/csv_generator.py
@@ -30,7 +30,7 @@ def save_csv(file, sorted_fields, version):
                                    lineterminator='\n')
 
         schema_writer.writerow(["ECS_Version", "Indexed", "Field_Set", "Field",
-                                "Type", "Level", "Example", "Description"])
+                                "Type", "Level", "Normalization", "Example", "Description"])
         for field in sorted_fields:
             key_parts = field['flat_name'].split('.')
             if len(key_parts) == 1:
@@ -46,6 +46,7 @@ def save_csv(file, sorted_fields, version):
                 field['flat_name'],
                 field['type'],
                 field['level'],
+                ', '.join(field['normalize']),
                 field.get('example', ''),
                 field['short'],
             ])
@@ -59,6 +60,7 @@ def save_csv(file, sorted_fields, version):
                         mf['flat_name'],
                         mf['type'],
                         field['level'],
+                        '',
                         field.get('example', ''),
                         field['short'],
                     ])
diff --git a/scripts/schema_reader.py b/scripts/schema_reader.py
--- a/scripts/schema_reader.py
+++ b/scripts/schema_reader.py
@@ -93,6 +93,7 @@ def schema_fields_as_dictionary(schema):
 
 
 def field_set_defaults(field):
+    dict_set_default(field, 'normalize', [])
     if field['type'] == 'keyword':
         dict_set_default(field, 'ignore_above', 1024)
     if field['type'] == 'text':


Test patch:
diff --git a/scripts/tests/test_ecs_spec.py b/scripts/tests/test_ecs_spec.py
--- a/scripts/tests/test_ecs_spec.py
+++ b/scripts/tests/test_ecs_spec.py
@@ -90,6 +90,12 @@ def test_nested_includes_reusable_fields(self):
         self.assertIn('os.name', observer_keys)
         self.assertIn('os.name', user_agent_keys)
 
+    def test_related_fields_always_arrays(self):
+        for (field_name, field) in self.ecs_nested['related']['fields'].items():
+            self.assertIn('normalize', field.keys())
+            self.assertIn('array', field['normalize'],
+                          "All fields under `related.*` should be arrays")
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/scripts/tests/test_schema_reader.py b/scripts/tests/test_schema_reader.py
--- a/scripts/tests/test_schema_reader.py
+++ b/scripts/tests/test_schema_reader.py
@@ -57,7 +57,7 @@ def test_set_default_values_no_overwrite(self):
     def test_field_set_defaults_no_short(self):
         field = {'description': 'a field', 'type': 'faketype'}
         schema_reader.field_set_defaults(field)
-        self.assertEqual(field, {'description': 'a field', 'short': 'a field', 'type': 'faketype'})
+        self.assertEqual(field, {'description': 'a field', 'short': 'a field', 'type': 'faketype', 'normalize': []})
 
     def test_field_set_multi_field_defaults_missing_name(self):
         field = {
@@ -213,7 +213,8 @@ def test_cleanup_fields_recursive(self):
                                     'flat_name': 'base_set1.reusable_fieldset.reusable_field',
                                     'dashed_name': 'base-set1-reusable-fieldset-reusable-field',
                                     'ignore_above': 1024,
-                                    'short': 'A test field'
+                                    'short': 'A test field',
+                                    'normalize': [],
                                 }
                             }
                         }
@@ -240,7 +241,9 @@ def test_cleanup_fields_recursive(self):
                                     'flat_name': 'base_set2.reusable_fieldset.reusable_field',
                                     'dashed_name': 'base-set2-reusable-fieldset-reusable-field',
                                     'ignore_above': 1024,
-                                    'short': 'A test field'
+                                    'short': 'A test field',
+                                    'normalize': [],
+
                                 }
                             }
                         }