event_store_client 2.3.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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