diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f3d96..3019535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,36 +1,37 @@ - [\[Unreleased\]](#unreleased) + - [Changed](#changed) - [\[0.13.0\] - 2024-02-01](#0130---2024-02-01) - [Added](#added) - - [Changed](#changed) + - [Changed](#changed-1) - [\[0.12.0\] - 2024-01-07](#0120---2024-01-07) - [Added](#added-1) - - [Changed](#changed-1) + - [Changed](#changed-2) - [\[0.11.0\] - 2024-01-02](#0110---2024-01-02) - [Added](#added-2) - - [Changed](#changed-2) + - [Changed](#changed-3) - [\[0.10.0\] - 2023-12-31](#0100---2023-12-31) - [Added](#added-3) - [\[0.9.1\] - 2023-12-12](#091---2023-12-12) - - [Changed](#changed-3) + - [Changed](#changed-4) - [Fixed](#fixed) - [\[0.9.0\] - 2023-12-12](#090---2023-12-12) - [Added](#added-4) - - [Changed](#changed-4) + - [Changed](#changed-5) - [\[0.8.0\] - 2023-12-11](#080---2023-12-11) - [Added](#added-5) - - [Changed](#changed-5) + - [Changed](#changed-6) - [Removed](#removed) - [\[0.7.0\] - 2023-10-27](#070---2023-10-27) - [Added](#added-6) - - [Changed](#changed-6) + - [Changed](#changed-7) - [\[0.6.0\] - 2023-10-11](#060---2023-10-11) - [Added](#added-7) - - [Changed](#changed-7) + - [Changed](#changed-8) - [\[0.5.0\] - 2023-10-09](#050---2023-10-09) - [Added](#added-8) - [\[0.4.0\] - 2023-09-28](#040---2023-09-28) - [Added](#added-9) - - [Changed](#changed-8) + - [Changed](#changed-9) - [Removed](#removed-1) - [\[0.3.0\] - 2023-09-26](#030---2023-09-26) - [Added](#added-10) @@ -42,6 +43,13 @@ ## [Unreleased] +### Changed + +- **(BREAKING)** Replace trasitions metadata `:ids_tree`, and `:ids_matrix` with `:ids` property. This property is a hash with the following keys: + - `:tree`, a graph/tree representation of the transitions ids. + - `:level_parent`, a hash with the level (depth) of each transition and its parent id. + - `:matrix`, a matrix representation of the transitions ids. It is a simplification of the `:tree` property. + ## [0.13.0] - 2024-02-01 ### Added diff --git a/README.md b/README.md index 59b0f07..004196a 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi - [`BCDD::Result::Context::Expectations`](#bcddresultcontextexpectations) - [Mixin add-ons](#mixin-add-ons) - [`BCDD::Result.transitions`](#bcddresulttransitions) - - [`ids_tree` *versus* `ids_matrix`](#ids_tree-versus-ids_matrix) + - [`metadata: {ids:}`](#metadata-ids) - [Configuration](#configuration) - [Turning on/off](#turning-onoff) - [Setting a `trace_id` fetcher](#setting-a-trace_id-fetcher) @@ -1860,8 +1860,11 @@ result.transitions :metadata => { :duration => 0, # milliseconds :trace_id => nil, # can be set through configuration - :ids_tree => [0, [[1, []], [2, []]]], - :ids_matrix => {0 => [0, 0], 1 => [1, 1], 2 => [2, 1]} + :ids => { + :tree => [0, [[1, []], [2, []]]], + :matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]}, + :level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]} + } }, :records=> [ { @@ -1942,18 +1945,21 @@ result.transitions

⬆️  back to top

-### `ids_tree` *versus* `ids_matrix` - -The `:ids_matrix`. It is a simplification of the `:ids_tree` property (a graph/tree representation of the transitions ids). +### `metadata: {ids:}` -The matrix rows are the direct transitions from the root transition block, and the columns are the transitions nested from the direct transitions. +The `:ids` metadata property is a hash with three properties: +- `:tree`, a graph/tree representation of the transitions ids. +- `:level_parent`, a hash with the level (depth) of each transition and its parent id. +- `:matrix`, a matrix representation of the transitions ids. It is a simplification of the `:tree` property. -Use these data structures to build your own visualization of the transitions. +Use these data structures to build your own visualization. > Check out [Transitions Listener example](examples/single_listener/lib/single_transitions_listener.rb) to see how a listener can be used to build a visualization of the transitions, using these properties. ```ruby -# ids_tree # +# tree: +# A graph representation (array of arrays) of the transitions ids. +# 0 # [0, [ |- 1 # [1, [[2, []]]], | |- 2 # [3, []], @@ -1964,7 +1970,24 @@ Use these data structures to build your own visualization of the transitions. | |- 7 # [8, []] |- 8 # ]] -# ids_matrix # { +# level_parent: +# Transition ids are the keys, and the level (depth) and parent id the values. + # { +0 # 0 => [0, 0], +|- 1 # 1 => [1, 0], +| |- 2 # 2 => [2, 1], +|- 3 # 3 => [1, 0], +|- 4 # 4 => [1, 0], +| |- 5 # 5 => [2, 4], +| |- 6 # 6 => [2, 4], +| |- 7 # 7 => [3, 6], +|- 8 # 8 => [1, 0] + # } + +# matrix: +# The rows are the direct transitions from the root transition block, +# and the columns are the nested transitions from the direct ones. + # { 0 | 1 | 2 | 3 | 4 # 0 => [0, 0], - | - | - | - | - # 1 => [1, 1], 0 | | | | # 2 => [1, 2], @@ -1994,8 +2017,14 @@ result = SumDivisionsByTwo.call(20, 10) # => # result.transitions - -{:version=>1, :records=>[], :metadata=>{:duration=>0, :ids_tree=>[]}} +{ + :version=>1, + :records=>[], + :metadata=>{ + :duration=>0, + :ids=>{:tree=>[], :matrix=>{}, :level_parent=>{}}, :trace_id=>nil + } +} ```

⬆️  back to top

@@ -2020,7 +2049,7 @@ Use it to build your additional logic on top of the transitions tracking. Exampl - Log the transitions. - Perform a trace of the transitions. - Instrument the transitions (measure/report). - - Build a visualization of the transitions (Diagrams, using the `records` + `:ids_tree` and `:ids_matrix` properties). + - Build a visualization of the transitions (Diagrams, using the `records` + `metadata: {ids:}` properties). After implementing your listener, you can set it to the `BCDD::Result.config.transitions.listener=`: @@ -2086,8 +2115,11 @@ class MyTransitionsListener # :metadata => { # :duration => 0, # :trace_id => nil, - # :ids_tree => [0, [[1, []], [2, []]]], - # :ids_matrix => {0 => [0, 0], 1 => [1, 1], 2 => [2, 1]} + # :ids => { + # :tree => [0, [[1, []], [2, []]]], + # :matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]}, + # :level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]} + # } # }, # :records => [ # # ... diff --git a/examples/multiple_listeners/db/setup.rb b/examples/multiple_listeners/db/setup.rb index d7cae10..70662c9 100644 --- a/examples/multiple_listeners/db/setup.rb +++ b/examples/multiple_listeners/db/setup.rb @@ -15,8 +15,7 @@ t.string :trace_id, index: true t.integer :version, null: false t.integer :duration, null: false, index: true - t.json :ids_tree, null: false, default: [] - t.json :ids_matrix, null: false, default: {} + t.json :ids, null: false, default: {} t.json :records, null: false, default: [] t.timestamps diff --git a/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb b/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb index 9784e7a..9818af3 100644 --- a/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb +++ b/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb @@ -15,8 +15,7 @@ def on_finish(transitions:) trace_id: metadata[:trace_id], version: transitions[:version], duration: metadata[:duration], - ids_tree: metadata[:ids_tree], - ids_matrix: metadata[:ids_matrix], + ids: metadata[:ids], records: transitions[:records] ) rescue ::StandardError => e diff --git a/examples/multiple_listeners/lib/runtime_breaker.rb b/examples/multiple_listeners/lib/runtime_breaker.rb index e29cae6..348d44b 100644 --- a/examples/multiple_listeners/lib/runtime_breaker.rb +++ b/examples/multiple_listeners/lib/runtime_breaker.rb @@ -6,6 +6,6 @@ module RuntimeBreaker def self.try_to_interrupt(env:) return unless String(ENV[env]).strip.start_with?(/1|t/) - raise Interruption, "Runtime breaker activated (#{env})" + raise Interruption, "#{env}" end end diff --git a/examples/multiple_listeners/lib/transitions_listener/stdout.rb b/examples/multiple_listeners/lib/transitions_listener/stdout.rb index 53f5587..dc2134a 100644 --- a/examples/multiple_listeners/lib/transitions_listener/stdout.rb +++ b/examples/multiple_listeners/lib/transitions_listener/stdout.rb @@ -22,9 +22,9 @@ def on_record(record:) end MapNestedMessages = ->(transitions, buffer, hide_given_and_continue) do - ids_matrix = transitions.dig(:metadata, :ids_matrix) + ids_level_parent = transitions.dig(:metadata, :ids, :level_parent) - messages = buffer.filter_map { |(id, msg)| "#{' ' * ids_matrix[id].last}#{msg}" if ids_matrix[id] } + messages = buffer.filter_map { |(id, msg)| "#{' ' * ids_level_parent[id].first}#{msg}" if ids_level_parent[id] } messages.reject! { _1.match?(/\(_(given|continue)_\)/) } if hide_given_and_continue @@ -47,8 +47,12 @@ def before_interruption(exception:, transitions:) bc.add_silencer { |line| /lib\/bcdd\/result/.match?(line) } bc.add_silencer { |line| line.include?(RUBY_VERSION) } - backtrace = bc.clean(exception.backtrace) + dir = "#{FileUtils.pwd[1..]}/" - puts "\nException: #{exception.message} (#{exception.class}); Backtrace: #{backtrace.join(", ")}" + cb = bc.clean(exception.backtrace) + cb.each { _1.sub!(dir, '') } + cb.reject! { _1.match?(/block \(\d levels?\) in|in `block in|internal:kernel/) } + + puts "\nException:\n #{exception.message} (#{exception.class})\n\nBacktrace:\n #{cb.join("\n ")}" end end diff --git a/examples/service_objects/app/services/application_service.rb b/examples/service_objects/app/services/application_service.rb index a25d908..5d7de67 100644 --- a/examples/service_objects/app/services/application_service.rb +++ b/examples/service_objects/app/services/application_service.rb @@ -14,7 +14,7 @@ def self.inherited(subclass) class << self def input=(klass) - const_defined?(:Input, false) and raise ArgumentError, 'Attributes class already defined' + const_defined?(:Input, false) and raise ArgumentError, "#{self}::Input class already defined" unless klass.is_a?(::Class) && klass < Input raise ArgumentError, 'must be a ApplicationService::Input subclass' diff --git a/examples/single_listener/lib/single_transitions_listener.rb b/examples/single_listener/lib/single_transitions_listener.rb index 17c51da..2d25f36 100644 --- a/examples/single_listener/lib/single_transitions_listener.rb +++ b/examples/single_listener/lib/single_transitions_listener.rb @@ -57,9 +57,9 @@ def on_record(record:) end MapNestedMessages = ->(transitions, buffer, hide_given_and_continue) do - ids_matrix = transitions.dig(:metadata, :ids_matrix) + ids_level_parent = transitions.dig(:metadata, :ids, :level_parent) - messages = buffer.filter_map { |(id, msg)| "#{' ' * ids_matrix[id].last}#{msg}" if ids_matrix[id] } + messages = buffer.filter_map { |(id, msg)| "#{' ' * ids_level_parent[id].first}#{msg}" if ids_level_parent[id] } messages.reject! { _1.match?(/\(_(given|continue)_\)/) } if hide_given_and_continue @@ -74,8 +74,11 @@ def on_record(record:) # :metadata => { # :duration => 0, # :trace_id => nil, - # :ids_tree => [0, [[1, []], [2, []]]], - # :ids_matrix => {0 => [0, 0], 1 => [1, 1], 2 => [2, 1]} + # :ids => { + # :tree => [0, [[1, []], [2, []]]], + # :matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]}, + # :level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]} + # } # }, # :records => [ # # ... diff --git a/lib/bcdd/result/transitions/tracking.rb b/lib/bcdd/result/transitions/tracking.rb index 2363983..ec4be82 100644 --- a/lib/bcdd/result/transitions/tracking.rb +++ b/lib/bcdd/result/transitions/tracking.rb @@ -11,10 +11,11 @@ module Tracking EMPTY_ARRAY = [].freeze EMPTY_HASH = {}.freeze EMPTY_TREE = Tree.new(nil).freeze + EMPTY_IDS = { tree: EMPTY_ARRAY, matrix: EMPTY_HASH, level_parent: EMPTY_HASH }.freeze EMPTY = { version: VERSION, records: EMPTY_ARRAY, - metadata: { duration: 0, ids_tree: EMPTY_ARRAY, ids_matrix: EMPTY_HASH, trace_id: nil }.freeze + metadata: { duration: 0, ids: EMPTY_IDS, trace_id: nil }.freeze }.freeze def self.instance diff --git a/lib/bcdd/result/transitions/tracking/enabled.rb b/lib/bcdd/result/transitions/tracking/enabled.rb index c256cc3..53740cf 100644 --- a/lib/bcdd/result/transitions/tracking/enabled.rb +++ b/lib/bcdd/result/transitions/tracking/enabled.rb @@ -141,7 +141,9 @@ def map_transitions trace_id = Config.instance.trace_id.call - metadata = { duration: duration, ids_tree: tree.nested_ids, ids_matrix: tree.ids_matrix, trace_id: trace_id } + ids = { tree: tree.ids, matrix: tree.ids_matrix, level_parent: tree.ids_level_parent } + + metadata = { duration: duration, trace_id: trace_id, ids: ids } { version: Tracking::VERSION, records: records, metadata: metadata } end diff --git a/lib/bcdd/result/transitions/tree.rb b/lib/bcdd/result/transitions/tree.rb index b1263ee..f2d142c 100644 --- a/lib/bcdd/result/transitions/tree.rb +++ b/lib/bcdd/result/transitions/tree.rb @@ -89,13 +89,17 @@ def move_to_root! move_to!(root) end - NestedIds = ->(node) { [node.id, node.children.map(&NestedIds)] } + Ids = ->(node) { [node.id, node.children.map(&Ids)] } - def nested_ids - NestedIds[root] + def ids + Ids[root] end - IdsMatrix = ->(tree, row, col, ids, previous) do + def ids_list + ids.flatten + end + + IdsMatrix = ->(tree, row, col, memo, previous) do last_row = previous[0] tree.each_with_index do |node, index| @@ -103,22 +107,34 @@ def nested_ids id, leaf = node - ids[id] = previous == [row, col] ? [row, col + 1] : [row, col] + memo[id] = previous == [row, col] ? [row, col + 1] : [row, col] - previous = ids[id] + previous = memo[id] - IdsMatrix[leaf, row, col + 1, ids, previous] + IdsMatrix[leaf, row, col + 1, memo, previous] end end def ids_matrix current = [0, 0] - ids = { 0 => current } + memo = { 0 => current } + + IdsMatrix[ids[1], 1, 1, memo, current] + + memo + end + + IdsLevelParent = ->((id, node), parent = 0, level = 0, memo = {}) do + memo[id] = [level, parent] - IdsMatrix[nested_ids[1], 1, 1, ids, current] + node.each { |leaf| IdsLevelParent[leaf, id, level + 1, memo] } + + memo + end - ids + def ids_level_parent + IdsLevelParent[ids] end end end diff --git a/sig/bcdd/result/transitions.rbs b/sig/bcdd/result/transitions.rbs index fa9530d..c03e31b 100644 --- a/sig/bcdd/result/transitions.rbs +++ b/sig/bcdd/result/transitions.rbs @@ -115,19 +115,21 @@ class BCDD::Result def move_down!: (?Integer level) -> Tree def move_to_root!: () -> Tree - NestedIds: ^(Node) -> Array[untyped] - - def nested_ids: () -> Array[untyped] - + Ids: ^(Node) -> Array[untyped] IdsMatrix: ::Proc + IdsLevelParent: ::Proc + def ids: () -> Array[untyped] + def ids_list: () -> Array[Integer] def ids_matrix: () -> untyped + def ids_level_parent: () -> untyped end module Tracking EMPTY_ARRAY: Array[untyped] EMPTY_HASH: Hash[untyped, untyped] EMPTY_TREE: Transitions::Tree + EMPTY_IDS: Hash[untyped, untyped] VERSION: Integer EMPTY: Hash[Symbol, untyped] diff --git a/test/bcdd/result/callable_and_then/results_from_different_sources_test.rb b/test/bcdd/result/callable_and_then/results_from_different_sources_test.rb index 3b236eb..dce449d 100644 --- a/test/bcdd/result/callable_and_then/results_from_different_sources_test.rb +++ b/test/bcdd/result/callable_and_then/results_from_different_sources_test.rb @@ -80,7 +80,7 @@ def teardown assert_transitions(result1, size: 3) - assert_equal([0, [[1, []]]], result1.transitions[:metadata][:ids_tree]) + assert_equal([0, [[1, []]]], result1.transitions[:metadata][:ids][:tree]) root = { id: 0, name: 'NormalizeAndValidateEmail', desc: nil } @@ -116,7 +116,7 @@ def teardown assert_transitions(result2, size: 5) - assert_equal([0, [[1, []], [2, []]]], result2.transitions[:metadata][:ids_tree]) + assert_equal([0, [[1, []], [2, []]]], result2.transitions[:metadata][:ids][:tree]) root = { id: 0, name: 'NormalizeAndValidateEmail', desc: nil } diff --git a/test/bcdd/result/context/callable_and_then/results_from_different_sources_test.rb b/test/bcdd/result/context/callable_and_then/results_from_different_sources_test.rb index 6a16df3..a4a8492 100644 --- a/test/bcdd/result/context/callable_and_then/results_from_different_sources_test.rb +++ b/test/bcdd/result/context/callable_and_then/results_from_different_sources_test.rb @@ -80,7 +80,7 @@ def teardown assert_transitions(result1, size: 3) - assert_equal([0, [[1, []]]], result1.transitions[:metadata][:ids_tree]) + assert_equal([0, [[1, []]]], result1.transitions[:metadata][:ids][:tree]) root = { id: 0, name: 'NormalizeAndValidateEmail', desc: nil } @@ -116,7 +116,7 @@ def teardown assert_transitions(result2, size: 5) - assert_equal([0, [[1, []], [2, []]]], result2.transitions[:metadata][:ids_tree]) + assert_equal([0, [[1, []], [2, []]]], result2.transitions[:metadata][:ids][:tree]) root = { id: 0, name: 'NormalizeAndValidateEmail', desc: nil } diff --git a/test/bcdd/result/transitions/duration_test.rb b/test/bcdd/result/transitions/duration_test.rb index 32d6d9b..d8907b1 100644 --- a/test/bcdd/result/transitions/duration_test.rb +++ b/test/bcdd/result/transitions/duration_test.rb @@ -21,6 +21,6 @@ class BCDD::Result::TransitionsDurationTest < Minitest::Test assert(result.transitions[:metadata][:duration] > 299) - assert_equal([0, []], result.transitions[:metadata][:ids_tree]) + assert_equal([0, []], result.transitions[:metadata][:ids][:tree]) end end diff --git a/test/bcdd/result/transitions/enabled/with_source/singleton/nested_test.rb b/test/bcdd/result/transitions/enabled/with_source/singleton/nested_test.rb index 7aa5232..1fef88e 100644 --- a/test/bcdd/result/transitions/enabled/with_source/singleton/nested_test.rb +++ b/test/bcdd/result/transitions/enabled/with_source/singleton/nested_test.rb @@ -108,7 +108,7 @@ def sum_divisions(divisions) assert_equal( [0, [[1, []], [2, []], [3, []]]], - result1.transitions[:metadata][:ids_tree] + result1.transitions[:metadata][:ids][:tree] ) { @@ -120,7 +120,7 @@ def sum_divisions(divisions) assert_equal( [0, [[1, [[2, []]]], [3, []], [4, []]]], - result2.transitions[:metadata][:ids_tree] + result2.transitions[:metadata][:ids][:tree] ) { @@ -132,7 +132,7 @@ def sum_divisions(divisions) assert_equal( [0, [[1, [[2, []]]], [3, [[4, []]]], [5, []]]], - result3.transitions[:metadata][:ids_tree] + result3.transitions[:metadata][:ids][:tree] ) { @@ -144,7 +144,7 @@ def sum_divisions(divisions) assert_equal( [0, [[1, [[2, []]]], [3, [[4, []]]], [5, [[6, []]]]]], - result4.transitions[:metadata][:ids_tree] + result4.transitions[:metadata][:ids][:tree] ) assert_predicate(result1.transitions[:records][0][:and_then], :empty?) diff --git a/test/bcdd/result/transitions/tree_test.rb b/test/bcdd/result/transitions/tree_test.rb index 884f0ee..762c87d 100644 --- a/test/bcdd/result/transitions/tree_test.rb +++ b/test/bcdd/result/transitions/tree_test.rb @@ -128,7 +128,7 @@ class TreeTest < Minitest::Test assert_equal('child', tree.current_value) end - test '#nested_ids' do + test '#ids' do tree = Tree.new('root') tree.insert!('child') tree.insert!('grandchild') @@ -156,7 +156,7 @@ class TreeTest < Minitest::Test ]], [7, []] ]], - tree.nested_ids + tree.ids ) tree.move_to_root! @@ -219,5 +219,65 @@ class TreeTest < Minitest::Test tree.ids_matrix ) end + + test '#ids_list' do + tree = Tree.new('root') + + tree.insert!('child') + tree.insert!('grandchild') + tree.insert!('great-grandchild') + + tree.move_to_root! + + tree.insert!('new-child') + tree.insert!('new-grandchild') + + tree.move_up! + + tree.insert!('new-grandchild__child') + + tree.move_to_root! + + tree.insert!('new-new-child') + + assert_equal( + [0, 1, 2, 3, 4, 5, 6, 7], + tree.ids_list + ) + end + + test '#ids_level_parent' do + tree = Tree.new('root') + tree.insert!('child') + tree.insert!('grandchild') + tree.insert!('great-grandchild') + + tree.move_to_root! + + tree.insert!('new-child') + tree.insert!('new-grandchild') + + tree.move_up! + + tree.insert!('new-grandchild__child') + + tree.move_to_root! + + tree.insert!('new-new-child') + + assert_equal( + { + 0 => [0, 0], + 1 => [1, 0], + 2 => [2, 1], + 3 => [3, 2], + 4 => [1, 0], + 5 => [2, 4], + 6 => [2, 4], + 7 => [1, 0] + }, + tree.ids_level_parent + ) + end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index bbbf458..e4cd426 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -116,11 +116,11 @@ def assert_transitions_metadata(transitions, trace_id) metadata = transitions[:metadata] - assert_equal(%i[duration ids_matrix ids_tree trace_id], metadata.keys.sort) + assert_equal(%i[duration ids trace_id], metadata.keys.sort) assert_instance_of(Integer, metadata[:duration]) - assert_instance_of(Array, metadata[:ids_tree]) - assert_instance_of(Hash, metadata[:ids_matrix]) + + assert_transitions_metadata_ids(metadata) assert_transitions_metadata_trace_id(metadata, trace_id) end @@ -131,6 +131,13 @@ def assert_transitions_metadata_trace_id(metadata, trace_id) trace_id.nil? ? assert_nil(metadata_trace_id) : assert_equal(trace_id, metadata_trace_id) end + def assert_transitions_metadata_ids(metadata) + assert_instance_of(Hash, metadata[:ids]) + assert_instance_of(Array, metadata[:ids][:tree]) + assert_instance_of(Hash, metadata[:ids][:matrix]) + assert_instance_of(Hash, metadata[:ids][:level_parent]) + end + TimeValue = ->(value) { value.is_a?(::Time) && value.utc? } AndThenValue = ->(value) { value.is_a?(::Hash) && value.empty? }