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
@@ -18,7 +18,7 @@ module EventStoreClient
18
18
  # @param event [EventStoreClient::DeserializedEvent]
19
19
  # @return [EventStoreClient::SerializedEvent]
20
20
  def serialize(event)
21
- Serializer::EventSerializer.call(event, serializer: serializer)
21
+ Serializer::EventSerializer.call(event, serializer: serializer, config: config)
22
22
  end
23
23
 
24
24
  # @param event_or_raw_event [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
@@ -32,7 +32,7 @@ module EventStoreClient
32
32
  # Links don't need to be encrypted
33
33
  return Default.new(serializer: serializer, config: config).serialize(event) if event.link?
34
34
 
35
- serialized = Serializer::EventSerializer.call(event, serializer: serializer)
35
+ serialized = Serializer::EventSerializer.call(event, serializer: serializer, config: config)
36
36
  encryption_schema =
37
37
  if event.class.respond_to?(:encryption_schema)
38
38
  event.class.encryption_schema
@@ -70,7 +70,7 @@ module EventStoreClient
70
70
  decrypted_data =
71
71
  EventStoreClient::DataDecryptor.new(
72
72
  data: event.data,
73
- schema: event.metadata['encryption'],
73
+ schema: event.custom_metadata['encryption'],
74
74
  repository: key_repository
75
75
  ).call
76
76
  event.class.new(**event.to_h.merge(data: decrypted_data, skip_validation: true))
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This matcher is defined to test options which are defined by using
4
+ # EventStoreClient::Extensions::OptionsExtension option. Example:
5
+ # Let's say you have next class
6
+ # class SomeClass
7
+ # include EventStoreClient::Extensions::OptionsExtension
8
+ #
9
+ # option(:some_opt) { '1' }
10
+ # end
11
+ #
12
+ # To test that its instance has the proper option with the proper default value you can use this
13
+ # matcher:
14
+ # RSpec.describe SomeClass do
15
+ # subject { described_class.new }
16
+ #
17
+ # # Check that :some_opt is present
18
+ # it { is_expected.to have_option(:some_opt) }
19
+ # # Check that :some_opt is present and has the correct default value
20
+ # it { is_expected.to have_option(:some_opt).with_default_value('1') }
21
+ # end
22
+ #
23
+ # If you have more complex implementation of default value of your option - you should handle it
24
+ # customly. For example:
25
+ # class SomeClass
26
+ # include EventStoreClient::Extensions::OptionsExtension
27
+ #
28
+ # option(:some_opt) { calc_value }
29
+ # end
30
+ # You could test it like so:
31
+ # RSpec.described SomeClass do
32
+ # let(:instance) { described_class.new }
33
+ #
34
+ # describe ':some_opt default value' do
35
+ # subject { instance.some_opt }
36
+ #
37
+ # let(:value) { 'some val' }
38
+ #
39
+ # before do
40
+ # allow(instance).to receive(:calc_value).and_return(value)
41
+ # end
42
+ #
43
+ # it { is_expected.to eq(value) }
44
+ # end
45
+ # end
46
+ RSpec::Matchers.define :has_option do |option_name|
47
+ match do |obj|
48
+ option_presence = obj.class.respond_to?(:options) && obj.class.options.include?(option_name)
49
+ if @default_value
50
+ option_presence && obj.class.allocate.public_send(option_name) == @default_value
51
+ else
52
+ option_presence
53
+ end
54
+ end
55
+
56
+ failure_message do |obj|
57
+ option_presence = obj.class.respond_to?(:options) && obj.class.options.include?(option_name)
58
+ if option_presence && @default_value
59
+ msg = "Expected #{obj.class} to have `#{option_name.inspect}' option with #{@default_value.inspect}"
60
+ msg += ' default value, but default value is'
61
+ msg += " #{obj.class.allocate.public_send(option_name).inspect}"
62
+ else
63
+ msg = "Expected #{obj} to have `#{option_name.inspect}' option."
64
+ end
65
+
66
+ msg
67
+ end
68
+
69
+ description do
70
+ expected_list = RSpec::Matchers::EnglishPhrasing.list(expected)
71
+ sentences =
72
+ @chained_method_clauses.map do |(method_name, method_args)|
73
+ next '' if method_name == :required_kwargs
74
+
75
+ english_name = RSpec::Matchers::EnglishPhrasing.split_words(method_name)
76
+ arg_list = RSpec::Matchers::EnglishPhrasing.list(method_args)
77
+ " #{english_name}#{arg_list}"
78
+ end.join
79
+
80
+ "have#{expected_list} option#{sentences}"
81
+ end
82
+
83
+ chain :with_default_value do |val|
84
+ @default_value = val
85
+ end
86
+ end
87
+
88
+ RSpec::Matchers.alias_matcher :have_option, :has_option
@@ -7,8 +7,8 @@ module EventStoreClient
7
7
  class EventDeserializer
