summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2017-05-02 09:16:56 -0400
committerMark Benvenuto <mark.benvenuto@mongodb.com>2017-05-02 09:16:56 -0400
commit49cf0ebd80e8b007a6bcd08d3c956b149afa6f81 (patch)
tree5426b926fda611d8f956ab05bf987895881c491e
parent3d06c50d9427ac64785d2b62ca368010890aa9be (diff)
downloadmongo-49cf0ebd80e8b007a6bcd08d3c956b149afa6f81.tar.gz
SERVER-28827 BinData for IDL
-rw-r--r--buildscripts/idl/idl/binder.py18
-rw-r--r--buildscripts/idl/idl/bson.py18
-rw-r--r--buildscripts/idl/idl/cpp_types.py101
-rw-r--r--buildscripts/idl/idl/errors.py8
-rw-r--r--buildscripts/idl/idl/generator.py15
-rw-r--r--buildscripts/idl/sample/sample.idl21
-rw-r--r--buildscripts/idl/tests/context.py1
-rw-r--r--buildscripts/idl/tests/test_binder.py132
-rw-r--r--buildscripts/idl/tests/test_generator.py73
-rw-r--r--buildscripts/idl/tests/test_parser.py2
-rw-r--r--src/mongo/bson/bsonelement.h26
-rw-r--r--src/mongo/idl/basic_types.idl32
-rw-r--r--src/mongo/idl/idl_parser.cpp25
-rw-r--r--src/mongo/idl/idl_parser.h13
-rw-r--r--src/mongo/idl/idl_test.cpp270
-rw-r--r--src/mongo/idl/idl_test_types.h31
-rw-r--r--src/mongo/idl/unittest.idl52
-rw-r--r--src/mongo/idl/unittest_import.idl20
18 files changed, 808 insertions, 50 deletions
diff --git a/buildscripts/idl/idl/binder.py b/buildscripts/idl/idl/binder.py
index bec32eead89..c6f7ee5032c 100644
--- a/buildscripts/idl/idl/binder.py
+++ b/buildscripts/idl/idl/binder.py
@@ -21,6 +21,7 @@ from typing import Union
from . import ast
from . import bson
+from . import cpp_types
from . import errors
from . import syntax
@@ -121,6 +122,14 @@ def _validate_cpp_type(ctxt, idl_type, syntax_type):
if idl_type.cpp_type in ["std::int32_t", "std::int64_t", "std::uint32_t", "std::uint64_t"]:
return
+ # Only allow 16-byte arrays since they are for MD5 and UUID
+ if idl_type.cpp_type.replace(" ", "") == "std::array<std::uint8_t,16>":
+ return
+
+ # Support vector for variable length BinData.
+ if idl_type.cpp_type == "std::vector<std::uint8_t>":
+ return
+
# Check for std fixed integer types which are not allowed. These are not allowed even if they
# have the "std::" prefix.
for std_numeric_type in [
@@ -142,8 +151,9 @@ def _validate_type_properties(ctxt, idl_type, syntax_type):
if len(idl_type.bson_serialization_type) == 1:
bson_type = idl_type.bson_serialization_type[0]
+
if bson_type == "any":
- # For any, a deserialer is required but the user can try to get away with the default
+ # For any, a deserializer is required but the user can try to get away with the default
# serialization for their C++ type.
if idl_type.deserializer is None:
ctxt.add_missing_ast_required_field_error(idl_type, syntax_type, idl_type.name,
@@ -154,7 +164,7 @@ def _validate_type_properties(ctxt, idl_type, syntax_type):
ctxt.add_missing_ast_required_field_error(idl_type, syntax_type, idl_type.name,
"deserializer")
- elif not bson_type in ["object"]:
+ elif not bson_type in ["object", "bindata"]:
if idl_type.deserializer is None:
ctxt.add_missing_ast_required_field_error(idl_type, syntax_type, idl_type.name,
"deserializer")
@@ -166,6 +176,10 @@ def _validate_type_properties(ctxt, idl_type, syntax_type):
if idl_type.serializer is not None:
ctxt.add_not_custom_scalar_serialization_not_supported_error(
idl_type, syntax_type, idl_type.name, bson_type)
+
+ if bson_type == "bindata" and idl_type.default:
+ ctxt.add_bindata_no_default(idl_type, syntax_type, idl_type.name)
+
else:
# Now, this is a list of scalar types
if idl_type.deserializer is None:
diff --git a/buildscripts/idl/idl/bson.py b/buildscripts/idl/idl/bson.py
index 66106e0c8cd..214b67a7bfe 100644
--- a/buildscripts/idl/idl/bson.py
+++ b/buildscripts/idl/idl/bson.py
@@ -97,14 +97,16 @@ _BINDATA_SUBTYPE = {
'scalar': True,
'bindata_enum': 'Function'
},
- "binary": {
- 'scalar': False,
- 'bindata_enum': 'ByteArrayDeprecated'
- },
- "uuid_old": {
- 'scalar': False,
- 'bindata_enum': 'bdtUUID'
- },
+ # Also simply known as type 2, deprecated, and requires special handling
+ #"binary": {
+ # 'scalar': False,
+ # 'bindata_enum': 'ByteArrayDeprecated'
+ #},
+ # Deprecated
+ # "uuid_old": {
+ # 'scalar': False,
+ # 'bindata_enum': 'bdtUUID'
+ # },
"uuid": {
'scalar': True,
'bindata_enum': 'newUUID'
diff --git a/buildscripts/idl/idl/cpp_types.py b/buildscripts/idl/idl/cpp_types.py
index 0673ef5e683..c549740dd1b 100644
--- a/buildscripts/idl/idl/cpp_types.py
+++ b/buildscripts/idl/idl/cpp_types.py
@@ -30,8 +30,10 @@ from . import writer
def _is_primitive_type(cpp_type):
# type: (unicode) -> bool
"""Return True if a cpp_type is a primitive type and should not be returned as reference."""
+ cpp_type = cpp_type.replace(' ', '')
return cpp_type in [
- 'bool', 'double', 'std::int32_t', 'std::uint32_t', 'std::uint64_t', 'std::int64_t'
+ 'bool', 'double', 'std::int32_t', 'std::uint32_t', 'std::uint64_t', 'std::int64_t',
+ 'std::array<std::uint8_t,16>'
]
@@ -226,6 +228,62 @@ class _CppTypeView(CppTypeBase):
expression=expression, )
+class _CppTypeVector(CppTypeBase):
+ """Base type for C++ Std::Vector Types information."""
+
+ def __init__(self, field):
+ # type: (ast.Field) -> None
+ super(_CppTypeVector, self).__init__(field)
+
+ def get_type_name(self):
+ # type: () -> unicode
+ return 'std::vector<std::uint8_t>'
+
+ def get_storage_type(self):
+ # type: () -> unicode
+ return self.get_type_name()
+
+ def get_getter_setter_type(self):
+ # type: () -> unicode
+ return 'ConstDataRange'
+
+ def return_by_reference(self):
+ # type: () -> bool
+ return False
+
+ def disable_xvalue(self):
+ # type: () -> bool
+ return True
+
+ def is_view_type(self):
+ # type: () -> bool
+ return True
+
+ def get_getter_body(self, member_name):
+ # type: (unicode) -> unicode
+ return common.template_args(
+ 'return ConstDataRange(reinterpret_cast<const char*>($member_name.data()), $member_name.size());',
+ member_name=member_name)
+
+ def get_setter_body(self, member_name):
+ # type: (unicode) -> unicode
+ return common.template_args(
+ '$member_name = ${value};',
+ member_name=member_name,
+ value=self.get_transform_to_storage_type("value"))
+
+ def get_transform_to_getter_type(self, expression):
+ # type: (unicode) -> Optional[unicode]
+ return common.template_args('makeCDR(${expression});', expression=expression)
+
+ def get_transform_to_storage_type(self, expression):
+ # type: (unicode) -> Optional[unicode]
+ return common.template_args(
+ 'std::vector<std::uint8_t>(reinterpret_cast<const uint8_t*>(${expression}.data()), ' +
+ 'reinterpret_cast<const uint8_t*>(${expression}.data()) + ${expression}.length())',
+ expression=expression)
+
+
class _CppTypeDelegating(CppTypeBase):
"""Delegates all method calls a nested instance of CppTypeBase. Used to build other classes."""
@@ -404,12 +462,15 @@ class _CppTypeOptional(_CppTypeDelegating):
def get_cpp_type(field):
# type: (ast.Field) -> CppTypeBase
+ # pylint: disable=redefined-variable-type
"""Get the C++ Type information for the given field."""
cpp_type_info = None # type: Any
if field.cpp_type == 'std::string':
cpp_type_info = _CppTypeView(field, 'std::string', 'StringData')
+ elif field.cpp_type == 'std::vector<std::uint8_t>':
+ cpp_type_info = _CppTypeVector(field)
else:
cpp_type_info = _CppTypeBasic(field) # pylint: disable=redefined-variable-type
@@ -507,6 +568,41 @@ class _ObjectBsonCppTypeBase(BsonCppTypeBase):
return "localObject"
+class _BinDataBsonCppTypeBase(BsonCppTypeBase):
+ """Custom C++ support for all binData BSON types."""
+
+ def __init__(self, field):
+ # type: (ast.Field) -> None
+ super(_BinDataBsonCppTypeBase, self).__init__(field)
+
+ def gen_deserializer_expression(self, indented_writer, object_instance):
+ # type: (writer.IndentedTextWriter, unicode) -> unicode
+ return common.template_args(
+ '${object_instance}._binDataVector()', object_instance=object_instance)
+
+ def has_serializer(self):
+ # type: () -> bool
+ return True
+
+ def gen_serializer_expression(self, indented_writer, expression):
+ # type: (writer.IndentedTextWriter, unicode) -> unicode
+ if self._field.serializer:
+ method_name = writer.get_method_name(self._field.serializer)
+ indented_writer.write_line(
+ common.template_args(
+ 'ConstDataRange tempCDR = ${expression}.${method_name}();',
+ expression=expression,
+ method_name=method_name))
+ else:
+ indented_writer.write_line(
+ common.template_args(
+ 'ConstDataRange tempCDR = makeCDR(${expression});', expression=expression))
+
+ return common.template_args(
+ 'BSONBinData(tempCDR.data(), tempCDR.length(), ${bindata_subtype})',
+ bindata_subtype=bson.cpp_bindata_subtype_type_name(self._field.bindata_subtype))
+
+
# For some fields, we want to support custom serialization but defer most of the serialization to
# the core BSONElement class. This means that callers need to only process a string, a vector of
# bytes, or a integer, not a BSONElement or BSONObj.
@@ -524,5 +620,8 @@ def get_bson_cpp_type(field):
if field.bson_serialization_type[0] == 'object':
return _ObjectBsonCppTypeBase(field)
+ if field.bson_serialization_type[0] == 'bindata':
+ return _BinDataBsonCppTypeBase(field)
+
# Unsupported type
return None
diff --git a/buildscripts/idl/idl/errors.py b/buildscripts/idl/idl/errors.py
index efedd6cd354..08d773fc022 100644
--- a/buildscripts/idl/idl/errors.py
+++ b/buildscripts/idl/idl/errors.py
@@ -59,6 +59,7 @@ ERROR_ID_BAD_NUMERIC_CPP_TYPE = "ID0022"
ERROR_ID_BAD_ARRAY_TYPE_NAME = "ID0023"
ERROR_ID_ARRAY_NO_DEFAULT = "ID0024"
ERROR_ID_BAD_IMPORT = "ID0025"
+ERROR_ID_BAD_BINDATA_DEFAULT = "ID0026"
class IDLError(Exception):
@@ -413,6 +414,13 @@ class ParserContext(object):
self._add_error(location, ERROR_ID_BAD_IMPORT,
"Could not resolve import '%s', file not found" % (imported_file_name))
+ def add_bindata_no_default(self, location, ast_type, ast_parent):
+ # type: (common.SourceLocation, unicode, unicode) -> None
+ # pylint: disable=invalid-name
+ """Add an error about 'any' being used in a list of bson types."""
+ self._add_error(location, ERROR_ID_BAD_BINDATA_DEFAULT,
+ ("Default values are not allowed for %s '%s'") % (ast_type, ast_parent))
+
def _assert_unique_error_messages():
# type: () -> None
diff --git a/buildscripts/idl/idl/generator.py b/buildscripts/idl/idl/generator.py
index b816331e5ba..320c0f5435d 100644
--- a/buildscripts/idl/idl/generator.py
+++ b/buildscripts/idl/idl/generator.py
@@ -278,7 +278,7 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
if cpp_type_info.disable_xvalue():
self._writer.write_template(
'const ${param_type} get${method_name}() const& { ${body} }')
- self._writer.write_template('const ${param_type} get${method_name}() && = delete;')
+ self._writer.write_template('void get${method_name}() && = delete;')
else:
self._writer.write_template(
'const ${param_type} get${method_name}() const { ${body} }')
@@ -351,6 +351,7 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
# Generate user includes second
header_list = [
'mongo/base/string_data.h',
+ 'mongo/base/data_range.h',
'mongo/bson/bsonobj.h',
'mongo/bson/bsonobjbuilder.h',
'mongo/idl/idl_parser.h',
@@ -422,7 +423,7 @@ class _CppSourceFileWriter(_CppFileWriterBase):
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);' % (common.title_case(field.struct_type))
+ return '%s::parse(tempContext, localObject)' % (common.title_case(field.struct_type))
elif field.deserializer and 'BSONElement::' in field.deserializer:
method_name = writer.get_method_name(field.deserializer)
return '%s.%s()' % (element_name, method_name)
@@ -431,9 +432,10 @@ class _CppSourceFileWriter(_CppFileWriterBase):
bson_cpp_type = cpp_types.get_bson_cpp_type(field)
if bson_cpp_type:
- # Call a method like: Class::method(StringData value)
+ # Call a static class method with the signature:
+ # Class Class::method(StringData value)
# or
- # Call a method like: Class::method(const BSONObj& value)
+ # Class::method(const BSONObj& value)
expression = bson_cpp_type.gen_deserializer_expression(self._writer, element_name)
if field.deserializer:
method_name = writer.get_method_name_from_qualified_method_name(
@@ -449,7 +451,8 @@ class _CppSourceFileWriter(_CppFileWriterBase):
0] == 'object'
return expression
else:
- # Call a method like: Class::method(const BSONElement& value)
+ # Call a static class method with the signature:
+ # Class Class::method(const BSONElement& value)
method_name = writer.get_method_name_from_qualified_method_name(field.deserializer)
return '%s(%s)' % (method_name, element_name)
@@ -637,7 +640,7 @@ class _CppSourceFileWriter(_CppFileWriterBase):
if field.array:
self._writer.write_template(
- 'BSONArrayBuilder arrayBuilder(builder->subarrayStart(""${field_name}"));')
+ 'BSONArrayBuilder arrayBuilder(builder->subarrayStart("${field_name}"));')
with self._block('for (const auto& item : ${access_member}) {', '}'):
self._writer.write_line(
'BSONObjBuilder subObjBuilder(arrayBuilder.subobjStart());')
diff --git a/buildscripts/idl/sample/sample.idl b/buildscripts/idl/sample/sample.idl
index f0c3e8121a5..b35691b2c47 100644
--- a/buildscripts/idl/sample/sample.idl
+++ b/buildscripts/idl/sample/sample.idl
@@ -55,6 +55,21 @@ types:
- double
deserializer: "mongo::BSONElement::numberInt"
+ bindata_generic:
+ bson_serialization_type: bindata
+ bindata_subtype: generic
+ description: "A BSON bindata of "
+ cpp_type: "std::vector<std::uint8_t>"
+ deserializer: "mongo::BSONElement::_binDataVector"
+
+ bindata_uuid:
+ bson_serialization_type: bindata
+ bindata_subtype: uuid
+ description: "A BSON bindata of uuid sub type"
+ cpp_type: "std::array<std::uint8_t, 16>"
+ deserializer: "mongo::BSONElement::uuid"
+
+
structs:
default_values:
description: UnitTest for a single safeInt32
@@ -80,4 +95,10 @@ structs:
vectorField:
type: array<int>
description: "An example int array field with default value"
+ binDataField:
+ type: bindata_generic
+ description: "A binData of generic subtype"
+ uuidField:
+ type: bindata_uuid
+ description: "A binData of uuid subtype"
diff --git a/buildscripts/idl/tests/context.py b/buildscripts/idl/tests/context.py
index bf83a640c81..c4b93de196f 100644
--- a/buildscripts/idl/tests/context.py
+++ b/buildscripts/idl/tests/context.py
@@ -21,6 +21,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')
import idl.ast # pylint: disable=wrong-import-position
import idl.binder # pylint: disable=wrong-import-position
+import idl.compiler # pylint: disable=wrong-import-position
import idl.errors # pylint: disable=wrong-import-position
import idl.generator # pylint: disable=wrong-import-position
import idl.parser # pylint: disable=wrong-import-position
diff --git a/buildscripts/idl/tests/test_binder.py b/buildscripts/idl/tests/test_binder.py
index 78f8213774b..144885cc81e 100644
--- a/buildscripts/idl/tests/test_binder.py
+++ b/buildscripts/idl/tests/test_binder.py
@@ -68,7 +68,7 @@ class TestBinder(testcase.IDLTestcase):
textwrap.dedent("""
global:
cpp_namespace: 'something'
- cpp_includes:
+ cpp_includes:
- 'bar'
- 'foo'"""))
self.assertEquals(spec.globals.cpp_namespace, "something")
@@ -111,6 +111,8 @@ class TestBinder(testcase.IDLTestcase):
"std::uint32_t",
"std::int32_t",
"std::uint64_t",
+ "std::vector<std::uint8_t>",
+ "std::array<std::uint8_t, 16>",
]:
self.assert_bind(
textwrap.dedent("""
@@ -149,7 +151,7 @@ class TestBinder(testcase.IDLTestcase):
"""))
# Test supported bindata_subtype
- for bindata_subtype in ["generic", "function", "binary", "uuid_old", "uuid", "md5"]:
+ for bindata_subtype in ["generic", "function", "uuid", "md5"]:
self.assert_bind(
textwrap.dedent("""
types:
@@ -158,7 +160,6 @@ class TestBinder(testcase.IDLTestcase):
cpp_type: foo
bson_serialization_type: bindata
bindata_subtype: %s
- default: foo
deserializer: BSONElement::fake
""" % (bindata_subtype)))
@@ -260,7 +261,7 @@ class TestBinder(testcase.IDLTestcase):
deserializer: BSONElement::fake
"""), idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE)
- # Test bindata_subtype wrong
+ # Test fake bindata_subtype is wrong
self.assert_bind_fail(
textwrap.dedent("""
types:
@@ -272,6 +273,27 @@ class TestBinder(testcase.IDLTestcase):
deserializer: BSONElement::fake
"""), idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE)
+ # Test deprecated bindata_subtype 'binary', and 'uuid_old' are wrong
+ self.assert_bind_fail(
+ textwrap.dedent("""
+ types:
+ foofoo:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type: bindata
+ bindata_subtype: binary
+ """), idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE)
+
+ self.assert_bind_fail(
+ textwrap.dedent("""
+ types:
+ foofoo:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type: bindata
+ bindata_subtype: uuid_old
+ """), idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE)
+
# Test bindata_subtype on wrong type
self.assert_bind_fail(
textwrap.dedent("""
@@ -284,6 +306,18 @@ class TestBinder(testcase.IDLTestcase):
deserializer: BSONElement::fake
"""), idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_TYPE)
+ # Test bindata with default
+ self.assert_bind_fail(
+ textwrap.dedent("""
+ types:
+ foofoo:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type: bindata
+ bindata_subtype: uuid
+ default: 42
+ """), idl.errors.ERROR_ID_BAD_BINDATA_DEFAULT)
+
# Test bindata in list of types
self.assert_bind_fail(
textwrap.dedent("""
@@ -291,7 +325,7 @@ class TestBinder(testcase.IDLTestcase):
foofoo:
description: foo
cpp_type: foo
- bson_serialization_type:
+ bson_serialization_type:
- bindata
- string
"""), idl.errors.ERROR_ID_BAD_BSON_TYPE)
@@ -303,7 +337,7 @@ class TestBinder(testcase.IDLTestcase):
foofoo:
description: foo
cpp_type: StringData
- bson_serialization_type:
+ bson_serialization_type:
- bindata
- string
"""), idl.errors.ERROR_ID_BAD_BSON_TYPE)
@@ -315,11 +349,35 @@ class TestBinder(testcase.IDLTestcase):
foofoo:
description: foo
cpp_type: foo
- bson_serialization_type:
+ bson_serialization_type:
- any
- int
"""), idl.errors.ERROR_ID_BAD_ANY_TYPE_USE)
+ # Test object in list of types
+ self.assert_bind_fail(
+ textwrap.dedent("""
+ types:
+ foofoo:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type:
+ - object
+ - int
+ """), idl.errors.ERROR_ID_BAD_BSON_TYPE_LIST)
+
+ # Test fake in list of types
+ self.assert_bind_fail(
+ textwrap.dedent("""
+ types:
+ foofoo:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type:
+ - int
+ - fake
+ """), idl.errors.ERROR_ID_BAD_BSON_TYPE)
+
# Test unsupported serialization
for bson_type in [
"bool", "date", "null", "decimal", "double", "int", "long", "objectid", "regex",
@@ -365,7 +423,7 @@ class TestBinder(testcase.IDLTestcase):
foofoo:
description: foo
cpp_type: std::int32_t
- bson_serialization_type:
+ bson_serialization_type:
- int
- string
"""), idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD)
@@ -373,7 +431,7 @@ class TestBinder(testcase.IDLTestcase):
# Test array as name
self.assert_bind_fail(
textwrap.dedent("""
- types:
+ types:
array<foo>:
description: foo
cpp_type: foo
@@ -398,7 +456,7 @@ class TestBinder(testcase.IDLTestcase):
""")
self.assert_bind(test_preamble + textwrap.dedent("""
- structs:
+ structs:
foo:
description: foo
strict: true
@@ -424,7 +482,7 @@ class TestBinder(testcase.IDLTestcase):
# Test array as name
self.assert_bind_fail(test_preamble + textwrap.dedent("""
- structs:
+ structs:
array<foo>:
description: foo
strict: true
@@ -464,7 +522,7 @@ class TestBinder(testcase.IDLTestcase):
description: foo
strict: false
fields:
- foo:
+ foo:
type: string
"""))
@@ -475,7 +533,7 @@ class TestBinder(testcase.IDLTestcase):
description: foo
strict: false
fields:
- foo:
+ foo:
type: string
default: bar
"""))
@@ -483,7 +541,7 @@ class TestBinder(testcase.IDLTestcase):
# Test array as field type
self.assert_bind(test_preamble + textwrap.dedent("""
structs:
- foo:
+ foo:
description: foo
strict: true
fields:
@@ -502,7 +560,7 @@ class TestBinder(testcase.IDLTestcase):
deserializer: foo
structs:
- foo:
+ foo:
description: foo
strict: true
fields:
@@ -523,12 +581,35 @@ class TestBinder(testcase.IDLTestcase):
serializer: foo
deserializer: foo
default: foo
+
+ bindata:
+ description: foo
+ cpp_type: foo
+ bson_serialization_type: bindata
+ bindata_subtype: uuid
""")
+ # Test field of a struct type with a default
+ self.assert_bind_fail(test_preamble + textwrap.dedent("""
+ structs:
+ foo:
+ description: foo
+ fields:
+ field1: string
+
+ bar:
+ description: foo
+ fields:
+ field2:
+ type: foo
+ default: foo
+
+ """), idl.errors.ERROR_ID_FIELD_MUST_BE_EMPTY_FOR_IGNORED)
+
# Test array as field name
self.assert_bind_fail(test_preamble + textwrap.dedent("""
structs:
- foo:
+ foo:
description: foo
strict: true
fields:
@@ -538,7 +619,7 @@ class TestBinder(testcase.IDLTestcase):
# Test recursive array as field type
self.assert_bind_fail(test_preamble + textwrap.dedent("""
structs:
- foo:
+ foo:
description: foo
strict: true
fields:
@@ -548,7 +629,7 @@ class TestBinder(testcase.IDLTestcase):
# Test inherited default with array
self.assert_bind_fail(test_preamble + textwrap.dedent("""
structs:
- foo:
+ foo:
description: foo
strict: true
fields:
@@ -567,7 +648,7 @@ class TestBinder(testcase.IDLTestcase):
deserializer: foo
structs:
- foo:
+ foo:
description: foo
strict: true
fields:
@@ -576,11 +657,24 @@ class TestBinder(testcase.IDLTestcase):
default: 123
"""), idl.errors.ERROR_ID_ARRAY_NO_DEFAULT)
+ # Test bindata with default
+ self.assert_bind_fail(test_preamble + textwrap.dedent("""
+ structs:
+ foo:
+ description: foo
+ strict: true
+ fields:
+ foo:
+ type: bindata
+ default: 42
+ """), idl.errors.ERROR_ID_BAD_BINDATA_DEFAULT)
+
def test_ignored_field_negative(self):
# type: () -> None
"""Test that if a field is marked as ignored, no other properties are set."""
for test_value in [
"optional: true",
+ "default: foo",
]:
self.assert_bind_fail(
textwrap.dedent("""
diff --git a/buildscripts/idl/tests/test_generator.py b/buildscripts/idl/tests/test_generator.py
new file mode 100644
index 00000000000..1c7ede7621e
--- /dev/null
+++ b/buildscripts/idl/tests/test_generator.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python2
+# Copyright (C) 2017 MongoDB Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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/>.
+#
+"""
+Test cases for IDL Generator.
+
+This file exists to verify code coverage for the generator.py file. To run code coverage, run in the
+idl base directory:
+
+$ coverage run run_tests.py && coverage html
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+import unittest
+
+# import package so that it works regardless of whether we run as a module or file
+if __package__ is None:
+ import sys
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+ from context import idl
+ import testcase
+else:
+ from .context import idl
+ from . import testcase
+
+
+class TestGenerator(testcase.IDLTestcase):
+ """Test the IDL Generator."""
+
+ def test_compile(self):
+ # type: () -> None
+ """Exercise the code generator so code coverage can be measured."""
+ base_dir = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+ src_dir = os.path.join(
+ base_dir,
+ 'src', )
+ idl_dir = os.path.join(src_dir, 'mongo', 'idl')
+
+ args = idl.compiler.CompilerArgs()
+ args.output_suffix = "_codecoverage_gen"
+ args.import_directories = [src_dir]
+
+ unittest_idl_file = os.path.join(idl_dir, 'unittest.idl')
+ if not os.path.exists(unittest_idl_file):
+ unittest.skip("Skipping IDL Generator testing since %s could not be found." %
+ (unittest_idl_file))
+ return
+
+ args.input_file = os.path.join(idl_dir, 'unittest_import.idl')
+ self.assertTrue(idl.compiler.compile_idl(args))
+
+ args.input_file = unittest_idl_file
+ self.assertTrue(idl.compiler.compile_idl(args))
+
+
+if __name__ == '__main__':
+
+ unittest.main()
diff --git a/buildscripts/idl/tests/test_parser.py b/buildscripts/idl/tests/test_parser.py
index 96a3ba6d48c..cdab68e1457 100644
--- a/buildscripts/idl/tests/test_parser.py
+++ b/buildscripts/idl/tests/test_parser.py
@@ -66,7 +66,7 @@ class TestParser(testcase.IDLTestcase):
self.assert_parse(
textwrap.dedent("""
global:
- cpp_includes:
+ cpp_includes:
- 'bar'
- 'foo'"""))
diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h
index 2d815383b2c..dd68d333f63 100644
--- a/src/mongo/bson/bsonelement.h
+++ b/src/mongo/bson/bsonelement.h
@@ -35,6 +35,7 @@
#include <string>
#include <vector>
+#include "mongo/base/data_range.h"
#include "mongo/base/data_type_endian.h"
#include "mongo/base/data_view.h"
#include "mongo/base/string_data_comparator_interface.h"
@@ -468,6 +469,19 @@ public:
return (BinDataType)c;
}
+ std::vector<uint8_t> _binDataVector() const {
+ if (binDataType() != ByteArrayDeprecated) {
+ return std::vector<uint8_t>(reinterpret_cast<const uint8_t*>(value()) + 5,
+ reinterpret_cast<const uint8_t*>(value()) + 5 +
+ valuestrsize());
+ } else {
+ // Skip the extra int32 size
+ return std::vector<uint8_t>(reinterpret_cast<const uint8_t*>(value()) + 4,
+ reinterpret_cast<const uint8_t*>(value()) + 4 +
+ valuestrsize() - 4);
+ }
+ }
+
/** Retrieve the regex std::string for a Regex element */
const char* regex() const {
verify(type() == RegEx);
@@ -592,6 +606,18 @@ public:
return result;
}
+ const std::array<unsigned char, 16> md5() const {
+ int len = 0;
+ const char* data = nullptr;
+ if (type() == BinData && binDataType() == BinDataType::MD5Type)
+ data = binData(len);
+ uassert(550418, "md5 must be a 16-byte binary field with MD5 (5) subtype", len == 16);
+ std::array<unsigned char, 16> result;
+ memcpy(&result, data, len);
+ return result;
+ }
+
+
Date_t timestampTime() const {
unsigned long long t = ConstDataView(value() + 4).read<LittleEndian<unsigned int>>();
return Date_t::fromMillisSinceEpoch(t * 1000);
diff --git a/src/mongo/idl/basic_types.idl b/src/mongo/idl/basic_types.idl
index 76438ecc01f..1dd9495d65e 100644
--- a/src/mongo/idl/basic_types.idl
+++ b/src/mongo/idl/basic_types.idl
@@ -54,11 +54,33 @@ types:
cpp_type: "bool"
deserializer: "mongo::BSONElement::boolean"
- # TODO: support bindata
- # bindata:
- # bson_serialization_type: bindata
- # cpp_type: "std:"
- # deserializer: "mongo::BSONElement::str"
+ bindata_generic:
+ bson_serialization_type: bindata
+ bindata_subtype: generic
+ description: "A BSON bindata of "
+ cpp_type: "std::vector<std::uint8_t>"
+ deserializer: "mongo::BSONElement::_binDataVector"
+
+ bindata_function:
+ bson_serialization_type: bindata
+ bindata_subtype: function
+ description: "A BSON bindata of function sub type"
+ cpp_type: "std::vector<std::uint8_t>"
+ deserializer: "mongo::BSONElement::_binDataVector"
+
+ bindata_uuid:
+ bson_serialization_type: bindata
+ bindata_subtype: uuid
+ description: "A BSON bindata of uuid sub type"
+ cpp_type: "std::array<std::uint8_t, 16>"
+ deserializer: "mongo::BSONElement::uuid"
+
+ bindata_md5:
+ bson_serialization_type: bindata
+ bindata_subtype: md5
+ description: "A BSON bindata of uuid sub type"
+ cpp_type: "std::array<std::uint8_t, 16>"
+ deserializer: "mongo::BSONElement::md5"
objectid:
bson_serialization_type: objectid
diff --git a/src/mongo/idl/idl_parser.cpp b/src/mongo/idl/idl_parser.cpp
index fee9678f485..c9450664c71 100644
--- a/src/mongo/idl/idl_parser.cpp
+++ b/src/mongo/idl/idl_parser.cpp
@@ -218,4 +218,29 @@ std::vector<std::string> transformVector(const std::vector<StringData>& input) {
return output;
}
+std::vector<ConstDataRange> transformVector(const std::vector<std::vector<std::uint8_t>>& input) {
+ std::vector<ConstDataRange> output;
+
+ output.reserve(input.size());
+
+ std::transform(begin(input), end(input), std::back_inserter(output), [](auto&& vec) {
+ return makeCDR(vec);
+ });
+
+ return output;
+}
+
+std::vector<std::vector<std::uint8_t>> transformVector(const std::vector<ConstDataRange>& input) {
+ std::vector<std::vector<std::uint8_t>> output;
+
+ output.reserve(input.size());
+
+ std::transform(begin(input), end(input), std::back_inserter(output), [](auto&& cdr) {
+ return std::vector<std::uint8_t>(reinterpret_cast<const uint8_t*>(cdr.data()),
+ reinterpret_cast<const uint8_t*>(cdr.data()) +
+ cdr.length());
+ });
+
+ return output;
+}
} // namespace mongo
diff --git a/src/mongo/idl/idl_parser.h b/src/mongo/idl/idl_parser.h
index 2a6ee4a1db7..e249be26b87 100644
--- a/src/mongo/idl/idl_parser.h
+++ b/src/mongo/idl/idl_parser.h
@@ -142,5 +142,18 @@ private:
*/
std::vector<StringData> transformVector(const std::vector<std::string>& input);
std::vector<std::string> transformVector(const std::vector<StringData>& input);
+std::vector<ConstDataRange> transformVector(const std::vector<std::vector<std::uint8_t>>& input);
+std::vector<std::vector<std::uint8_t>> transformVector(const std::vector<ConstDataRange>& input);
+
+/**
+ * Get a ConstDataRange from a vector or an array of bytes.
+ */
+inline ConstDataRange makeCDR(const std::vector<uint8_t>& value) {
+ return ConstDataRange(reinterpret_cast<const char*>(value.data()), value.size());
+}
+
+inline ConstDataRange makeCDR(const std::array<uint8_t, 16>& value) {
+ return ConstDataRange(reinterpret_cast<const char*>(value.data()), value.size());
+}
} // namespace mongo
diff --git a/src/mongo/idl/idl_test.cpp b/src/mongo/idl/idl_test.cpp
index 55c1cf0b6f9..4f26af506c9 100644
--- a/src/mongo/idl/idl_test.cpp
+++ b/src/mongo/idl/idl_test.cpp
@@ -37,7 +37,41 @@ using namespace mongo::idl::test;
namespace mongo {
-// Use a seperate function to get better error messages when types do not match.
+namespace {
+
+bool isEquals(ConstDataRange left, const std::vector<uint8_t>& right) {
+ auto rightCDR = makeCDR(right);
+ return std::equal(left.data(),
+ left.data() + left.length(),
+ rightCDR.data(),
+ rightCDR.data() + rightCDR.length());
+}
+
+bool isEquals(const std::array<uint8_t, 16>& left, const std::array<uint8_t, 16>& right) {
+ return std::equal(
+ left.data(), left.data() + left.size(), right.data(), right.data() + right.size());
+}
+
+bool isEqual(const ConstDataRange& left, const ConstDataRange& right) {
+ return std::equal(
+ left.data(), left.data() + left.length(), right.data(), right.data() + right.length());
+}
+
+bool isEquals(const std::vector<ConstDataRange>& left,
+ const std::vector<std::vector<std::uint8_t>>& rightVector) {
+ auto right = transformVector(rightVector);
+ return std::equal(
+ left.data(), left.data() + left.size(), right.data(), right.data() + right.size(), isEqual);
+}
+
+bool isEquals(const std::vector<std::array<std::uint8_t, 16>>& left,
+ const std::vector<std::array<std::uint8_t, 16>>& right) {
+ return std::equal(
+ left.data(), left.data() + left.size(), right.data(), right.data() + right.size());
+}
+
+
+// Use a separate function to get better error messages when types do not match.
template <typename T1, typename T2>
void assert_same_types() {
MONGO_STATIC_ASSERT(std::is_same<T1, T2>::value);
@@ -187,8 +221,7 @@ TEST(IDLOneTypeTests, TestSafeInt32) {
TEST(IDLOneTypeTests, TestNamespaceString) {
IDLParserErrorContext ctxt("root");
- auto testDoc = BSON(One_namespacestring::kValueFieldName
- << "foo.bar");
+ auto testDoc = BSON(One_namespacestring::kValueFieldName << "foo.bar");
auto element = testDoc.firstElement();
ASSERT_EQUALS(element.type(), String);
@@ -440,6 +473,10 @@ TEST(IDLFieldTests, TestOptionalFields) {
const boost::optional<mongo::StringData>>();
assert_same_types<decltype(testStruct.getField3()),
const boost::optional<mongo::BSONObj>>();
+ assert_same_types<decltype(testStruct.getField4()),
+ const boost::optional<ConstDataRange>>();
+ assert_same_types<decltype(testStruct.getField5()),
+ const boost::optional<std::array<std::uint8_t, 16>>>();
ASSERT_EQUALS("Foo", testStruct.getField1().get());
ASSERT_FALSE(testStruct.getField2().is_initialized());
@@ -480,9 +517,8 @@ TEST(IDLFieldTests, TestOptionalFields) {
}
}
-
// Positive: Test a nested struct
-TEST(IDLNestedStruct, TestDuplicatTypes) {
+TEST(IDLNestedStruct, TestDuplicateTypes) {
IDLParserErrorContext ctxt("root");
@@ -544,6 +580,12 @@ TEST(IDLArrayTests, TestSimpleArrays) {
IDLParserErrorContext ctxt("root");
// Positive: Test document
+ uint8_t array1[] = {1, 2, 3};
+ uint8_t array2[] = {4, 6, 8};
+
+ uint8_t array15[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+ uint8_t array16[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+
auto testDoc = BSON("field1" << BSON_ARRAY("Foo"
<< "Bar"
<< "???")
@@ -551,13 +593,20 @@ TEST(IDLArrayTests, TestSimpleArrays) {
<< BSON_ARRAY(1 << 2 << 3)
<< "field3"
<< BSON_ARRAY(1.2 << 3.4 << 5.6)
-
- );
+ << "field4"
+ << BSON_ARRAY(BSONBinData(array1, 3, BinDataGeneral)
+ << BSONBinData(array2, 3, BinDataGeneral))
+ << "field5"
+ << BSON_ARRAY(BSONBinData(array15, 16, newUUID)
+ << BSONBinData(array16, 16, newUUID)));
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>&>();
+ assert_same_types<decltype(testStruct.getField4()), const std::vector<ConstDataRange>>();
+ assert_same_types<decltype(testStruct.getField5()),
+ const std::vector<std::array<std::uint8_t, 16>>&>();
std::vector<StringData> field1{"Foo", "Bar", "???"};
ASSERT_TRUE(field1 == testStruct.getField1());
@@ -566,6 +615,14 @@ TEST(IDLArrayTests, TestSimpleArrays) {
std::vector<double> field3{1.2, 3.4, 5.6};
ASSERT_TRUE(field3 == testStruct.getField3());
+ std::vector<std::vector<uint8_t>> field4{{1, 2, 3}, {4, 6, 8}};
+ ASSERT_TRUE(isEquals(testStruct.getField4(), field4));
+
+ std::vector<std::array<uint8_t, 16>> field5{
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}};
+ ASSERT_TRUE(isEquals(testStruct.getField5(), field5));
+
// Positive: Test we can roundtrip from the just parsed document
{
BSONObjBuilder builder;
@@ -582,6 +639,8 @@ TEST(IDLArrayTests, TestSimpleArrays) {
array_fields.setField1(field1);
array_fields.setField2(field2);
array_fields.setField3(field3);
+ array_fields.setField4(transformVector(field4));
+ array_fields.setField5(field5);
array_fields.serialize(&builder);
auto serializedDoc = builder.obj();
@@ -611,6 +670,10 @@ TEST(IDLArrayTests, TestSimpleOptionalArrays) {
const boost::optional<std::vector<std::int32_t>>>();
assert_same_types<decltype(testStruct.getField3()),
const boost::optional<std::vector<double>>>();
+ assert_same_types<decltype(testStruct.getField4()),
+ const boost::optional<std::vector<ConstDataRange>>>();
+ assert_same_types<decltype(testStruct.getField5()),
+ const boost::optional<std::vector<std::array<std::uint8_t, 16>>>>();
std::vector<StringData> field1{"Foo", "Bar", "???"};
ASSERT_TRUE(field1 == testStruct.getField1().get());
@@ -736,6 +799,11 @@ TEST(IDLArrayTests, TestArraysOfComplexTypes) {
<< BSON_ARRAY(BSONObj() << BSONObj())
<< "field5"
<< BSON_ARRAY(BSONObj() << BSONObj() << BSONObj())
+ << "field6"
+ << BSON_ARRAY(BSON("value"
+ << "hello")
+ << BSON("value"
+ << "world"))
<< "field1o"
<< BSON_ARRAY(1 << 2 << 3)
<< "field2o"
@@ -744,7 +812,14 @@ TEST(IDLArrayTests, TestArraysOfComplexTypes) {
<< "field3o"
<< BSON_ARRAY(1 << "2")
<< "field4o"
- << BSON_ARRAY(BSONObj() << BSONObj()));
+ << BSON_ARRAY(BSONObj() << BSONObj())
+ << "field6o"
+ << BSON_ARRAY(BSON("value"
+ << "goodbye")
+ << BSON("value"
+ << "world"))
+
+ );
auto testStruct = Complex_array_fields::parse(ctxt, testDoc);
assert_same_types<decltype(testStruct.getField1()), const std::vector<std::int64_t>&>();
@@ -754,6 +829,7 @@ TEST(IDLArrayTests, TestArraysOfComplexTypes) {
assert_same_types<decltype(testStruct.getField4()),
const std::vector<mongo::ObjectBasicType>&>();
assert_same_types<decltype(testStruct.getField5()), const std::vector<mongo::BSONObj>&>();
+ assert_same_types<decltype(testStruct.getField6()), const std::vector<mongo::One_string>&>();
assert_same_types<decltype(testStruct.getField1o()),
const boost::optional<std::vector<std::int64_t>>>();
@@ -765,11 +841,188 @@ TEST(IDLArrayTests, TestArraysOfComplexTypes) {
const boost::optional<std::vector<mongo::ObjectBasicType>>>();
assert_same_types<decltype(testStruct.getField5o()),
const boost::optional<std::vector<mongo::BSONObj>>>();
+ assert_same_types<decltype(testStruct.getField6o()),
+ const boost::optional<std::vector<mongo::One_string>>>();
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());
+
+ ASSERT_EQUALS(testStruct.getField6().size(), 2u);
+ ASSERT_EQUALS(testStruct.getField6()[0].getValue(), "hello");
+ ASSERT_EQUALS(testStruct.getField6()[1].getValue(), "world");
+ ASSERT_EQUALS(testStruct.getField6o().get().size(), 2u);
+ ASSERT_EQUALS(testStruct.getField6o().get()[0].getValue(), "goodbye");
+ ASSERT_EQUALS(testStruct.getField6o().get()[1].getValue(), "world");
+}
+
+template <typename ParserT, BinDataType bindata_type>
+void TestBinDataVector() {
+ IDLParserErrorContext ctxt("root");
+
+ // Positive: Test document with only a generic bindata field
+ uint8_t testData[] = {1, 2, 3};
+ auto testDoc = BSON("value" << BSONBinData(testData, 3, bindata_type));
+ auto testStruct = ParserT::parse(ctxt, testDoc);
+
+ assert_same_types<decltype(testStruct.getValue()), const ConstDataRange>();
+
+ std::vector<std::uint8_t> expected{1, 2, 3};
+
+ ASSERT_TRUE(isEquals(testStruct.getValue(), expected));
+
+ // 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;
+ ParserT one_new;
+ one_new.setValue(makeCDR(expected));
+ testStruct.serialize(&builder);
+
+ auto serializedDoc = builder.obj();
+ ASSERT_BSONOBJ_EQ(testDoc, serializedDoc);
+ }
+}
+
+TEST(IDLBinData, TestGeneric) {
+ TestBinDataVector<One_bindata, BinDataGeneral>();
+}
+
+TEST(IDLBinData, TestFunction) {
+ TestBinDataVector<One_function, Function>();
+}
+
+template <typename ParserT, BinDataType bindata_type>
+void TestBinDataArray() {
+ IDLParserErrorContext ctxt("root");
+
+ // Positive: Test document with only a generic bindata field
+ uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+ auto testDoc = BSON("value" << BSONBinData(testData, 16, bindata_type));
+ auto testStruct = ParserT::parse(ctxt, testDoc);
+
+ assert_same_types<decltype(testStruct.getValue()), const std::array<uint8_t, 16>>();
+
+ std::array<std::uint8_t, 16> expected{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+
+ ASSERT_TRUE(isEquals(testStruct.getValue(), expected));
+
+ // 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;
+ ParserT one_new;
+ one_new.setValue(expected);
+ one_new.serialize(&builder);
+
+ auto serializedDoc = builder.obj();
+ ASSERT_BSONOBJ_EQ(testDoc, serializedDoc);
+ }
+}
+
+TEST(IDLBinData, TestUUID) {
+ TestBinDataArray<One_uuid, newUUID>();
+}
+
+TEST(IDLBinData, TestMD5) {
+ TestBinDataArray<One_md5, MD5Type>();
+
+ // Negative: Test document with a incorrectly size md5 field
+ {
+ IDLParserErrorContext ctxt("root");
+
+ uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+ auto testDoc = BSON("value" << BSONBinData(testData, 15, MD5Type));
+ ASSERT_THROWS(One_md5::parse(ctxt, testDoc), UserException);
+ }
+}
+
+// Test if a given value for a given bson document parses successfully or fails if the bson types
+// mismatch.
+template <typename ParserT, BinDataType Parser_bindata_type, BinDataType Test_bindata_type>
+void TestBinDataParse() {
+ IDLParserErrorContext ctxt("root");
+
+ uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+ auto testDoc = BSON("value" << BSONBinData(testData, 16, Test_bindata_type));
+
+ auto element = testDoc.firstElement();
+ ASSERT_EQUALS(element.type(), BinData);
+ ASSERT_EQUALS(element.binDataType(), Test_bindata_type);
+
+ if (Parser_bindata_type != Test_bindata_type) {
+ ASSERT_THROWS(ParserT::parse(ctxt, testDoc), UserException);
+ } else {
+ (void)ParserT::parse(ctxt, testDoc);
+ }
+}
+
+template <typename ParserT, BinDataType Parser_bindata_type>
+void TestBinDataParser() {
+ TestBinDataParse<ParserT, Parser_bindata_type, BinDataGeneral>();
+ TestBinDataParse<ParserT, Parser_bindata_type, Function>();
+ TestBinDataParse<ParserT, Parser_bindata_type, MD5Type>();
+ TestBinDataParse<ParserT, Parser_bindata_type, newUUID>();
+}
+
+TEST(IDLBinData, TestParse) {
+ TestBinDataParser<One_bindata, BinDataGeneral>();
+ TestBinDataParser<One_function, Function>();
+ TestBinDataParser<One_uuid, newUUID>();
+ TestBinDataParser<One_md5, MD5Type>();
+}
+
+// Mixed: test a type that accepts a custom bindata type
+TEST(IDLBinData, TestCustomType) {
+ IDLParserErrorContext ctxt("root");
+
+ uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+ auto testDoc = BSON("value" << BSONBinData(testData, 14, BinDataGeneral));
+
+ auto element = testDoc.firstElement();
+ ASSERT_EQUALS(element.type(), BinData);
+ ASSERT_EQUALS(element.binDataType(), BinDataGeneral);
+
+ auto testStruct = One_bindata_custom::parse(ctxt, testDoc);
+ std::vector<std::uint8_t> testVector = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+ ASSERT_TRUE(testStruct.getValue().getVector() == testVector);
+
+ // 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;
+ One_bindata_custom one_new;
+ one_new.setValue(testVector);
+ one_new.serialize(&builder);
+
+ auto serializedDoc = builder.obj();
+ ASSERT_BSONOBJ_EQ(testDoc, serializedDoc);
+ }
}
/**
@@ -831,4 +1084,5 @@ TEST(IDLCustomType, TestDerivedParser) {
}
}
+} // namespace
} // namespace mongo
diff --git a/src/mongo/idl/idl_test_types.h b/src/mongo/idl/idl_test_types.h
index 41f75c796f0..6e03d48b470 100644
--- a/src/mongo/idl/idl_test_types.h
+++ b/src/mongo/idl/idl_test_types.h
@@ -28,6 +28,9 @@
#pragma once
+#include <vector>
+
+#include "mongo/base/data_range.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
@@ -82,4 +85,32 @@ private:
BSONObj _obj;
};
+/**
+ * Simple class that demonstrates the contract a class must implement to parse a BSON "bindata"
+ * variable length type
+ * from the IDL parser.
+ */
+class BinDataCustomType {
+public:
+ BinDataCustomType() {}
+ BinDataCustomType(std::vector<std::uint8_t>& vec) : _vec(std::move(vec)) {}
+
+ static BinDataCustomType parse(const std::vector<std::uint8_t> vec) {
+ BinDataCustomType b;
+ b._vec = std::move(vec);
+ return b;
+ }
+
+ ConstDataRange serialize() const {
+ return makeCDR(_vec);
+ }
+
+ const std::vector<std::uint8_t>& getVector() const {
+ return _vec;
+ }
+
+private:
+ std::vector<std::uint8_t> _vec;
+};
+
} // namespace mongo
diff --git a/src/mongo/idl/unittest.idl b/src/mongo/idl/unittest.idl
index 5d49ea37560..7aecd720ad4 100644
--- a/src/mongo/idl/unittest.idl
+++ b/src/mongo/idl/unittest.idl
@@ -41,6 +41,20 @@ types:
##################################################################################################
#
+# Test a custom non-BSONElement deserialization and serialization methods for a bindata type
+#
+##################################################################################################
+
+ bindata_custom:
+ bson_serialization_type: bindata
+ bindata_subtype: generic
+ description: "A MongoDB BinDataCustomType"
+ cpp_type: "mongo::BinDataCustomType"
+ serializer: mongo::BinDataCustomType::serialize
+ deserializer: mongo::BinDataCustomType::parse
+
+##################################################################################################
+#
# Test a custom non-BSONElement deserialization and serialization methods for an any type
#
##################################################################################################
@@ -81,6 +95,11 @@ types:
- double
deserializer: "mongo::BSONElement::numberInt"
+##################################################################################################
+#
+# Unit test structs for a single value to ensure type validation works correctly
+#
+##################################################################################################
structs:
@@ -172,6 +191,17 @@ structs:
##################################################################################################
#
+# Test a custom non-BSONElement deserialization and serialization methods for a bindata type
+#
+##################################################################################################
+
+ one_bindata_custom:
+ description: UnitTest for a custom bindata
+ fields:
+ value: bindata_custom
+
+##################################################################################################
+#
# Test a custom non-BSONElement deserialization and serialization methods for an any type
#
##################################################################################################
@@ -248,6 +278,12 @@ structs:
field3:
type: object
optional: true
+ field4:
+ type: bindata_generic
+ optional: true
+ field5:
+ type: bindata_uuid
+ optional: true
##################################################################################################
#
@@ -269,6 +305,10 @@ structs:
type: array<int>
field3:
type: array<double>
+ field4:
+ type: array<bindata_generic>
+ field5:
+ type: array<bindata_uuid>
optional_array_fields:
description: UnitTest for arrays of optional simple types
@@ -282,6 +322,12 @@ structs:
field3:
type: array<double>
optional: true
+ field4:
+ type: array<bindata_generic>
+ optional: true
+ field5:
+ type: array<bindata_uuid>
+ optional: true
##################################################################################################
#
@@ -302,6 +348,8 @@ structs:
type: array<object_basic_type>
field5:
type: array<object>
+ field6:
+ type: array<one_string>
field1o:
type: array<safeInt32>
optional: true
@@ -317,3 +365,7 @@ structs:
field5o:
type: array<object>
optional: true
+ field6o:
+ type: array<one_string>
+ optional: true
+#
diff --git a/src/mongo/idl/unittest_import.idl b/src/mongo/idl/unittest_import.idl
index a2064766133..0e9403f65d5 100644
--- a/src/mongo/idl/unittest_import.idl
+++ b/src/mongo/idl/unittest_import.idl
@@ -58,6 +58,26 @@ structs:
fields:
value: bool
+ one_bindata:
+ description: UnitTest for a single bindata_generic
+ fields:
+ value: bindata_generic
+
+ one_function:
+ description: UnitTest for a single bindata_function
+ fields:
+ value: bindata_function
+
+ one_uuid:
+ description: UnitTest for a single bindata_uuid
+ fields:
+ value: bindata_uuid
+
+ one_md5:
+ description: UnitTest for a single bindata_md5
+ fields:
+ value: bindata_md5
+
one_objectid:
description: UnitTest for a single objectid
fields: