event_store_client 2.3.0 → 3.1.0

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/appending_events.md +16 -22
  4. data/docs/catch_up_subscriptions.md +35 -70
  5. data/docs/configuration.md +6 -10
  6. data/docs/deleting_streams.md +14 -4
  7. data/docs/encrypting_events.md +2 -6
  8. data/docs/linking_events.md +20 -26
  9. data/docs/reading_events.md +33 -62
  10. data/lib/event_store_client/adapters/grpc/client.rb +13 -9
  11. data/lib/event_store_client/adapters/grpc/commands/command.rb +0 -2
  12. data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +1 -1
  13. data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +11 -5
  14. data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +2 -6
  15. data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +7 -5
  16. data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +7 -5
  17. data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +5 -10
  18. data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +5 -8
  19. data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +3 -5
  20. data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +6 -8
  21. data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +11 -13
  22. data/lib/event_store_client/adapters/grpc.rb +0 -1
  23. data/lib/event_store_client/deserialized_event.rb +21 -3
  24. data/lib/event_store_client/errors.rb +110 -0
  25. data/lib/event_store_client/mapper/default.rb +1 -1
  26. data/lib/event_store_client/mapper/encrypted.rb +2 -2
  27. data/lib/event_store_client/rspec/has_option_matcher.rb +88 -0
  28. data/lib/event_store_client/serializer/event_deserializer.rb +4 -2
  29. data/lib/event_store_client/serializer/event_serializer.rb +41 -17
  30. data/lib/event_store_client/version.rb +1 -1
  31. data/lib/event_store_client.rb +3 -1
  32. metadata +24 -22
@@ -1,6 +1,4 @@
1
- # @title Reading events
2
-
3
- # Reading events
1
+ # Reading Events
4
2
 
5
3
  ## Reading from a stream
6
4
 
@@ -8,18 +6,7 @@ The simplest way to read a stream forwards is to supply a stream name.
8
6
 
9
7
  ```ruby
10
8
  EventStoreClient.client.read('some-stream')
11
- # => Success([#<EventStoreClient::DeserializedEvent 0x1>, #<EventStoreClient::DeserializedEvent 0x1>])
12
- ```
13
-
14
- This will return either `Dry::Monads::Success` with the list of events attached or `Dry::Monads::Failure` with an error. You can handle the result like this:
15
-
16
- ```ruby
17
- result = EventStoreClient.client.read('some-stream')
18
- if result.success?
19
- result.success.each do |event|
20
- # do something with an event
21
- end
22
- end
9
+ # => [#<EventStoreClient::DeserializedEvent 0x1>, #<EventStoreClient::DeserializedEvent 0x1>]
23
10
  ```
24
11
 
25
12
  ### Request options customization
@@ -68,17 +55,17 @@ As well as being able to read a stream forwards you can also go backwards. This
68
55
  EventStoreClient.client.read('some-stream', options: { direction: 'Backwards', from_revision: :end })
69
56
  ```
70
57
 
71
- ## Checking if the stream exists
58
+ ## Checking if stream exists
72
59
 
73
- In case a stream with given name does not exist, `Dry::Monads::Failure` will be returned with value `:stream_not_found`:
60
+ In case a stream with given name does not exist - `EventStoreClient::StreamNotFoundError` error will be raised:
74
61
 
75
62
  ```ruby
76
- result = EventStoreClient.client.read('non-existing-stream')
77
- # => Failure(:stream_not_found)
78
- result.failure?
79
- # => true
80
- result.failure
81
- # => :stream_not_found
63
+ begin
64
+ EventStoreClient.client.read('non-existing-stream')
65
+ rescue EventStoreClient::StreamNotFoundError => e
66
+ puts e.message # => Stream "non-existing-stream" does not exist.
67
+ puts e.stream_name # => "non-existing-stream"
68
+ end
82
69
  ```
83
70
 
84
71
  ## Reading from the $all stream
@@ -103,46 +90,40 @@ If you would like to skip deserialization of the `#read` result, you should use
103
90
 
104
91
  ```ruby
105
92
  EventStoreClient.client.read('some-stream', skip_deserialization: true)
106
- # => Success([<EventStore::Client::Streams::ReadResp ...>])
93
+ # => [<EventStore::Client::Streams::ReadResp ...>]
107
94
  ```
108
95
 
109
96
  ## Filtering
110
97
 
111
- The filtering feature is only available for the`$all` stream.
98
+ The filtering feature is only available for the `$all` stream.
112
99
 
113
100
  Retrieve events from streams with name starting with `some-stream`:
114
101
 
115
102
  ```ruby
116
- result =
103
+ events =
117
104
  EventStoreClient.client.read('$all', options: { filter: { stream_identifier: { prefix: ['some-stream'] } } })
118
- if result.success?
119
- result.success.each do |e|
120
- # iterate through events
121
- end
105
+ events.each do |event|
106
+ # iterate through events
122
107
  end
123
108
  ```
124
109
 
125
110
  Retrieve events with name starting with `some-event`:
126
111
 
127
112
  ```ruby
128
- result =
113
+ events =
129
114
  EventStoreClient.client.read('$all', options: { event_type: { prefix: ['some-event'] } })
130
- if result.success?
131
- result.success.each do |e|
132
- # iterate through events
133
- end
115
+ events.each do |event|
116
+ # iterate through events
134
117
  end
135
118
  ```
136
119
 
137
120
  Retrieving events from stream `some-stream-1` and `some-stream-2`:
138
121
 
139
122
  ```ruby
140
- result =
123
+ events =
141
124
  EventStoreClient.client.read('$all', options: { filter: { stream_identifier: { prefix: ['some-stream-1', 'some-stream-2'] } } })
142
- if result.success?
143
- result.success.each do |e|
144
- # iterate through events
145
- end
125
+ events.each do |event|
126
+ # iterate through events
146
127
  end
147
128
  ```
148
129
 
@@ -151,43 +132,33 @@ end
151
132
  You can use `#read_paginated`, the ready-to-go implementation of pagination which returns an array of result pages:
152
133
 
153
134
  ```ruby
154
- EventStoreClient.client.read_paginated('some-stream').each do |result|
155
- if result.success?
156
- result.success.each do |event|
157
- # do something with event
158
- end
135
+ EventStoreClient.client.read_paginated('some-stream').each do |events|
136
+ events.each do |event|
137
+ # iterate through events
159
138
  end
160
139
  end
161
140
 
162
- EventStoreClient.client.read_paginated('$all').each do |result|
163
- if result.success?
164
- result.success.each do |event|
165
- # do something with event
166
- end
141
+ EventStoreClient.client.read_paginated('$all').each do |events|
142
+ events.each do |event|
143
+ # iterate through events
167
144
  end
168
145
  end
169
146
  ```
170
147
 
171
-
172
-
173
148
  ### Paginating backward reads
174
149
 
175
150
  Just supply a call with `:direction` option and with `:from_position`/`:from_revision` option(depending on what stream you read from):
176
151
 
177
152
  ```ruby
178
- EventStoreClient.client.read_paginated('some-stream', options: { direction: 'Backwards', from_revision: :end }).each do |result|
179
- if result.success?
180
- result.each do |event|
181
- # do something with event
182
- end
153
+ EventStoreClient.client.read_paginated('some-stream', options: { direction: 'Backwards', from_revision: :end }).each do |events|
154
+ events.each do |event|
155
+ # iterate through events
183
156
  end
184
157
  end
185
158
 
186
- EventStoreClient.client.read_paginated('$all', options: { direction: 'Backwards', from_position: :end }).each do |result|
187
- if result.success?
188
- result.each do |event|
189
- # do something with event
190
- end
159
+ EventStoreClient.client.read_paginated('$all', options: { direction: 'Backwards', from_position: :end }).each do |events|
160
+ events.each do |event|
161
+ # iterate through events
191
162
  end
192
163
  end
193
164
  ```