8
8
  class << self
9
9
  # @param raw_event [EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
10
- # @param serializer [#serialize, #deserialize]
11
10
  # @param config [EventStoreClient::Config]
11
+ # @param serializer [#serialize, #deserialize]
12
12
  # @return [EventStoreClient::DeserializedEvent]
13
13
  def call(raw_event, config:, serializer: Serializer::Json)
14
14
  new(config: config, serializer: serializer).call(raw_event)
@@ -19,6 +19,7 @@ module EventStoreClient
19
19
  private :serializer, :config
20
20
 
21
21
  # @param serializer [#serialize, #deserialize]
22
+ # @param config [EventStoreClient::Config]
22
23
  def initialize(serializer:, config:)
23
24
  @serializer = serializer
24
25
  @config = config
@@ -29,7 +30,7 @@ module EventStoreClient
29
30
  def call(raw_event)
30
31
  data = serializer.deserialize(normalize_serialized(raw_event.data))
31
32
  custom_metadata = serializer.deserialize(normalize_serialized(raw_event.custom_metadata))
32
- metadata = custom_metadata.merge(raw_event.metadata.to_h)
33
+ metadata = raw_event.metadata.to_h
33
34
 
34
35
  event_class(metadata['type']).new(
35
36
  skip_validation: true,
@@ -38,6 +39,7 @@ module EventStoreClient
38
39
  type: metadata['type'],
39
40
  data: data,
40
41
  metadata: metadata,
42
+ custom_metadata: custom_metadata,
41
43
  stream_revision: raw_event.stream_revision,
42
44
  commit_position: raw_event.commit_position,
43
45
  prepare_position: raw_event.prepare_position,
@@ -3,35 +3,39 @@
3
3
  module EventStoreClient
4
4
  module Serializer
5
5
  class EventSerializer
6
- ALLOWED_EVENT_METADATA = %w[type content-type created_at].freeze
6
+ # So far there are only these keys can be persisted in the metadata. You can pass **whatever**
7
+ # you want into a metadata hash, but all keys, except these - will be rejected. Define
8
+ # whitelisted keys and cut unwanted keys explicitly(later in this class).
9
+ ALLOWED_EVENT_METADATA = %w[type content-type].freeze
7
10
 
8
11
  class << self
9
12
  # @param event [EventStoreClient::DeserializedEvent]
13
+ # @param config [EventStoreClient::Config]
10
14
  # @param serializer [#serialize, #deserialize]
11
15
  # @return [EventStoreClient::SerializedEvent]
12
- def call(event, serializer: Serializer::Json)
13
- new(serializer: serializer).call(event)
16
+ def call(event, config:, serializer: Serializer::Json)
17
+ new(serializer: serializer, config: config).call(event)
14
18
  end
15
19
  end
16
20
 
17
- attr_reader :serializer
18
- private :serializer
21
+ attr_reader :serializer, :config
22
+ private :serializer, :config
19
23
 
20
24
  # @param serializer [#serialize, #deserialize]
21
- def initialize(serializer:)
25
+ # @param config [EventStoreClient::Config]
26
+ def initialize(serializer:, config:)
22
27
  @serializer = serializer
28
+ @config = config
23
29
  end
24
30
 
25
31
  # @param event [EventStoreClient::DeserializedEvent]
26
32
  # @return [EventStoreClient::SerializedEvent]
27
33
  def call(event)
28
- event_metadata = metadata(event)
29
- event_custom_metadata = custom_metadata(event, event_metadata)
30
34
  SerializedEvent.new(
31
35
  id: event.id || SecureRandom.uuid,
32
36
  data: data(event),
33
- custom_metadata: event_custom_metadata,
34
- metadata: event_metadata.slice(*ALLOWED_EVENT_METADATA),
37
+ metadata: metadata(event),
38
+ custom_metadata: custom_metadata(event),
35
39
  serializer: serializer
36
40
  )
37
41
  end
@@ -42,17 +46,24 @@ module EventStoreClient
42
46
  # @return [Hash]
43
47
  def metadata(event)
44
48
  metadata = serializer.deserialize(serializer.serialize(event.metadata))
45
- metadata['created_at'] ||= Time.now.utc.to_s
46
- metadata
49
+ # 'created' is returned in the metadata hash of the event when reading from a stream. It,
50
+ # however, can not be overridden - it is always defined automatically by EventStore db when
51
+ # appending new event. Thus, just ignore it - no need even to mention it in the
52
+ # #log_metadata_difference method's message.
53
+ metadata = metadata.slice(*(metadata.keys - ['created']))
54
+ filtered_metadata = metadata.slice(*ALLOWED_EVENT_METADATA)
55
+ log_metadata_difference(metadata) unless filtered_metadata == metadata
56
+ filtered_metadata
47
57
  end
48
58
 
59
+ # Compute custom metadata for the event. **Exactly these** values you can see in ES admin's
60
+ # web UI under "Metadata" section of the event.
49
61
  # @param event [EventStoreClient::DeserializedEvent]
50
- # @param metadata [Hash]
51
62
  # @return [Hash]
52
- def custom_metadata(event, metadata)
53
- metadata.
54
- slice('created_at', 'encryption', 'content-type', 'transaction').
55
- merge('type' => event.type.to_s)
63
+ def custom_metadata(event)
64
+ custom_metadata = serializer.deserialize(serializer.serialize(event.custom_metadata))
65
+ custom_metadata['created_at'] ||= Time.now.utc.to_s
66
+ custom_metadata
56
67
  end
57
68
 
58
69
  # @param event [EventStoreClient::DeserializedEvent]
@@ -64,6 +75,19 @@ module EventStoreClient
64
75
 
65
76
  serializer.deserialize(serializer.serialize(event.data))
66
77
  end
78
+
79
+ # @param metadata [Hash]
80
+ # @return [void]
81
+ def log_metadata_difference(metadata)
82
+ rest_hash = metadata.slice(*(metadata.keys - ALLOWED_EVENT_METADATA))
83
+ debug_message = <<~TEXT
84
+ Next keys were filtered from metadata during serialization: \
85
+ #{(metadata.keys - ALLOWED_EVENT_METADATA).map(&:inspect).join(', ')}. If you would like \
86
+ to provide your custom values in the metadata - please provide them via custom_metadata. \
87
+ Example: EventStoreClient::DeserializedEvent.new(custom_metadata: #{rest_hash.inspect})
88
+ TEXT
89
+ config.logger&.debug(debug_message)
90
+ end
67
91
  end
68
92
  end
69
93
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EventStoreClient
4
- VERSION = '2.3.0'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'json'
4
4
  require 'set'
5
+ require 'securerandom'
5
6
 
7
+ require 'event_store_client/errors'
6
8
  require 'event_store_client/serializer/json'
7
9
  require 'event_store_client/serializer/event_serializer'
8
10
  require 'event_store_client/serializer/event_deserializer'
@@ -29,7 +31,7 @@ module EventStoreClient
29
31
  end
30
32
 
31
33
  # @param name [Symbol, String]
32
- # @return [EventStore::Config]
34
+ # @return [EventStoreClient::Config]
33
35
  def config(name = :default)
34
36
  @config[name] ||= Config.new(name: name)
35
37
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_store_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Wilgosz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-23 00:00:00.000000000 Z
11
+ date: 2023-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: dry-monads
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: grpc
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +44,14 @@ dependencies:
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '3.11'
47
+ version: '3.12'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: '3.11'
54
+ version: '3.12'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: simplecov
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +128,28 @@ dependencies:
142
128
  requirements:
143
129
  - - "~>"
144
130
  - !ruby/object:Gem::Version
145
- version: '1'
131
+ version: 1.13.0
146
132
  type: :development
147
133
  prerelease: false
148
134
  version_requirements: !ruby/object:Gem::Requirement
149
135
  requirements:
150
136
  - - "~>"
151
137
  - !ruby/object:Gem::Version
152
- version: '1'
138
+ version: 1.13.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: dry-monads
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.6'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.6'
153
153
  description: Easy to use client for event-sources applications written in ruby
154
154
  email:
155
155
  - sebastian@driggl.com
@@ -221,10 +221,12 @@ files:
221
221
  - lib/event_store_client/data_encryptor.rb
222
222
  - lib/event_store_client/deserialized_event.rb
223
223
  - lib/event_store_client/encryption_metadata.rb
224
+ - lib/event_store_client/errors.rb
224
225
  - lib/event_store_client/extensions/options_extension.rb
225
226
  - lib/event_store_client/mapper.rb
226
227
  - lib/event_store_client/mapper/default.rb
227
228
  - lib/event_store_client/mapper/encrypted.rb
229
+ - lib/event_store_client/rspec/has_option_matcher.rb
228
230
  - lib/event_store_client/serialized_event.rb
229
231
  - lib/event_store_client/serializer/event_deserializer.rb
230
232
  - lib/event_store_client/serializer/event_serializer.rb
@@ -244,14 +246,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
244
246
  requirements:
245
247
  - - ">="
246
248
  - !ruby/object:Gem::Version
247
- version: '0'
249
+ version: 3.0.0
248
250
  required_rubygems_version: !ruby/object:Gem::Requirement
249
251
  requirements:
250
252
  - - ">="
251
253
  - !ruby/object:Gem::Version
252
254
  version: '0'
253
255
  requirements: []
254
- rubygems_version: 3.3.7
256
+ rubygems_version: 3.4.10
255
257
  signing_key:
256
258
  specification_version: 4
257
259
  summary: Ruby integration for https://eventstore.org