grumlin 0.23.0 → 1.0.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -9
- data/Gemfile.lock +1 -1
- data/README.md +100 -142
- data/Rakefile +1 -1
- data/bin/console +18 -3
- data/doc/middlewares.md +49 -10
- data/lib/async/channel.rb +54 -56
- data/lib/grumlin/benchmark/repository.rb +10 -14
- data/lib/grumlin/client.rb +93 -95
- data/lib/grumlin/config.rb +33 -33
- data/lib/grumlin/dummy_transaction.rb +13 -15
- data/lib/grumlin/edge.rb +18 -20
- data/lib/grumlin/expressions/cardinality.rb +5 -9
- data/lib/grumlin/expressions/column.rb +5 -9
- data/lib/grumlin/expressions/expression.rb +7 -11
- data/lib/grumlin/expressions/operator.rb +5 -9
- data/lib/grumlin/expressions/order.rb +5 -9
- data/lib/grumlin/expressions/p.rb +27 -31
- data/lib/grumlin/expressions/pop.rb +5 -9
- data/lib/grumlin/expressions/scope.rb +5 -9
- data/lib/grumlin/expressions/t.rb +5 -9
- data/lib/grumlin/expressions/text_p.rb +5 -9
- data/lib/grumlin/expressions/with_options.rb +17 -21
- data/lib/grumlin/features/feature_list.rb +8 -12
- data/lib/grumlin/features/neptune_features.rb +5 -9
- data/lib/grumlin/features/tinkergraph_features.rb +5 -9
- data/lib/grumlin/features.rb +8 -10
- data/lib/grumlin/middlewares/apply_shortcuts.rb +4 -8
- data/lib/grumlin/middlewares/build_query.rb +16 -20
- data/lib/grumlin/middlewares/builder.rb +15 -0
- data/lib/grumlin/middlewares/cast_results.rb +3 -7
- data/lib/grumlin/middlewares/find_blocklisted_steps.rb +14 -0
- data/lib/grumlin/middlewares/find_mutating_steps.rb +9 -0
- data/lib/grumlin/middlewares/middleware.rb +6 -10
- data/lib/grumlin/middlewares/run_query.rb +3 -7
- data/lib/grumlin/middlewares/serialize_to_bytecode.rb +5 -9
- data/lib/grumlin/middlewares/serialize_to_steps.rb +4 -8
- data/lib/grumlin/path.rb +11 -13
- data/lib/grumlin/property.rb +14 -16
- data/lib/grumlin/query_validators/blocklisted_steps_validator.rb +22 -0
- data/lib/grumlin/query_validators/validator.rb +36 -0
- data/lib/grumlin/repository/error_handling_strategy.rb +36 -40
- data/lib/grumlin/repository/instance_methods.rb +115 -118
- data/lib/grumlin/repository.rb +82 -58
- data/lib/grumlin/request_dispatcher.rb +55 -57
- data/lib/grumlin/request_error_factory.rb +53 -55
- data/lib/grumlin/shortcut.rb +19 -21
- data/lib/grumlin/shortcuts/properties.rb +12 -16
- data/lib/grumlin/shortcuts/storage.rb +67 -74
- data/lib/grumlin/shortcuts/upserts.rb +19 -22
- data/lib/grumlin/shortcuts.rb +23 -25
- data/lib/grumlin/shortcuts_applyer.rb +27 -29
- data/lib/grumlin/step.rb +88 -90
- data/lib/grumlin/step_data.rb +12 -14
- data/lib/grumlin/steppable.rb +23 -25
- data/lib/grumlin/steps.rb +52 -54
- data/lib/grumlin/steps_serializers/bytecode.rb +53 -56
- data/lib/grumlin/steps_serializers/human_readable_bytecode.rb +17 -21
- data/lib/grumlin/steps_serializers/serializer.rb +7 -11
- data/lib/grumlin/steps_serializers/string.rb +26 -30
- data/lib/grumlin/test/rspec/db_cleaner_context.rb +8 -12
- data/lib/grumlin/test/rspec/gremlin_context.rb +18 -16
- data/lib/grumlin/test/rspec.rb +1 -5
- data/lib/grumlin/transaction.rb +34 -36
- data/lib/grumlin/transport.rb +71 -73
- data/lib/grumlin/traversal_start.rb +31 -33
- data/lib/grumlin/traversal_strategies/options_strategy.rb +3 -7
- data/lib/grumlin/traverser.rb +5 -7
- data/lib/grumlin/typed_value.rb +11 -13
- data/lib/grumlin/typing.rb +70 -72
- data/lib/grumlin/version.rb +1 -1
- data/lib/grumlin/vertex.rb +14 -16
- data/lib/grumlin/vertex_property.rb +14 -16
- data/lib/grumlin/with_extension.rb +17 -19
- data/lib/grumlin.rb +13 -0
- metadata +9 -6
- data/lib/grumlin/middlewares/frozen_builder.rb +0 -18
- data/lib/grumlin/sugar.rb +0 -15
@@ -1,158 +1,155 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Grumlin
|
4
|
-
|
5
|
-
module InstanceMethods # rubocop:disable Metrics/ModuleLength
|
6
|
-
include Grumlin::Expressions
|
3
|
+
module Grumlin::Repository::InstanceMethods # rubocop:disable Metrics/ModuleLength
|
4
|
+
include Grumlin::Expressions
|
7
5
|
|
8
|
-
|
6
|
+
extend Forwardable
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
sleep_method: ->(n) { Async::Task.current.sleep(n) },
|
13
|
-
tries: 5,
|
14
|
-
sleep: ->(n) { (n**2) + 1 + rand }
|
15
|
-
}.freeze
|
8
|
+
def_delegator "self.class", :shortcuts
|
9
|
+
def_delegator :self, :__, :g
|
16
10
|
|
17
|
-
|
11
|
+
UPSERT_RETRY_PARAMS = {
|
12
|
+
on: [Grumlin::AlreadyExistsError, Grumlin::ConcurrentModificationError],
|
13
|
+
sleep_method: ->(n) { Async::Task.current.sleep(n) },
|
14
|
+
tries: 5,
|
15
|
+
sleep: ->(n) { (n**2) + 1 + rand }
|
16
|
+
}.freeze
|
18
17
|
|
19
|
-
|
18
|
+
DEFAULT_ERROR_HANDLING_STRATEGY = Grumlin::Repository::ErrorHandlingStrategy.new(mode: :retry, **UPSERT_RETRY_PARAMS)
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
def __
|
21
|
+
shortcuts.traversal_start_class.new(pool: Grumlin.default_pool, middlewares: self.class.middlewares)
|
22
|
+
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
def drop_vertex(id, start: g)
|
25
|
+
start.V(id).drop.iterate
|
26
|
+
end
|
28
27
|
|
29
|
-
|
30
|
-
|
28
|
+
def drop_in_batches(traversal, batch_size: 10_000) # rubocop:disable Metrics/AbcSize
|
29
|
+
total_count = traversal.count.next
|
31
30
|
|
32
|
-
|
31
|
+
batches = (total_count / batch_size) + 1
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
Console.logger.info(self) do
|
34
|
+
"drop_in_batches: total_count: #{total_count}, batch_size: #{batch_size}, batches: #{batches}"
|
35
|
+
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
batches.times do |batch|
|
38
|
+
Console.logger.info(self) { "drop_in_batches: deleting batch #{batch + 1}/#{batches}..." }
|
39
|
+
traversal.limit(batch_size).drop.iterate
|
40
|
+
Console.logger.info(self) { "drop_in_batches: batch #{batch + 1}/#{batches} deleted" }
|
41
|
+
end
|
43
42
|
|
44
|
-
|
43
|
+
return if traversal.count.next.zero?
|
45
44
|
|
46
|
-
|
45
|
+
drop_in_batches(traversal, batch_size: batch_size)
|
47
46
|
|
48
|
-
|
49
|
-
|
47
|
+
Console.logger.info(self) { "drop_in_batches: finished." }
|
48
|
+
end
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
def drop_edge(id = nil, from: nil, to: nil, label: nil, start: g) # rubocop:disable Metrics/AbcSize
|
51
|
+
raise ArgumentError, "either id or from:, to: and label: must be passed" if [id, from, to, label].all?(&:nil?)
|
52
|
+
return start.E(id).drop.iterate unless id.nil?
|
54
53
|
|
55
|
-
|
54
|
+
raise ArgumentError, "from:, to: and label: must be passed" if [from, to, label].any?(&:nil?)
|
56
55
|
|
57
|
-
|
58
|
-
|
56
|
+
start.V(from).outE(label).where(__.inV.hasId(to)).limit(1).drop.iterate
|
57
|
+
end
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
def add_vertex(label, id = nil, start: g, **properties)
|
60
|
+
id ||= properties[T.id]
|
61
|
+
properties = except(properties, T.id)
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
t = start.addV(label)
|
64
|
+
t = t.props(T.id => id) unless id.nil?
|
65
|
+
t.props(**properties).next
|
66
|
+
end
|
68
67
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
68
|
+
def add_edge(label, id = nil, from:, to:, start: g, **properties)
|
69
|
+
id ||= properties[T.id]
|
70
|
+
properties = except(properties, T.label)
|
71
|
+
properties[T.id] = id
|
73
72
|
|
74
|
-
|
75
|
-
|
73
|
+
start.addE(label).from(__.V(from)).to(__.V(to)).props(**properties).next
|
74
|
+
end
|
75
|
+
|
76
|
+
def upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params) # rubocop:disable Metrics/ParameterLists
|
77
|
+
with_upsert_error_handling(on_failure, params) do
|
78
|
+
create_properties, update_properties = cleanup_properties(create_properties, update_properties)
|
79
|
+
|
80
|
+
start.upsertV(label, id, create_properties, update_properties).id.next
|
81
|
+
end
|
82
|
+
end
|
76
83
|
|
77
|
-
|
78
|
-
|
84
|
+
# vertices:
|
85
|
+
# [["label", "id", {create: :properties}, {update: properties}]]
|
86
|
+
# params can override Retryable config from UPSERT_RETRY_PARAMS
|
87
|
+
def upsert_vertices(vertices, batch_size: 100, on_failure: :retry, start: g, **params)
|
88
|
+
vertices.each_slice(batch_size) do |slice|
|
89
|
+
with_upsert_error_handling(on_failure, params) do
|
90
|
+
slice.reduce(start) do |t, (label, id, create_properties, update_properties)|
|
79
91
|
create_properties, update_properties = cleanup_properties(create_properties, update_properties)
|
80
92
|
|
81
|
-
|
82
|
-
end
|
93
|
+
t.upsertV(label, id, create_properties, update_properties)
|
94
|
+
end.id.iterate
|
83
95
|
end
|
96
|
+
end
|
97
|
+
end
|
84
98
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
t.upsertV(label, id, create_properties, update_properties)
|
95
|
-
end.id.iterate
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
+
# Only from and to are used to find the existing edge, if one wants to assign an id to a created edge,
|
100
|
+
# it must be passed as T.id in create_properties.
|
101
|
+
def upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, # rubocop:disable Metrics/ParameterLists
|
102
|
+
on_failure: :retry, start: g, **params)
|
103
|
+
with_upsert_error_handling(on_failure, params) do
|
104
|
+
create_properties, update_properties = cleanup_properties(create_properties, update_properties, T.label)
|
105
|
+
start.upsertE(label, from, to, create_properties, update_properties).id.next
|
106
|
+
end
|
107
|
+
end
|
99
108
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
109
|
+
# edges:
|
110
|
+
# [["label", "from", "to", {create: :properties}, {update: properties}]]
|
111
|
+
# params can override Retryable config from UPSERT_RETRY_PARAMS
|
112
|
+
def upsert_edges(edges, batch_size: 100, on_failure: :retry, start: g, **params)
|
113
|
+
edges.each_slice(batch_size) do |slice|
|
114
|
+
with_upsert_error_handling(on_failure, params) do
|
115
|
+
slice.reduce(start) do |t, (label, from, to, create_properties, update_properties)|
|
105
116
|
create_properties, update_properties = cleanup_properties(create_properties, update_properties, T.label)
|
106
|
-
start.upsertE(label, from, to, create_properties, update_properties).id.next
|
107
|
-
end
|
108
|
-
end
|
109
117
|
|
110
|
-
|
111
|
-
|
112
|
-
# params can override Retryable config from UPSERT_RETRY_PARAMS
|
113
|
-
def upsert_edges(edges, batch_size: 100, on_failure: :retry, start: g, **params)
|
114
|
-
edges.each_slice(batch_size) do |slice|
|
115
|
-
with_upsert_error_handling(on_failure, params) do
|
116
|
-
slice.reduce(start) do |t, (label, from, to, create_properties, update_properties)|
|
117
|
-
create_properties, update_properties = cleanup_properties(create_properties, update_properties, T.label)
|
118
|
-
|
119
|
-
t.upsertE(label, from, to, create_properties, update_properties)
|
120
|
-
end.id.iterate
|
121
|
-
end
|
122
|
-
end
|
118
|
+
t.upsertE(label, from, to, create_properties, update_properties)
|
119
|
+
end.id.iterate
|
123
120
|
end
|
121
|
+
end
|
122
|
+
end
|
124
123
|
|
125
|
-
|
124
|
+
private
|
126
125
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
126
|
+
def with_upsert_error_handling(on_failure, params, &block)
|
127
|
+
if params.any?
|
128
|
+
ErrorHandlingStrategy.new(mode: on_failure, **UPSERT_RETRY_PARAMS.merge(params))
|
129
|
+
else
|
130
|
+
DEFAULT_ERROR_HANDLING_STRATEGY
|
131
|
+
end.apply!(&block)
|
132
|
+
end
|
134
133
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
134
|
+
def with_upsert_retry(retry_params, &block)
|
135
|
+
retry_params = UPSERT_RETRY_PARAMS.merge((retry_params))
|
136
|
+
Retryable.retryable(**retry_params, &block)
|
137
|
+
end
|
139
138
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
139
|
+
# A polyfill for Hash#except for ruby 2.x environments without ActiveSupport
|
140
|
+
# TODO: delete and use native Hash#except after ruby 2.7 is deprecated.
|
141
|
+
def except(hash, *keys)
|
142
|
+
return hash.except(*keys) if hash.respond_to?(:except)
|
144
143
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
144
|
+
hash.each_with_object({}) do |(k, v), res|
|
145
|
+
res[k] = v unless keys.include?(k)
|
146
|
+
end
|
147
|
+
end
|
149
148
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
155
|
-
end
|
149
|
+
def cleanup_properties(create_properties, update_properties, *props_to_cleanup)
|
150
|
+
props_to_cleanup = [T.id, T.label] if props_to_cleanup.empty?
|
151
|
+
[create_properties, update_properties].map do |props|
|
152
|
+
except(props, props_to_cleanup)
|
156
153
|
end
|
157
154
|
end
|
158
155
|
end
|
data/lib/grumlin/repository.rb
CHANGED
@@ -1,83 +1,107 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Grumlin
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
3
|
+
module Grumlin::Repository
|
4
|
+
RETURN_MODES = {
|
5
|
+
list: :toList,
|
6
|
+
none: :iterate,
|
7
|
+
single: :next,
|
8
|
+
traversal: :nil
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def self.extended(base)
|
12
|
+
super
|
13
|
+
base.extend(Grumlin::Shortcuts)
|
14
|
+
base.include(Grumlin::Repository::InstanceMethods)
|
15
|
+
|
16
|
+
base.shortcuts_from(Grumlin::Shortcuts::Properties)
|
17
|
+
base.shortcuts_from(Grumlin::Shortcuts::Upserts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.new
|
21
|
+
@repository ||= Class.new do # rubocop:disable Naming/MemoizedInstanceVariableName
|
22
|
+
extend Grumlin::Repository
|
23
|
+
end.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def inherited(subclass)
|
27
|
+
super
|
28
|
+
subclass.middlewares = Grumlin::Middlewares::Builder.new do |b|
|
29
|
+
b.use(middlewares)
|
19
30
|
end
|
31
|
+
end
|
20
32
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end.new
|
33
|
+
def read_only!
|
34
|
+
middlewares do |b|
|
35
|
+
b.insert_after Grumlin::Middlewares::ApplyShortcuts, Grumlin::Middlewares::FindMutatingSteps
|
25
36
|
end
|
37
|
+
end
|
26
38
|
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
def middlewares
|
40
|
+
@middlewares ||= Grumlin::Middlewares::Builder.new do |b|
|
41
|
+
b.use(Grumlin.default_middlewares)
|
42
|
+
end
|
30
43
|
|
31
|
-
|
32
|
-
|
33
|
-
|
44
|
+
yield(@middlewares) if block_given?
|
45
|
+
@middlewares
|
46
|
+
end
|
34
47
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
48
|
+
def query(name, return_mode: :list, postprocess_with: nil, &query_block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
49
|
+
return_mode = validate_return_mode!(return_mode)
|
50
|
+
postprocess_with = validate_postprocess_with!(postprocess_with)
|
39
51
|
|
40
|
-
|
52
|
+
define_method name do |*args, query_params: {}, **params, &block|
|
53
|
+
t = instance_exec(*args, **params, &query_block)
|
54
|
+
return t if t.nil? || (t.respond_to?(:empty?) && t.empty?)
|
41
55
|
|
42
|
-
|
56
|
+
unless t.is_a?(Grumlin::Step)
|
57
|
+
raise Grumlin::WrongQueryResult,
|
58
|
+
"queries must return #{Grumlin::Step}, nil or an empty collection. Given: #{t.class}"
|
59
|
+
end
|
43
60
|
|
44
|
-
|
61
|
+
return block.call(t) unless block.nil?
|
45
62
|
|
46
|
-
|
63
|
+
return t.profile.next if query_params[:profile] == true
|
47
64
|
|
48
|
-
|
49
|
-
return postprocess_with.call(result) if postprocess_with.respond_to?(:call)
|
50
|
-
return send(postprocess_with, result) unless postprocess_with.nil?
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
65
|
+
return_mode = self.class.validate_return_mode!(query_params[:return_mode] || return_mode)
|
54
66
|
|
55
|
-
|
56
|
-
shortcut :addV, override: true do |*args|
|
57
|
-
super(*args).props(**block.call(*args)) # rubocop:disable Performance/RedundantBlockCall
|
58
|
-
end
|
59
|
-
end
|
67
|
+
return t if return_mode == :traversal
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
69
|
+
t.public_send(RETURN_MODES[return_mode]).tap do |result|
|
70
|
+
return postprocess_with.call(result) if postprocess_with.respond_to?(:call)
|
71
|
+
return send(postprocess_with, result) unless postprocess_with.nil?
|
64
72
|
end
|
65
73
|
end
|
74
|
+
end
|
66
75
|
|
67
|
-
|
68
|
-
|
76
|
+
def default_vertex_properties(&block)
|
77
|
+
shortcut :addV, override: true do |*args|
|
78
|
+
super(*args).props(**block.call(*args)) # rubocop:disable Performance/RedundantBlockCall
|
79
|
+
end
|
80
|
+
end
|
69
81
|
|
70
|
-
|
82
|
+
def default_edge_properties(&block)
|
83
|
+
shortcut :addE, override: true do |*args|
|
84
|
+
super(*args).props(**block.call(*args)) # rubocop:disable Performance/RedundantBlockCall
|
71
85
|
end
|
86
|
+
end
|
72
87
|
|
73
|
-
|
74
|
-
|
75
|
-
postprocess_with.is_a?(String) || postprocess_with.respond_to?(:call)
|
76
|
-
return postprocess_with
|
77
|
-
end
|
88
|
+
def validate_return_mode!(return_mode)
|
89
|
+
return return_mode if RETURN_MODES.include?(return_mode)
|
78
90
|
|
79
|
-
|
80
|
-
|
91
|
+
raise ArgumentError, "unsupported return mode #{return_mode}. Supported modes: #{RETURN_MODES.keys}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def validate_postprocess_with!(postprocess_with)
|
95
|
+
if postprocess_with.nil? || postprocess_with.is_a?(Symbol) ||
|
96
|
+
postprocess_with.is_a?(String) || postprocess_with.respond_to?(:call)
|
97
|
+
return postprocess_with
|
81
98
|
end
|
99
|
+
|
100
|
+
raise ArgumentError,
|
101
|
+
"postprocess_with must be a String, Symbol or a callable object, given: #{postprocess_with.class}"
|
82
102
|
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
attr_writer :middlewares
|
83
107
|
end
|
@@ -1,80 +1,78 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
attr_reader :requests
|
3
|
+
class Grumlin::RequestDispatcher
|
4
|
+
attr_reader :requests
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
SUCCESS = {
|
7
|
+
200 => :success,
|
8
|
+
204 => :no_content,
|
9
|
+
206 => :partial_content
|
10
|
+
}.freeze
|
12
11
|
|
13
|
-
|
12
|
+
class DispatcherError < Grumlin::Error; end
|
14
13
|
|
15
|
-
|
14
|
+
class RequestAlreadyAddedError < DispatcherError; end
|
16
15
|
|
17
|
-
|
16
|
+
class UnknownRequestError < DispatcherError; end
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
def initialize
|
19
|
+
@requests = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_request(request)
|
23
|
+
raise RequestAlreadyAddedError if @requests.include?(request[:requestId])
|
24
|
+
|
25
|
+
Async::Channel.new.tap do |channel|
|
26
|
+
@requests[request[:requestId]] = { request: request, result: [], channel: channel }
|
21
27
|
end
|
28
|
+
end
|
22
29
|
|
23
|
-
|
24
|
-
|
30
|
+
# builds a response object, when it's ready sends it to the client via a channel
|
31
|
+
# TODO: sometimes response does not include requestID, no idea how to handle it so far.
|
32
|
+
def add_response(response) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
33
|
+
request_id = response[:requestId]
|
34
|
+
raise UnknownRequestError unless ongoing_request?(request_id)
|
25
35
|
|
26
|
-
|
27
|
-
|
36
|
+
begin
|
37
|
+
request = @requests[request_id]
|
38
|
+
|
39
|
+
Grumlin::RequestErrorFactory.build(request, response).tap do |err|
|
40
|
+
raise err unless err.nil?
|
28
41
|
end
|
29
|
-
end
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
request
|
39
|
-
|
40
|
-
RequestErrorFactory.build(request, response).tap do |err|
|
41
|
-
raise err unless err.nil?
|
42
|
-
end
|
43
|
-
|
44
|
-
case SUCCESS[response.dig(:status, :code)]
|
45
|
-
when :success
|
46
|
-
request[:result] << response.dig(:result, :data)
|
47
|
-
request[:channel] << request[:result]
|
48
|
-
close_request(request_id)
|
49
|
-
when :partial_content then request[:result] << response.dig(:result, :data)
|
50
|
-
when :no_content
|
51
|
-
request[:channel] << []
|
52
|
-
close_request(request_id)
|
53
|
-
end
|
54
|
-
rescue StandardError => e
|
55
|
-
request[:channel].exception(e)
|
43
|
+
case SUCCESS[response.dig(:status, :code)]
|
44
|
+
when :success
|
45
|
+
request[:result] << response.dig(:result, :data)
|
46
|
+
request[:channel] << request[:result]
|
47
|
+
close_request(request_id)
|
48
|
+
when :partial_content then request[:result] << response.dig(:result, :data)
|
49
|
+
when :no_content
|
50
|
+
request[:channel] << []
|
56
51
|
close_request(request_id)
|
57
52
|
end
|
53
|
+
rescue StandardError => e
|
54
|
+
request[:channel].exception(e)
|
55
|
+
close_request(request_id)
|
58
56
|
end
|
57
|
+
end
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
def ongoing_request?(request_id)
|
60
|
+
@requests.include?(request_id)
|
61
|
+
end
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
@requests.clear
|
63
|
+
def clear
|
64
|
+
@requests.each do |_id, request|
|
65
|
+
request[:channel].close!
|
69
66
|
end
|
67
|
+
@requests.clear
|
68
|
+
end
|
70
69
|
|
71
|
-
|
70
|
+
private
|
72
71
|
|
73
|
-
|
74
|
-
|
72
|
+
def close_request(request_id)
|
73
|
+
raise UnknownRequestError unless ongoing_request?(request_id)
|
75
74
|
|
76
|
-
|
77
|
-
|
78
|
-
end
|
75
|
+
request = @requests.delete(request_id)
|
76
|
+
request[:channel].close
|
79
77
|
end
|
80
78
|
end
|