@@ -31,8 +31,8 @@ module EventStoreClient
31
31
  # puts proposed_msg_opts.proposed_message
32
32
  # end
33
33
  # ```
34
- # @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure, Array<Dry::Monads::Result::Success, Dry::Monads::Result::Failure>]
35
- # Returns monads' Success/Failure in case whether request was performed.
34
+ # @return [EventStore::Client::Streams::AppendResp, Array<EventStore::Client::Streams::AppendResp>]
35
+ # @raise [EventStoreClient::WrongExpectedVersionError]
36
36
  def append_to_stream(stream_name, events_or_event, options: {}, credentials: {}, &blk)
37
37
  if events_or_event.is_a?(Array)
38
38
  Commands::Streams::AppendMultiple.new(config: config, **credentials).call(
@@ -93,7 +93,8 @@ module EventStoreClient
93
93
  # )
94
94
  # end
95
95
  # ```
96
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
96
+ # @return [Array<EventStoreClient::DeserializedEvent>, Array<EventStore::Client::Streams::ReadResp>]
97
+ # @raise [EventStoreClient::StreamNotFoundError]
97
98
  def read(stream_name, options: {}, skip_deserialization: config.skip_deserialization,
98
99
  skip_decryption: config.skip_decryption, credentials: {}, &blk)
99
100
  Commands::Streams::Read.new(config: config, **credentials).call(
@@ -106,8 +107,9 @@ module EventStoreClient
106
107
  end
107
108
 
108
109
  # @see {#read} for available params
109
- # @return [Enumerator] enumerator will yield Dry::Monads::Success or Dry::Monads::Failure on
110
- # each iteration
110
+ # @return [Enumerator] enumerator will yield EventStoreClient::DeserializedEvent or
111
+ # EventStore::Client::Streams::ReadResp on each iteration depending on `:skip_deserialization`
112
+ # argument value
111
113
  def read_paginated(stream_name, options: {}, credentials: {},
112
114
  skip_deserialization: config.skip_deserialization,
113
115
  skip_decryption: config.skip_decryption, &blk)
@@ -136,7 +138,8 @@ module EventStoreClient
136
138
  # opts.stream_identifier.stream_name = 'overridden-stream-name'
137
139
  # end
138
140
  # ```
139
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
141
+ # @return [EventStore::Client::Streams::DeleteResp]
142
+ # @raise [EventStoreClient::StreamDeletionError]
140
143
  def hard_delete_stream(stream_name, options: {}, credentials: {}, &blk)
141
144
  Commands::Streams::HardDelete.
142
145
  new(config: config, **credentials).
@@ -159,7 +162,8 @@ module EventStoreClient
159
162
  # opts.stream_identifier.stream_name = 'overridden-stream-name'
160
163
  # end
161
164
  # ```
162
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
165
+ # @return [EventStore::Client::Streams::DeleteResp]
166
+ # @raise [EventStoreClient::StreamDeletionError]
163
167
  def delete_stream(stream_name, options: {}, credentials: {}, &blk)
164
168
  Commands::Streams::Delete.
165
169
  new(config: config, **credentials).
@@ -218,7 +222,7 @@ module EventStoreClient
218
222
  # )
219
223
  # end
220
224
  # ```
221
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
225
+ # @return [void] will block current Thread until killed
222
226
  def subscribe_to_stream(stream_name, handler:, options: {}, credentials: {},
223
227
  skip_deserialization: config.skip_deserialization,
224
228
  skip_decryption: config.skip_decryption, &blk)
@@ -274,7 +278,7 @@ module EventStoreClient
274
278
  # @param credentials [Hash]
275
279
  # @option credentials [String] :username
276
280
  # @option credentials [String] :password
277
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
281
+ # @return [EventStore::Client::Gossip::ClusterInfo]
278
282
  def cluster_info(credentials: {})
279
283
  Commands::Gossip::ClusterInfo.new(config: config, **credentials).call
280
284
  end
@@ -14,8 +14,6 @@ module EventStoreClient
14
14
  end
15
15
  end
16
16
 
17
- include Dry::Monads[:try, :result]
18
-
19
17
  attr_reader :connection, :config
20
18
  private :connection, :config
21
19
 
@@ -11,7 +11,7 @@ module EventStoreClient
11
11
  # @api private
12
12
  # @see {EventStoreClient::GRPC::Client#cluster_info}
13
13
  def call
14
- Success(retry_request { service.read(request.new, metadata: metadata) })
14
+ retry_request { service.read(request.new, metadata: metadata) }
15
15
  end
16
16
  end
17
17
  end
@@ -20,7 +20,7 @@ module EventStoreClient
20
20
  response = retry_request(skip_retry: config.eventstore_url.throw_on_append_failure) do
21
21
  service.append(payload, metadata: metadata)
22
22
  end
23
- validate_response(response)
23
+ validate_response(response, caused_by: event)
24
24
  end
25
25
 
26
26
  private
@@ -41,11 +41,17 @@ module EventStoreClient
41
41
  end
42
42
 
43
43
  # @param resp [EventStore::Client::Streams::AppendResp]
44
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
45
- def validate_response(resp)
46
- return Success(resp) if resp.success
44
+ # @param caused_by [EventStoreClient::DeserializedEvent]
45
+ # @return [EventStore::Client::Streams::AppendResp]
46
+ # @raise [EventStoreClient::WrongExpectedVersionError]
47
+ def validate_response(resp, caused_by:)
48
+ return resp if resp.success
47
49
 
48
- Failure(resp.wrong_expected_version)
50
+ error = WrongExpectedVersionError.new(
51
+ resp.wrong_expected_version,
52
+ caused_by: caused_by
53
+ )
54
+ raise error
49
55
  end
50
56
  end
51
57
  end
@@ -10,9 +10,8 @@ module EventStoreClient
10
10
  # @api private
11
11
  # @see {EventStoreClient::GRPC::Client#append_to_stream}
12
12
  def call(stream_name, events, options:, &blk)
13
- result = []
14
- events.each.with_index do |event, index|
15
- response = Commands::Streams::Append.new(
13
+ events.map.with_index do |event, index|
14
+ Commands::Streams::Append.new(
16
15
  config: config, **connection_options
17
16
  ).call(
18
17
  stream_name, event, options: options
@@ -21,10 +20,7 @@ module EventStoreClient
21
20
 
22
21
  yield(req_opts, proposed_msg_opts) if blk
23
22
  end
24
- result.push(response)
25
- break if response.failure?
26
23
  end
27
- result
28
24
  end
29
25
 
30
26
  private
@@ -13,11 +13,13 @@ module EventStoreClient
13
13
  def call(stream_name, options:, &blk)
14
14
  options = normalize_options(stream_name, options)
15
15
  yield options if blk
16
- Success(
17
- retry_request { service.delete(request.new(options: options), metadata: metadata) }
18
- )
19
- rescue ::GRPC::FailedPrecondition
20
- Failure(:stream_not_found)
16
+ retry_request { service.delete(request.new(options: options), metadata: metadata) }
17
+ rescue ::GRPC::FailedPrecondition => e
18
+ # GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
19
+ # be existing, or :expected_revision option value does not match the current state of
20
+ # the stream. So, re-raise our own error, and pass there the original message - just in
21
+ # case.
22
+ raise StreamDeletionError.new(stream_name, details: e.message)
21
23
  end
22
24
 
23
25
  private
@@ -13,11 +13,13 @@ module EventStoreClient
13
13
  def call(stream_name, options:, &blk)
14
14
  options = normalize_options(stream_name, options)
15
15
  yield options if blk
16
- Success(
17
- retry_request { service.delete(request.new(options: options), metadata: metadata) }
18
- )
19
- rescue ::GRPC::FailedPrecondition
20
- Failure(:stream_not_found)
16
+ retry_request { service.delete(request.new(options: options), metadata: metadata) }
17
+ rescue ::GRPC::FailedPrecondition => e
18
+ # GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
19
+ # be existing, or :expected_revision option value does not match the current state of
20
+ # the stream. So, re-raise our own error, and pass there the original message - just in
21
+ # case.
22
+ raise StreamDeletionError.new(stream_name, details: e.message)
21
23
  end
22
24
 
23
25
  private
@@ -10,18 +10,13 @@ module EventStoreClient
10
10
  # @api private
11
11
  # @see {EventStoreClient::GRPC::Client#link_to}
12
12
  def call(stream_name, events, options:, &blk)
13
- result = []
14
13
  link_cmd = Commands::Streams::LinkTo.new(config: config, **connection_options)
15
- events.each.with_index do |event, index|
16
- response =
17
- link_cmd.call(stream_name, event, options: options) do |req_opts, proposed_msg_opts|
18
- req_opts.options.revision += index if has_revision_option?(req_opts.options)
19
- yield(req_opts, proposed_msg_opts) if blk
20
- end
21
- result.push(response)
22
- break if response.failure?
14
+ events.map.with_index do |event, index|
15
+ link_cmd.call(stream_name, event, options: options) do |req_opts, proposed_msg_opts|
16
+ req_opts.options.revision += index if has_revision_option?(req_opts.options)
17
+ yield(req_opts, proposed_msg_opts) if blk
18
+ end
23
19
  end
24
- result
25
20
  end
26
21
 
27
22
  private
@@ -40,22 +40,19 @@ module EventStoreClient
40
40
 
41
41
  paginate_options(opts, position)
42
42
  end
43
- unless response.success?
44
- yielder << response
45
- raise StopIteration
46
- end
47
43
  processed_response =
48
44
  EventStoreClient::GRPC::Shared::Streams::ProcessResponses.
49
45
  new(config: config).
50
46
  call(
51
- response.success,
47
+ response,
52
48
  skip_deserialization,
53
49
  skip_decryption
54
50
  )
55
- yielder << processed_response if processed_response.success.any?
56
- raise StopIteration if end_reached?(response.success, max_count)
57
51
 
58
- position = calc_next_position(response.success, direction, stream_name)
52
+ yielder << processed_response if processed_response.any?
53
+ raise StopIteration if end_reached?(response, max_count)
54
+
55
+ position = calc_next_position(response, direction, stream_name)
59
56
  raise StopIteration if position.negative?
60
57
  end
61
58
  end
@@ -28,11 +28,9 @@ module EventStoreClient
28
28
 
29
29
  handler.call(result) if result
30
30
  end
31
- Success(
32
- retry_request do
33
- service.read(request.new(options: options), metadata: metadata, &callback)
34
- end
35
- )
31
+ retry_request do
32
+ service.read(request.new(options: options), metadata: metadata, &callback)
33
+ end
36
34
  end
37
35
 
38
36
  private
@@ -5,8 +5,6 @@ module EventStoreClient
5
5
  module Shared
6
6
  module Streams
7
7
  class ProcessResponse
8
- include Dry::Monads[:result]
9
-
10
8
  attr_reader :config
11
9
  private :config
12
10
 
@@ -19,15 +17,15 @@ module EventStoreClient
19
17
  # @param response [EventStore::Client::Streams::ReadResp]
20
18
  # @param skip_deserialization [Boolean]
21
19
  # @param skip_decryption [Boolean]
22
- # @return [Dry::Monads::Success, Dry::Monads::Failure, nil]
20
+ # @return [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp, nil]
21
+ # @raise [EventStoreClient::StreamNotFoundError]
23
22
  def call(response, skip_deserialization, skip_decryption)
24
- return Failure(:stream_not_found) if response.stream_not_found
25
- return Success(response) if skip_deserialization
23
+ non_existing_stream = response.stream_not_found&.stream_identifier&.stream_name
24
+ raise StreamNotFoundError, non_existing_stream if non_existing_stream
25
+ return response if skip_deserialization
26
26
  return unless response.event&.event
27
27
 
28
- Success(
29
- config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
30
- )
28
+ config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
31
29
  end
32
30
  end
33
31
  end
@@ -5,8 +5,6 @@ module EventStoreClient
5
5
  module Shared
6
6
  module Streams
7
7
  class ProcessResponses
8
- include Dry::Monads[:result]
9
-
10
8
  attr_reader :config
11
9
  private :config
12
10
 
@@ -19,20 +17,20 @@ module EventStoreClient
19
17
  # @param responses [Array<EventStore::Client::Streams::ReadResp>]
20
18
  # @param skip_deserialization [Boolean]
21
19
  # @param skip_decryption [Boolean]
22
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
20
+ # @return [Array<EventStoreClient::DeserializedEvent>, Array<EventStore::Client::Streams::ReadResp>]
21
+ # @raise [EventStoreClient::StreamNotFoundError]
23
22
  def call(responses, skip_deserialization, skip_decryption)
24
- return Failure(:stream_not_found) if responses.first&.stream_not_found
25
- return Success(responses) if skip_deserialization
23
+ non_existing_stream = responses.first&.stream_not_found&.stream_identifier&.stream_name
24
+ raise StreamNotFoundError, non_existing_stream if non_existing_stream
25
+ return responses if skip_deserialization
26
26
 
27
- events =
28
- responses.map do |response|
29
- # It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
30
- # example. Such responses should be skipped. See generated files for more info.
31
- next unless response.event&.event
27
+ responses.map do |response|
28
+ # It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
29
+ # example. Such responses should be skipped. See generated files for more info.
30
+ next unless response.event&.event
32
31
 
33
- config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
34
- end
35
- Success(events.compact)
32
+ config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
33
+ end.compact
36
34
  end
37
35
  end
38
36
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc'
4
- require 'dry-monads'
5
4
 
6
5
  # Load all generated by google-protobuf files
7
6
  Dir[File.expand_path('grpc/generated/*.rb', __dir__)].each { |f| require f }
@@ -7,8 +7,8 @@ module EventStoreClient
7
7
  InvalidDataError = Class.new(StandardError)
8
8
  private_constant :InvalidDataError
9
9
 
10
- attr_reader :id, :type, :title, :data, :metadata, :stream_name, :stream_revision,
11
- :prepare_position, :commit_position
10
+ attr_reader :id, :type, :title, :data, :metadata, :custom_metadata, :stream_name,
11
+ :stream_revision, :prepare_position, :commit_position
12
12
 
13
13
  # @args [Hash] opts
14
14
  # @option opts [Boolean] :skip_validation
@@ -34,6 +34,7 @@ module EventStoreClient
34
34
  'type' => @type,
35
35
  'content-type' => payload_content_type
36
36
  )
37
+ @custom_metadata = args[:custom_metadata] || {}
37
38
  @stream_name = args[:stream_name]
38
39
  @stream_revision = args[:stream_revision]
39
40
  @prepare_position = args[:prepare_position]
@@ -57,7 +58,7 @@ module EventStoreClient
57
58
  def ==(other)
58
59
  return false unless other.is_a?(EventStoreClient::DeserializedEvent)
59
60
 
60
- to_h == other.to_h
61
+ meaningful_attrs(to_h) == meaningful_attrs(other.to_h)
61
62
  end
62
63
 
63
64
  # @return [Hash]
@@ -75,8 +76,25 @@ module EventStoreClient
75
76
  type == LINK_TYPE
76
77
  end
77
78
 
79
+ # Detect whether an event is a system event
80
+ # @return [Boolean]
81
+ def system?
82
+ return false unless type
83
+
84
+ type.start_with?('$')
85
+ end
86
+
78
87
  private
79
88
 
89
+ # When comparing two events - we drop commit_position and prepare_position from attributes list
90
+ # to compare. This is because their value are different when retrieving the same event from
91
+ # '$all' stream and from specific stream.
92
+ # @param hash [Hash]
93
+ # @return [Hash]
94
+ def meaningful_attrs(hash)
95
+ hash.delete_if { |key, _val| %i(commit_position prepare_position).include?(key) }
96
+ end
97
+
80
98
  def validate(data)
81
99
  return unless schema
82
100
 
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ class Error < StandardError
5
+ # @return [Hash]
6
+ def as_json(*)
7
+ to_h.transform_keys(&:to_s)
8
+ end
9
+
10
+ # @return [Hash]
11
+ def to_h
12
+ hash =
13
+ instance_variables.each_with_object({}) do |var, result|
14
+ key = var.to_s
15
+ key[0] = '' # remove @ sign
16
+ result[key.to_sym] = instance_variable_get(var)
17
+ end
18
+ hash[:message] = message
19
+ hash[:backtrace] = backtrace
20
+ hash
21
+ end
22
+ end
23
+
24
+ class StreamNotFoundError < Error
25
+ attr_reader :stream_name
26
+
27
+ # @param stream_name [String]
28
+ def initialize(stream_name)
29
+ @stream_name = stream_name
30
+ super("Stream #{stream_name.inspect} does not exist.")
31
+ end
32
+ end
33
+
34
+ class WrongExpectedVersionError < Error
35
+ attr_reader :wrong_expected_version, :caused_by
36
+
37
+ # @param wrong_expected_version [EventStore::Client::Streams::AppendResp::WrongExpectedVersion]
38
+ # @param caused_by [EventStoreClient::DeserializedEvent] an event on which
39
+ # WrongExpectedVersionError error happened. It can be useful when appending array of events -
40
+ # based on it you will know which events were appended and which weren't.
41
+ def initialize(wrong_expected_version, caused_by:)
42
+ @wrong_expected_version = wrong_expected_version
43
+ @caused_by = caused_by
44
+ super(user_friendly_message)
45
+ end
46
+
47
+ private
48
+
49
+ # @return [String]
50
+ def user_friendly_message
51
+ return expected_stream_exists if wrong_expected_version.expected_stream_exists
52
+ return expected_no_stream if wrong_expected_version.expected_no_stream
53
+ return current_no_stream if wrong_expected_version.current_no_stream
54
+ unless wrong_expected_version.expected_revision == wrong_expected_version.current_revision
55
+ return unmatched_stream_revision
56
+ end
57
+
58
+ # Unhandled case. Could happen if something else would be added to proto and I don't add it
59
+ # here.
60
+ self.class.to_s
61
+ end
62
+
63
+ # @return [String]
64
+ def expected_stream_exists
65
+ "Expected stream to exist, but it doesn't."
66
+ end
67
+
68
+ # @return [String]
69
+ def expected_no_stream
70
+ "Expected stream to be absent, but it actually exists."
71
+ end
72
+
73
+ # @return [String]
74
+ def current_no_stream
75
+ <<~TEXT.strip
76
+ Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
77
+ stream does not exist.
78
+ TEXT
79
+ end
80
+
81
+ # @return [String]
82
+ def unmatched_stream_revision
83
+ <<~TEXT.strip
84
+ Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
85
+ actual stream revision is #{wrong_expected_version.current_revision.inspect}.
86
+ TEXT
87
+ end
88
+ end
89
+
90
+ class StreamDeletionError < Error
91
+ attr_reader :stream_name, :details
92
+
93
+ # @param stream_name [String]
94
+ # @param details [String]
95
+ def initialize(stream_name, details:)
96
+ @stream_name = stream_name
97
+ @details = details
98
+ super(user_friendly_message)
99
+ end
100
+
101
+ # @return [String]
102
+ def user_friendly_message
103
+ <<~TEXT.strip
104
+ Could not delete #{stream_name.inspect} stream. It seems that a stream with that \
105
+ name does not exist, has already been deleted or its state does not match the \
106
+ provided :expected_revision option. Please check #details for more info.
107
+ TEXT
108
+ end
109
+ end
110
+ end