diff options
-rw-r--r-- | buildscripts/idl/idl/ast.py | 4 | ||||
-rw-r--r-- | buildscripts/idl/idl/binder.py | 145 | ||||
-rw-r--r-- | buildscripts/idl/idl/common.py | 12 | ||||
-rw-r--r-- | buildscripts/idl/idl/errors.py | 97 | ||||
-rw-r--r-- | buildscripts/idl/idl/generator.py | 48 | ||||
-rw-r--r-- | buildscripts/idl/idl/parser.py | 12 | ||||
-rw-r--r-- | buildscripts/idl/idl/syntax.py | 18 | ||||
-rw-r--r-- | buildscripts/idl/tests/test_binder.py | 390 | ||||
-rw-r--r-- | buildscripts/idl/tests/test_import.py | 2 | ||||
-rw-r--r-- | buildscripts/idl/tests/test_parser.py | 81 | ||||
-rw-r--r-- | src/mongo/idl/idl_test.cpp | 227 | ||||
-rw-r--r-- | src/mongo/idl/idl_test_types.h | 50 | ||||
-rw-r--r-- | src/mongo/idl/unittest.idl | 74 |
13 files changed, 1112 insertions, 48 deletions
diff --git a/buildscripts/idl/idl/ast.py b/buildscripts/idl/idl/ast.py index b8fb88a6479..3a9df8340e8 100644 --- a/buildscripts/idl/idl/ast.py +++ b/buildscripts/idl/idl/ast.py @@ -79,6 +79,7 @@ class Struct(common.SourceLocation): self.name = None # type: unicode self.description = None # type: unicode self.strict = True # type: bool + self.chained_types = [] # type: List[Field] self.fields = [] # type: List[Field] super(Struct, self).__init__(file_name, line, column) @@ -88,7 +89,7 @@ class Field(common.SourceLocation): An instance of a field in a struct. Name is always populated. - A struct will either have a struct_type or a cpp_type, but not both. + A field will either have a struct_type or a cpp_type, but not both. Not all fields are set, it depends on the input document. """ @@ -102,6 +103,7 @@ class Field(common.SourceLocation): self.cpp_name = None # type: unicode self.optional = False # type: bool self.ignore = False # type: bool + self.chained = False # type: bool # Properties specific to fields which are types. self.cpp_type = None # type: unicode diff --git a/buildscripts/idl/idl/binder.py b/buildscripts/idl/idl/binder.py index c6f7ee5032c..fbbcaf660ca 100644 --- a/buildscripts/idl/idl/binder.py +++ b/buildscripts/idl/idl/binder.py @@ -12,15 +12,17 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # +# pylint: disable=too-many-lines """Transform idl.syntax trees from the parser into well-defined idl.ast trees.""" from __future__ import absolute_import, print_function, unicode_literals import re -from typing import Union +from typing import List, Union from . import ast from . import bson +from . import common from . import cpp_types from . import errors from . import syntax @@ -31,8 +33,8 @@ def _validate_single_bson_type(ctxt, idl_type, syntax_type): """Validate bson serialization type is correct for a type.""" bson_type = idl_type.bson_serialization_type[0] - # Any is only valid if it is the only bson type specified - if bson_type == "any": + # Any and Chain are only valid if they are the only bson types specified + if bson_type in ["any", "chain"]: return True if not bson.is_valid_bson_type(bson_type): @@ -64,8 +66,8 @@ def _validate_bson_types_list(ctxt, idl_type, syntax_type): return _validate_single_bson_type(ctxt, idl_type, syntax_type) for bson_type in bson_types: - if bson_type == "any": - ctxt.add_bad_any_type_use_error(idl_type, syntax_type, idl_type.name) + if bson_type in ["any", "chain"]: + ctxt.add_bad_any_type_use_error(idl_type, bson_type, syntax_type, idl_type.name) return False if not bson.is_valid_bson_type(bson_type): @@ -141,8 +143,24 @@ def _validate_cpp_type(ctxt, idl_type, syntax_type): return +def _validate_chain_type_properties(ctxt, idl_type, syntax_type): + # type: (errors.ParserContext, Union[syntax.Type, ast.Field], unicode) -> None + """Validate a chained type has both a deserializer and serializer.""" + assert len( + idl_type.bson_serialization_type) == 1 and idl_type.bson_serialization_type[0] == 'chain' + + if idl_type.deserializer is None: + ctxt.add_missing_ast_required_field_error(idl_type, syntax_type, idl_type.name, + "deserializer") + + if idl_type.serializer is None: + ctxt.add_missing_ast_required_field_error(idl_type, syntax_type, idl_type.name, + "serializer") + + def _validate_type_properties(ctxt, idl_type, syntax_type): # type: (errors.ParserContext, Union[syntax.Type, ast.Field], unicode) -> None + # pylint: disable=too-many-branches """Validate each type or field is correct.""" # Validate bson type restrictions @@ -153,11 +171,14 @@ def _validate_type_properties(ctxt, idl_type, syntax_type): bson_type = idl_type.bson_serialization_type[0] if bson_type == "any": - # For any, a deserializer 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, "deserializer") + elif bson_type == "chain": + _validate_chain_type_properties(ctxt, idl_type, syntax_type) + elif bson_type == "string": # Strings support custom serialization unlike other non-object scalar types if idl_type.deserializer is None: @@ -197,6 +218,22 @@ def _validate_types(ctxt, parsed_spec): _validate_type(ctxt, idl_type) +def _is_duplicate_field(ctxt, field_container, fields, ast_field): + # type: (errors.ParserContext, unicode, List[ast.Field], ast.Field) -> bool + """Return True if there is a naming conflict for a given field.""" + + # This is normally tested in the parser as part of duplicate detection in a map + if ast_field.name in [field.name for field in fields]: + for field in fields: + if field.name == ast_field.name: + duplicate_field = field + + ctxt.add_duplicate_field_error(ast_field, field_container, ast_field.name, duplicate_field) + return True + + return False + + def _bind_struct(ctxt, parsed_spec, struct): # type: (errors.ParserContext, syntax.IDLSpec, syntax.Struct) -> ast.Struct """ @@ -215,9 +252,26 @@ def _bind_struct(ctxt, parsed_spec, struct): if ast_struct.name.startswith("array<"): ctxt.add_array_not_valid_error(ast_struct, "struct", ast_struct.name) - for field in struct.fields: + # Merge chained types as chained fields + if struct.chained_types: + if ast_struct.strict: + ctxt.add_chained_type_no_strict_error(ast_struct, ast_struct.name) + + for chained_type in struct.chained_types: + ast_field = _bind_chained_type(ctxt, parsed_spec, ast_struct, chained_type) + if ast_field and not _is_duplicate_field(ctxt, chained_type, ast_struct.fields, + ast_field): + ast_struct.fields.append(ast_field) + + # Merge chained structs as a chained struct and ignored fields + for chained_struct in struct.chained_structs or []: + _bind_chained_struct(ctxt, parsed_spec, ast_struct, chained_struct) + + # Parse the fields last so that they are serialized after chained stuff. + for field in struct.fields or []: ast_field = _bind_field(ctxt, parsed_spec, field) - if ast_field: + if ast_field and not _is_duplicate_field(ctxt, ast_struct.name, ast_struct.fields, + ast_field): ast_struct.fields.append(ast_field) return ast_struct @@ -265,7 +319,7 @@ def _bind_field(ctxt, parsed_spec, field): _validate_ignored_field(ctxt, field) return ast_field - (struct, idltype) = parsed_spec.symbols.resolve_field_type(ctxt, field) + (struct, idltype) = parsed_spec.symbols.resolve_field_type(ctxt, field, field.name, field.type) if not struct and not idltype: return None @@ -274,7 +328,7 @@ def _bind_field(ctxt, parsed_spec, field): ast_field.array = True if field.default or (idltype and idltype.default): - ctxt.add_array_no_default(field, field.name) + ctxt.add_array_no_default_error(field, field.name) # Copy over only the needed information if this a struct or a type if struct: @@ -301,6 +355,77 @@ def _bind_field(ctxt, parsed_spec, field): return ast_field +def _bind_chained_type(ctxt, parsed_spec, location, chained_type): + # type: (errors.ParserContext, syntax.IDLSpec, common.SourceLocation, unicode) -> ast.Field + """Bind the specified chained type.""" + (struct, idltype) = parsed_spec.symbols.resolve_field_type(ctxt, location, chained_type, + chained_type) + if not idltype: + if struct: + ctxt.add_chained_type_not_found_error(location, chained_type) + + return None + + if len(idltype.bson_serialization_type) != 1 or idltype.bson_serialization_type[0] != 'chain': + ctxt.add_chained_type_wrong_type_error(location, chained_type, + idltype.bson_serialization_type[0]) + return None + + ast_field = ast.Field(location.file_name, location.line, location.column) + ast_field.name = idltype.name + ast_field.cpp_name = idltype.name + ast_field.description = idltype.description + ast_field.chained = True + + ast_field.cpp_type = idltype.cpp_type + ast_field.bson_serialization_type = idltype.bson_serialization_type + ast_field.serializer = idltype.serializer + ast_field.deserializer = idltype.deserializer + + return ast_field + + +def _bind_chained_struct(ctxt, parsed_spec, ast_struct, chained_struct): + # type: (errors.ParserContext, syntax.IDLSpec, ast.Struct, unicode) -> None + """Bind the specified chained struct.""" + (struct, idltype) = parsed_spec.symbols.resolve_field_type(ctxt, ast_struct, chained_struct, + chained_struct) + if not struct: + if idltype: + ctxt.add_chained_struct_not_found_error(ast_struct, chained_struct) + + return None + + if struct.strict: + ctxt.add_chained_nested_struct_no_strict_error(ast_struct, ast_struct.name, chained_struct) + + if struct.chained_types or struct.chained_structs: + ctxt.add_chained_nested_struct_no_nested_error(ast_struct, ast_struct.name, chained_struct) + + # Configure a field for the chained struct. + ast_field = ast.Field(ast_struct.file_name, ast_struct.line, ast_struct.column) + ast_field.name = struct.name + ast_field.cpp_name = struct.name + ast_field.description = struct.description + ast_field.struct_type = struct.name + ast_field.bson_serialization_type = ["object"] + + ast_field.chained = True + + if not _is_duplicate_field(ctxt, chained_struct, ast_struct.fields, ast_field): + ast_struct.fields.append(ast_field) + else: + return + + # Merge all the fields from resolved struct into this ast struct as 'ignored'. + for field in struct.fields or []: + ast_field = _bind_field(ctxt, parsed_spec, field) + if ast_field and not _is_duplicate_field(ctxt, chained_struct, ast_struct.fields, + ast_field): + ast_field.ignore = True + ast_struct.fields.append(ast_field) + + def _bind_globals(parsed_spec): # type: (syntax.IDLSpec) -> ast.Global """Bind the globals object from the idl.syntax tree into the idl.ast tree by doing a deep copy.""" diff --git a/buildscripts/idl/idl/common.py b/buildscripts/idl/idl/common.py index 15ef4af77dd..920cf3b37bd 100644 --- a/buildscripts/idl/idl/common.py +++ b/buildscripts/idl/idl/common.py @@ -20,6 +20,7 @@ Classes which are shared among both the IDL idl.syntax and idl.AST trees. from __future__ import absolute_import, print_function, unicode_literals +import os import string from typing import Mapping @@ -63,3 +64,14 @@ class SourceLocation(object): self.file_name = file_name self.line = line self.column = column + + def __str__(self): + # type: () -> str + """ + Return a formatted location. + + Example location message: + test.idl: (17, 4) + """ + msg = "%s: (%d, %d)" % (os.path.basename(self.file_name), self.line, self.column) + return msg # type: ignore diff --git a/buildscripts/idl/idl/errors.py b/buildscripts/idl/idl/errors.py index 08d773fc022..3b301000ebe 100644 --- a/buildscripts/idl/idl/errors.py +++ b/buildscripts/idl/idl/errors.py @@ -60,6 +60,13 @@ ERROR_ID_BAD_ARRAY_TYPE_NAME = "ID0023" ERROR_ID_ARRAY_NO_DEFAULT = "ID0024" ERROR_ID_BAD_IMPORT = "ID0025" ERROR_ID_BAD_BINDATA_DEFAULT = "ID0026" +ERROR_ID_CHAINED_TYPE_NOT_FOUND = "ID0027" +ERROR_ID_CHAINED_TYPE_WRONG_BSON_TYPE = "ID0028" +ERROR_ID_CHAINED_DUPLICATE_FIELD = "ID0029" +ERROR_ID_CHAINED_NO_TYPE_STRICT = "ID0030" +ERROR_ID_CHAINED_STRUCT_NOT_FOUND = "ID0031" +ERROR_ID_CHAINED_NO_NESTED_STRUCT_STRICT = "ID0032" +ERROR_ID_CHAINED_NO_NESTED_CHAINED = "ID0033" class IDLError(Exception): @@ -228,13 +235,19 @@ class ParserContext(object): """Return True if this YAML node is a Scalar.""" return self._is_node_type(node, node_name, "scalar") - def is_sequence_node(self, node, node_name): + def is_scalar_sequence(self, node, node_name): # type: (Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode], unicode) -> bool - """Return True if this YAML node is a Sequence.""" - return self._is_node_type(node, node_name, "sequence") + """Return True if this YAML node is a Sequence of Scalars.""" + if self._is_node_type(node, node_name, "sequence"): + for seq_node in node.value: + if not self.is_scalar_node(seq_node, node_name): + return False + return True + return False - def is_sequence_or_scalar_node(self, node, node_name): + def is_scalar_sequence_or_scalar_node(self, node, node_name): # type: (Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode], unicode) -> bool + # pylint: disable=invalid-name """Return True if the YAML node is a Scalar or Sequence.""" if not node.id == "scalar" and not node.id == "sequence": self._add_node_error( @@ -242,6 +255,10 @@ class ParserContext(object): "Illegal node type '%s' for '%s', expected either node type 'scalar' or 'sequence'" % (node.id, node_name)) return False + + if node.id == "sequence": + return self.is_scalar_sequence(node, node_name) + return True def is_scalar_bool_node(self, node, node_name): @@ -270,7 +287,7 @@ class ParserContext(object): def get_list(self, node): # type: (Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode]) -> List[unicode] """Get a YAML scalar or sequence node as a list of strings.""" - assert self.is_sequence_or_scalar_node(node, "unknown") + assert self.is_scalar_sequence_or_scalar_node(node, "unknown") if node.id == "scalar": return [node.value] else: @@ -287,8 +304,8 @@ class ParserContext(object): # type: (yaml.nodes.Node, unicode) -> None """Add an error about a struct without fields.""" self._add_node_error(node, ERROR_ID_EMPTY_FIELDS, - "Struct '%s' must have fields specified but no fields were found" % - (name)) + ("Struct '%s' must either have fields, chained_types, or " + + " chained_structs specified but neither were found") % (name)) def add_missing_required_field_error(self, node, node_parent, node_name): # type: (yaml.nodes.Node, unicode, unicode) -> None @@ -376,13 +393,13 @@ class ParserContext(object): " use bson type '%s', use a bson_serialization_type of 'any' instead.") % (ast_type, ast_parent, bson_type_name)) - def add_bad_any_type_use_error(self, location, ast_type, ast_parent): - # type: (common.SourceLocation, unicode, unicode) -> None + def add_bad_any_type_use_error(self, location, bson_type, ast_type, ast_parent): + # type: (common.SourceLocation, unicode, 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_ANY_TYPE_USE, ( - "The BSON Type 'any' is not allowed in a list of bson serialization types for" + - "%s '%s'. It must be only a single bson type.") % (ast_type, ast_parent)) + "The BSON Type '%s' is not allowed in a list of bson serialization types for" + + "%s '%s'. It must be only a single bson type.") % (bson_type, ast_type, ast_parent)) def add_bad_cpp_numeric_type_use_error(self, location, ast_type, ast_parent, cpp_type): # type: (common.SourceLocation, unicode, unicode, unicode) -> None @@ -393,14 +410,14 @@ class ParserContext(object): " 'std::uint32_t', 'std::uint64_t', and 'std::int64_t' are supported.") % (cpp_type, ast_type, ast_parent)) - def add_bad_array_type_name(self, location, field_name, type_name): + def add_bad_array_type_name_error(self, location, field_name, type_name): # type: (common.SourceLocation, unicode, unicode) -> None """Add an error about a field type having a malformed type name.""" self._add_error(location, ERROR_ID_BAD_ARRAY_TYPE_NAME, ("'%s' is not a valid array type for field '%s'. A valid array type" + " is in the form 'array<type_name>'.") % (type_name, field_name)) - def add_array_no_default(self, location, field_name): + def add_array_no_default_error(self, location, field_name): # type: (common.SourceLocation, unicode) -> None """Add an error about an array having a type with a default value.""" self._add_error( @@ -421,6 +438,60 @@ class ParserContext(object): self._add_error(location, ERROR_ID_BAD_BINDATA_DEFAULT, ("Default values are not allowed for %s '%s'") % (ast_type, ast_parent)) + def add_chained_type_not_found_error(self, location, type_name): + # type: (common.SourceLocation, unicode) -> None + # pylint: disable=invalid-name + """Add an error about a chained_type not found.""" + self._add_error(location, ERROR_ID_CHAINED_TYPE_NOT_FOUND, + ("Type '%s' is not a valid chained type") % (type_name)) + + def add_chained_type_wrong_type_error(self, location, type_name, bson_type_name): + # type: (common.SourceLocation, unicode, unicode) -> None + # pylint: disable=invalid-name + """Add an error about a chained_type being the wrong type.""" + self._add_error(location, ERROR_ID_CHAINED_TYPE_WRONG_BSON_TYPE, + ("Chained Type '%s' has the wrong bson serialization type '%s', only" + + "'chain' is supported for chained types.") % (type_name, bson_type_name)) + + def add_duplicate_field_error(self, location, field_container, field_name, duplicate_location): + # type: (common.SourceLocation, unicode, unicode, common.SourceLocation) -> None + """Add an error about duplicate fields as a result of chained structs/types.""" + self._add_error(location, ERROR_ID_CHAINED_DUPLICATE_FIELD, ( + "Chained Struct or Type '%s' duplicates an existing field '%s' at location" + "'%s'.") % + (field_container, field_name, duplicate_location)) + + def add_chained_type_no_strict_error(self, location, struct_name): + # type: (common.SourceLocation, unicode) -> None + # pylint: disable=invalid-name + """Add an error about strict parser validate and chained types.""" + self._add_error(location, ERROR_ID_CHAINED_NO_TYPE_STRICT, + ("Strict IDL parser validation is not supported with chained types for " + + "struct '%s'. Specify 'strict: false' for this struct.") % (struct_name)) + + def add_chained_struct_not_found_error(self, location, struct_name): + # type: (common.SourceLocation, unicode) -> None + # pylint: disable=invalid-name + """Add an error about a chained_struct not found.""" + self._add_error(location, ERROR_ID_CHAINED_STRUCT_NOT_FOUND, + ("Type '%s' is not a valid chained struct") % (struct_name)) + + def add_chained_nested_struct_no_strict_error(self, location, struct_name, nested_struct_name): + # type: (common.SourceLocation, unicode, unicode) -> None + # pylint: disable=invalid-name + """Add an error about strict parser validate and chained types.""" + self._add_error(location, ERROR_ID_CHAINED_NO_NESTED_STRUCT_STRICT, + ("Strict IDL parser validation is not supported for a chained struct '%s'" + + " contained by struct '%s'. Specify 'strict: false' for this struct.") % + (nested_struct_name, struct_name)) + + def add_chained_nested_struct_no_nested_error(self, location, struct_name, chained_name): + # type: (common.SourceLocation, unicode, unicode) -> None + # pylint: disable=invalid-name + """Add an error about struct's chaining being a struct with nested chaining.""" + self._add_error(location, ERROR_ID_CHAINED_NO_NESTED_CHAINED, + ("Struct '%s' is not allowed to nest struct '%s' since it has chained" + + " structs and/or types.") % (struct_name, chained_name)) + def _assert_unique_error_messages(): # type: () -> None diff --git a/buildscripts/idl/idl/generator.py b/buildscripts/idl/idl/generator.py index 320c0f5435d..71c49491897 100644 --- a/buildscripts/idl/idl/generator.py +++ b/buildscripts/idl/idl/generator.py @@ -54,8 +54,11 @@ def _get_bson_type_check(bson_element, ctxt_name, field): """Get the C++ bson type check for a field.""" bson_types = field.bson_serialization_type if len(bson_types) == 1: - if bson_types[0] == 'any': - # Skip BSON valiation when any + if bson_types[0] in ['any', 'chain']: + # Skip BSON validation for 'any' types since they are required to validate the + # BSONElement. + # Skip BSON validation for 'chain' types since they process the raw BSONObject the + # encapsulating IDL struct parser is passed. return None if not bson_types[0] == 'bindata': @@ -121,7 +124,7 @@ class _FieldUsageChecker(object): # type: () -> None """Output the code to check for missing fields.""" for field in self.fields: - if (not field.optional) and (not field.ignore): + if (not field.optional) and (not field.ignore) and (not field.chained): with writer.IndentedScopedBlock(self._writer, 'if (usedFields.find("%s") == usedFields.end()) {' % (field.name), '}'): @@ -507,11 +510,23 @@ class _CppSourceFileWriter(_CppFileWriterBase): self._gen_array_deserializer(field) return - # May be an empty block if the type is any - with self._predicate(_get_bson_type_check('element', 'ctxt', field)): + if field.chained: + # Do not generate a predicate check since we always call these deserializers. - object_value = self._gen_field_deserializer_expression('element', field) - self._writer.write_line('%s = %s;' % (_get_field_member_name(field), object_value)) + if field.struct_type: + # Do not generate a new parser context, reuse the current one since we are not + # entering a nested document. + expression = '%s::parse(ctxt, bsonObject)' % (common.title_case(field.struct_type)) + else: + method_name = writer.get_method_name_from_qualified_method_name(field.deserializer) + expression = "%s(bsonObject)" % (method_name) + + self._writer.write_line('%s = %s;' % (_get_field_member_name(field), expression)) + else: + # May be an empty block if the type is 'any' + with self._predicate(_get_bson_type_check('element', 'ctxt', field)): + object_value = self._gen_field_deserializer_expression('element', field) + self._writer.write_line('%s = %s;' % (_get_field_member_name(field), object_value)) def gen_deserializer_methods(self, struct): # type: (ast.Struct) -> None @@ -543,6 +558,10 @@ class _CppSourceFileWriter(_CppFileWriterBase): first_field = True for field in struct.fields: + # Do not parse chained fields as fields since they are actually chained types. + if field.chained: + continue + field_predicate = 'fieldName == "%s"' % (field.name) field_usage_check.add(field) @@ -563,6 +582,15 @@ class _CppSourceFileWriter(_CppFileWriterBase): self._writer.write_empty_line() + # Parse chained types + for field in struct.fields: + if not field.chained: + continue + + # Simply generate deserializers since these are all 'any' types + self.gen_field_deserializer(field) + self._writer.write_empty_line() + # Check for required fields field_usage_check.add_final_checks() self._writer.write_empty_line() @@ -638,7 +666,11 @@ class _CppSourceFileWriter(_CppFileWriterBase): with self._with_template(template_params): - if field.array: + if field.chained: + # Just directly call the serializer for chained structs without opening up a nested + # document. + self._writer.write_template('${access_member}.serialize(builder);') + elif field.array: self._writer.write_template( 'BSONArrayBuilder arrayBuilder(builder->subarrayStart("${field_name}"));') with self._block('for (const auto& item : ${access_member}) {', '}'): diff --git a/buildscripts/idl/idl/parser.py b/buildscripts/idl/idl/parser.py index 350d04a2805..8a4020f4725 100644 --- a/buildscripts/idl/idl/parser.py +++ b/buildscripts/idl/idl/parser.py @@ -85,7 +85,10 @@ def _generic_parser( if ctxt.is_scalar_bool_node(second_node, first_name): syntax_node.__dict__[first_name] = ctxt.get_bool(second_node) elif rule_desc.node_type == "scalar_or_sequence": - if ctxt.is_sequence_or_scalar_node(second_node, first_name): + if ctxt.is_scalar_sequence_or_scalar_node(second_node, first_name): + syntax_node.__dict__[first_name] = ctxt.get_list(second_node) + elif rule_desc.node_type == "sequence": + if ctxt.is_scalar_sequence(second_node, first_name): syntax_node.__dict__[first_name] = ctxt.get_list(second_node) elif rule_desc.node_type == "mapping": if ctxt.is_mapping_node(second_node, first_name): @@ -138,7 +141,7 @@ def _parse_global(ctxt, spec, node): def _parse_imports(ctxt, spec, node): # type: (errors.ParserContext, syntax.IDLSpec, Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode]) -> None """Parse an imports section in the IDL file.""" - if not ctxt.is_sequence_node(node, "imports"): + if not ctxt.is_scalar_sequence(node, "imports"): return if spec.imports: @@ -250,10 +253,13 @@ def _parse_struct(ctxt, spec, name, node): _generic_parser(ctxt, node, "struct", struct, { "description": _RuleDesc('scalar', _RuleDesc.REQUIRED), "fields": _RuleDesc('mapping', mapping_parser_func=_parse_fields), + "chained_types": _RuleDesc('sequence'), + "chained_structs": _RuleDesc('sequence'), "strict": _RuleDesc("bool_scalar"), }) - if struct.fields is None: + # TODO: SHOULD WE ALLOW STRUCTS ONLY WITH CHAINED STUFF and no fields??? + if struct.fields is None and struct.chained_types is None and struct.chained_structs is None: ctxt.add_empty_struct_error(node, struct.name) spec.symbols.add_struct(ctxt, struct) diff --git a/buildscripts/idl/idl/syntax.py b/buildscripts/idl/idl/syntax.py index a7a8a10d249..d1c5b72d45a 100644 --- a/buildscripts/idl/idl/syntax.py +++ b/buildscripts/idl/idl/syntax.py @@ -134,13 +134,13 @@ class SymbolTable(object): for idltype in imported_symbols.types: self.add_type(ctxt, idltype) - def resolve_field_type(self, ctxt, field): - # type: (errors.ParserContext, Field) -> Tuple[Optional[Struct], Optional[Type]] + def resolve_field_type(self, ctxt, location, field_name, type_name): + # type: (errors.ParserContext, common.SourceLocation, unicode, unicode) -> Tuple[Optional[Struct], Optional[Type]] """Find the type or struct a field refers to or log an error.""" - return self._resolve_field_type(ctxt, field, field.type) + return self._resolve_field_type(ctxt, location, field_name, type_name) - def _resolve_field_type(self, ctxt, field, type_name): - # type: (errors.ParserContext, Field, unicode) -> Tuple[Optional[Struct], Optional[Type]] + def _resolve_field_type(self, ctxt, location, field_name, type_name): + # type: (errors.ParserContext, common.SourceLocation, unicode, unicode) -> Tuple[Optional[Struct], Optional[Type]] """Find the type or struct a field refers to or log an error.""" for idltype in self.types: if idltype.name == type_name: @@ -153,12 +153,12 @@ class SymbolTable(object): if type_name.startswith('array<'): array_type_name = parse_array_type(type_name) if not array_type_name: - ctxt.add_bad_array_type_name(field, field.name, type_name) + ctxt.add_bad_array_type_name_error(location, field_name, type_name) return (None, None) - return self._resolve_field_type(ctxt, field, array_type_name) + return self._resolve_field_type(ctxt, location, field_name, array_type_name) - ctxt.add_unknown_type_error(field, field.name, type_name) + ctxt.add_unknown_type_error(location, field_name, type_name) return (None, None) @@ -260,6 +260,8 @@ class Struct(common.SourceLocation): self.name = None # type: unicode self.description = None # type: unicode self.strict = True # type: bool + self.chained_types = None # type: List[unicode] + self.chained_structs = None # type: List[unicode] self.fields = None # type: List[Field] # Internal property that is not represented as syntax. An imported struct is read from an diff --git a/buildscripts/idl/tests/test_binder.py b/buildscripts/idl/tests/test_binder.py index 144885cc81e..1b63aff4bf1 100644 --- a/buildscripts/idl/tests/test_binder.py +++ b/buildscripts/idl/tests/test_binder.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # +# pylint: disable=too-many-lines """Test cases for IDL binder.""" from __future__ import absolute_import, print_function, unicode_literals @@ -137,7 +138,7 @@ class TestBinder(testcase.IDLTestcase): default: foo """)) - # Test object + # Test 'any' self.assert_bind( textwrap.dedent(""" types: @@ -150,6 +151,19 @@ class TestBinder(testcase.IDLTestcase): default: foo """)) + # Test 'chain' + self.assert_bind( + textwrap.dedent(""" + types: + foofoo: + description: foo + cpp_type: foo + bson_serialization_type: chain + serializer: foo + deserializer: foo + default: foo + """)) + # Test supported bindata_subtype for bindata_subtype in ["generic", "function", "uuid", "md5"]: self.assert_bind( @@ -342,7 +356,7 @@ class TestBinder(testcase.IDLTestcase): - string """), idl.errors.ERROR_ID_BAD_BSON_TYPE) - # Test any in list of types + # Test 'any' in list of types self.assert_bind_fail( textwrap.dedent(""" types: @@ -378,6 +392,18 @@ class TestBinder(testcase.IDLTestcase): - fake """), idl.errors.ERROR_ID_BAD_BSON_TYPE) + # Test 'chain' in list of types + self.assert_bind_fail( + textwrap.dedent(""" + types: + foofoo: + description: foo + cpp_type: foo + bson_serialization_type: + - chain + - int + """), idl.errors.ERROR_ID_BAD_ANY_TYPE_USE) + # Test unsupported serialization for bson_type in [ "bool", "date", "null", "decimal", "double", "int", "long", "objectid", "regex", @@ -406,7 +432,7 @@ class TestBinder(testcase.IDLTestcase): """ % (bson_type)), idl.errors.ERROR_ID_CUSTOM_SCALAR_SERIALIZATION_NOT_SUPPORTED) - # Test any serialization needs deserializer + # Test 'any' serialization needs deserializer self.assert_bind_fail( textwrap.dedent(""" types: @@ -416,6 +442,28 @@ class TestBinder(testcase.IDLTestcase): bson_serialization_type: any """), idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD) + # Test 'chain' serialization needs deserializer + self.assert_bind_fail( + textwrap.dedent(""" + types: + foofoo: + description: foo + cpp_type: foo + bson_serialization_type: chain + serializer: bar + """), idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD) + + # Test 'chain' serialization needs serializer + self.assert_bind_fail( + textwrap.dedent(""" + types: + foofoo: + description: foo + cpp_type: foo + bson_serialization_type: chain + deserializer: bar + """), idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD) + # Test list of bson types needs deserializer self.assert_bind_fail( textwrap.dedent(""" @@ -689,6 +737,342 @@ class TestBinder(testcase.IDLTestcase): %s """ % (test_value)), idl.errors.ERROR_ID_FIELD_MUST_BE_EMPTY_FOR_IGNORED) + def test_chained_type_positive(self): + # type: () -> None + """Positive parser chaining test cases.""" + # Setup some common types + test_preamble = textwrap.dedent(""" + types: + string: + description: foo + cpp_type: foo + bson_serialization_type: string + serializer: foo + deserializer: foo + default: foo + + + foo1: + description: foo + cpp_type: foo + bson_serialization_type: chain + serializer: foo + deserializer: foo + default: foo + + """) + + # Chaining only + self.assert_bind(test_preamble + textwrap.dedent(""" + structs: + bar1: + description: foo + strict: false + chained_types: + - foo1 + """)) + + def test_chained_type_negative(self): + # type: () -> None + """Negative parser chaining test cases.""" + # Setup some common types + test_preamble = textwrap.dedent(""" + types: + string: + description: foo + cpp_type: foo + bson_serialization_type: string + serializer: foo + deserializer: foo + default: foo + + + foo1: + description: foo + cpp_type: foo + bson_serialization_type: chain + serializer: foo + deserializer: foo + default: foo + + """) + + # Chaining with strict struct + self.assert_bind_fail(test_preamble + textwrap.dedent(""" + structs: + bar1: + description: foo + strict: true + chained_types: + - foo1 + """), idl.errors.ERROR_ID_CHAINED_NO_TYPE_STRICT) + + # Non-'any' type as chained type + self.assert_bind_fail(test_preamble + textwrap.dedent(""" + structs: + bar1: + description: foo + strict: false + chained_types: + - string + """), idl.errors.ERROR_ID_CHAINED_TYPE_WRONG_BSON_TYPE) + + # Duplicate chained types + self.assert_bind_fail(test_preamble + textwrap.dedent(""" + structs: + bar1: + description: foo + strict: false + chained_types: + - foo1 + - foo1 + """), idl.errors.ERROR_ID_CHAINED_DUPLICATE_FIELD) + + # Chaining and fields only with same name + self.assert_bind_fail(test_preamble + textwrap.dedent(""" + structs: + bar1: + description: foo + strict: false + chained_types: + - foo1 + fields: + foo1: string + """), idl.errors.ERROR_ID_CHAINED_DUPLICATE_FIELD) + + def test_chained_struct_positive(self): + # type: () -> None + """Positive parser chaining test cases.""" + # Setup some common types + test_preamble = textwrap.dedent(""" + types: + string: + description: foo + cpp_type: foo + bson_serialization_type: string + serializer: foo + deserializer: foo + default: foo + + + foo1: + description: foo + cpp_type: foo + bson_serialization_type: chain + serializer: foo + deserializer: foo + default: foo + + structs: + chained: + description: foo + strict: false + chained_types: + - foo1 + + chained2: + description: foo + strict: false + fields: + field1: string + """) + + # A struct with only chaining + self.assert_bind(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: true + chained_structs: + - chained2 + """))) + + # Chaining struct's fields and explicit fields + self.assert_bind(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: true + chained_structs: + - chained2 + fields: + str1: string + """))) + + # Chained types and structs + self.assert_bind(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: false + chained_types: + - foo1 + chained_structs: + - chained2 + fields: + str1: string + """))) + + # Non-strict chained struct + self.assert_bind(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: false + chained_structs: + - chained2 + fields: + foo1: string + """))) + + def test_chained_struct_negative(self): + # type: () -> None + """Negative parser chaining test cases.""" + # Setup some common types + test_preamble = textwrap.dedent(""" + types: + string: + description: foo + cpp_type: foo + bson_serialization_type: string + serializer: foo + deserializer: foo + default: foo + + + foo1: + description: foo + cpp_type: foo + bson_serialization_type: chain + serializer: foo + deserializer: foo + default: foo + + structs: + chained: + description: foo + strict: false + fields: + field1: string + + chained2: + description: foo + strict: false + fields: + field1: string + """) + + # Type as chained struct + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: true + chained_structs: + - foo1 + """)), idl.errors.ERROR_ID_CHAINED_STRUCT_NOT_FOUND) + + # Struct as chained type + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: false + chained_types: + - chained + """)), idl.errors.ERROR_ID_CHAINED_TYPE_NOT_FOUND) + + # Duplicated field names across chained struct's fields and fields + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: false + chained_structs: + - chained + fields: + field1: string + """)), idl.errors.ERROR_ID_CHAINED_DUPLICATE_FIELD) + + # Duplicated field names across chained structs + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: false + chained_structs: + - chained + - chained2 + """)), idl.errors.ERROR_ID_CHAINED_DUPLICATE_FIELD) + + # Duplicate chained structs + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: true + chained_structs: + - chained + - chained + """)), idl.errors.ERROR_ID_CHAINED_DUPLICATE_FIELD) + + # Chained struct with strict true + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: true + fields: + field1: string + + foobar: + description: foo + strict: false + chained_structs: + - bar1 + fields: + f1: string + + """)), idl.errors.ERROR_ID_CHAINED_NO_NESTED_STRUCT_STRICT) + + # Chained struct with nested chained struct + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: false + chained_structs: + - chained + + foobar: + description: foo + strict: false + chained_structs: + - bar1 + fields: + f1: string + + """)), idl.errors.ERROR_ID_CHAINED_NO_NESTED_CHAINED) + + # Chained struct with nested chained type + self.assert_bind_fail(test_preamble + indent_text(1, + textwrap.dedent(""" + bar1: + description: foo + strict: false + chained_types: + - foo1 + + foobar: + description: foo + strict: false + chained_structs: + - bar1 + fields: + f1: bar1 + + """)), idl.errors.ERROR_ID_CHAINED_NO_NESTED_CHAINED) + if __name__ == '__main__': diff --git a/buildscripts/idl/tests/test_import.py b/buildscripts/idl/tests/test_import.py index c498f8c1322..d0466d423b1 100644 --- a/buildscripts/idl/tests/test_import.py +++ b/buildscripts/idl/tests/test_import.py @@ -85,7 +85,7 @@ class TestImport(testcase.IDLTestcase): self.assert_parse_fail( textwrap.dedent(""" - imports: + imports: a: "a.idl" b: "b.idl" """), idl.errors.ERROR_ID_IS_NODE_TYPE) diff --git a/buildscripts/idl/tests/test_parser.py b/buildscripts/idl/tests/test_parser.py index cdab68e1457..5013229d92f 100644 --- a/buildscripts/idl/tests/test_parser.py +++ b/buildscripts/idl/tests/test_parser.py @@ -125,6 +125,13 @@ class TestParser(testcase.IDLTestcase): cpp_includes: inc1: 'foo'"""), idl.errors.ERROR_ID_IS_NODE_TYPE_SCALAR_OR_SEQUENCE) + # cpp_includes as a sequence of tuples + self.assert_parse_fail( + textwrap.dedent(""" + global: + cpp_includes: + - inc1: 'foo'"""), idl.errors.ERROR_ID_IS_NODE_TYPE) + # Unknown scalar self.assert_parse_fail( textwrap.dedent(""" @@ -446,6 +453,80 @@ class TestParser(testcase.IDLTestcase): default: foo """), idl.errors.ERROR_ID_DUPLICATE_SYMBOL) + def test_chained_type_positive(self): + # type: () -> None + """Positive parser chaining test cases.""" + self.assert_parse( + textwrap.dedent(""" + structs: + foo1: + description: foo + chained_types: + - foo1 + - foo2 + """)) + + def test_chained_type_negative(self): + # type: () -> None + """Negative parser chaining test cases.""" + self.assert_parse_fail( + textwrap.dedent(""" + structs: + foo1: + description: foo + chained_types: foo1 + fields: + foo: bar + """), idl.errors.ERROR_ID_IS_NODE_TYPE) + + self.assert_parse_fail( + textwrap.dedent(""" + structs: + foo1: + description: foo + chained_types: + foo1: bar + fields: + foo: bar + """), idl.errors.ERROR_ID_IS_NODE_TYPE) + + def test_chained_struct_positive(self): + # type: () -> None + """Positive parser chaining test cases.""" + self.assert_parse( + textwrap.dedent(""" + structs: + foo1: + description: foo + chained_structs: + - foo1 + - foo2 + """)) + + def test_chained_struct_negative(self): + # type: () -> None + """Negative parser chaining test cases.""" + self.assert_parse_fail( + textwrap.dedent(""" + structs: + foo1: + description: foo + chained_structs: foo1 + fields: + foo: bar + """), idl.errors.ERROR_ID_IS_NODE_TYPE) + + self.assert_parse_fail( + textwrap.dedent(""" + structs: + foo1: + description: foo + chained_structs: + foo1: bar + fields: + foo: bar + """), idl.errors.ERROR_ID_IS_NODE_TYPE) + if __name__ == '__main__': diff --git a/src/mongo/idl/idl_test.cpp b/src/mongo/idl/idl_test.cpp index 4f26af506c9..00328d11648 100644 --- a/src/mongo/idl/idl_test.cpp +++ b/src/mongo/idl/idl_test.cpp @@ -227,6 +227,8 @@ TEST(IDLOneTypeTests, TestNamespaceString) { ASSERT_EQUALS(element.type(), String); auto testStruct = One_namespacestring::parse(ctxt, testDoc); + assert_same_types<decltype(testStruct.getValue()), const NamespaceString&>(); + ASSERT_EQUALS(testStruct.getValue(), NamespaceString("foo.bar")); // Positive: Test we can roundtrip from the just parsed document @@ -1084,5 +1086,230 @@ TEST(IDLCustomType, TestDerivedParser) { } } +// Chained type testing +// Check each of types +// Check for round-tripping of fields and documents + +// Positive: demonstrate a class struct chained types +TEST(IDLChainedType, TestChainedType) { + IDLParserErrorContext ctxt("root"); + + auto testDoc = BSON("field1" + << "abc" + << "field2" + << 5); + + auto testStruct = Chained_struct_only::parse(ctxt, testDoc); + + assert_same_types<decltype(testStruct.getChainedType()), const mongo::ChainedType&>(); + assert_same_types<decltype(testStruct.getAnotherChainedType()), + const mongo::AnotherChainedType&>(); + + ASSERT_EQUALS(testStruct.getChainedType().getField1(), "abc"); + ASSERT_EQUALS(testStruct.getAnotherChainedType().getField2(), 5); + + // 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; + Chained_struct_only one_new; + ChainedType ct; + ct.setField1("abc"); + one_new.setChainedType(ct); + AnotherChainedType act; + act.setField2(5); + one_new.setAnotherChainedType(act); + one_new.serialize(&builder); + + auto serializedDoc = builder.obj(); + ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); + } +} + +// Positive: demonstrate a struct with chained types ignoring extra fields +TEST(IDLChainedType, TestExtraFields) { + IDLParserErrorContext ctxt("root"); + + auto testDoc = BSON("field1" + << "abc" + << "field2" + << 5 + << "field3" + << 123456); + + auto testStruct = Chained_struct_only::parse(ctxt, testDoc); + ASSERT_EQUALS(testStruct.getChainedType().getField1(), "abc"); + ASSERT_EQUALS(testStruct.getAnotherChainedType().getField2(), 5); +} + + +// Negative: demonstrate a struct with chained types with duplicate fields +TEST(IDLChainedType, TestDuplicateFields) { + IDLParserErrorContext ctxt("root"); + + auto testDoc = BSON("field1" + << "abc" + << "field2" + << 5 + << "field2" + << 123456); + + ASSERT_THROWS(Chained_struct_only::parse(ctxt, testDoc), UserException); +} + + +// Positive: demonstrate a struct with chained structs +TEST(IDLChainedType, TestChainedStruct) { + IDLParserErrorContext ctxt("root"); + + auto testDoc = BSON("anyField" << 123.456 << "objectField" << BSON("random" + << "pair") + << "field3" + << "abc"); + + auto testStruct = Chained_struct_mixed::parse(ctxt, testDoc); + + assert_same_types<decltype(testStruct.getChained_any_basic_type()), + const Chained_any_basic_type&>(); + assert_same_types<decltype(testStruct.getChained_object_basic_type()), + const Chained_object_basic_type&>(); + + ASSERT_EQUALS(testStruct.getField3(), "abc"); + + // Positive: Test we can roundtrip from the just parsed document + { + BSONObjBuilder builder; + testStruct.serialize(&builder); + auto loopbackDoc = builder.obj(); + + ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); + } +} + +// Negative: demonstrate a struct with chained structs and extra fields +TEST(IDLChainedType, TestChainedStructWithExtraFields) { + IDLParserErrorContext ctxt("root"); + + // Extra field + { + auto testDoc = BSON("field3" + << "abc" + << "anyField" + << 123.456 + << "objectField" + << BSON("random" + << "pair") + << "extraField" + << 787); + ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), UserException); + } + + + // Duplicate any + { + auto testDoc = BSON("field3" + << "abc" + << "anyField" + << 123.456 + << "objectField" + << BSON("random" + << "pair") + << "anyField" + << 787); + ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), UserException); + } + + // Duplicate object + { + auto testDoc = BSON("objectField" << BSON("fake" + << "thing") + << "field3" + << "abc" + << "anyField" + << 123.456 + << "objectField" + << BSON("random" + << "pair")); + ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), UserException); + } + + // Duplicate field3 + { + auto testDoc = BSON("field3" + << "abc" + << "anyField" + << 123.456 + << "objectField" + << BSON("random" + << "pair") + << "field3" + << "def"); + ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), UserException); + } +} + + +// Positive: demonstrate a struct with chained structs and types +TEST(IDLChainedType, TestChainedMixedStruct) { + IDLParserErrorContext ctxt("root"); + + auto testDoc = BSON("field1" + << "abc" + << "field2" + << 5 + << "stringField" + << "def" + << "field3" + << 456); + + auto testStruct = Chained_struct_type_mixed::parse(ctxt, testDoc); + + assert_same_types<decltype(testStruct.getChainedType()), const mongo::ChainedType&>(); + assert_same_types<decltype(testStruct.getAnotherChainedType()), + const mongo::AnotherChainedType&>(); + + ASSERT_EQUALS(testStruct.getChainedType().getField1(), "abc"); + ASSERT_EQUALS(testStruct.getAnotherChainedType().getField2(), 5); + ASSERT_EQUALS(testStruct.getChained_string_basic_type().getStringField(), "def"); + ASSERT_EQUALS(testStruct.getField3(), 456); + + // 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; + Chained_struct_type_mixed one_new; + ChainedType ct; + ct.setField1("abc"); + one_new.setChainedType(ct); + AnotherChainedType act; + act.setField2(5); + one_new.setAnotherChainedType(act); + one_new.setField3(456); + Chained_string_basic_type csbt; + csbt.setStringField("def"); + one_new.setChained_string_basic_type(csbt); + one_new.serialize(&builder); + + auto serializedDoc = builder.obj(); + ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); + } +} + } // namespace } // namespace mongo diff --git a/src/mongo/idl/idl_test_types.h b/src/mongo/idl/idl_test_types.h index 6e03d48b470..44e26c47912 100644 --- a/src/mongo/idl/idl_test_types.h +++ b/src/mongo/idl/idl_test_types.h @@ -113,4 +113,54 @@ private: std::vector<std::uint8_t> _vec; }; +/** + * Simple class that demonstrates the contract a class must implement to parse an IDL "chain" type + * from the IDL parser. + */ +class ChainedType { +public: + static ChainedType parse(const BSONObj& obj) { + ChainedType object; + object._str = obj["field1"].str(); + return object; + } + + void serialize(BSONObjBuilder* builder) const { + builder->append("field1", _str); + } + + StringData getField1() const { + return _str; + } + void setField1(StringData value) { + _str = value.toString(); + } + +private: + std::string _str; +}; + +class AnotherChainedType { +public: + static AnotherChainedType parse(const BSONObj& obj) { + AnotherChainedType object; + object._num = obj["field2"].numberLong(); + return object; + } + + void serialize(BSONObjBuilder* builder) const { + builder->append("field2", _num); + } + + std::int64_t getField2() const { + return _num; + } + void setField2(std::int64_t value) { + _num = value; + } + +private: + std::int64_t _num; +}; + } // namespace mongo diff --git a/src/mongo/idl/unittest.idl b/src/mongo/idl/unittest.idl index 7aecd720ad4..6b6337ed69a 100644 --- a/src/mongo/idl/unittest.idl +++ b/src/mongo/idl/unittest.idl @@ -97,6 +97,27 @@ types: ################################################################################################## # +# Test types used in parser chaining testing +# +################################################################################################## + + ChainedType: + bson_serialization_type: chain + description: "An Chain Type to test chaining" + cpp_type: "mongo::ChainedType" + serializer: mongo::ChainedType::serialize + deserializer: mongo::ChainedType::parse + + AnotherChainedType: + bson_serialization_type: chain + description: "Another Chain Type to test chaining" + cpp_type: "mongo::AnotherChainedType" + serializer: mongo::AnotherChainedType::serialize + deserializer: mongo::AnotherChainedType::parse + + +################################################################################################## +# # Unit test structs for a single value to ensure type validation works correctly # ################################################################################################## @@ -368,4 +389,55 @@ structs: field6o: type: array<one_string> optional: true -# + +################################################################################################## +# +# Test Chained Types +# +################################################################################################## + + chained_string_basic_type: + description: Base struct type for a chained string + strict: false + fields: + stringField: string + + chained_any_basic_type: + description: Base struct type for a chained any + strict: false + fields: + anyField: any_basic_type + + chained_object_basic_type: + description: Base struct type for a chained object + strict: false + fields: + objectField: object_basic_type + + chained_struct_only: + description: UnitTest for chained struct with only chained types + strict: false + chained_types: + - ChainedType + - AnotherChainedType + + chained_struct_mixed: + description: Chained struct with chained structs and fields + strict: true + chained_structs: + - chained_any_basic_type + - chained_object_basic_type + fields: + field3: string + + chained_struct_type_mixed: + description: Chained struct with chained types, structs, and fields + strict: false + chained_types: + - ChainedType + - AnotherChainedType + chained_structs: + - chained_string_basic_type + fields: + field3: + type: int |