event_store_client 2.0.4 → 2.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +0 -4
  4. data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +0 -4
  5. data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +1 -32
  6. data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +0 -3
  7. data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +0 -3
  8. data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +2 -2
  9. data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +0 -3
  10. data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +0 -3
  11. data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +0 -2
  12. data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +0 -2
  13. data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +0 -2
  14. data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +2 -1
  15. data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +4 -3
  16. data/lib/event_store_client/adapters/grpc.rb +3 -1
  17. data/lib/event_store_client/data_decryptor.rb +5 -2
  18. data/lib/event_store_client/deserialized_event.rb +9 -2
  19. data/lib/event_store_client/mapper/default.rb +12 -27
  20. data/lib/event_store_client/mapper/encrypted.rb +38 -62
  21. data/lib/event_store_client/serialized_event.rb +43 -0
  22. data/lib/event_store_client/serializer/event_deserializer.rb +74 -0
  23. data/lib/event_store_client/serializer/event_serializer.rb +69 -0
  24. data/lib/event_store_client/serializer/json.rb +20 -11
  25. data/lib/event_store_client/version.rb +1 -1
  26. data/lib/event_store_client.rb +4 -1
  27. metadata +19 -32
  28. data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +0 -52
  29. data/lib/event_store_client/event.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 857f587d240bbcb824b885cb5fa2c0b077351fcb12dead2d074ba23036252460
4
- data.tar.gz: fa56d9d29c29faf05ddc1b20d9de6f1aec46d9d80a330184131b5902238f3310
3
+ metadata.gz: 215ed29dc778024dfc7122cc295ec29e112284f67cb3ed4e74960d47a1e0a32e
4
+ data.tar.gz: 0be0bb78dfe0ce97fb3d171a9d145d82091e81978944c1dfb891d2609df12356
5
5
  SHA512:
6
- metadata.gz: c584fa5f7817f4631a6f73e91f7a2ee99b153e49b9b56980317f4a61f4d87ae197037b56e204f6f71729006f5c493404510136f131fa8efcca3c1b150cc18c5a
7
- data.tar.gz: 3f995f97559e9b305eac4d0cf7ba0bbef9c6e85a399a2109385f9cab03669791e3a2915c5a3b7564b3956f2d216370dcd8150814a38001a5219994873a8d0c3a
6
+ metadata.gz: ba84c9bd5b6148f4c8eaa4332e4a264364edcdf2642ef680a403a29d0406306e19db1f70422e711601ffe349df50c8b54a458f201d45288f711864aa6823c683
7
+ data.tar.gz: d2932967bcd5018fe330b0923231ee83062c3a8a713bcd6e6e92d8ddfa5bd633609415dd6ecf6b76d479d115b34cdd02a4de3f3f8b5c57a4010e29b40e0d8b6e
data/README.md CHANGED
@@ -44,6 +44,10 @@ See documentation chapters for the usage reference:
44
44
  - [Deleting streams](docs/deleting_streams.md)
45
45
  - [Encrypting events](docs/encrypting_events.md)
46
46
 
