summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2017-04-19 10:08:26 -0400
committerMark Benvenuto <mark.benvenuto@mongodb.com>2017-04-19 15:30:27 -0400
commitebd361aae87504474e7db011f08da7aeb0923167 (patch)
tree3b07cb1415b5397dc1c5285622a300efc47c4dea
parent72f19039beebcb3e087dc1efbe6fac31526d2fd0 (diff)
downloadmongo-ebd361aae87504474e7db011f08da7aeb0923167.tar.gz
SERVER-28514 Add Array support for IDL
-rw-r--r--buildscripts/idl/idl/ast.py5
-rw-r--r--buildscripts/idl/idl/binder.py14
-rw-r--r--buildscripts/idl/idl/errors.py17
-rw-r--r--buildscripts/idl/idl/generator.py498
-rw-r--r--buildscripts/idl/idl/syntax.py36
-rw-r--r--buildscripts/idl/sample/sample.idl4
-rw-r--r--buildscripts/idl/tests/test_binder.py71
-rw-r--r--src/mongo/idl/idl_parser.cpp34
-rw-r--r--src/mongo/idl/idl_parser.h23
-rw-r--r--src/mongo/idl/idl_test.cpp300
-rw-r--r--src/mongo/idl/unittest.idl79
11 files changed, 942 insertions, 139 deletions
diff --git a/buildscripts/idl/idl/ast.py b/buildscripts/idl/idl/ast.py
index 1337958b718..6c350d92459 100644
--- a/buildscripts/idl/idl/ast.py
+++ b/buildscripts/idl/idl/ast.py
@@ -110,7 +110,10 @@ class Field(common.SourceLocation):
self.bindata_subtype = None # type: unicode
self.default = None # type: unicode
- # Properties specific to fields with are structs.
+ # Properties specific to fields which are structs.
self.struct_type = None # type: unicode
+ # Properties specific to fields which are arrays.
+ self.array = False # type: bool
+
super(Field, self).__init__(file_name, line, column)
diff --git a/buildscripts/idl/idl/binder.py b/buildscripts/idl/idl/binder.py
index 39aa0ee4b63..b46eec84c33 100644
--- a/buildscripts/idl/idl/binder.py
+++ b/buildscripts/idl/idl/binder.py
@@ -89,7 +89,7 @@ def _validate_type(ctxt, idl_type):
"""Validate each type is correct."""
# Validate naming restrictions
- if idl_type.name.startswith("array"):
+ if idl_type.name.startswith("array<"):
ctxt.add_array_not_valid_error(idl_type, "type", idl_type.name)
_validate_type_properties(ctxt, idl_type, 'type')
@@ -196,7 +196,7 @@ def _bind_struct(ctxt, parsed_spec, struct):
ast_struct.strict = struct.strict
# Validate naming restrictions
- if ast_struct.name.startswith("array"):
+ if ast_struct.name.startswith("array<"):
ctxt.add_array_not_valid_error(ast_struct, "struct", ast_struct.name)
for field in struct.fields:
@@ -237,7 +237,7 @@ def _bind_field(ctxt, parsed_spec, field):
ast_field.optional = field.optional
# Validate naming restrictions
- if ast_field.name.startswith("array"):
+ if ast_field.name.startswith("array<"):
ctxt.add_array_not_valid_error(ast_field, "field", ast_field.name)
if field.ignore:
@@ -245,11 +245,17 @@ def _bind_field(ctxt, parsed_spec, field):
_validate_ignored_field(ctxt, field)
return ast_field
- # TODO: support array
(struct, idltype) = parsed_spec.symbols.resolve_field_type(ctxt, field)
if not struct and not idltype:
return None
+ # If the field type is an array, mark the AST version as such.
+ if syntax.parse_array_type(field.type):
+ ast_field.array = True
+
+ if field.default or (idltype and idltype.default):
+ ctxt.add_array_no_default(field, field.name)
+
# Copy over only the needed information if this a struct or a type
if struct:
ast_field.struct_type = struct.name
diff --git a/buildscripts/idl/idl/errors.py b/buildscripts/idl/idl/errors.py
index 86e3b119d88..47986c47435 100644
--- a/buildscripts/idl/idl/errors.py
+++ b/buildscripts/idl/idl/errors.py
@@ -55,6 +55,8 @@ ERROR_ID_FIELD_MUST_BE_EMPTY_FOR_STRUCT = "ID0019"
ERROR_ID_CUSTOM_SCALAR_SERIALIZATION_NOT_SUPPORTED = "ID0020"
ERROR_ID_BAD_ANY_TYPE_USE = "ID0021"
ERROR_ID_BAD_NUMERIC_CPP_TYPE = "ID0022"
+ERROR_ID_BAD_ARRAY_TYPE_NAME = "ID0023"
+ERROR_ID_ARRAY_NO_DEFAULT = "ID0024"
class IDLError(Exception):
@@ -388,6 +390,21 @@ class ParserContext(object):
" 'std::uint32_t', 'std::uint64_t', and 'std::int64_t' are supported.") %
(cpp_type, ast_type, ast_parent))
+ def add_bad_array_type_name(self, location, field_name, type_name):
+ # type: (common.SourceLocation, unicode, unicode) -> None
+ """Add an error about a field type having a malformed type name."""
+ self._add_error(location, ERROR_ID_BAD_ARRAY_TYPE_NAME,
+ ("'%s' is not a valid array type for field '%s'. A valid array type" +
+ " is in the form 'array<type_name>'.") % (type_name, field_name))
+
+ def add_array_no_default(self, location, field_name):
+ # type: (common.SourceLocation, unicode) -> None
+ """Add an error about an array having a type with a default value."""
+ self._add_error(
+ location, ERROR_ID_ARRAY_NO_DEFAULT,
+ "Field '%s' is not allowed to have both a default value and be an array type" %
+ (field_name))
+
def _assert_unique_error_messages():
# type: () -> None
diff --git a/buildscripts/idl/idl/generator.py b/buildscripts/idl/idl/generator.py
index d458572a717..90f18f02129 100644
--- a/buildscripts/idl/idl/generator.py
+++ b/buildscripts/idl/idl/generator.py
@@ -12,6 +12,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+# pylint: disable=too-many-lines
"""IDL C++ Code Generator."""
from __future__ import absolute_import, print_function, unicode_literals
@@ -21,7 +22,7 @@ import os
import string
import sys
import textwrap
-from typing import List, Union
+from typing import List, Mapping, Union
from . import ast
from . import bson
@@ -120,21 +121,36 @@ def _qualify_optional_type(cpp_type, field):
return cpp_type
-def _get_field_parameter_type(field):
+def _qualify_array_type(cpp_type, field):
+ # type: (unicode, ast.Field) -> unicode
+ """Qualify the type if the field is an array."""
+ if field.array:
+ cpp_type = "std::vector<%s>" % (cpp_type)
+
+ return cpp_type
+
+
+def _get_field_getter_setter_type(field):
# type: (ast.Field) -> unicode
- """Get the C++ type name for a parameter for a field."""
+ """Get the C++ type name for the getter/setter parameter for a field."""
assert field.cpp_type is not None or field.struct_type is not None
- cpp_type = _get_view_type(_get_field_cpp_type(field))
+ cpp_type = _get_field_cpp_type(field)
+
+ cpp_type = _get_view_type(cpp_type)
+
+ cpp_type = _qualify_array_type(cpp_type, field)
return _qualify_optional_type(cpp_type, field)
-def _get_field_member_type(field):
+def _get_field_storage_type(field):
# type: (ast.Field) -> unicode
- """Get the C++ type name for a class member for a field."""
+ """Get the C++ type name for the storage of class member for a field."""
cpp_type = _get_field_cpp_type(field)
+ cpp_type = _qualify_array_type(cpp_type, field)
+
return _qualify_optional_type(cpp_type, field)
@@ -144,8 +160,37 @@ def _get_field_member_name(field):
return '_%s' % (_camel_case(field.name))
-def _get_bson_type_check(field):
- # type: (ast.Field) -> unicode
+def _get_return_by_reference(field):
+ # type: (ast.Field) -> bool
+ """Return True if the type should be returned by reference."""
+ # For non-view types, return a reference for types:
+ # 1. arrays
+ # 2. nested structs
+ # But do not return a reference for:
+ # 1. std::int32_t and other primitive types
+ # 2. optional types
+ cpp_type = _get_field_cpp_type(field)
+
+ if not _is_view_type(cpp_type) and (not field.optional and
+ (not _is_primitive_type(cpp_type) or field.array)):
+ return True
+
+ return False
+
+
+def _get_disable_xvalue(field):
+ # type: (ast.Field) -> bool
+ """Return True if the type should have the xvalue getter disabled."""
+ # Any we return references or view types, we should disable the xvalue.
+ # For view types like StringData, the return type and member storage types are different
+ # so returning a reference is not supported.
+ cpp_type = _get_field_cpp_type(field)
+
+ return _is_view_type(cpp_type) or _get_return_by_reference(field)
+
+
+def _get_bson_type_check(bson_element, ctxt_name, field):
+ # type: (unicode, unicode, ast.Field) -> unicode
"""Get the C++ bson type check for a field."""
bson_types = field.bson_serialization_type
if len(bson_types) == 1:
@@ -154,13 +199,14 @@ def _get_bson_type_check(field):
return None
if not bson_types[0] == 'bindata':
- return 'ctxt.checkAndAssertType(element, %s)' % bson.cpp_bson_type_name(bson_types[0])
+ return '%s.checkAndAssertType(%s, %s)' % (ctxt_name, bson_element,
+ bson.cpp_bson_type_name(bson_types[0]))
else:
- return 'ctxt.checkAndAssertBinDataType(element, %s)' % bson.cpp_bindata_subtype_type_name(
- field.bindata_subtype)
+ return '%s.checkAndAssertBinDataType(%s, %s)' % (
+ ctxt_name, bson_element, bson.cpp_bindata_subtype_type_name(field.bindata_subtype))
else:
type_list = '{%s}' % (', '.join([bson.cpp_bson_type_name(b) for b in bson_types]))
- return 'ctxt.checkAndAssertTypes(element, %s)' % type_list
+ return '%s.checkAndAssertTypes(%s, %s)' % (ctxt_name, bson_element, type_list)
def _access_member(field):
@@ -185,6 +231,15 @@ def fill_spaces(count):
return fill
+def _template_format(template, template_params):
+ # type: (unicode, Mapping[unicode,unicode]) -> unicode
+ """Write a template to the stream."""
+ # Ignore the types since we use unicode literals and this expects str but works fine with
+ # unicode.
+ # See https://docs.python.org/2/library/string.html#template-strings
+ return string.Template(template).substitute(template_params) # type: ignore
+
+
def indent_text(count, unindented_text):
# type: (int, unicode) -> unicode
"""Indent each line of a multi-line string."""
@@ -207,10 +262,11 @@ class _IndentedTextWriter(object):
"""Create an indented text writer."""
self._stream = stream
self._indent = 0
+ self._template_context = None # type: Mapping[unicode, unicode]
def write_unindented_line(self, msg):
# type: (unicode) -> None
- """Write an unindented line to the stream."""
+ """Write an unindented line to the stream, no template formatting applied."""
self._stream.write(msg)
self._stream.write("\n")
@@ -227,7 +283,26 @@ class _IndentedTextWriter(object):
def write_line(self, msg):
# type: (unicode) -> None
- """Write a line to the stream."""
+ """Write a line to the stream, no template formatting applied."""
+ self._stream.write(indent_text(self._indent, msg))
+ self._stream.write("\n")
+
+ def set_template_mapping(self, template_params):
+ # type: (Mapping[unicode,unicode]) -> None
+ """Set the current template mapping parameters for string.Template formatting."""
+ assert not self._template_context
+ self._template_context = template_params
+
+ def clear_template_mapping(self):
+ # type: () -> None
+ """Clear the current template mapping parameters for string.Template formatting."""
+ assert self._template_context
+ self._template_context = None
+
+ def write_template(self, template):
+ # type: (unicode) -> None
+ """Write a template to the stream."""
+ msg = _template_format(template, self._template_context)
self._stream.write(indent_text(self._indent, msg))
self._stream.write("\n")
@@ -237,6 +312,26 @@ class _IndentedTextWriter(object):
self._stream.write("\n")
+class _TemplateContext(object):
+ """Set the template context for the writer."""
+
+ def __init__(self, writer, template_params):
+ # type: (_IndentedTextWriter, Mapping[unicode,unicode]) -> None
+ """Create a template context."""
+ self._writer = writer
+ self._template_context = template_params
+
+ def __enter__(self):
+ # type: () -> None
+ """Set the template mapping for the writer."""
+ self._writer.set_template_mapping(self._template_context)
+
+ def __exit__(self, *args):
+ # type: (*str) -> None
+ """Clear the template mapping for the writer."""
+ self._writer.clear_template_mapping()
+
+
class _EmptyBlock(object):
"""Do not generate an indented block."""
@@ -278,7 +373,7 @@ class _UnindentedScopedBlock(object):
class _IndentedScopedBlock(object):
- """Generate a block, and indent the contents."""
+ """Generate a block, template the parameters, and indent the contents."""
def __init__(self, writer, opening, closing):
# type: (_IndentedTextWriter, unicode, unicode) -> None
@@ -290,14 +385,14 @@ class _IndentedScopedBlock(object):
def __enter__(self):
# type: () -> None
"""Write the beginning of the block and then indent."""
- self._writer.write_line(self._opening)
+ self._writer.write_template(self._opening)
self._writer.indent()
def __exit__(self, *args):
# type: (*str) -> None
"""Unindent the block and print the ending."""
self._writer.unindent()
- self._writer.write_line(self._closing)
+ self._writer.write_template(self._closing)
class _FieldUsageChecker(object):
@@ -400,6 +495,11 @@ class _CppFileWriterBase(object):
* %s
*/""" % (description)))
+ def _with_template(self, template_params):
+ # type: (Mapping[unicode,unicode]) -> _TemplateContext
+ """Generate a template context for the current parameters."""
+ return _TemplateContext(self._writer, template_params)
+
def _block(self, opening, closing):
# type: (unicode, unicode) -> Union[_IndentedScopedBlock,_EmptyBlock]
"""Generate an indented block if opening is not empty."""
@@ -451,68 +551,127 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
# type: (ast.Field) -> None
"""Generate the C++ getter definition for a field."""
cpp_type = _get_field_cpp_type(field)
- param_type = _get_field_parameter_type(field)
+ param_type = _get_field_getter_setter_type(field)
member_name = _get_field_member_name(field)
optional_ampersand = ""
- disable_xvalue = False
+ if _get_return_by_reference(field):
+ optional_ampersand = "&"
+
+ disable_xvalue = _get_disable_xvalue(field)
+
if not _is_view_type(cpp_type):
- if not field.optional and not _is_primitive_type(cpp_type):
- optional_ampersand = '&'
- disable_xvalue = True
- body = 'return %s;' % (member_name)
+ body_template = 'return $member_name;'
else:
- body = 'return %s{%s};' % (param_type, member_name)
- disable_xvalue = True
+ if field.array:
+ # Delegate to a function to the do the transformation between vectors.
+ if field.optional:
+ body_template = """\
+ if (${member_name}.is_initialized()) {
+ return transformVector(${member_name}.get());
+ } else {
+ return boost::none;
+ }
+ """
+ else:
+ body_template = 'return transformVector(${member_name});'
+ else:
+ body_template = 'return ${param_type}{${member_name}};'
+
+ template_params = {
+ 'method_name': _title_case(field.name),
+ 'member_name': member_name,
+ 'optional_ampersand': optional_ampersand,
+ 'param_type': param_type,
+ }
+
+ body = _template_format(body_template, template_params)
# Generate a getter that disables xvalue for view types (i.e. StringData), constructed
# optional types, and non-primitive types.
- if disable_xvalue:
- self._writer.write_line('const %s%s get%s() const& { %s }' %
- (param_type, optional_ampersand, _title_case(field.name), body))
- self._writer.write_line("const %s%s get%s() && = delete;" %
- (param_type, optional_ampersand, _title_case(field.name)))
- else:
- self._writer.write_line('const %s%s get%s() const { %s }' %
- (param_type, optional_ampersand, _title_case(field.name), body))
+ template_params['body'] = body
+
+ with self._with_template(template_params):
+
+ if disable_xvalue:
+ self._writer.write_template(
+ 'const ${param_type}${optional_ampersand} get${method_name}() const& { ${body} }'
+ )
+ self._writer.write_template(
+ 'const ${param_type}${optional_ampersand} get${method_name}() && = delete;')
+ else:
+ self._writer.write_template(
+ 'const ${param_type}${optional_ampersand} get${method_name}() const { ${body} }')
def gen_setter(self, field):
# type: (ast.Field) -> None
"""Generate the C++ setter definition for a field."""
cpp_type = _get_field_cpp_type(field)
- param_type = _get_field_parameter_type(field)
+ param_type = _get_field_getter_setter_type(field)
member_name = _get_field_member_name(field)
+ template_params = {
+ 'method_name': _title_case(field.name),
+ 'member_name': member_name,
+ 'param_type': param_type,
+ }
+
if _is_view_type(cpp_type):
- if not field.optional:
- self._writer.write_line('void set%s(%s value) & { %s = value.%s(); }' %
- (_title_case(field.name), param_type, member_name,
- _get_view_type_to_base_method(cpp_type)))
+ template_params['view_to_base_method'] = _get_view_type_to_base_method(cpp_type)
- else:
- # We need to convert between two different types of optional<T> and yet retain the
- # ability for the user to specific an uninitialized optional. This occurs for
- # mongo::StringData and std::string paired together.
- with self._block('void set%s(%s value) {' % (_title_case(field.name), param_type),
- "}"):
- self._writer.write_line(
- textwrap.dedent("""\
- if (value.is_initialized()) {
- %s = value.get().%s();
- } else {
- %s = boost::none;
- }
- """ % (member_name, _get_view_type_to_base_method(cpp_type), member_name)))
+ with self._with_template(template_params):
+
+ if field.array:
+ if not field.optional:
+ self._writer.write_template(
+ 'void set${method_name}(${param_type} value) & { ${member_name} = transformVector(value); }'
+ )
+
+ else:
+ # We need to convert between two different types of optional<T> and yet provide
+ # the ability for the user to specific an uninitialized optional. This occurs
+ # for vector<mongo::StringData> and vector<std::string> paired together.
+ with self._block('void set${method_name}(${param_type} value) & {', "}"):
+ self._writer.write_template(
+ textwrap.dedent("""\
+ if (value.is_initialized()) {
+ ${member_name} = transformVector(value.get());
+ } else {
+ ${member_name} = boost::none;
+ }
+ """))
+ else:
+ if not field.optional:
+ self._writer.write_template(
+ 'void set${method_name}(${param_type} value) & { ${member_name} = value.${view_to_base_method}(); }'
+ )
+
+ else:
+ # We need to convert between two different types of optional<T> and yet provide
+ # the ability for the user to specific an uninitialized optional. This occurs
+ # for mongo::StringData and std::string paired together.
+ with self._block('void set${method_name}(${param_type} value) & {', "}"):
+ self._writer.write_template(
+ textwrap.dedent("""\
+ if (value.is_initialized()) {
+ ${member_name} = value.get().${view_to_base_method}();
+ } else {
+ ${member_name} = boost::none;
+ }
+ """))
else:
- self._writer.write_line('void set%s(%s value) { %s = std::move(value); }' %
- (_title_case(field.name), param_type, member_name))
+ with self._with_template(template_params):
+ self._writer.write_template(
+ 'void set${method_name}(${param_type} value) & { ${member_name} = std::move(value); }'
+ )
+
self._writer.write_empty_line()
def gen_member(self, field):
# type: (ast.Field) -> None
"""Generate the C++ class member definition for a field."""
- member_type = _get_field_member_type(field)
+ member_type = _get_field_storage_type(field)
member_name = _get_field_member_name(field)
self._writer.write_line('%s %s;' % (member_type, member_name))
@@ -531,6 +690,7 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
'boost/optional.hpp',
'cstdint',
'string',
+ 'vector',
]
header_list.sort()
@@ -592,49 +752,101 @@ class _CppSourceFileWriter(_CppFileWriterBase):
"""Create a C++ .cpp file code writer."""
super(_CppSourceFileWriter, self).__init__(writer)
- def gen_field_deserializer(self, field):
- # type: (ast.Field) -> None
- """Generate the C++ deserializer piece for a few field."""
- # May be an empty block if the type is any
- type_predicate = _get_bson_type_check(field)
+ def _gen_field_deserializer_expression(self, element_name, field):
+ # type: (unicode, ast.Field) -> unicode
+ # pylint: disable=invalid-name
+ """
+ Generate the C++ deserializer piece for a field.
+
+ Writes multiple lines into the generated file.
+ Returns the final statement to access the deserialized value.
+ """
- with self._predicate(type_predicate):
+ if field.struct_type:
+ self._writer.write_line('IDLParserErrorContext tempContext("%s", &ctxt);' %
+ (field.name))
+ self._writer.write_line('const auto localObject = %s.Obj();' % (element_name))
+ return '%s::parse(tempContext, localObject);' % (_title_case(field.struct_type))
+ elif 'BSONElement::' in field.deserializer:
+ method_name = _get_method_name(field.deserializer)
+ return '%s.%s()' % (element_name, method_name)
+ else:
+ # Custom method, call the method on object.
+ # TODO: avoid this string hack in the future
+ if len(field.bson_serialization_type) == 1 and field.bson_serialization_type[
+ 0] == 'string':
+ # Call a method like: Class::method(StringData value)
+ self._writer.write_line('auto tempValue = %s.valueStringData();' % (element_name))
- if field.struct_type:
- self._writer.write_line('IDLParserErrorContext tempContext("%s", &ctxt);' %
- (field.name))
- self._writer.write_line('const auto localObject = element.Obj();')
- self._writer.write_line('object.%s = %s::parse(tempContext, localObject);' % (
- _get_field_member_name(field), _title_case(field.struct_type)))
- elif 'BSONElement::' in field.deserializer:
method_name = _get_method_name(field.deserializer)
- self._writer.write_line('object.%s = element.%s();' %
- (_get_field_member_name(field), method_name))
+
+ return '%s(tempValue)' % (method_name)
+ elif len(field.bson_serialization_type) == 1 and field.bson_serialization_type[
+ 0] == 'object':
+ # Call a method like: Class::method(const BSONObj& value)
+ method_name = _get_method_name_from_qualified_method_name(field.deserializer)
+ self._writer.write_line('const BSONObj localObject = %s.Obj();' % (element_name))
+ return '%s(localObject)' % (method_name)
else:
- # Custom method, call the method on object
- # TODO: avoid this string hack in the future
- if len(field.bson_serialization_type) == 1 and field.bson_serialization_type[
- 0] == 'string':
- # Call a method like: Class::method(StringData value)
- self._writer.write_line('auto tempValue = element.valueStringData();')
-
- method_name = _get_method_name(field.deserializer)
-
- self._writer.write_line('object.%s = %s(tempValue);' %
- (_get_field_member_name(field), method_name))
- elif len(field.bson_serialization_type) == 1 and field.bson_serialization_type[
- 0] == 'object':
- # Call a method like: Class::method(const BSONObj& value)
- method_name = _get_method_name_from_qualified_method_name(field.deserializer)
- self._writer.write_line('const BSONObj localObject = element.Obj();')
- self._writer.write_line('object.%s = %s(localObject);' %
- (_get_field_member_name(field), method_name))
- else:
- # Call a method like: Class::method(const BSONElement& value)
- method_name = _get_method_name_from_qualified_method_name(field.deserializer)
+ # Call a method like: Class::method(const BSONElement& value)
+ method_name = _get_method_name_from_qualified_method_name(field.deserializer)
+
+ return '%s(%s)' % (method_name, element_name)
+
+ def _gen_array_deserializer(self, field):
+ # type: (ast.Field) -> None
+ """Generate the C++ deserializer piece for an array field."""
+ cpp_type = _get_field_cpp_type(field)
+
+ self._writer.write_line('std::uint32_t expectedFieldNumber{0};')
+ self._writer.write_line('const IDLParserErrorContext arrayCtxt("%s", &ctxt);' %
+ (field.name))
+ self._writer.write_line('std::vector<%s> values;' % (cpp_type))
+ self._writer.write_empty_line()
+
+ self._writer.write_line('const BSONObj arrayObject = element.Obj();')
+
+ with self._block('for (const auto& arrayElement : arrayObject) {', '}'):
+
+ self._writer.write_line(
+ 'const auto arrayFieldName = arrayElement.fieldNameStringData();')
+ self._writer.write_line('std::uint32_t fieldNumber;')
+ self._writer.write_empty_line()
+
+ # Check the array field names are integers
+ self._writer.write_line(
+ 'Status status = parseNumberFromString(arrayFieldName, &fieldNumber);')
+ with self._predicate('status.isOK()'):
+
+ # Check that the array field names are sequential
+ with self._predicate('fieldNumber != expectedFieldNumber'):
+ self._writer.write_line('arrayCtxt.throwBadArrayFieldNumberSequence(' +
+ 'fieldNumber, expectedFieldNumber);')
+
+ with self._predicate(_get_bson_type_check('arrayElement', 'arrayCtxt', field)):
+ array_value = self._gen_field_deserializer_expression('arrayElement', field)
+ self._writer.write_line('values.emplace_back(%s);' % (array_value))
+
+ with self._block('else {', '}'):
+ self._writer.write_line('arrayCtxt.throwBadArrayFieldNumberValue(arrayFieldName);')
+
+ self._writer.write_line('++expectedFieldNumber;')
- self._writer.write_line('object.%s = %s(element);' %
- (_get_field_member_name(field), method_name))
+ self._writer.write_line('object.%s = std::move(values);' % (_get_field_member_name(field)))
+
+ def gen_field_deserializer(self, field):
+ # type: (ast.Field) -> None
+ """Generate the C++ deserializer piece for a field."""
+ if field.array:
+ self._gen_array_deserializer(field)
+ return
+
+ # May be an empty block if the type is any
+ with self._predicate(_get_bson_type_check('element', 'ctxt', field)):
+
+ object_value = self._gen_field_deserializer_expression('element', field)
+ self._writer.write_line('object.%s = %s;' %
+ (_get_field_member_name(field), object_value))
def gen_deserializer_method(self, struct):
# type: (ast.Struct) -> None
@@ -643,6 +855,7 @@ class _CppSourceFileWriter(_CppFileWriterBase):
func_def = '%s %s::parse(const IDLParserErrorContext& ctxt, const BSONObj& bsonObject)' % (
_title_case(struct.name), _title_case(struct.name))
with self._block('%s {' % (func_def), '}'):
+
self._writer.write_line('%s object;' % _title_case(struct.name))
field_usage_check = _FieldUsageChecker(self._writer)
@@ -659,7 +872,7 @@ class _CppSourceFileWriter(_CppFileWriterBase):
first_field = True
for field in struct.fields:
- field_predicate = 'fieldName == "%s"' % field.name
+ field_predicate = 'fieldName == "%s"' % (field.name)
field_usage_check.add(field)
with self._predicate(field_predicate, not first_field):
@@ -685,6 +898,70 @@ class _CppSourceFileWriter(_CppFileWriterBase):
self._writer.write_line('return object;')
+ def _gen_serializer_method_custom(self, field):
+ # type: (ast.Field) -> None
+ """Generate the serialize method definition for a custom type."""
+
+ # Generate custom serialization
+ method_name = _get_method_name(field.serializer)
+
+ template_params = {
+ 'field_name': field.name,
+ 'method_name': method_name,
+ 'access_member': _access_member(field),
+ }
+
+ with self._with_template(template_params):
+
+ if len(field.bson_serialization_type) == 1 and \
+ field.bson_serialization_type[0] == 'string':
+ # TODO: expand this out to be less then a string only hack
+
+ if field.array:
+ self._writer.write_template(
+ 'BSONArrayBuilder arrayBuilder(builder->subarrayStart("${field_name}"));')
+ with self._block('for (const auto& item : ${access_member}) {', '}'):
+ # self._writer.write_template('auto tempValue = ;')
+ self._writer.write_template('arrayBuilder.append(item.${method_name}());')
+ else:
+ # self._writer.write_template(
+ # 'auto tempValue = ;')
+ self._writer.write_template(
+ 'builder->append("${field_name}", ${access_member}.${method_name}());')
+ else:
+ if field.array:
+ self._writer.write_template(
+ 'BSONArrayBuilder arrayBuilder(builder->subarrayStart("${field_name}"));')
+ with self._block('for (const auto& item : ${access_member}) {', '}'):
+ self._writer.write_line(
+ 'BSONObjBuilder subObjBuilder(arrayBuilder.subobjStart());')
+ self._writer.write_template('item.${method_name}(&subObjBuilder);')
+ else:
+ self._writer.write_template('${access_member}.${method_name}(builder);')
+
+ def _gen_serializer_method_struct(self, field):
+ # type: (ast.Field) -> None
+ """Generate the serialize method definition for a struct type."""
+
+ template_params = {
+ 'field_name': field.name,
+ 'access_member': _access_member(field),
+ }
+
+ with self._with_template(template_params):
+
+ if field.array:
+ self._writer.write_template(
+ 'BSONArrayBuilder arrayBuilder(builder->subarrayStart(""${field_name}"));')
+ with self._block('for (const auto& item : ${access_member}) {', '}'):
+ self._writer.write_line(
+ 'BSONObjBuilder subObjBuilder(arrayBuilder.subobjStart());')
+ self._writer.write_line('item.serialize(&subObjBuilder);')
+ else:
+ self._writer.write_template(
+ 'BSONObjBuilder subObjBuilder(builder->subobjStart("${field_name}"));')
+ self._writer.write_template('${access_member}.serialize(&subObjBuilder);')
+
def gen_serializer_method(self, struct):
# type: (ast.Struct) -> None
"""Generate the serialize method definition."""
@@ -702,7 +979,7 @@ class _CppSourceFileWriter(_CppFileWriterBase):
optional_block_start = None
if field.optional:
optional_block_start = 'if (%s) {' % (member_name)
- elif field.struct_type:
+ elif field.struct_type or field.serializer:
# Introduce a new scope for required nested object serialization.
optional_block_start = '{'
@@ -710,31 +987,15 @@ class _CppSourceFileWriter(_CppFileWriterBase):
if not field.struct_type:
if field.serializer:
- # Generate custom serialization
- method_name = _get_method_name(field.serializer)
-
- if len(field.bson_serialization_type) == 1 and \
- field.bson_serialization_type[0] == 'string':
- # TODO: expand this out to be less then a string only hack
- self._writer.write_line('auto tempValue = %s.%s();' %
- (_access_member(field), method_name))
- self._writer.write_line(
- 'builder->append("%s", std::move(tempValue));' % (field.name))
- else:
- self._writer.write_line('%s.%s(builder);' %
- (_access_member(field), method_name))
-
+ self._gen_serializer_method_custom(field)
else:
# Generate default serialization using BSONObjBuilder::append
+ # Note: BSONObjBuilder::append has overrides for std::vector also
self._writer.write_line('builder->append("%s", %s);' %
(field.name, _access_member(field)))
-
else:
- self._writer.write_line(
- 'BSONObjBuilder subObjBuilder(builder->subobjStart("%s"));' %
- (field.name))
- self._writer.write_line('%s.serialize(&subObjBuilder);' %
- (_access_member(field)))
+ self._gen_serializer_method_struct(field)
+
# Add a blank line after each block
self._writer.write_empty_line()
@@ -805,8 +1066,11 @@ def generate_code(spec, output_base_dir, header_file_name, source_file_name):
_generate_header(spec, header_file_name)
- include_h_file_name = os.path.relpath(
- os.path.normpath(header_file_name), os.path.normpath(output_base_dir))
+ if output_base_dir:
+ include_h_file_name = os.path.relpath(
+ os.path.normpath(header_file_name), os.path.normpath(output_base_dir))
+ else:
+ include_h_file_name = header_file_name
# Normalize to POSIX style for consistency across Windows and POSIX.
include_h_file_name = include_h_file_name.replace("\\", "/")
diff --git a/buildscripts/idl/idl/syntax.py b/buildscripts/idl/idl/syntax.py
index 0470a52e0cf..9ec520c0235 100644
--- a/buildscripts/idl/idl/syntax.py
+++ b/buildscripts/idl/idl/syntax.py
@@ -55,6 +55,22 @@ class IDLSpec(object):
#TODO self.imports = None # type: Optional[Imports]
+def parse_array_type(name):
+ # type: (unicode) -> unicode
+ """Parse a type name of the form 'array<type>' and extract type."""
+ if not name.startswith("array<") and not name.endswith(">"):
+ return None
+
+ name = name[len("array<"):]
+ name = name[:-1]
+
+ # V1 restriction, ban nested array types to reduce scope.
+ if name.startswith("array<") and name.endswith(">"):
+ return None
+
+ return name
+
+
class SymbolTable(object):
"""
IDL Symbol Table.
@@ -106,16 +122,28 @@ class SymbolTable(object):
def resolve_field_type(self, ctxt, field):
# type: (errors.ParserContext, Field) -> Tuple[Optional[Struct], Optional[Type]]
"""Find the type or struct a field refers to or log an error."""
+ return self._resolve_field_type(ctxt, field, field.type)
+
+ def _resolve_field_type(self, ctxt, field, type_name):
+ # type: (errors.ParserContext, Field, unicode) -> Tuple[Optional[Struct], Optional[Type]]
+ """Find the type or struct a field refers to or log an error."""
for idltype in self.types:
- if idltype.name == field.type:
+ if idltype.name == type_name:
return (None, idltype)
for struct in self.structs:
- if struct.name == field.type:
+ if struct.name == type_name:
return (struct, None)
- # TODO: handle array
- ctxt.add_unknown_type_error(field, field.name, field.type)
+ if type_name.startswith('array<'):
+ array_type_name = parse_array_type(type_name)
+ if not array_type_name:
+ ctxt.add_bad_array_type_name(field, field.name, type_name)
+ return (None, None)
+
+ return self._resolve_field_type(ctxt, field, array_type_name)
+
+ ctxt.add_unknown_type_error(field, field.name, type_name)
return (None, None)
diff --git a/buildscripts/idl/sample/sample.idl b/buildscripts/idl/sample/sample.idl
index bb836cfe2a4..f0c3e8121a5 100644
--- a/buildscripts/idl/sample/sample.idl
+++ b/buildscripts/idl/sample/sample.idl
@@ -77,3 +77,7 @@ structs:
type: string
optional: true
description: "An optional string"
+ vectorField:
+ type: array<int>
+ description: "An example int array field with default value"
+
diff --git a/buildscripts/idl/tests/test_binder.py b/buildscripts/idl/tests/test_binder.py
index 612a2eb8960..ff1d0351c57 100644
--- a/buildscripts/idl/tests/test_binder.py
+++ b/buildscripts/idl/tests/test_binder.py
@@ -455,7 +455,6 @@ class TestBinder(testcase.IDLTestcase):
bson_serialization_type: string
serializer: foo
deserializer: foo
- default: foo
""")
# Short type
@@ -491,6 +490,35 @@ class TestBinder(testcase.IDLTestcase):
default: bar
"""))
+ # Test array as field type
+ self.assert_bind(test_preamble + textwrap.dedent("""
+ structs:
+ foo:
+ description: foo
+ strict: true
+ fields:
+ foo: array<string>
+ """))
+
+ # Test array as field type
+ self.assert_bind(
+ textwrap.dedent("""
+ types:
+ arrayfake:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type: string
+ serializer: foo
+ deserializer: foo
+
+ structs:
+ foo:
+ description: foo
+ strict: true
+ fields:
+ arrayOfString: arrayfake
+ """))
+
def test_field_negative(self):
# type: () -> None
"""Negative field tests."""
@@ -517,6 +545,47 @@ class TestBinder(testcase.IDLTestcase):
array<foo>: string
"""), idl.errors.ERROR_ID_ARRAY_NOT_VALID_TYPE)
+ # Test recursive array as field type
+ self.assert_bind_fail(test_preamble + textwrap.dedent("""
+ structs:
+ foo:
+ description: foo
+ strict: true
+ fields:
+ foo: array<array<string>>
+ """), idl.errors.ERROR_ID_BAD_ARRAY_TYPE_NAME)
+
+ # Test inherited default with array
+ self.assert_bind_fail(test_preamble + textwrap.dedent("""
+ structs:
+ foo:
+ description: foo
+ strict: true
+ fields:
+ foo: array<string>
+ """), idl.errors.ERROR_ID_ARRAY_NO_DEFAULT)
+
+ # Test non-inherited default with array
+ self.assert_bind_fail(
+ textwrap.dedent("""
+ types:
+ string:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type: string
+ serializer: foo
+ deserializer: foo
+
+ structs:
+ foo:
+ description: foo
+ strict: true
+ fields:
+ foo:
+ type: array<string>
+ default: 123
+ """), idl.errors.ERROR_ID_ARRAY_NO_DEFAULT)
+
def test_ignored_field_negative(self):
# type: () -> None
"""Test that if a field is marked as ignored, no other properties are set."""
diff --git a/src/mongo/idl/idl_parser.cpp b/src/mongo/idl/idl_parser.cpp
index 5e1edd7af4c..fee9678f485 100644
--- a/src/mongo/idl/idl_parser.cpp
+++ b/src/mongo/idl/idl_parser.cpp
@@ -184,4 +184,38 @@ void IDLParserErrorContext::throwUnknownField(StringData fieldName) const {
std::string path = getElementPath(fieldName);
uasserted(40415, str::stream() << "BSON field '" << path << "' is an unknown field.");
}
+
+void IDLParserErrorContext::throwBadArrayFieldNumberValue(StringData value) const {
+ std::string path = getElementPath(StringData());
+ uasserted(40422,
+ str::stream() << "BSON array field '" << path << "' has an invalid value '" << value
+ << "' for an array field name.");
+}
+void IDLParserErrorContext::throwBadArrayFieldNumberSequence(std::uint32_t actualValue,
+ std::uint32_t expectedValue) const {
+ std::string path = getElementPath(StringData());
+ uasserted(40423,
+ str::stream() << "BSON array field '" << path << "' has a non-sequential value '"
+ << actualValue
+ << "' for an array field name, expected value '"
+ << expectedValue
+ << "'.");
+}
+
+std::vector<StringData> transformVector(const std::vector<std::string>& input) {
+ return std::vector<StringData>(begin(input), end(input));
+}
+
+std::vector<std::string> transformVector(const std::vector<StringData>& input) {
+ std::vector<std::string> output;
+
+ output.reserve(input.size());
+
+ std::transform(begin(input), end(input), std::back_inserter(output), [](auto&& str) {
+ return str.toString();
+ });
+
+ return output;
+}
+
} // namespace mongo
diff --git a/src/mongo/idl/idl_parser.h b/src/mongo/idl/idl_parser.h
index a7aa8255eb6..2a6ee4a1db7 100644
--- a/src/mongo/idl/idl_parser.h
+++ b/src/mongo/idl/idl_parser.h
@@ -93,15 +93,26 @@ public:
void throwDuplicateField(const BSONElement& element) const;
/**
- * Throw an error message about the required field missing form the document.
+ * Throw an error message about the required field missing from the document.
*/
void throwMissingField(StringData fieldName) const;
/**
- * Throw an error message about the required field missing form the document.
+ * Throw an error message about an unknown field in a document.
*/
void throwUnknownField(StringData fieldName) const;
+ /**
+ * Throw an error message about an array field name not being a valid unsigned integer.
+ */
+ void throwBadArrayFieldNumberValue(StringData value) const;
+
+ /**
+ * Throw an error message about the array field name not being the next number in the sequence.
+ */
+ void throwBadArrayFieldNumberSequence(std::uint32_t actualValue,
+ std::uint32_t expectedValue) const;
+
private:
/**
* See comment on getElementPath below.
@@ -124,4 +135,12 @@ private:
const IDLParserErrorContext* _predecessor;
};
+/**
+ * Transform a vector of input type to a vector of output type.
+ *
+ * Used by the IDL generated code to transform between vectors of view, and non-view types.
+ */
+std::vector<StringData> transformVector(const std::vector<std::string>& input);
+std::vector<std::string> transformVector(const std::vector<StringData>& input);
+
} // namespace mongo
diff --git a/src/mongo/idl/idl_test.cpp b/src/mongo/idl/idl_test.cpp
index 5fa8e153614..c625b9e769d 100644
--- a/src/mongo/idl/idl_test.cpp
+++ b/src/mongo/idl/idl_test.cpp
@@ -38,7 +38,7 @@ namespace mongo {
// Use a seperate function to get better error messages when types do not match.
template <typename T1, typename T2>
void assert_same_types() {
- static_assert(std::is_same<T1, T2>::value, "expected correct type");
+ MONGO_STATIC_ASSERT(std::is_same<T1, T2>::value);
}
template <typename ParserT, typename TestT, BSONType Test_bson_type>
@@ -377,12 +377,9 @@ TEST(IDLFieldTests, TestOptionalFields) {
<< "Foo");
auto testStruct = Optional_field::parse(ctxt, testDoc);
- static_assert(std::is_same<decltype(testStruct.getField2()),
- const boost::optional<std::int32_t>>::value,
- "expected int32");
- static_assert(std::is_same<decltype(testStruct.getField1()),
- const boost::optional<mongo::StringData>>::value,
- "expected StringData");
+ assert_same_types<decltype(testStruct.getField2()), const boost::optional<std::int32_t>>();
+ assert_same_types<decltype(testStruct.getField1()),
+ const boost::optional<mongo::StringData>>();
ASSERT_EQUALS("Foo", testStruct.getField1().get());
ASSERT_FALSE(testStruct.getField2().is_initialized());
@@ -423,8 +420,291 @@ TEST(IDLFieldTests, TestOptionalFields) {
}
}
-/// TODO: Array tests
-// Validate array parsing
-// Check array vs non-array
+
+// Positive: Test a nested struct
+TEST(IDLNestedStruct, TestDuplicatTypes) {
+ IDLParserErrorContext ctxt("root");
+
+
+ // Positive: Test document
+ auto testDoc = BSON(
+
+ "field1" << BSON("field1" << 1 << "field2" << 2 << "field3" << 3) <<
+
+ "field3" << BSON("field1" << 4 << "field2" << 5 << "field3" << 6));
+ auto testStruct = NestedWithDuplicateTypes::parse(ctxt, testDoc);
+
+ assert_same_types<decltype(testStruct.getField1()), const RequiredStrictField3&>();
+ assert_same_types<decltype(testStruct.getField2()),
+ const boost::optional<RequiredNonStrictField3>>();
+ assert_same_types<decltype(testStruct.getField3()), const RequiredStrictField3&>();
+
+ ASSERT_EQUALS(1, testStruct.getField1().getField1());
+ ASSERT_EQUALS(2, testStruct.getField1().getField2());
+ ASSERT_EQUALS(3, testStruct.getField1().getField3());
+
+ ASSERT_FALSE(testStruct.getField2());
+
+ ASSERT_EQUALS(4, testStruct.getField3().getField1());
+ ASSERT_EQUALS(5, testStruct.getField3().getField2());
+ ASSERT_EQUALS(6, testStruct.getField3().getField3());
+
+ // Positive: Test we can roundtrip from the just parsed document
+ {
+ BSONObjBuilder builder;
+ testStruct.serialize(&builder);
+ auto loopbackDoc = builder.obj();
+
+ ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc);
+ }
+
+ // Positive: Test we can serialize from nothing the same document
+ {
+ BSONObjBuilder builder;
+ NestedWithDuplicateTypes nested_structs;
+ RequiredStrictField3 f1;
+ f1.setField1(1);
+ f1.setField1(2);
+ f1.setField1(3);
+ nested_structs.setField1(f1);
+ RequiredStrictField3 f3;
+ f3.setField1(4);
+ f3.setField1(5);
+ f3.setField1(6);
+ nested_structs.setField3(f3);
+ testStruct.serialize(&builder);
+
+ auto serializedDoc = builder.obj();
+ ASSERT_BSONOBJ_EQ(testDoc, serializedDoc);
+ }
+}
+
+// Positive: Arrays of simple types
+TEST(IDLArrayTests, TestSimpleArrays) {
+ IDLParserErrorContext ctxt("root");
+
+ // Positive: Test document
+ auto testDoc = BSON("field1" << BSON_ARRAY("Foo"
+ << "Bar"
+ << "???")
+ << "field2"
+ << BSON_ARRAY(1 << 2 << 3)
+ << "field3"
+ << BSON_ARRAY(1.2 << 3.4 << 5.6)
+
+ );
+ auto testStruct = Simple_array_fields::parse(ctxt, testDoc);
+
+ assert_same_types<decltype(testStruct.getField1()), const std::vector<mongo::StringData>>();
+ assert_same_types<decltype(testStruct.getField2()), const std::vector<std::int32_t>&>();
+ assert_same_types<decltype(testStruct.getField3()), const std::vector<double>&>();
+
+ std::vector<StringData> field1{"Foo", "Bar", "???"};
+ ASSERT_TRUE(field1 == testStruct.getField1());
+ std::vector<std::int32_t> field2{1, 2, 3};
+ ASSERT_TRUE(field2 == testStruct.getField2());
+ std::vector<double> field3{1.2, 3.4, 5.6};
+ ASSERT_TRUE(field3 == testStruct.getField3());
+
+ // Positive: Test we can roundtrip from the just parsed document
+ {
+ BSONObjBuilder builder;
+ testStruct.serialize(&builder);
+ auto loopbackDoc = builder.obj();
+
+ ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc);
+ }
+
+ // Positive: Test we can serialize from nothing the same document
+ {
+ BSONObjBuilder builder;
+ Simple_array_fields array_fields;
+ array_fields.setField1(field1);
+ array_fields.setField2(field2);
+ array_fields.setField3(field3);
+ testStruct.serialize(&builder);
+
+ auto serializedDoc = builder.obj();
+ ASSERT_BSONOBJ_EQ(testDoc, serializedDoc);
+ }
+}
+
+// Positive: Optional Arrays
+TEST(IDLArrayTests, TestSimpleOptionalArrays) {
+ IDLParserErrorContext ctxt("root");
+
+ // Positive: Test document
+ auto testDoc = BSON("field1" << BSON_ARRAY("Foo"
+ << "Bar"
+ << "???")
+ << "field2"
+ << BSON_ARRAY(1 << 2 << 3)
+ << "field3"
+ << BSON_ARRAY(1.2 << 3.4 << 5.6)
+
+ );
+ auto testStruct = Optional_array_fields::parse(ctxt, testDoc);
+
+ assert_same_types<decltype(testStruct.getField1()),
+ const boost::optional<std::vector<mongo::StringData>>>();
+ assert_same_types<decltype(testStruct.getField2()),
+ const boost::optional<std::vector<std::int32_t>>>();
+ assert_same_types<decltype(testStruct.getField3()),
+ const boost::optional<std::vector<double>>>();
+
+ std::vector<StringData> field1{"Foo", "Bar", "???"};
+ ASSERT_TRUE(field1 == testStruct.getField1().get());
+ std::vector<std::int32_t> field2{1, 2, 3};
+ ASSERT_TRUE(field2 == testStruct.getField2().get());
+ std::vector<double> field3{1.2, 3.4, 5.6};
+ ASSERT_TRUE(field3 == testStruct.getField3().get());
+
+ // Positive: Test we can roundtrip from the just parsed document
+ {
+ BSONObjBuilder builder;
+ testStruct.serialize(&builder);
+ auto loopbackDoc = builder.obj();
+
+ ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc);
+ }
+
+ // Positive: Test we can serialize from nothing the same document
+ {
+ BSONObjBuilder builder;
+ Optional_array_fields array_fields;
+ array_fields.setField1(field1);
+ array_fields.setField2(field2);
+ array_fields.setField3(field3);
+ testStruct.serialize(&builder);
+
+ auto serializedDoc = builder.obj();
+ ASSERT_BSONOBJ_EQ(testDoc, serializedDoc);
+ }
+}
+
+// Negative: Test mixed type arrays
+TEST(IDLArrayTests, TestBadArrays) {
+ IDLParserErrorContext ctxt("root");
+
+ // Negative: Test not an array
+ {
+ auto testDoc = BSON("field1" << 123);
+
+ ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), UserException);
+ }
+
+ // Negative: Test array with mixed types
+ {
+ auto testDoc = BSON("field1" << BSON_ARRAY(1.2 << 3.4 << 5.6));
+
+ ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), UserException);
+ }
+}
+
+// Positive: Test arrays with good field names but made with BSONObjBuilder
+TEST(IDLArrayTests, TestGoodArrays) {
+ IDLParserErrorContext ctxt("root");
+
+ {
+ BSONObjBuilder builder;
+ {
+ BSONObjBuilder subBuilder(builder.subarrayStart("field1"));
+ subBuilder.append("0", 1);
+ subBuilder.append("1", 2);
+ }
+
+ auto testDoc = builder.obj();
+
+ Simple_int_array::parse(ctxt, testDoc);
+ }
+}
+
+// Negative: Test arrays with bad field names
+TEST(IDLArrayTests, TestBadArrayFieldNames) {
+ IDLParserErrorContext ctxt("root");
+
+ // Negative: string fields
+ {
+ BSONObjBuilder builder;
+ {
+ BSONObjBuilder subBuilder(builder.subarrayStart("field1"));
+ subBuilder.append("0", 1);
+ subBuilder.append("foo", 2);
+ }
+ auto testDoc = builder.obj();
+
+ ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), UserException);
+ }
+
+ // Negative: bad start
+ {
+ BSONObjBuilder builder;
+ {
+ BSONObjBuilder subBuilder(builder.subarrayStart("field1"));
+ subBuilder.append("1", 1);
+ subBuilder.append("2", 2);
+ }
+ auto testDoc = builder.obj();
+
+ ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), UserException);
+ }
+
+ // Negative: non-sequentially increasing
+ {
+ BSONObjBuilder builder;
+ {
+ BSONObjBuilder subBuilder(builder.subarrayStart("field1"));
+ subBuilder.append("0", 1);
+ subBuilder.append("2", 2);
+ }
+ auto testDoc = builder.obj();
+
+ ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), UserException);
+ }
+}
+
+// Postitive: Test arrays with complex types
+TEST(IDLArrayTests, TestArraysOfComplexTypes) {
+ IDLParserErrorContext ctxt("root");
+
+ // Positive: Test document
+ auto testDoc = BSON("field1" << BSON_ARRAY(1 << 2 << 3) << "field2" << BSON_ARRAY("a.b"
+ << "c.d")
+ << "field3"
+ << BSON_ARRAY(1 << "2")
+ << "field4"
+ << BSON_ARRAY(BSONObj() << BSONObj())
+ << "field1o"
+ << BSON_ARRAY(1 << 2 << 3)
+ << "field2o"
+ << BSON_ARRAY("a.b"
+ << "c.d")
+ << "field3o"
+ << BSON_ARRAY(1 << "2")
+ << "field4o"
+ << BSON_ARRAY(BSONObj() << BSONObj()));
+ auto testStruct = Complex_array_fields::parse(ctxt, testDoc);
+
+ assert_same_types<decltype(testStruct.getField1()), const std::vector<std::int64_t>&>();
+ assert_same_types<decltype(testStruct.getField2()),
+ const std::vector<mongo::NamespaceString>&>();
+ assert_same_types<decltype(testStruct.getField3()), const std::vector<mongo::AnyBasicType>&>();
+ assert_same_types<decltype(testStruct.getField4()),
+ const std::vector<mongo::ObjectBasicType>&>();
+
+ assert_same_types<decltype(testStruct.getField1o()),
+ const boost::optional<std::vector<std::int64_t>>>();
+ assert_same_types<decltype(testStruct.getField2o()),
+ const boost::optional<std::vector<mongo::NamespaceString>>>();
+ assert_same_types<decltype(testStruct.getField3o()),
+ const boost::optional<std::vector<mongo::AnyBasicType>>>();
+ assert_same_types<decltype(testStruct.getField4o()),
+ const boost::optional<std::vector<mongo::ObjectBasicType>>>();
+
+ std::vector<std::int64_t> field1{1, 2, 3};
+ ASSERT_TRUE(field1 == testStruct.getField1());
+ std::vector<NamespaceString> field2{{"a", "b"}, {"c", "d"}};
+ ASSERT_TRUE(field2 == testStruct.getField2());
+}
} // namespace mongo
diff --git a/src/mongo/idl/unittest.idl b/src/mongo/idl/unittest.idl
index c08a31cb6ce..d0705b7d260 100644
--- a/src/mongo/idl/unittest.idl
+++ b/src/mongo/idl/unittest.idl
@@ -211,6 +211,21 @@ structs:
##################################################################################################
#
+# Nested Structs with duplicate types
+#
+##################################################################################################
+ NestedWithDuplicateTypes:
+ description: UnitTest for a non-strict struct with 3 required fields
+ strict: false
+ fields:
+ field1: RequiredStrictField3
+ field2:
+ type: RequiredNonStrictField3
+ optional: true
+ field3: RequiredStrictField3
+
+##################################################################################################
+#
# Structs to test various options for fields
#
##################################################################################################
@@ -309,3 +324,67 @@ structs:
field2:
type: int
optional: true
+
+##################################################################################################
+#
+# Test array of simple types
+#
+##################################################################################################
+ simple_int_array:
+ description: UnitTest for arrays of ints
+ fields:
+ field1:
+ type: array<int>
+
+ simple_array_fields:
+ description: UnitTest for arrays of simple types
+ fields:
+ field1:
+ type: array<string>
+ field2:
+ type: array<int>
+ field3:
+ type: array<double>
+
+ optional_array_fields:
+ description: UnitTest for arrays of optional simple types
+ fields:
+ field1:
+ type: array<string>
+ optional: true
+ field2:
+ type: array<int>
+ optional: true
+ field3:
+ type: array<double>
+ optional: true
+
+##################################################################################################
+#
+# Test array of complex types
+#
+##################################################################################################
+
+ complex_array_fields:
+ description: UnitTest for arrays of complex optional and non-optional simple types
+ fields:
+ field1:
+ type: array<safeInt32>
+ field2:
+ type: array<namespacestring>
+ field3:
+ type: array<any_basic_type>
+ field4:
+ type: array<object_basic_type>
+ field1o:
+ type: array<safeInt32>
+ optional: true
+ field2o:
+ type: array<namespacestring>
+ optional: true
+ field3o:
+ type: array<any_basic_type>
+ optional: true
+ field4o:
+ type: array<object_basic_type>
+ optional: true