diff --git a/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp b/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp index a8a8cf617..8e8bb15f5 100644 --- a/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp +++ b/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp @@ -153,6 +153,20 @@ node_type_matches_value_type(SchemaTree::Node::Type type, Value const& value) -> KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs ) -> bool; +/** + * @param node_id_value_pairs + * @param schema_tree + * @return A result containing a bitmap where every bit corresponds to the ID of a node in the + * schema tree, and the set bits correspond to the nodes in the subtree defined by all paths from + * the root node to the nodes in `node_id_value_pairs`; or an error code indicating a failure: + * - std::errc::result_out_of_range if a node ID in `node_id_value_pairs` doesn't exist in the + * schema tree. + */ +[[nodiscard]] auto get_schema_subtree_bitmap( + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs, + SchemaTree const& schema_tree +) -> OUTCOME_V2_NAMESPACE::std_result>; + /** * Inserts the given key-value pair into the JSON object (map). * @param node The schema tree node of the key to insert. @@ -175,6 +189,34 @@ node_type_matches_value_type(SchemaTree::Node::Type type, Value const& value) -> */ [[nodiscard]] auto decode_as_encoded_text_ast(Value const& val) -> std::optional; +/** + * Serializes the given node-ID-value pairs into a `nlohmann::json` object. + * @param schema_tree + * @param node_id_value_pairs + * @param schema_subtree_bitmap + * @return A result containing the serialized JSON object or an error code indicating the failure: + * - std::errc::protocol_error if a value in the log event couldn't be decoded, or it couldn't be + * inserted into a JSON object. + */ +[[nodiscard]] auto serialize_node_id_value_pairs_to_json( + SchemaTree const& schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs, + vector const& schema_subtree_bitmap +) -> OUTCOME_V2_NAMESPACE::std_result; + +/** + * @param node A non-root schema tree node. + * @param parent_node_id_to_key_names + * @return true if `node`'s key is unique among its sibling nodes with `parent_node_id_to_key_names` + * updated to keep track of this unique key name. + * @return false if a sibling of `node` has the same key. + */ +[[nodiscard]] auto check_key_uniqueness_among_sibling_nodes( + SchemaTree::Node const& node, + std::unordered_map>& + parent_node_id_to_key_names +) -> bool; + auto node_type_matches_value_type(SchemaTree::Node::Type type, Value const& value) -> bool { switch (type) { case SchemaTree::Node::Type::Obj: @@ -202,6 +244,7 @@ auto validate_node_id_value_pairs( try { std::unordered_map> parent_node_id_to_key_names; + std::vector key_duplication_checked_node_id_bitmap(schema_tree.get_size(), false); for (auto const& [node_id, value] : node_id_value_pairs) { auto const& node{schema_tree.get_node(node_id)}; if (node.is_root()) { @@ -226,20 +269,38 @@ auto validate_node_id_value_pairs( return std::errc::operation_not_permitted; } - // We checked that the node isn't the root above, so we can query the underlying ID - // safely without a repeated check. - auto const parent_node_id{node.get_parent_id_unsafe()}; - auto const key_name{node.get_key_name()}; - if (parent_node_id_to_key_names.contains(parent_node_id)) { - auto const [it, new_key_inserted]{ - parent_node_id_to_key_names.at(parent_node_id).emplace(key_name) - }; - if (false == new_key_inserted) { - // The key is duplicated under the same parent + if (false + == check_key_uniqueness_among_sibling_nodes(node, parent_node_id_to_key_names)) + { + return std::errc::protocol_not_supported; + } + + // Iteratively check if there's any key duplication in the node's ancestors until: + // 1. The ancestor has already been checked. We only need to check an ancestor node + // once since if there are key duplications among its siblings, it would've been + // caught when the sibling was first checked (the order in which siblings get checked + // doesn't affect the results). + // 2. We reach the root node. + auto next_ancestor_node_id_to_check{node.get_parent_id_unsafe()}; + while (false == key_duplication_checked_node_id_bitmap[next_ancestor_node_id_to_check]) + { + auto const& node_to_check{schema_tree.get_node(next_ancestor_node_id_to_check)}; + if (node_to_check.is_root()) { + key_duplication_checked_node_id_bitmap[node_to_check.get_id()] = true; + break; + } + + if (false + == check_key_uniqueness_among_sibling_nodes( + node_to_check, + parent_node_id_to_key_names + )) + { return std::errc::protocol_not_supported; } - } else { - parent_node_id_to_key_names.emplace(parent_node_id, std::unordered_set{key_name}); + + key_duplication_checked_node_id_bitmap[next_ancestor_node_id_to_check] = true; + next_ancestor_node_id_to_check = node_to_check.get_parent_id_unsafe(); } } } catch (SchemaTree::OperationFailed const& ex) { @@ -269,6 +330,38 @@ auto is_leaf_node( return true; } +auto get_schema_subtree_bitmap( + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs, + SchemaTree const& schema_tree +) -> OUTCOME_V2_NAMESPACE::std_result> { + vector schema_subtree_bitmap(schema_tree.get_size(), false); + for (auto const& [node_id, val] : node_id_value_pairs) { + if (node_id >= schema_subtree_bitmap.size()) { + return std::errc::result_out_of_range; + } + schema_subtree_bitmap[node_id] = true; + + // Iteratively mark the parents as true + auto optional_parent_id{schema_tree.get_node(node_id).get_parent_id()}; + while (true) { + // Ideally, we'd use this if statement as the loop condition, but clang-tidy will + // complain about an unchecked `optional` access. + if (false == optional_parent_id.has_value()) { + // Reached the root + break; + } + auto const parent_id{optional_parent_id.value()}; + if (schema_subtree_bitmap[parent_id]) { + // Parent already set by other child + break; + } + schema_subtree_bitmap[parent_id] = true; + optional_parent_id = schema_tree.get_node(parent_id).get_parent_id(); + } + } + return schema_subtree_bitmap; +} + auto insert_kv_pair_into_json_obj( SchemaTree::Node const& node, std::optional const& optional_val, @@ -332,54 +425,13 @@ auto decode_as_encoded_text_ast(Value const& val) -> std::optional { ? val.get_immutable_view().decode_and_unparse() : val.get_immutable_view().decode_and_unparse(); } -} // namespace - -auto KeyValuePairLogEvent::create( - std::shared_ptr schema_tree, - NodeIdValuePairs node_id_value_pairs, - UtcOffset utc_offset -) -> OUTCOME_V2_NAMESPACE::std_result { - if (auto const ret_val{validate_node_id_value_pairs(*schema_tree, node_id_value_pairs)}; - std::errc{} != ret_val) - { - return ret_val; - } - return KeyValuePairLogEvent{std::move(schema_tree), std::move(node_id_value_pairs), utc_offset}; -} - -auto KeyValuePairLogEvent::get_schema_subtree_bitmap( -) const -> OUTCOME_V2_NAMESPACE::std_result> { - auto schema_subtree_bitmap{vector(m_schema_tree->get_size(), false)}; - for (auto const& [node_id, val] : m_node_id_value_pairs) { - if (node_id >= schema_subtree_bitmap.size()) { - return std::errc::result_out_of_range; - } - schema_subtree_bitmap[node_id] = true; - - // Iteratively mark the parents as true - auto optional_parent_id{m_schema_tree->get_node(node_id).get_parent_id()}; - while (true) { - // Ideally, we'd use this if statement as the loop condition, but clang-tidy will - // complain about an unchecked `optional` access. - if (false == optional_parent_id.has_value()) { - // Reached the root - break; - } - auto const parent_id{optional_parent_id.value()}; - if (schema_subtree_bitmap[parent_id]) { - // Parent already set by other child - break; - } - schema_subtree_bitmap[parent_id] = true; - optional_parent_id = m_schema_tree->get_node(parent_id).get_parent_id(); - } - } - return schema_subtree_bitmap; -} -auto KeyValuePairLogEvent::serialize_to_json( -) const -> OUTCOME_V2_NAMESPACE::std_result { - if (m_node_id_value_pairs.empty()) { +auto serialize_node_id_value_pairs_to_json( + SchemaTree const& schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs, + vector const& schema_subtree_bitmap +) -> OUTCOME_V2_NAMESPACE::std_result { + if (node_id_value_pairs.empty()) { return nlohmann::json::object(); } @@ -393,12 +445,6 @@ auto KeyValuePairLogEvent::serialize_to_json( // vector grows). std::stack dfs_stack; - auto const schema_subtree_bitmap_ret{get_schema_subtree_bitmap()}; - if (schema_subtree_bitmap_ret.has_error()) { - return schema_subtree_bitmap_ret.error(); - } - auto const& schema_subtree_bitmap{schema_subtree_bitmap_ret.value()}; - // Traverse the schema tree in DFS order, but only traverse the nodes that are set in // `schema_subtree_bitmap`. // @@ -408,7 +454,7 @@ auto KeyValuePairLogEvent::serialize_to_json( // // On the way up, add the current node's `nlohmann::json::object_t` to the parent's // `nlohmann::json::object_t`. - auto const& root_schema_tree_node{m_schema_tree->get_root()}; + auto const& root_schema_tree_node{schema_tree.get_root()}; auto root_json_obj = nlohmann::json::object_t(); dfs_stack.emplace( @@ -424,13 +470,13 @@ auto KeyValuePairLogEvent::serialize_to_json( continue; } auto const child_schema_tree_node_id{top.get_next_child_schema_tree_node()}; - auto const& child_schema_tree_node{m_schema_tree->get_node(child_schema_tree_node_id)}; - if (m_node_id_value_pairs.contains(child_schema_tree_node_id)) { + auto const& child_schema_tree_node{schema_tree.get_node(child_schema_tree_node_id)}; + if (node_id_value_pairs.contains(child_schema_tree_node_id)) { // Handle leaf node if (false == insert_kv_pair_into_json_obj( child_schema_tree_node, - m_node_id_value_pairs.at(child_schema_tree_node_id), + node_id_value_pairs.at(child_schema_tree_node_id), top.get_json_obj() )) { @@ -452,4 +498,109 @@ auto KeyValuePairLogEvent::serialize_to_json( return root_json_obj; } + +auto check_key_uniqueness_among_sibling_nodes( + SchemaTree::Node const& node, + std::unordered_map>& + parent_node_id_to_key_names +) -> bool { + // The caller checks that the given node is not the root, so we can query the underlying + // parent ID safely without a check. + auto const parent_node_id{node.get_parent_id_unsafe()}; + auto const key_name{node.get_key_name()}; + auto const parent_node_id_to_key_names_it{parent_node_id_to_key_names.find(parent_node_id)}; + if (parent_node_id_to_key_names_it != parent_node_id_to_key_names.end()) { + auto const [it, new_key_inserted]{parent_node_id_to_key_names_it->second.emplace(key_name)}; + if (false == new_key_inserted) { + // The key is duplicated under the same parent + return false; + } + } else { + parent_node_id_to_key_names.emplace(parent_node_id, std::unordered_set{key_name}); + } + return true; +} +} // namespace + +auto KeyValuePairLogEvent::create( + std::shared_ptr auto_gen_keys_schema_tree, + std::shared_ptr user_gen_keys_schema_tree, + NodeIdValuePairs auto_gen_node_id_value_pairs, + NodeIdValuePairs user_gen_node_id_value_pairs, + UtcOffset utc_offset +) -> OUTCOME_V2_NAMESPACE::std_result { + if (nullptr == auto_gen_keys_schema_tree || nullptr == user_gen_keys_schema_tree) { + return std::errc::invalid_argument; + } + + if (auto const ret_val{validate_node_id_value_pairs( + *auto_gen_keys_schema_tree, + auto_gen_node_id_value_pairs + )}; + std::errc{} != ret_val) + { + return ret_val; + } + + if (auto const ret_val{validate_node_id_value_pairs( + *user_gen_keys_schema_tree, + user_gen_node_id_value_pairs + )}; + std::errc{} != ret_val) + { + return ret_val; + } + + return KeyValuePairLogEvent{ + std::move(auto_gen_keys_schema_tree), + std::move(user_gen_keys_schema_tree), + std::move(auto_gen_node_id_value_pairs), + std::move(user_gen_node_id_value_pairs), + utc_offset + }; +} + +auto KeyValuePairLogEvent::get_auto_gen_keys_schema_subtree_bitmap( +) const -> OUTCOME_V2_NAMESPACE::std_result> { + return get_schema_subtree_bitmap(m_auto_gen_node_id_value_pairs, *m_auto_gen_keys_schema_tree); +} + +auto KeyValuePairLogEvent::get_user_gen_keys_schema_subtree_bitmap( +) const -> outcome_v2::std_result> { + return get_schema_subtree_bitmap(m_user_gen_node_id_value_pairs, *m_user_gen_keys_schema_tree); +} + +auto KeyValuePairLogEvent::serialize_to_json( +) const -> OUTCOME_V2_NAMESPACE::std_result> { + auto const auto_gen_keys_schema_subtree_bitmap_result{get_auto_gen_keys_schema_subtree_bitmap() + }; + if (auto_gen_keys_schema_subtree_bitmap_result.has_error()) { + return auto_gen_keys_schema_subtree_bitmap_result.error(); + } + auto serialized_auto_gen_kv_pairs_result{serialize_node_id_value_pairs_to_json( + *m_auto_gen_keys_schema_tree, + m_auto_gen_node_id_value_pairs, + auto_gen_keys_schema_subtree_bitmap_result.value() + )}; + if (serialized_auto_gen_kv_pairs_result.has_error()) { + return serialized_auto_gen_kv_pairs_result.error(); + } + + auto const user_gen_keys_schema_subtree_bitmap_result{get_user_gen_keys_schema_subtree_bitmap() + }; + if (user_gen_keys_schema_subtree_bitmap_result.has_error()) { + return user_gen_keys_schema_subtree_bitmap_result.error(); + } + auto serialized_user_gen_kv_pairs_result{serialize_node_id_value_pairs_to_json( + *m_user_gen_keys_schema_tree, + m_user_gen_node_id_value_pairs, + user_gen_keys_schema_subtree_bitmap_result.value() + )}; + if (serialized_user_gen_kv_pairs_result.has_error()) { + return serialized_user_gen_kv_pairs_result.error(); + } + + return {std::move(serialized_auto_gen_kv_pairs_result.value()), + std::move(serialized_user_gen_kv_pairs_result.value())}; +} } // namespace clp::ffi diff --git a/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp b/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp index f6334d378..2929c7498 100644 --- a/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp +++ b/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp @@ -17,10 +17,13 @@ namespace clp::ffi { /** * A log event containing key-value pairs. Each event contains: - * - A collection of node-ID & value pairs, where each pair represents a leaf `SchemaTreeNode` in - * the `SchemaTree`. - * - A reference to the `SchemaTree` - * - The UTC offset of the current log event + * - A reference to the schema tree for auto-generated keys. + * - A reference to the schema tree for user-generated keys. + * - A collection of auto-generated node-ID & value pairs, where each pair represents a leaf + * `SchemaTree::Node` in the schema tree for auto-generated keys. + * - A collection of user-generated node-ID & value pairs, where each pair represents a leaf + * `SchemaTree::Node` in the schema tree for user-generated keys. + * - The UTC offset of the current log event. */ class KeyValuePairLogEvent { public: @@ -29,15 +32,21 @@ class KeyValuePairLogEvent { // Factory functions /** - * @param schema_tree - * @param node_id_value_pairs + * @param auto_gen_keys_schema_tree + * @param user_gen_keys_schema_tree + * @param auto_gen_node_id_value_pairs + * @param user_gen_node_id_value_pairs * @param utc_offset * @return A result containing the key-value pair log event or an error code indicating the - * failure. See `validate_node_id_value_pairs` for the possible error codes. + * failure: + * - std::errc::invalid_argument if any of the given schema tree pointers are null. + * - Forwards `validate_node_id_value_pairs`'s return values. */ [[nodiscard]] static auto create( - std::shared_ptr schema_tree, - NodeIdValuePairs node_id_value_pairs, + std::shared_ptr auto_gen_keys_schema_tree, + std::shared_ptr user_gen_keys_schema_tree, + NodeIdValuePairs auto_gen_node_id_value_pairs, + NodeIdValuePairs user_gen_node_id_value_pairs, UtcOffset utc_offset ) -> OUTCOME_V2_NAMESPACE::std_result; @@ -53,51 +62,77 @@ class KeyValuePairLogEvent { ~KeyValuePairLogEvent() = default; // Methods - [[nodiscard]] auto get_schema_tree() const -> SchemaTree const& { return *m_schema_tree; } + [[nodiscard]] auto get_auto_gen_keys_schema_tree() const -> SchemaTree const& { + return *m_auto_gen_keys_schema_tree; + } - [[nodiscard]] auto get_node_id_value_pairs() const -> NodeIdValuePairs const& { - return m_node_id_value_pairs; + [[nodiscard]] auto get_user_gen_keys_schema_tree() const -> SchemaTree const& { + return *m_user_gen_keys_schema_tree; } - [[nodiscard]] auto get_utc_offset() const -> UtcOffset { return m_utc_offset; } + [[nodiscard]] auto get_auto_gen_node_id_value_pairs() const -> NodeIdValuePairs const& { + return m_auto_gen_node_id_value_pairs; + } + + [[nodiscard]] auto get_user_gen_node_id_value_pairs() const -> NodeIdValuePairs const& { + return m_user_gen_node_id_value_pairs; + } /** * @return A result containing a bitmap where every bit corresponds to the ID of a node in the - * schema tree, and the set bits correspond to the nodes in the subtree defined by all paths - * from the root node to the nodes in `node_id_value_pairs`; or an error code indicating a - * failure: - * - std::errc::result_out_of_range if a node ID in `node_id_value_pairs` doesn't exist in the - * schema tree. + * schema tree for auto-generated keys, and the set bits correspond to the nodes in the subtree + * defined by all paths from the root node to the nodes in `m_auto_gen_node_id_value_pairs`; or + * an error code indicating a failure: + * - Forwards `get_schema_subtree_bitmap`'s return values. */ - [[nodiscard]] auto get_schema_subtree_bitmap( + [[nodiscard]] auto get_auto_gen_keys_schema_subtree_bitmap( ) const -> OUTCOME_V2_NAMESPACE::std_result>; /** - * Serializes the log event into a `nlohmann::json` object. - * @return A result containing the serialized JSON object or an error code indicating the - * failure: - * - std::errc::protocol_error if a value in the log event couldn't be decoded or it couldn't be - * inserted into a JSON object. - * - std::errc::result_out_of_range if a node ID in the log event doesn't exist in the schema - * tree. + * @return A result containing a bitmap where every bit corresponds to the ID of a node in the + * schema tree for user-generated keys, and the set bits correspond to the nodes in the subtree + * defined by all paths from the root node to the nodes in `m_user_gen_node_id_value_pairs`; or + * an error code indicating a failure: + * - Forwards `get_schema_subtree_bitmap`'s return values. + */ + [[nodiscard]] auto get_user_gen_keys_schema_subtree_bitmap( + ) const -> OUTCOME_V2_NAMESPACE::std_result>; + + [[nodiscard]] auto get_utc_offset() const -> UtcOffset { return m_utc_offset; } + + /** + * Serializes the log event into `nlohmann::json` objects. + * @return A result containing a pair or an error code indicating the failure: + * - The pair: + * - Serialized auto-generated key-value pairs as a JSON object + * - Serialized user-generated key-value pairs as a JSON object + * - The possible error codes: + * - Forwards `get_auto_gen_keys_schema_subtree_bitmap`'s return values on failure. + * - Forwards `serialize_node_id_value_pairs_to_json`'s return values on failure. */ [[nodiscard]] auto serialize_to_json( - ) const -> OUTCOME_V2_NAMESPACE::std_result; + ) const -> OUTCOME_V2_NAMESPACE::std_result>; private: // Constructor KeyValuePairLogEvent( - std::shared_ptr schema_tree, - NodeIdValuePairs node_id_value_pairs, + std::shared_ptr auto_gen_keys_schema_tree, + std::shared_ptr user_gen_keys_schema_tree, + NodeIdValuePairs auto_gen_node_id_value_pairs, + NodeIdValuePairs user_gen_node_id_value_pairs, UtcOffset utc_offset ) - : m_schema_tree{std::move(schema_tree)}, - m_node_id_value_pairs{std::move(node_id_value_pairs)}, + : m_auto_gen_keys_schema_tree{std::move(auto_gen_keys_schema_tree)}, + m_user_gen_keys_schema_tree{std::move(user_gen_keys_schema_tree)}, + m_auto_gen_node_id_value_pairs{std::move(auto_gen_node_id_value_pairs)}, + m_user_gen_node_id_value_pairs{std::move(user_gen_node_id_value_pairs)}, m_utc_offset{utc_offset} {} // Variables - std::shared_ptr m_schema_tree; - NodeIdValuePairs m_node_id_value_pairs; + std::shared_ptr m_auto_gen_keys_schema_tree; + std::shared_ptr m_user_gen_keys_schema_tree; + NodeIdValuePairs m_auto_gen_node_id_value_pairs; + NodeIdValuePairs m_user_gen_node_id_value_pairs; UtcOffset m_utc_offset{0}; }; } // namespace clp::ffi diff --git a/components/core/src/clp/ffi/SchemaTree.hpp b/components/core/src/clp/ffi/SchemaTree.hpp index 46494fa71..4efbbf81e 100644 --- a/components/core/src/clp/ffi/SchemaTree.hpp +++ b/components/core/src/clp/ffi/SchemaTree.hpp @@ -128,6 +128,8 @@ class SchemaTree { ~Node() = default; // Methods + [[nodiscard]] auto operator==(Node const& rhs) const -> bool = default; + [[nodiscard]] auto get_id() const -> id_t { return m_id; } [[nodiscard]] auto is_root() const -> bool { return false == m_parent_id.has_value(); } @@ -249,6 +251,10 @@ class SchemaTree { ~SchemaTree() = default; // Methods + [[nodiscard]] auto operator==(SchemaTree const& rhs) const -> bool { + return m_tree_nodes == rhs.m_tree_nodes; + } + [[nodiscard]] auto get_size() const -> size_t { return m_tree_nodes.size(); } [[nodiscard]] auto get_root() const -> Node const& { return m_tree_nodes[cRootId]; } diff --git a/components/core/src/clp/ffi/ir_stream/Deserializer.hpp b/components/core/src/clp/ffi/ir_stream/Deserializer.hpp index 3418a39ae..d31699cd2 100644 --- a/components/core/src/clp/ffi/ir_stream/Deserializer.hpp +++ b/components/core/src/clp/ffi/ir_stream/Deserializer.hpp @@ -115,7 +115,8 @@ class Deserializer { Deserializer(IrUnitHandler ir_unit_handler) : m_ir_unit_handler{std::move(ir_unit_handler)} {} // Variables - std::shared_ptr m_schema_tree{std::make_shared()}; + std::shared_ptr m_auto_gen_keys_schema_tree{std::make_shared()}; + std::shared_ptr m_user_gen_keys_schema_tree{std::make_shared()}; UtcOffset m_utc_offset{0}; IrUnitHandler m_ir_unit_handler; bool m_is_complete{false}; @@ -183,9 +184,13 @@ auto Deserializer::deserialize_next_ir_unit(ReaderInterface& read auto const ir_unit_type{optional_ir_unit_type.value()}; switch (ir_unit_type) { case IrUnitType::LogEvent: { - auto result{ - deserialize_ir_unit_kv_pair_log_event(reader, tag, m_schema_tree, m_utc_offset) - }; + auto result{deserialize_ir_unit_kv_pair_log_event( + reader, + tag, + m_auto_gen_keys_schema_tree, + m_user_gen_keys_schema_tree, + m_utc_offset + )}; if (result.has_error()) { return result.error(); } @@ -207,7 +212,7 @@ auto Deserializer::deserialize_next_ir_unit(ReaderInterface& read } auto const node_locator{result.value()}; - if (m_schema_tree->has_node(node_locator)) { + if (m_user_gen_keys_schema_tree->has_node(node_locator)) { return std::errc::protocol_error; } @@ -217,7 +222,7 @@ auto Deserializer::deserialize_next_ir_unit(ReaderInterface& read return ir_error_code_to_errc(err); } - std::ignore = m_schema_tree->insert_node(node_locator); + std::ignore = m_user_gen_keys_schema_tree->insert_node(node_locator); break; } diff --git a/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp index 5e1813a3e..cea4a1b84 100644 --- a/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp +++ b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp @@ -551,7 +551,8 @@ auto deserialize_ir_unit_utc_offset_change(ReaderInterface& reader auto deserialize_ir_unit_kv_pair_log_event( ReaderInterface& reader, encoded_tag_t tag, - std::shared_ptr schema_tree, + std::shared_ptr auto_gen_keys_schema_tree, + std::shared_ptr user_gen_keys_schema_tree, UtcOffset utc_offset ) -> OUTCOME_V2_NAMESPACE::std_result { auto const schema_result{deserialize_schema(reader, tag)}; @@ -579,7 +580,9 @@ auto deserialize_ir_unit_kv_pair_log_event( } return KeyValuePairLogEvent::create( - std::move(schema_tree), + std::move(auto_gen_keys_schema_tree), + std::move(user_gen_keys_schema_tree), + {}, std::move(node_id_value_pairs), utc_offset ); diff --git a/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp index 68ed4408b..451f627db 100644 --- a/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp +++ b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp @@ -57,10 +57,12 @@ namespace clp::ffi::ir_stream { * Deserializes a key-value pair log event IR unit. * @param reader * @param tag - * @param schema_tree Schema tree used to construct the KV-pair log event. + * @param auto_gen_keys_schema_tree Schema tree for auto-generated keys, used to construct the + * KV-pair log event. + * @param user_gen_keys_schema_tree Schema tree for user-generated keys, used to construct the + * KV-pair log event. * @param utc_offset UTC offset used to construct the KV-pair log event. - * @return A result containing the deserialized log event or an error code indicating the - * failure: + * @return A result containing the deserialized log event or an error code indicating the failure: * - std::errc::result_out_of_range if the IR stream is truncated. * - std::errc::protocol_error if the IR stream is corrupted. * - std::errc::protocol_not_supported if the IR stream contains an unsupported metadata format @@ -72,7 +74,8 @@ namespace clp::ffi::ir_stream { [[nodiscard]] auto deserialize_ir_unit_kv_pair_log_event( ReaderInterface& reader, encoded_tag_t tag, - std::shared_ptr schema_tree, + std::shared_ptr auto_gen_keys_schema_tree, + std::shared_ptr user_gen_keys_schema_tree, UtcOffset utc_offset ) -> OUTCOME_V2_NAMESPACE::std_result; } // namespace clp::ffi::ir_stream diff --git a/components/core/tests/test-ffi_IrUnitHandlerInterface.cpp b/components/core/tests/test-ffi_IrUnitHandlerInterface.cpp index 5b8ad82cd..8f76a2f1a 100644 --- a/components/core/tests/test-ffi_IrUnitHandlerInterface.cpp +++ b/components/core/tests/test-ffi_IrUnitHandlerInterface.cpp @@ -87,9 +87,13 @@ auto test_ir_unit_handler_interface(clp::ffi::ir_stream::IrUnitHandlerInterface auto test_ir_unit_handler_interface(clp::ffi::ir_stream::IrUnitHandlerInterface auto& handler ) -> void { - auto test_log_event_result{ - KeyValuePairLogEvent::create(std::make_shared(), {}, cTestUtcOffset) - }; + auto test_log_event_result{KeyValuePairLogEvent::create( + std::make_shared(), + std::make_shared(), + {}, + {}, + cTestUtcOffset + )}; REQUIRE( (false == test_log_event_result.has_error() && IRErrorCode::IRErrorCode_Success @@ -127,7 +131,7 @@ TEMPLATE_TEST_CASE( REQUIRE( (optional_log_event.has_value() && optional_log_event.value().get_utc_offset() == cTestUtcOffset - && optional_log_event.value().get_node_id_value_pairs().empty()) + && optional_log_event.value().get_user_gen_node_id_value_pairs().empty()) ); auto const& optional_schema_tree_locator{handler.get_schema_tree_node_locator()}; REQUIRE( diff --git a/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp b/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp index 2e9cfb691..9ffee4f68 100644 --- a/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp +++ b/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "../src/clp/ffi/encoding_methods.hpp" #include "../src/clp/ffi/KeyValuePairLogEvent.hpp" @@ -81,6 +82,25 @@ auto insert_invalid_node_id_value_pairs_with_node_type_errors( KeyValuePairLogEvent::NodeIdValuePairs& invalid_node_id_value_pairs ) -> void; +/** + * Asserts that `KeyValuePairLogEvent` creation fails with the expected error code. + * @param auto_gen_keys_schema_tree + * @param user_gen_keys_schema_tree + * @param auto_gen_node_id_value_pairs + * @param user_gen_node_id_value_pairs + * @param utc_offset + * @param expected_error_code + * @return Whether the assertion succeeded. + */ +[[nodiscard]] auto assert_kv_pair_log_event_creation_failure( + std::shared_ptr auto_gen_keys_schema_tree, + std::shared_ptr user_gen_keys_schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs auto_gen_node_id_value_pairs, + KeyValuePairLogEvent::NodeIdValuePairs user_gen_node_id_value_pairs, + UtcOffset utc_offset, + std::errc expected_error_code +) -> bool; + template requires(std::is_same_v || std::is_same_v) @@ -197,6 +217,24 @@ auto insert_invalid_node_id_value_pairs_with_node_type_errors( invalid_node_id_value_pairs.emplace(node_id, Value{}); } } + +auto assert_kv_pair_log_event_creation_failure( + std::shared_ptr auto_gen_keys_schema_tree, + std::shared_ptr user_gen_keys_schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs auto_gen_node_id_value_pairs, + KeyValuePairLogEvent::NodeIdValuePairs user_gen_node_id_value_pairs, + UtcOffset utc_offset, + std::errc expected_error_code +) -> bool { + auto const result{KeyValuePairLogEvent::create( + std::move(auto_gen_keys_schema_tree), + std::move(user_gen_keys_schema_tree), + std::move(auto_gen_node_id_value_pairs), + std::move(user_gen_node_id_value_pairs), + utc_offset + )}; + return result.has_error() && result.error() == expected_error_code; +} } // namespace TEST_CASE("ffi_Value_basic", "[ffi][Value]") { @@ -250,22 +288,23 @@ TEST_CASE("ffi_KeyValuePairLogEvent_create", "[ffi]") { * | * |------------> <1:a:Obj> * | | - * |--> <2:a:Int> |--> <3:b:Obj> - * | - * |------------> <4:c:Obj> - * | | - * |--> <5:d:Str> |--> <7:a:UnstructuredArray> - * | | - * |--> <6:d:Bool> |--> <8:d:Str> - * | | - * |--> <10:e:Obj> |--> <9:d:Float> - * | - * |--> <11:f:Obj> + * |--> <2:b:Int> |--> <3:b:Obj> + * | | | + * |--> <12:a:Int> | |------------> <4:c:Obj> + * | | | + * | |--> <5:d:Str> |--> <7:a:UnstructuredArray> + * | | | + * | |--> <6:d:Bool> |--> <8:d:Str> + * | | | + * | |--> <10:e:Obj> |--> <9:d:Float> + * | | + * |--> <13:b:Bool> |--> <11:f:Obj> */ - auto const schema_tree{std::make_shared()}; + auto const auto_gen_keys_schema_tree{std::make_shared()}; + auto const user_gen_keys_schema_tree{std::make_shared()}; std::vector const locators{ {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj}, - {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int}, + {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int}, {1, "b", SchemaTree::Node::Type::Obj}, {3, "c", SchemaTree::Node::Type::Obj}, {3, "d", SchemaTree::Node::Type::Str}, @@ -274,63 +313,88 @@ TEST_CASE("ffi_KeyValuePairLogEvent_create", "[ffi]") { {4, "d", SchemaTree::Node::Type::Str}, {4, "d", SchemaTree::Node::Type::Float}, {3, "e", SchemaTree::Node::Type::Obj}, - {4, "f", SchemaTree::Node::Type::Obj} + {4, "f", SchemaTree::Node::Type::Obj}, + {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int}, + {1, "b", SchemaTree::Node::Type::Bool} }; for (auto const& locator : locators) { - REQUIRE_NOTHROW(schema_tree->insert_node(locator)); + REQUIRE_NOTHROW(auto_gen_keys_schema_tree->insert_node(locator)); + REQUIRE_NOTHROW(user_gen_keys_schema_tree->insert_node(locator)); } + REQUIRE((*auto_gen_keys_schema_tree == *user_gen_keys_schema_tree)); + SECTION("Test empty ID-value pairs") { - KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs; auto const result{KeyValuePairLogEvent::create( - schema_tree, - std::move(node_id_value_pairs), + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + {}, + {}, UtcOffset{0} )}; REQUIRE_FALSE(result.has_error()); } + SECTION("Test schema tree pointers being null") { + REQUIRE(assert_kv_pair_log_event_creation_failure( + nullptr, + user_gen_keys_schema_tree, + {}, + {}, + UtcOffset{0}, + std::errc::invalid_argument + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + nullptr, + {}, + {}, + UtcOffset{0}, + std::errc::invalid_argument + )); + } + SECTION("Test mismatched types") { KeyValuePairLogEvent::NodeIdValuePairs invalid_node_id_value_pairs; // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) // Int: insert_invalid_node_id_value_pairs_with_node_type_errors( - *schema_tree, + *user_gen_keys_schema_tree, 2, invalid_node_id_value_pairs ); // Float: insert_invalid_node_id_value_pairs_with_node_type_errors( - *schema_tree, + *user_gen_keys_schema_tree, 9, invalid_node_id_value_pairs ); // Bool: insert_invalid_node_id_value_pairs_with_node_type_errors( - *schema_tree, + *user_gen_keys_schema_tree, 6, invalid_node_id_value_pairs ); // Str: insert_invalid_node_id_value_pairs_with_node_type_errors( - *schema_tree, + *user_gen_keys_schema_tree, 5, invalid_node_id_value_pairs ); // UnstructuredArray: insert_invalid_node_id_value_pairs_with_node_type_errors( - *schema_tree, + *user_gen_keys_schema_tree, 7, invalid_node_id_value_pairs ); // Obj: insert_invalid_node_id_value_pairs_with_node_type_errors( - *schema_tree, + *user_gen_keys_schema_tree, 3, invalid_node_id_value_pairs ); @@ -343,26 +407,37 @@ TEST_CASE("ffi_KeyValuePairLogEvent_create", "[ffi]") { } else { node_id_value_pair_to_test.emplace(node_id, std::nullopt); } - auto const result{KeyValuePairLogEvent::create( - schema_tree, - std::move(node_id_value_pair_to_test), - UtcOffset{0} - )}; - REQUIRE(result.has_error()); - auto const& err{result.error()}; - REQUIRE((std::errc::protocol_error == err)); + + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + node_id_value_pair_to_test, + {}, + UtcOffset{0}, + std::errc::protocol_error + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + {}, + node_id_value_pair_to_test, + UtcOffset{0}, + std::errc::protocol_error + )); } } SECTION("Test valid ID-value pairs") { - KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs; + constexpr std::string_view cJsonArrayToEncode{"[\"a\", 1, 0.1, null]"}; + constexpr std::string_view cStaticText{"Test"}; + KeyValuePairLogEvent::NodeIdValuePairs valid_node_id_value_pairs; /* * The sub schema tree of `node_id_value_pairs`: * <0:root:Obj> * | * |------------> <1:a:Obj> * | | - * |--> <2:a:Int> |--> <3:b:Obj> + * |--> <2:b:Int> |--> <3:b:Obj> * | * |------------> <4:c:Obj> * | | @@ -375,77 +450,206 @@ TEST_CASE("ffi_KeyValuePairLogEvent_create", "[ffi]") { * |--> <11:f:Obj> */ // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - node_id_value_pairs.emplace(2, Value{static_cast(0)}); - node_id_value_pairs.emplace(5, Value{string{"Test"}}); - node_id_value_pairs.emplace( + valid_node_id_value_pairs.emplace(2, Value{static_cast(0)}); + valid_node_id_value_pairs.emplace(5, Value{string{cStaticText}}); + valid_node_id_value_pairs.emplace( 8, Value{get_encoded_text_ast(cStringToEncode)} ); - node_id_value_pairs.emplace( + valid_node_id_value_pairs.emplace( 7, - Value{get_encoded_text_ast(cStringToEncode)} + Value{get_encoded_text_ast(cJsonArrayToEncode)} ); - node_id_value_pairs.emplace(10, Value{}); - node_id_value_pairs.emplace(11, std::nullopt); + valid_node_id_value_pairs.emplace(10, Value{}); + valid_node_id_value_pairs.emplace(11, std::nullopt); // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - auto const result{ - KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) - }; + auto const result{KeyValuePairLogEvent::create( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + valid_node_id_value_pairs, + valid_node_id_value_pairs, + UtcOffset{0} + )}; REQUIRE_FALSE(result.has_error()); - SECTION("Test duplicated key conflict on node #3") { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - node_id_value_pairs.emplace(6, Value{static_cast(false)}); - auto const result{ - KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + SECTION("Test JSON serialization") { + nlohmann::json const subtree_rooted_at_node_4 + = {{"a", nlohmann::json::parse(cJsonArrayToEncode)}, + {"d", cStringToEncode}, + {"f", nlohmann::json::object_t()}}; + nlohmann::json const subtree_rooted_at_node_3 + = {{"c", subtree_rooted_at_node_4}, {"d", cStaticText}, {"e", nullptr}}; + nlohmann::json const expected = { + {"a", {{"b", subtree_rooted_at_node_3}}}, + {"b", 0}, }; - REQUIRE(result.has_error()); - REQUIRE((std::errc::protocol_not_supported == result.error())); + + auto const& kv_pair_log_event{result.value()}; + auto const serialized_json_result{kv_pair_log_event.serialize_to_json()}; + REQUIRE_FALSE(serialized_json_result.has_error()); + auto const& [serialized_auto_gen_kv_pairs, serialized_user_gen_kv_pairs]{ + serialized_json_result.value() + }; + REQUIRE((serialized_auto_gen_kv_pairs == expected)); + REQUIRE((serialized_user_gen_kv_pairs == expected)); } - SECTION("Test duplicated key conflict on node #4") { + SECTION("Test duplicated key conflict under node #3") { + auto invalid_node_id_value_pairs{valid_node_id_value_pairs}; // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - node_id_value_pairs.emplace(9, Value{static_cast(0.0)}); - auto const result{ - KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) - }; - REQUIRE(result.has_error()); - REQUIRE((std::errc::protocol_not_supported == result.error())); + invalid_node_id_value_pairs.emplace(6, Value{static_cast(false)}); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + invalid_node_id_value_pairs, + valid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + valid_node_id_value_pairs, + invalid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); + } + + SECTION("Test duplicated key conflict under node #4") { + auto invalid_node_id_value_pairs{valid_node_id_value_pairs}; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + invalid_node_id_value_pairs.emplace(9, Value{static_cast(0.0)}); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + invalid_node_id_value_pairs, + valid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + valid_node_id_value_pairs, + invalid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); + } + + SECTION("Test duplicated keys among siblings of node #1") { + auto invalid_node_id_value_pairs{valid_node_id_value_pairs}; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + invalid_node_id_value_pairs.emplace(12, static_cast(0)); + // Node #12 has the same key as its sibling node #1 + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + invalid_node_id_value_pairs, + valid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + valid_node_id_value_pairs, + invalid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); + } + + SECTION("Test duplicated keys among siblings of node #3") { + auto invalid_node_id_value_pairs{valid_node_id_value_pairs}; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + invalid_node_id_value_pairs.emplace(13, false); + // Node #13 has the same key as its sibling node #3 + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + invalid_node_id_value_pairs, + valid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + valid_node_id_value_pairs, + invalid_node_id_value_pairs, + UtcOffset{0}, + std::errc::protocol_not_supported + )); } SECTION("Test invalid sub-tree on node #3") { - node_id_value_pairs.emplace(3, std::nullopt); - auto const result{ - KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) - }; + auto invalid_node_id_value_pairs{valid_node_id_value_pairs}; + invalid_node_id_value_pairs.emplace(3, std::nullopt); // Node #3 is empty, but its descendants appear in the sub schema tree (node #5 & #10) - REQUIRE(result.has_error()); - REQUIRE((std::errc::operation_not_permitted == result.error())); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + invalid_node_id_value_pairs, + valid_node_id_value_pairs, + UtcOffset{0}, + std::errc::operation_not_permitted + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + valid_node_id_value_pairs, + invalid_node_id_value_pairs, + UtcOffset{0}, + std::errc::operation_not_permitted + )); } SECTION("Test invalid sub-tree on node #4") { - node_id_value_pairs.emplace(4, Value{}); - auto const result{ - KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) - }; + auto invalid_node_id_value_pairs{valid_node_id_value_pairs}; + invalid_node_id_value_pairs.emplace(4, Value{}); // Node #4 is null, but its descendants appear in the sub schema tree (node #5 & #10) - REQUIRE(result.has_error()); - REQUIRE((std::errc::operation_not_permitted == result.error())); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + invalid_node_id_value_pairs, + valid_node_id_value_pairs, + UtcOffset{0}, + std::errc::operation_not_permitted + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + valid_node_id_value_pairs, + invalid_node_id_value_pairs, + UtcOffset{0}, + std::errc::operation_not_permitted + )); } } SECTION("Test out-of-bound node ID") { KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs_out_of_bound; node_id_value_pairs_out_of_bound.emplace( - static_cast(schema_tree->get_size()), + static_cast(user_gen_keys_schema_tree->get_size()), Value{} ); - auto const out_of_bound_result{KeyValuePairLogEvent::create( - schema_tree, - std::move(node_id_value_pairs_out_of_bound), - UtcOffset{0} - )}; - REQUIRE(out_of_bound_result.has_error()); - REQUIRE((std::errc::operation_not_permitted == out_of_bound_result.error())); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + node_id_value_pairs_out_of_bound, + {}, + UtcOffset{0}, + std::errc::operation_not_permitted + )); + REQUIRE(assert_kv_pair_log_event_creation_failure( + auto_gen_keys_schema_tree, + user_gen_keys_schema_tree, + {}, + node_id_value_pairs_out_of_bound, + UtcOffset{0}, + std::errc::operation_not_permitted + )); } } diff --git a/components/core/tests/test-ir_encoding_methods.cpp b/components/core/tests/test-ir_encoding_methods.cpp index 1ee1e3542..347dadb7a 100644 --- a/components/core/tests/test-ir_encoding_methods.cpp +++ b/components/core/tests/test-ir_encoding_methods.cpp @@ -1246,12 +1246,14 @@ TEMPLATE_TEST_CASE( auto const& deserialized_log_event{deserialized_log_events.at(idx)}; auto const num_leaves_in_json_obj{count_num_leaves(expect)}; - auto const num_kv_pairs{deserialized_log_event.get_node_id_value_pairs().size()}; + auto const num_kv_pairs{deserialized_log_event.get_user_gen_node_id_value_pairs().size()}; REQUIRE((num_leaves_in_json_obj == num_kv_pairs)); auto const serialized_json_result{deserialized_log_event.serialize_to_json()}; REQUIRE_FALSE(serialized_json_result.has_error()); - REQUIRE((expect == serialized_json_result.value())); + auto const& [auto_generated, user_generated]{serialized_json_result.value()}; + REQUIRE(auto_generated.empty()); + REQUIRE((expect == user_generated)); } auto const eof_result{deserializer.deserialize_next_ir_unit(reader)};