47
+ ### Subscriptions
48
+
49
+ We have written a gem that helps you to manage and handle Catch-up Subscriptions - `event_store_subscription`. You could check it [here](https://github.com/yousty/event_store_subscriptions).
50
+
47
51
  ## Contributing
48
52
 
49
53
  Do you want to contribute? Welcome!
@@ -2,10 +2,6 @@
2
2
 
3
3
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
4
4
 
5
- require 'event_store_client/adapters/grpc/generated/shared_pb'
6
- require 'event_store_client/adapters/grpc/generated/gossip_pb'
7
- require 'event_store_client/adapters/grpc/generated/gossip_services_pb'
8
-
9
5
  module EventStoreClient
10
6
  module GRPC
11
7
  module Cluster
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/shared_pb'
4
- require 'event_store_client/adapters/grpc/generated/gossip_pb'
5
- require 'event_store_client/adapters/grpc/generated/gossip_services_pb'
6
-
7
3
  module EventStoreClient
8
4
  module GRPC
9
5
  module Commands
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/streams_pb'
4
- require 'event_store_client/adapters/grpc/generated/streams_services_pb'
5
-
6
3
  module EventStoreClient
7
4
  module GRPC
8
5
  module Commands
@@ -11,8 +8,6 @@ module EventStoreClient
11
8
  use_request EventStore::Client::Streams::AppendReq
12
9
  use_service EventStore::Client::Streams::Streams::Stub
13
10
 
14
- ALLOWED_EVENT_METADATA = %w[type content-type created_at].freeze
15
-
16
11
  # @api private
17
12
  # @see {EventStoreClient::GRPC::Client#append_to_stream}
18
13
  def call(stream_name, event, options:, &blk)
@@ -26,8 +21,6 @@ module EventStoreClient
26
21
  service.append(payload, metadata: metadata)
27
22
  end
28
23
  validate_response(response)
29
- rescue ::GRPC::Unavailable => e
30
- Failure(e)
31
24
  end
32
25
 
33
26
  private
@@ -36,31 +29,7 @@ module EventStoreClient
36
29
  # @return [EventStore::Client::Streams::AppendReq::ProposedMessage]
37
30
  def proposed_message(event)
38
31
  serialized_event = config.mapper.serialize(event)
39
- event_metadata = JSON.parse(serialized_event.metadata)
40
- custom_metadata = custom_metadata(serialized_event.type, event_metadata)
41
- opts =
42
- {
43
- id: {
44
- string: serialized_event.id
45
- },
46
- data: serialized_event.data.b,
47
- custom_metadata: custom_metadata.to_json,
48
- metadata: event_metadata.slice(*ALLOWED_EVENT_METADATA)
49
- }
50
- EventStore::Client::Streams::AppendReq::ProposedMessage.new(opts)
51
- end
52
-
53
- # @param event_type [String]
54
- # @param event_metadata [Hash]
55
- # @return [Hash]
56
- def custom_metadata(event_type, event_metadata)
57
- {
58
- type: event_type,
59
- created_at: Time.now.utc,
60
- encryption: event_metadata['encryption'],
61
- 'content-type': event_metadata['content-type'],
62
- transaction: event_metadata['transaction']
63
- }.compact
32
+ EventStore::Client::Streams::AppendReq::ProposedMessage.new(serialized_event.to_grpc)
64
33
  end
65
34
 
66
35
  # @param stream_name [String]
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/streams_pb'
4
- require 'event_store_client/adapters/grpc/generated/streams_services_pb'
5
-
6
3
  module EventStoreClient
7
4
  module GRPC
8
5
  module Commands
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/streams_pb'
4
- require 'event_store_client/adapters/grpc/generated/streams_services_pb'
5
-
6
3
  module EventStoreClient
7
4
  module GRPC
8
5
  module Commands
@@ -8,8 +8,8 @@ module EventStoreClient
8
8
  # @see {EventStoreClient::GRPC::Client#hard_delete_stream}
9
9
  def call(stream_name, event, options:, &blk)
10
10
  append_cmd = Append.new(**connection_options)
11
- link_event = EventStoreClient::DeserializedEvent.new(
12
- id: event.id, type: '$>', data: event.title
11
+ link_event = DeserializedEvent.new(
12
+ id: event.id, type: DeserializedEvent::LINK_TYPE, data: event.title
13
13
  )
14
14
  append_cmd.call(stream_name, link_event, options: options, &blk)
15
15
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/streams_pb'
4
- require 'event_store_client/adapters/grpc/generated/streams_services_pb'
5
-
6
3
  module EventStoreClient
7
4
  module GRPC
8
5
  module Commands
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/streams_pb'
4
- require 'event_store_client/adapters/grpc/generated/streams_services_pb'
5
-
6
3
  module EventStoreClient
7
4
  module GRPC
8
5
  module Commands
@@ -2,8 +2,6 @@
2
2
 
3
3
  # rubocop:disable Metrics/AbcSize
4
4
 
5
- require 'event_store_client/adapters/grpc/generated/shared_pb'
6
-
7
5
  module EventStoreClient
8
6
  module GRPC
9
7
  module Options
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/shared_pb'
4
-
5
3
  module EventStoreClient
6
4
  module GRPC
7
5
  module Options
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'event_store_client/adapters/grpc/generated/shared_pb'
4
-
5
3
  module EventStoreClient
6
4
  module GRPC
7
5
  module Shared
@@ -6,6 +6,7 @@ module EventStoreClient
6
6
  module Streams
7
7
  class ProcessResponse
8
8
  include Dry::Monads[:result]
9
+ include Configuration
9
10
 
10
11
  # @api private
11
12
  # @param response [EventStore::Client::Streams::ReadResp]
@@ -18,7 +19,7 @@ module EventStoreClient
18
19
  return unless response.event&.event
19
20
 
20
21
  Success(
21
- EventDeserializer.new.call(response.event.event, skip_decryption: skip_decryption)
22
+ config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
22
23
  )
23
24
  end
24
25
  end
@@ -6,6 +6,7 @@ module EventStoreClient
6
6
  module Streams
7
7
  class ProcessResponses
8
8
  include Dry::Monads[:result]
9
+ include Configuration
9
10
 
10
11
  # @api private
11
12
  # @param responses [Array<EventStore::Client::Streams::ReadResp>]
@@ -17,12 +18,12 @@ module EventStoreClient
17
18
  return Success(responses) if skip_deserialization
18
19
 
19
20
  events =
20
- responses.map do |read_resp|
21
+ responses.map do |response|
21
22
  # It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
22
23
  # example. Such responses should be skipped. See generated files for more info.
23
- next unless read_resp.event&.event
24
+ next unless response.event&.event
24
25
 
25
- EventDeserializer.new.call(read_resp.event.event, skip_decryption: skip_decryption)
26
+ config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
26
27
  end
27
28
  Success(events.compact)
28
29
  end
@@ -3,10 +3,12 @@
3
3
  require 'grpc'
4
4
  require 'dry-monads'
5
5
 
6
+ # Load all generated by google-protobuf files
7
+ Dir[File.expand_path('grpc/generated/*.rb', __dir__)].each { |f| require f }
8
+
6
9
  require 'event_store_client/adapters/grpc/options/streams/read_options'
7
10
  require 'event_store_client/adapters/grpc/options/streams/write_options'
8
11
 
9
- require 'event_store_client/adapters/grpc/shared/event_deserializer'
10
12
  require 'event_store_client/adapters/grpc/shared/options/stream_options'
11
13
  require 'event_store_client/adapters/grpc/shared/options/filter_options'
12
14
  require 'event_store_client/adapters/grpc/shared/streams/process_response'
@@ -10,9 +10,11 @@ module EventStoreClient
10
10
 
11
11
  def call
12
12
  return encrypted_data if encryption_metadata.empty?
13
+ result = find_key(encryption_metadata['key'])
14
+ return encrypted_data unless result.success?
13
15
 
14
16
  decrypt_attributes(
15
- key: find_key(encryption_metadata['key']),
17
+ key: result.value!,
16
18
  data: encrypted_data,
17
19
  attributes: encryption_metadata['attributes']
18
20
  )
@@ -49,8 +51,9 @@ module EventStoreClient
49
51
  dupl
50
52
  end
51
53
 
54
+ # @return [Dry::Monads::Result]
52
55
  def find_key(identifier)
53
- key_repository.find(identifier).value!
56
+ key_repository.find(identifier)
54
57
  end
55
58
  end
56
59
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema'
4
-
5
3
  module EventStoreClient
6
4
  class DeserializedEvent
5
+ LINK_TYPE = '$>'
6
+
7
7
  InvalidDataError = Class.new(StandardError)
8
8
  private_constant :InvalidDataError
9
9
 
@@ -12,6 +12,7 @@ module EventStoreClient
12
12
 
13
13
  # @args [Hash] opts
14
14
  # @option opts [Boolean] :skip_validation
15
+ # @option opts [UUID] :id
15
16
  # @option opts [Hash] :data
16
17
  # @option opts [Hash] :metadata
17
18
  # @option opts [String] :type
@@ -68,6 +69,12 @@ module EventStoreClient
68
69
  end
69
70
  end
70
71
 
72
+ # Detect whether an event is a link event
73
+ # @return [Boolean]
74
+ def link?
75
+ type == LINK_TYPE
76
+ end
77
+
71
78
  private
72
79
 
73
80
  def validate(data)
@@ -1,47 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Layout/LineLength
4
+
3
5
  module EventStoreClient
4
6
  module Mapper
5
7
  class Default
6
8
  attr_reader :serializer
7
9
  private :serializer
8
10
 
11
+ # @param serializer [#serialize, #deserialize]
9
12
  def initialize(serializer: Serializer::Json)
10
13
  @serializer = serializer
11
14
  end
12
15
 
16
+ # @param event [EventStoreClient::DeserializedEvent]
17
+ # @return [EventStoreClient::SerializedEvent]
13
18
  def serialize(event)
14
- Event.new(
15
- id: event.respond_to?(:id) ? event.id : nil,
16
- type: (event.respond_to?(:type) ? event.type : nil) || event.class.to_s,
17
- data: serializer.serialize(event.data),
18
- metadata: serializer.serialize(event.metadata)
19
- )
19
+ Serializer::EventSerializer.call(event, serializer: serializer)
20
20
  end
21
21
 
22
- def deserialize(event, **)
23
- metadata = serializer.deserialize(event.metadata)
24
- data = serializer.deserialize(event.data)
22
+ # @param event_or_raw_event [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
23
+ # @return event [EventStoreClient::DeserializedEvent]
24
+ def deserialize(event_or_raw_event, **)
25
+ return event_or_raw_event if event_or_raw_event.is_a?(EventStoreClient::DeserializedEvent)
25
26
 
26
- event_class =
27
- begin
28
- Object.const_get(event.type)
29
- rescue NameError
30
- EventStoreClient.config.default_event_class
31
- end
32
- event_class.new(
33
- skip_validation: true,
34
- id: event.id,
35
- type: event.type,
36
- title: event.title,
37
- data: data,
38
- metadata: metadata,
39
- stream_revision: event.stream_revision,
40
- commit_position: event.commit_position,
41
- prepare_position: event.prepare_position,
42
- stream_name: event.stream_name
43
- )
27
+ Serializer::EventDeserializer.call(event_or_raw_event, serializer: serializer)
44
28
  end
45
29
  end
46
30
  end
47
31
  end
32
+ # rubocop:enable Layout/LineLength
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/AbcSize, Layout/LineLength, Style/IfUnlessModifier
4
+
3
5
  require 'event_store_client/encryption_metadata'
4
6
  require 'event_store_client/data_encryptor'
5
7
  require 'event_store_client/data_decryptor'
@@ -15,88 +17,62 @@ module EventStoreClient
15
17
  attr_reader :key_repository, :serializer
16
18
  private :key_repository, :serializer
17
19
 
18
- ##
19
- # Initializes the mapper with required dependencies. Accepts:
20
- # * +key_repoistory+ - repository stored encryption keys. Passed down to the +DataEncryptor+
21
- # * +serializer+ - object used to serialize data. By default JSON serializer is used.
20
+ # @param key_repository [#find, #create, #encrypt, #decrypt]
21
+ # See spec/support/dummy_repository.rb for the example of simple in-memory implementation
22
+ # @param serializer [#serialize, #deserialize]
22
23
  def initialize(key_repository, serializer: Serializer::Json)
23
24
  @key_repository = key_repository
24
25
  @serializer = serializer
25
26
  end
26
27
 
27
- ##
28
- # Encrypts the given event's subset of data.
29
- # Accepts specific event class instance with:
30
- # * +#data+ - hash with non-encrypted values.
31
- # * encryption_schema - hash with information which data to encrypt and
32
- # which key should be used as an identifier.
33
- # *Returns*: General +Event+ instance with encrypted data
28
+ # @param event [EventStoreClient::DeserializedEvent]
29
+ # @return [Hash]
34
30
  def serialize(event)
35
- # Links does not need to be encrypted
36
- return Default.new(serializer: serializer).serialize(event) if event.type == '$>'
31
+ # Links don't need to be encrypted
32
+ return Default.new(serializer: serializer).serialize(event) if event.link?
33
+
34
+ serialized = Serializer::EventSerializer.call(event, serializer: serializer)
35
+ encryption_schema =
36
+ if event.class.respond_to?(:encryption_schema)
37
+ event.class.encryption_schema
38
+ end
37
39
 
38
- encryption_schema = (
39
- event.class.respond_to?(:encryption_schema) &&
40
- event.class.encryption_schema
41
- )
42
40
  encryptor = EventStoreClient::DataEncryptor.new(
43
- data: event.data,
41
+ data: serialized.data,
44
42
  schema: encryption_schema,
45
43
  repository: key_repository
46
44
  )
47
45
  encryptor.call
48
- EventStoreClient::Event.new(
49
- id: event.respond_to?(:id) ? event.id : nil,
50
- data: serializer.serialize(encryptor.encrypted_data),
51
- metadata: serializer.serialize(
52
- event.metadata.merge(encryption: encryptor.encryption_metadata)
53
- ),
54
- type: event.class.to_s
55
- )
46
+ serialized.data = encryptor.encrypted_data
47
+ serialized.custom_metadata['encryption'] = encryptor.encryption_metadata
48
+ serialized
56
49
  end
57
50
 
58
- ##
59
51
  # Decrypts the given event's subset of data.
60
- # General +Event+ instance with encrypted data
61
- # * +#data+ - hash with encrypted values.
62
- # * encryption_metadata - hash with information which data to encrypt and
63
- # which key should be used as an identifier.
64
- # *Returns*: Specific event class with all data decrypted
65
- def deserialize(event, skip_decryption: false)
66
- metadata = serializer.deserialize(event.metadata)
67
- encryption_schema = metadata['encryption']
52
+ # @param event_or_raw_event [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
53
+ # @param skip_decryption [Boolean]
54
+ # @return event [EventStoreClient::DeserializedEvent]
55
+ def deserialize(event_or_raw_event, skip_decryption: false)
56
+ if skip_decryption
57
+ return Default.new(serializer: serializer).deserialize(event_or_raw_event)
58
+ end
68
59
 
69
- decrypted_data =
70
- if skip_decryption
71
- serializer.deserialize(event.data)
60
+ event =
61
+ if event_or_raw_event.is_a?(EventStoreClient::DeserializedEvent)
62
+ event_or_raw_event
72
63
  else
73
- EventStoreClient::DataDecryptor.new(
74
- data: serializer.deserialize(event.data),
75
- schema: encryption_schema,
76
- repository: key_repository
77
- ).call
78
- end
79
-
80
- event_class =
81
- begin
82
- Object.const_get(event.type)
83
- rescue NameError
84
- EventStoreClient.config.default_event_class
64
+ Serializer::EventDeserializer.call(event_or_raw_event, serializer: serializer)
85
65
  end
86
66
 
87
- event_class.new(
88
- skip_validation: true,
89
- id: event.id,
90
- type: event.type,
91
- title: event.title,
92
- data: decrypted_data,
93
- metadata: metadata,
94
- stream_revision: event.stream_revision,
95
- commit_position: event.commit_position,
96
- prepare_position: event.prepare_position,
97
- stream_name: event.stream_name
98
- )
67
+ decrypted_data =
68
+ EventStoreClient::DataDecryptor.new(
69
+ data: event.data,
70
+ schema: event.metadata['encryption'],
71
+ repository: key_repository
72
+ ).call
73
+ event.class.new(**event.to_h.merge(data: decrypted_data, skip_validation: true))
99
74
  end
100
75
  end
101
76
  end
102
77
  end
78
+ # rubocop:enable Metrics/AbcSize, Layout/LineLength, Style/IfUnlessModifier
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ class SerializedEvent
5
+ include Extensions::OptionsExtension
6
+
7
+ option(:id)
8
+ option(:data)
9
+ option(:custom_metadata)
10
+ option(:metadata)
11
+ option(:serializer)
12
+
13
+ # Constructs a hash that can be passed directly in the proposed_message attribute of the append
14
+ # request, or it can be used to instantiate the raw EventStore event.
15
+ # Example:
16
+ # ```ruby
17
+ # serialized_event = EventStoreClient::SerializedEvent.new(
18
+ # id: 'some id',
19
+ # data: { foo: :bar },
20
+ # custom_metadata: { bar: :baz },
21
+ # metadata: { baz: :foo },
22
+ # serializer: EventStoreClient::Serializer::Json
23
+ # )
24
+ # # Compute proposed_message
25
+ # EventStore::Client::Streams::AppendReq::ProposedMessage.new(
26
+ # serialized_event.to_grpc
27
+ # )
28
+ # # Compute raw event
29
+ # EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent.new(
30
+ # serialized_event.to_grpc
31
+ # )
32
+ # ```
33
+ # @return [Hash]
34
+ def to_grpc
35
+ {
36
+ id: { string: id },
37
+ data: serializer.serialize(data),
38
+ custom_metadata: serializer.serialize(custom_metadata),
39
+ metadata: metadata
40
+ }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/AbcSize, Layout/LineLength
4
+
5
+ module EventStoreClient
6
+ module Serializer
7
+ class EventDeserializer
8
+ class << self
9
+ # @param raw_event [EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
10
+ # @param serializer [#serialize, #deserialize]
11
+ # @return [EventStoreClient::DeserializedEvent]
12
+ def call(raw_event, serializer: Serializer::Json)
13
+ new(serializer: serializer).call(raw_event)
14
+ end
15
+ end
16
+
17
+ attr_reader :serializer
18
+ private :serializer
19
+
20
+ # @param serializer [#serialize, #deserialize]
21
+ def initialize(serializer:)
22
+ @serializer = serializer
23
+ end
24
+
25
+ # @param raw_event [EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
26
+ # @return [EventStoreClient::DeserializedEvent]
27
+ def call(raw_event)
28
+ data = serializer.deserialize(normalize_serialized(raw_event.data))
29
+ custom_metadata = serializer.deserialize(normalize_serialized(raw_event.custom_metadata))
30
+ metadata = custom_metadata.merge(raw_event.metadata.to_h)
31
+
32
+ event_class(metadata['type']).new(
33
+ skip_validation: true,
34
+ id: raw_event.id.string,
35
+ title: "#{raw_event.stream_revision}@#{raw_event.stream_identifier.stream_name}",
36
+ type: metadata['type'],
37
+ data: data,
38
+ metadata: metadata,
39
+ stream_revision: raw_event.stream_revision,
40
+ commit_position: raw_event.commit_position,
41
+ prepare_position: raw_event.prepare_position,
42
+ stream_name: raw_event.stream_identifier.stream_name
43
+ )
44
+ end
45
+
46
+ private
47
+
48
+ # @param event_type [String]
49
+ # @return [Class<EventStoreClient::DeserializedEvent>]
50
+ def event_class(event_type)
51
+ Object.const_get(event_type)
52
+ rescue NameError, TypeError
53
+ config.logger&.debug(<<~TEXT.strip)
54
+ Unable to resolve class by `#{event_type}' event type. \
55
+ Picking default `#{config.default_event_class}' event class to instantiate the event.
56
+ TEXT
57
+ config.default_event_class
58
+ end
59
+
60
+ # @param raw_data [String]
61
+ # @return [String]
62
+ def normalize_serialized(raw_data)
63
+ return serializer.serialize({}) if raw_data.empty?
64
+
65
+ raw_data
66
+ end
67
+
68
+ def config
69
+ EventStoreClient.config
70
+ end
71
+ end
72
+ end
73
+ end
74
+ # rubocop:enable Metrics/AbcSize, Layout/LineLength
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module Serializer
5
+ class EventSerializer
6
+ ALLOWED_EVENT_METADATA = %w[type content-type created_at].freeze
7
+
8
+ class << self
9
+ # @param event [EventStoreClient::DeserializedEvent]
10
+ # @param serializer [#serialize, #deserialize]
11
+ # @return [EventStoreClient::SerializedEvent]
12
+ def call(event, serializer: Serializer::Json)
13
+ new(serializer: serializer).call(event)
14
+ end
15
+ end
16
+
17
+ attr_reader :serializer
18
+ private :serializer
19
+
20
+ # @param serializer [#serialize, #deserialize]
21
+ def initialize(serializer:)
22
+ @serializer = serializer
23
+ end
24
+
25
+ # @param event [EventStoreClient::DeserializedEvent]
26
+ # @return [EventStoreClient::SerializedEvent]
27
+ def call(event)
28
+ event_metadata = metadata(event)
29
+ event_custom_metadata = custom_metadata(event, event_metadata)
30
+ SerializedEvent.new(
31
+ id: event.id || SecureRandom.uuid,
32
+ data: data(event),
33
+ custom_metadata: event_custom_metadata,
34
+ metadata: event_metadata.slice(*ALLOWED_EVENT_METADATA),
35
+ serializer: serializer
36
+ )
37
+ end
38
+
39
+ private
40
+
41
+ # @param event [EventStoreClient::DeserializedEvent]
42
+ # @return [Hash]
43
+ def metadata(event)
44
+ metadata = serializer.deserialize(serializer.serialize(event.metadata))
45
+ metadata['created_at'] ||= Time.now.utc.to_s
46
+ metadata
47
+ end
48
+
49
+ # @param event [EventStoreClient::DeserializedEvent]
50
+ # @param metadata [Hash]
51
+ # @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)
56
+ end
57
+
58
+ # @param event [EventStoreClient::DeserializedEvent]
59
+ # @return [Hash, String]
60
+ def data(event)
61
+ # Link events are special events. They contain special string value which shouldn't be
62
+ # serialized.
63
+ return event.data if event.link?
64
+
65
+ serializer.deserialize(serializer.serialize(event.data))
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Serializer
4
- module Json
5
- def self.deserialize(data)
6
- return data if data.is_a?(Hash)
3
+ module EventStoreClient
4
+ module Serializer
5
+ module Json
6
+ # @param data [String, Hash]
7
+ # @return [Hash]
8
+ def self.deserialize(data)
9
+ return data if data.is_a?(Hash)
7
10
 
8
- JSON.parse(data)
9
- rescue JSON::ParserError
10
- { 'message' => data }
11
- end
11
+ result = JSON.parse(data)
12
+ return result if result.is_a?(Hash)
13
+
14
+ { 'message' => result }
15
+ rescue JSON::ParserError
16
+ { 'message' => data }
17
+ end
12
18
 
13
- def self.serialize(data)
14
- return data if data.is_a?(String)
19
+ # @param data [String, Object]
20
+ # @return [String]
21
+ def self.serialize(data)
22
+ return data if data.is_a?(String)
15
23
 
16
- JSON.generate(data)
24
+ JSON.generate(data)
25
+ end
17
26
  end
18
27
  end
19
28
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EventStoreClient
4
- VERSION = '2.0.4'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -3,9 +3,12 @@
3
3
  module EventStoreClient
4
4
  end
5
5
 
6
+ require 'json'
6
7
  require 'event_store_client/types'
7
8
 
8
9
  require 'event_store_client/serializer/json'
10
+ require 'event_store_client/serializer/event_serializer'
11
+ require 'event_store_client/serializer/event_deserializer'
9
12
 
10
13
  require 'event_store_client/mapper'
11
14
 
@@ -15,8 +18,8 @@ require 'event_store_client/utils'
15
18
 
16
19
  require 'event_store_client/connection/url'
17
20
  require 'event_store_client/connection/url_parser'
18
- require 'event_store_client/event'
19
21
  require 'event_store_client/deserialized_event'
22
+ require 'event_store_client/serialized_event'
20
23
  require 'event_store_client/configuration'
21
24
 
22
25
  require 'event_store_client/adapters/grpc'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_store_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 2.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-10-27 00:00:00.000000000 Z
11
+ date: 2022-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable
@@ -38,34 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1'
41
- - !ruby/object:Gem::Dependency
42
- name: dry-schema
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1'
55
- - !ruby/object:Gem::Dependency
56
- name: dry-struct
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '1'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1'
69
41
  - !ruby/object:Gem::Dependency
70
42
  name: grpc
71
43
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +150,20 @@ dependencies:
178
150
  - - "~>"
179
151
  - !ruby/object:Gem::Version
180
152
  version: 0.9.5
153
+ - !ruby/object:Gem::Dependency
154
+ name: dry-schema
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1'
181
167
  description: Easy to use client for event-sources applications written in ruby
182
168
  email:
183
169
  - sebastian@driggl.com
@@ -240,7 +226,6 @@ files:
240
226
  - lib/event_store_client/adapters/grpc/generated/users_services_pb.rb
241
227
  - lib/event_store_client/adapters/grpc/options/streams/read_options.rb
242
228
  - lib/event_store_client/adapters/grpc/options/streams/write_options.rb
243
- - lib/event_store_client/adapters/grpc/shared/event_deserializer.rb
244
229
  - lib/event_store_client/adapters/grpc/shared/options/filter_options.rb
245
230
  - lib/event_store_client/adapters/grpc/shared/options/stream_options.rb
246
231
  - lib/event_store_client/adapters/grpc/shared/streams/process_response.rb
@@ -252,11 +237,13 @@ files:
252
237
  - lib/event_store_client/data_encryptor.rb
253
238
  - lib/event_store_client/deserialized_event.rb
254
239
  - lib/event_store_client/encryption_metadata.rb
255
- - lib/event_store_client/event.rb
256
240
  - lib/event_store_client/extensions/options_extension.rb
257
241
  - lib/event_store_client/mapper.rb
258
242
  - lib/event_store_client/mapper/default.rb
259
243
  - lib/event_store_client/mapper/encrypted.rb
244
+ - lib/event_store_client/serialized_event.rb
245
+ - lib/event_store_client/serializer/event_deserializer.rb
246
+ - lib/event_store_client/serializer/event_serializer.rb
260
247
  - lib/event_store_client/serializer/json.rb
261
248
  - lib/event_store_client/types.rb
262
249
  - lib/event_store_client/utils.rb
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # rubocop:disable Metrics/AbcSize
4
-
5
- module EventStoreClient
6
- module GRPC
7
- module Shared
8
- class EventDeserializer
9
- include Configuration
10
-
11
- # @param raw_event [
12
- # Array<EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent>,
13
- # Array<EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent>
14
- # ]
15
- # @param skip_decryption [Boolean]
16
- # @return [EventStoreClient::DeserializedEvent]
17
- def call(raw_event, skip_decryption: false)
18
- data = normalize_serialized(raw_event.data)
19
- custom_metadata = normalize_serialized(raw_event.custom_metadata)
20
-
21
- metadata =
22
- JSON.parse(custom_metadata).merge(raw_event.metadata.to_h).to_json
23
-
24
- event = EventStoreClient::Event.new(
25
- id: raw_event.id.string,
26
- title: "#{raw_event.stream_revision}@#{raw_event.stream_identifier.stream_name}",
27
- type: raw_event.metadata['type'],
28
- data: data,
29
- metadata: metadata,
30
- stream_revision: raw_event.stream_revision,
31
- commit_position: raw_event.commit_position,
32
- prepare_position: raw_event.prepare_position,
33
- stream_name: raw_event.stream_identifier.stream_name
34
- )
35
-
36
- config.mapper.deserialize(event, skip_decryption: skip_decryption)
37
- end
38
-
39
- private
40
-
41
- # @param raw_data [String, nil]
42
- # @return [String]
43
- def normalize_serialized(raw_data)
44
- return {}.to_json if raw_data.nil? || raw_data.empty?
45
-
46
- raw_data
47
- end
48
- end
49
- end
50
- end
51
- end
52
- # rubocop:enable Metrics/AbcSize
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry-struct'
4
- require 'securerandom'
5
- require 'json'
6
-
7
- module EventStoreClient
8
- class Event < Dry::Struct
9
- attribute :id, Types::Strict::String.optional.default(nil)
10
- attribute :type, Types::Strict::String
11
- attribute :title, Types::Strict::String.optional.default(nil)
12
- attribute :data, Types::Strict::String.default('{}')
13
- attribute :metadata, Types::Strict::String.default('{}')
14
- attribute :stream_name, Types::Strict::String.optional.default(nil)
15
- attribute :stream_revision, Types::Strict::Integer.optional.default(nil)
16
- attribute :commit_position, Types::Strict::Integer.optional.default(nil)
17
- attribute :prepare_position, Types::Strict::Integer.optional.default(nil)
18
-
19
- def initialize(args = {})
20
- args[:id] = SecureRandom.uuid if args[:id].nil?
21
- hash_meta = JSON.parse(args[:metadata] || '{}')
22
- hash_meta['created_at'] ||= Time.now
23
- args[:metadata] = JSON.generate(hash_meta)
24
- super(args)
25
- end
26
- end
27
- end