diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2017-05-02 09:16:56 -0400 |
---|---|---|
committer | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2017-05-02 09:16:56 -0400 |
commit | 49cf0ebd80e8b007a6bcd08d3c956b149afa6f81 (patch) | |
tree | 5426b926fda611d8f956ab05bf987895881c491e | |
parent | 3d06c50d9427ac64785d2b62ca368010890aa9be (diff) | |
download | mongo-49cf0ebd80e8b007a6bcd08d3c956b149afa6f81.tar.gz |
SERVER-28827 BinData for IDL
-rw-r--r-- | buildscripts/idl/idl/binder.py | 18 | ||||
-rw-r--r-- | buildscripts/idl/idl/bson.py | 18 | ||||
-rw-r--r-- | buildscripts/idl/idl/cpp_types.py | 101 | ||||
-rw-r--r-- | buildscripts/idl/idl/errors.py | 8 | ||||
-rw-r--r-- | buildscripts/idl/idl/generator.py | 15 | ||||
-rw-r--r-- | buildscripts/idl/sample/sample.idl | 21 | ||||
-rw-r--r-- | buildscripts/idl/tests/context.py | 1 | ||||
-rw-r--r-- | buildscripts/idl/tests/test_binder.py | 132 | ||||
-rw-r--r-- | buildscripts/idl/tests/test_generator.py | 73 | ||||
-rw-r--r-- | buildscripts/idl/tests/test_parser.py | 2 | ||||
-rw-r--r-- | src/mongo/bson/bsonelement.h | 26 | ||||
-rw-r--r-- | src/mongo/idl/basic_types.idl | 32 | ||||
-rw-r--r-- | src/mongo/idl/idl_parser.cpp | 25 | ||||
-rw-r--r-- | src/mongo/idl/idl_parser.h | 13 | ||||
-rw-r--r-- | src/mongo/idl/idl_test.cpp | 270 | ||||
-rw-r--r-- | src/mongo/idl/idl_test_types.h | 31 | ||||
-rw-r--r-- | src/mongo/idl/unittest.idl | 52 | ||||
-rw-r--r-- | src/mongo/idl/unittest_import.idl | 20 |
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: |