ruby_event_store 0.30.0 → 1.3.1

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +9 -1
  3. data/Makefile +15 -62
  4. data/lib/ruby_event_store/batch_enumerator.rb +15 -5
  5. data/lib/ruby_event_store/broker.rb +45 -0
  6. data/lib/ruby_event_store/client.rb +220 -130
  7. data/lib/ruby_event_store/composed_dispatcher.rb +24 -0
  8. data/lib/ruby_event_store/constants.rb +2 -0
  9. data/lib/ruby_event_store/correlated_commands.rb +42 -0
  10. data/lib/ruby_event_store/dispatcher.rb +20 -0
  11. data/lib/ruby_event_store/errors.rb +16 -16
  12. data/lib/ruby_event_store/event.rb +70 -14
  13. data/lib/ruby_event_store/expected_version.rb +2 -0
  14. data/lib/ruby_event_store/immediate_async_dispatcher.rb +17 -0
  15. data/lib/ruby_event_store/in_memory_repository.rb +45 -17
  16. data/lib/ruby_event_store/instrumented_dispatcher.rb +23 -0
  17. data/lib/ruby_event_store/instrumented_repository.rb +63 -0
  18. data/lib/ruby_event_store/link_by_metadata.rb +57 -0
  19. data/lib/ruby_event_store/mappers/default.rb +10 -26
  20. data/lib/ruby_event_store/mappers/encryption_key.rb +74 -0
  21. data/lib/ruby_event_store/mappers/encryption_mapper.rb +16 -0
  22. data/lib/ruby_event_store/mappers/forgotten_data.rb +30 -0
  23. data/lib/ruby_event_store/mappers/in_memory_encryption_key_repository.rb +34 -0
  24. data/lib/ruby_event_store/mappers/instrumented_mapper.rb +28 -0
  25. data/lib/ruby_event_store/mappers/json_mapper.rb +16 -0
  26. data/lib/ruby_event_store/mappers/null_mapper.rb +5 -8
  27. data/lib/ruby_event_store/mappers/pipeline.rb +31 -0
  28. data/lib/ruby_event_store/mappers/pipeline_mapper.rb +22 -0
  29. data/lib/ruby_event_store/mappers/protobuf.rb +13 -67
  30. data/lib/ruby_event_store/mappers/transformation/domain_event.rb +26 -0
  31. data/lib/ruby_event_store/mappers/transformation/encryption.rb +128 -0
  32. data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +24 -0
  33. data/lib/ruby_event_store/mappers/transformation/item.rb +56 -0
  34. data/lib/ruby_event_store/mappers/transformation/proto_event.rb +17 -0
  35. data/lib/ruby_event_store/mappers/transformation/protobuf_encoder.rb +30 -0
  36. data/lib/ruby_event_store/mappers/transformation/protobuf_nested_struct_metadata.rb +30 -0
  37. data/lib/ruby_event_store/mappers/transformation/serialization.rb +34 -0
  38. data/lib/ruby_event_store/mappers/transformation/serialized_record.rb +27 -0
  39. data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +24 -0
  40. data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +24 -0
  41. data/lib/ruby_event_store/metadata.rb +4 -2
  42. data/lib/ruby_event_store/projection.rb +34 -12
  43. data/lib/ruby_event_store/serialized_record.rb +3 -1
  44. data/lib/ruby_event_store/spec/broker_lint.rb +92 -0
  45. data/lib/ruby_event_store/spec/dispatcher_lint.rb +4 -36
  46. data/lib/ruby_event_store/spec/event_lint.rb +71 -0
  47. data/lib/ruby_event_store/spec/event_repository_lint.rb +1092 -962
  48. data/lib/ruby_event_store/spec/mapper_lint.rb +17 -0
  49. data/lib/ruby_event_store/spec/scheduler_lint.rb +9 -0
  50. data/lib/ruby_event_store/spec/subscriptions_lint.rb +111 -0
  51. data/lib/ruby_event_store/specification.rb +201 -56
  52. data/lib/ruby_event_store/specification_reader.rb +43 -0
  53. data/lib/ruby_event_store/specification_result.rb +212 -0
  54. data/lib/ruby_event_store/stream.rb +2 -0
  55. data/lib/ruby_event_store/subscriptions.rb +110 -0
  56. data/lib/ruby_event_store/transform_keys.rb +31 -0
  57. data/lib/ruby_event_store/version.rb +3 -1
  58. data/lib/ruby_event_store.rb +34 -4
  59. data/ruby_event_store.gemspec +1 -10
  60. metadata +47 -126
  61. data/exe/res-deprecated-read-api-migrator +0 -19
  62. data/lib/ruby_event_store/deprecated_read_api_rewriter.rb +0 -67
  63. data/lib/ruby_event_store/deprecated_read_api_runner.rb +0 -64
  64. data/lib/ruby_event_store/deprecations.rb +0 -7
  65. data/lib/ruby_event_store/pub_sub/broker.rb +0 -73
  66. data/lib/ruby_event_store/pub_sub/dispatcher.rb +0 -25
  67. data/lib/ruby_event_store/spec/event_broker_lint.rb +0 -211
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ class CorrelatedCommands
5
+
6
+ def initialize(event_store, command_bus)
7
+ @event_store = event_store
8
+ @command_bus = command_bus
9
+ end
10
+
11
+ class MiniEvent < Struct.new(:correlation_id, :message_id)
12
+ end
13
+
14
+ def call(command)
15
+ correlation_id = event_store.metadata[:correlation_id]
16
+ causation_id = event_store.metadata[:causation_id]
17
+
18
+ if correlation_id && causation_id
19
+ command.correlate_with(MiniEvent.new(
20
+ correlation_id,
21
+ causation_id,
22
+ )) if command.respond_to?(:correlate_with)
23
+ event_store.with_metadata(
24
+ causation_id: command.message_id,
25
+ ) do
26
+ command_bus.call(command)
27
+ end
28
+ else
29
+ event_store.with_metadata(
30
+ correlation_id: command.message_id,
31
+ causation_id: command.message_id,
32
+ ) do
33
+ command_bus.call(command)
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :event_store, :command_bus
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ class Dispatcher
5
+ def call(subscriber, event, _)
6
+ subscriber = subscriber.new if Class === subscriber
7
+ subscriber.call(event)
8
+ end
9
+
10
+ def verify(subscriber)
11
+ begin
12
+ subscriber_instance = Class === subscriber ? subscriber.new : subscriber
13
+ rescue ArgumentError
14
+ false
15
+ else
16
+ subscriber_instance.respond_to?(:call)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,25 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RubyEventStore
2
- WrongExpectedEventVersion = Class.new(StandardError)
3
- InvalidExpectedVersion = Class.new(StandardError)
4
- IncorrectStreamData = Class.new(StandardError)
5
- SubscriberNotExist = Class.new(StandardError)
6
- InvalidPageStart = Class.new(ArgumentError)
7
- InvalidPageSize = Class.new(ArgumentError)
8
- EventDuplicatedInStream = Class.new(StandardError)
9
- NotSupported = Class.new(StandardError)
10
- ReservedInternalName = Class.new(StandardError)
4
+ Error = Class.new(StandardError)
5
+ WrongExpectedEventVersion = Class.new(Error)
6
+ InvalidExpectedVersion = Class.new(Error)
7
+ IncorrectStreamData = Class.new(Error)
8
+ SubscriberNotExist = Class.new(Error)
9
+ InvalidPageStart = Class.new(Error)
10
+ InvalidPageStop = Class.new(Error)
11
+ InvalidPageSize = Class.new(Error)
12
+ EventDuplicatedInStream = Class.new(Error)
13
+ ReservedInternalName = Class.new(Error)
14
+ InvalidHandler = Class.new(Error)
15
+ ProtobufEncodingFailed = Class.new(Error)
11
16
 
12
- class EventNotFound < StandardError
17
+ class EventNotFound < Error
13
18
  attr_reader :event_id
19
+
14
20
  def initialize(event_id)
15
21
  super("Event not found: #{event_id}")
16
22
  @event_id = event_id
17
23
  end
18
24
  end
19
-
20
- class InvalidHandler < StandardError
21
- def initialize(object)
22
- super("#call method not found in #{object.inspect} subscriber. Are you sure it is a valid subscriber?")
23
- end
24
- end
25
25
  end
@@ -1,45 +1,76 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
 
3
5
  module RubyEventStore
6
+
7
+ # Data structure representing an event
4
8
  class Event
5
- def initialize(event_id: SecureRandom.uuid, metadata: nil, data: nil)
9
+ # Instantiates a new event
10
+ #
11
+ # @param event_id [String] event id
12
+ # @param data [Hash] event data which belong to your application domain
13
+ # @param metadata [Hash] event metadata which are technical and not
14
+ # part of your domain such as remote_ip, request_id, correlation_id,
15
+ # causation_id etc.
16
+ # @return [Event]
17
+ def initialize(event_id: SecureRandom.uuid, metadata: nil, data: {})
6
18
  @event_id = event_id.to_s
7
19
  @metadata = Metadata.new(metadata.to_h)
8
- @data = data.to_h
20
+ @data = data
9
21
  end
22
+
10
23
  attr_reader :event_id, :metadata, :data
11
24
 
25
+ # Event id
26
+ # @return [String]
12
27
  def message_id
13
28
  event_id
14
29
  end
15
30
 
16
- def type
31
+ # Type of event. Used when matching with subscribed handlers.
32
+ # @return [String]
33
+ def event_type
17
34
  self.class.name
18
35
  end
19
36
 
20
- def to_h
21
- {
22
- event_id: event_id,
23
- metadata: metadata.to_h,
24
- data: data,
25
- type: type,
26
- }
27
- end
28
-
37
+ # Timestamp from metadata
38
+ #
39
+ # @return [Time, nil]
29
40
  def timestamp
30
41
  metadata[:timestamp]
31
42
  end
32
43
 
44
+ # Two events are equal if:
45
+ # * they are of the same class
46
+ # * have identical event id
47
+ # * have identical data (verified with eql? method)
48
+ #
49
+ # @param other_event [Event, Object] object to compare
50
+ #
51
+ # Event equality ignores metadata!
52
+ # @return [TrueClass, FalseClass]
33
53
  def ==(other_event)
34
54
  other_event.instance_of?(self.class) &&
35
55
  other_event.event_id.eql?(event_id) &&
36
56
  other_event.data.eql?(data)
37
57
  end
38
58
 
59
+ # @private
39
60
  BIG_VALUE = 0b111111100100000010010010110011101011000101010101001100100110000
40
61
 
41
- # We don't use metadata because == does not use metadata
62
+ # Generates a Fixnum hash value for this object. This function
63
+ # have the property that a.eql?(b) implies a.hash == b.hash.
64
+ #
65
+ # The hash value is used along with eql? by the Hash class to
66
+ # determine if two objects reference the same hash key.
67
+ #
68
+ # This hash is based on
69
+ # * class
70
+ # * event_id
71
+ # * data
42
72
  def hash
73
+ # We don't use metadata because == does not use metadata
43
74
  [
44
75
  self.class,
45
76
  event_id,
@@ -47,27 +78,52 @@ module RubyEventStore
47
78
  ].hash ^ BIG_VALUE
48
79
  end
49
80
 
81
+ # Reads correlation_id from metadata.
82
+ # {http://railseventstore.org/docs/correlation_causation/ Find out more}
83
+ #
84
+ # @return [String, nil]
50
85
  def correlation_id
51
86
  metadata[:correlation_id]
52
87
  end
53
88
 
89
+ # Sets correlation_id in metadata.
90
+ # {http://railseventstore.org/docs/correlation_causation/ Find out more}
91
+ #
92
+ # @param val [String]
93
+ # @return [String]
54
94
  def correlation_id=(val)
55
95
  metadata[:correlation_id] = val
56
96
  end
57
97
 
98
+ # Reads causation_id from metadata.
99
+ # {http://railseventstore.org/docs/correlation_causation/ Find out more}
100
+ #
101
+ # @return [String, nil]
58
102
  def causation_id
59
103
  metadata[:causation_id]
60
104
  end
61
105
 
106
+ # Sets causation_id= in metadata.
107
+ # {http://railseventstore.org/docs/correlation_causation/ Find out more}
108
+ #
109
+ # @param val [String]
110
+ # @return [String]
62
111
  def causation_id=(val)
63
112
  metadata[:causation_id]= val
64
113
  end
65
114
 
115
+ # Sets correlation_id and causation_id in metadata based
116
+ # on correlation_id and message_id of the provided message.
117
+ # {http://railseventstore.org/docs/correlation_causation/ Find out more}
118
+ #
119
+ # @param other_message [Event, Proto, command] message to correlate with. Most likely an event or a command. Must respond to correlation_id and message_id.
120
+ # @return [String] set causation_id
66
121
  def correlate_with(other_message)
67
122
  self.correlation_id = other_message.correlation_id || other_message.message_id
68
123
  self.causation_id = other_message.message_id
124
+ self
69
125
  end
70
126
 
71
127
  alias_method :eql?, :==
72
128
  end
73
- end
129
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RubyEventStore
2
4
  class ExpectedVersion
3
5
  POSITION_DEFAULT = -1.freeze
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ class ImmediateAsyncDispatcher
5
+ def initialize(scheduler:)
6
+ @scheduler = scheduler
7
+ end
8
+
9
+ def call(subscriber, _, serialized_event)
10
+ @scheduler.call(subscriber, serialized_event)
11
+ end
12
+
13
+ def verify(subscriber)
14
+ @scheduler.verify(subscriber)
15
+ end
16
+ end
17
+ end
@@ -1,6 +1,6 @@
1
- require 'ostruct'
2
- require 'thread'
1
+ # frozen_string_literal: true
3
2
 
3
+ require 'ostruct'
4
4
  module RubyEventStore
5
5
  class InMemoryRepository
6
6
 
@@ -15,7 +15,7 @@ module RubyEventStore
15
15
  end
16
16
 
17
17
  def link_to_stream(event_ids, stream, expected_version)
18
- events = normalize_to_array(event_ids).map {|eid| read_event(eid)}
18
+ events = Array(event_ids).map {|eid| read_event(eid)}
19
19
  add_to_stream(events, expected_version, stream, nil)
20
20
  end
21
21
 
@@ -31,36 +31,64 @@ module RubyEventStore
31
31
  stream_of(stream.name).last
32
32
  end
33
33
 
34
- def read_event(event_id)
35
- global.find {|e| event_id.eql?(e.event_id)} or raise EventNotFound.new(event_id)
36
- end
37
-
38
34
  def read(spec)
39
- events = spec.global_stream? ? global : stream_of(spec.stream_name)
40
- events = events.reverse if spec.backward?
41
- events = events.drop(index_of(events, spec.start) + 1) unless spec.head?
42
- events = events[0...spec.count] if spec.limit?
35
+ events = read_scope(spec)
43
36
  if spec.batched?
44
37
  batch_reader = ->(offset, limit) { events.drop(offset).take(limit) }
45
38
  BatchEnumerator.new(spec.batch_size, events.size, batch_reader).each
39
+ elsif spec.first?
40
+ events.first
41
+ elsif spec.last?
42
+ events.last
46
43
  else
47
44
  events.each
48
45
  end
49
46
  end
50
47
 
48
+ def count(spec)
49
+ read_scope(spec).count
50
+ end
51
+
52
+ def update_messages(messages)
53
+ messages.each do |new_msg|
54
+ location = global.index{|m| new_msg.event_id.eql?(m.event_id)}
55
+ raise EventNotFound.new(new_msg.event_id) unless location
56
+ global[location] = new_msg
57
+ streams.values.each do |str|
58
+ location = str.index{|m| new_msg.event_id.eql?(m.event_id)}
59
+ str[location] = new_msg if location
60
+ end
61
+ end
62
+ end
63
+
64
+ def streams_of(event_id)
65
+ streams.select do |_, stream_events|
66
+ stream_events.any? { |event| event.event_id.eql?(event_id) }
67
+ end.map { |name, | Stream.new(name) }
68
+ end
69
+
51
70
  private
71
+ def read_scope(spec)
72
+ events = spec.stream.global? ? global : stream_of(spec.stream.name)
73
+ events = events.select{|e| spec.with_ids.any?{|x| x.eql?(e.event_id)}} if spec.with_ids?
74
+ events = events.select{|e| spec.with_types.any?{|x| x.eql?(e.event_type)}} if spec.with_types?
75
+ events = events.reverse if spec.backward?
76
+ events = events.drop(index_of(events, spec.start) + 1) if spec.start
77
+ events = events.take(index_of(events, spec.stop)) if spec.stop
78
+ events = events[0...spec.limit] if spec.limit?
79
+ events
80
+ end
52
81
 
53
- def stream_of(name)
54
- streams.fetch(name, Array.new)
82
+ def read_event(event_id)
83
+ global.find {|e| event_id.eql?(e.event_id)} or raise EventNotFound.new(event_id)
55
84
  end
56
85
 
57
- def normalize_to_array(events)
58
- return *events
86
+ def stream_of(name)
87
+ streams.fetch(name, Array.new)
59
88
  end
60
89
 
61
90
  def add_to_stream(events, expected_version, stream, include_global)
62
- events = normalize_to_array(events)
63
- append_with_synchronize(events, expected_version, stream, include_global)
91
+ append_with_synchronize(Array(events), expected_version, stream, include_global)
64
92
  end
65
93
 
66
94
  def last_stream_version(stream)
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ class InstrumentedDispatcher
5
+ def initialize(dispatcher, instrumentation)
6
+ @dispatcher = dispatcher
7
+ @instrumentation = instrumentation
8
+ end
9
+
10
+ def call(subscriber, event, serialized_event)
11
+ instrumentation.instrument("call.dispatcher.rails_event_store", event: event, subscriber: subscriber) do
12
+ dispatcher.call(subscriber, event, serialized_event)
13
+ end
14
+ end
15
+
16
+ def verify(subscriber)
17
+ dispatcher.verify(subscriber)
18
+ end
19
+
20
+ private
21
+ attr_reader :instrumentation, :dispatcher
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ class InstrumentedRepository
5
+ def initialize(repository, instrumentation)
6
+ @repository = repository
7
+ @instrumentation = instrumentation
8
+ end
9
+
10
+ def append_to_stream(events, stream, expected_version)
11
+ instrumentation.instrument("append_to_stream.repository.rails_event_store", events: events, stream: stream) do
12
+ repository.append_to_stream(events, stream, expected_version)
13
+ end
14
+ end
15
+
16
+ def link_to_stream(event_ids, stream, expected_version)
17
+ instrumentation.instrument("link_to_stream.repository.rails_event_store", event_ids: event_ids, stream: stream) do
18
+ repository.link_to_stream(event_ids, stream, expected_version)
19
+ end
20
+ end
21
+
22
+ def delete_stream(stream)
23
+ instrumentation.instrument("delete_stream.repository.rails_event_store", stream: stream) do
24
+ repository.delete_stream(stream)
25
+ end
26
+ end
27
+
28
+ def has_event?(event_id)
29
+ repository.has_event?(event_id)
30
+ end
31
+
32
+ def last_stream_event(stream)
33
+ repository.last_stream_event(stream)
34
+ end
35
+
36
+ def read(specification)
37
+ instrumentation.instrument("read.repository.rails_event_store", specification: specification) do
38
+ repository.read(specification)
39
+ end
40
+ end
41
+
42
+ def count(specification)
43
+ instrumentation.instrument("count.repository.rails_event_store", specification: specification) do
44
+ repository.count(specification)
45
+ end
46
+ end
47
+
48
+ def update_messages(messages)
49
+ instrumentation.instrument("update_messages.repository.rails_event_store", messages: messages) do
50
+ repository.update_messages(messages)
51
+ end
52
+ end
53
+
54
+ def streams_of(event_id)
55
+ instrumentation.instrument("streams_of.repository.rails_event_store", event_id: event_id) do
56
+ repository.streams_of(event_id)
57
+ end
58
+ end
59
+
60
+ private
61
+ attr_reader :repository, :instrumentation
62
+ end
63
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ class LinkByMetadata
5
+
6
+ def initialize(event_store:, key:, prefix: nil)
7
+ @event_store = event_store
8
+ @key = key
9
+ @prefix = prefix || ["$by", key, nil].join("_")
10
+ end
11
+
12
+ def call(event)
13
+ return unless event.metadata.has_key?(@key)
14
+
15
+ @event_store.link(
16
+ [event.event_id],
17
+ stream_name: "#{@prefix}#{event.metadata.fetch(@key)}"
18
+ )
19
+ end
20
+
21
+ end
22
+
23
+ class LinkByCorrelationId < LinkByMetadata
24
+ def initialize(event_store:, prefix: nil)
25
+ super(
26
+ event_store: event_store,
27
+ prefix: prefix,
28
+ key: :correlation_id,
29
+ )
30
+ end
31
+ end
32
+
33
+ class LinkByCausationId < LinkByMetadata
34
+ def initialize(event_store:, prefix: nil)
35
+ super(
36
+ event_store: event_store,
37
+ prefix: prefix,
38
+ key: :causation_id,
39
+ )
40
+ end
41
+ end
42
+
43
+ class LinkByEventType
44
+ def initialize(event_store:, prefix: nil)
45
+ @event_store = event_store
46
+ @prefix = prefix || "$by_type_"
47
+ end
48
+
49
+ def call(event)
50
+ @event_store.link(
51
+ [event.event_id],
52
+ stream_name: "#{@prefix}#{event.event_type}"
53
+ )
54
+ end
55
+ end
56
+
57
+ end
@@ -1,35 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module RubyEventStore
4
6
  module Mappers
5
- class Default
7
+ class Default < PipelineMapper
6
8
  def initialize(serializer: YAML, events_class_remapping: {})
7
- @serializer = serializer
8
- @events_class_remapping = events_class_remapping
9
- end
10
-
11
- def event_to_serialized_record(domain_event)
12
- SerializedRecord.new(
13
- event_id: domain_event.event_id,
14
- metadata: serializer.dump(domain_event.metadata.to_h),
15
- data: serializer.dump(domain_event.data),
16
- event_type: domain_event.class.name
17
- )
18
- end
19
-
20
- def serialized_record_to_event(record)
21
- event_type = events_class_remapping.fetch(record.event_type) { record.event_type }
22
- Object.const_get(event_type).new(
23
- event_id: record.event_id,
24
- metadata: serializer.load(record.metadata),
25
- data: serializer.load(record.data)
26
- )
9
+ super(Pipeline.new(
10
+ transformations: [
11
+ Transformation::EventClassRemapper.new(events_class_remapping),
12
+ Transformation::SymbolizeMetadataKeys.new,
13
+ Transformation::Serialization.new(serializer: serializer),
14
+ ]
15
+ ))
27
16
  end
28
-
29
- private
30
-
31
- attr_reader :serializer, :events_class_remapping
32
-
33
17
  end
34
18
  end
35
19
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module Mappers
5
+ class EncryptionKey
6
+ def initialize(cipher:, key:)
7
+ @cipher = cipher
8
+ @key = key
9
+ end
10
+
11
+ def encrypt(message, iv)
12
+ crypto = prepare_encrypt(cipher)
13
+ crypto.iv = iv
14
+ crypto.key = key
15
+
16
+ if crypto.authenticated?
17
+ encrypt_authenticated(crypto, message)
18
+ else
19
+ crypto.update(message) + crypto.final
20
+ end
21
+ end
22
+
23
+ def decrypt(message, iv)
24
+ crypto = prepare_decrypt(cipher)
25
+ crypto.iv = iv
26
+ crypto.key = key
27
+ ciphertext =
28
+ if crypto.authenticated?
29
+ ciphertext_from_authenticated(crypto, message)
30
+ else
31
+ message
32
+ end
33
+ (crypto.update(ciphertext) + crypto.final).force_encoding("UTF-8")
34
+ end
35
+
36
+ def random_iv
37
+ crypto = prepare_encrypt(cipher)
38
+ crypto.random_iv
39
+ end
40
+
41
+ attr_reader :cipher, :key
42
+
43
+ private
44
+
45
+ def ciphertext_from_authenticated(crypto, message)
46
+ prepare_auth_data(crypto)
47
+ crypto.auth_tag = message[-16...message.length]
48
+ message[0...-16]
49
+ end
50
+
51
+ def encrypt_authenticated(crypto, message)
52
+ prepare_auth_data(crypto)
53
+ crypto.update(message) + crypto.final + crypto.auth_tag
54
+ end
55
+
56
+ def prepare_auth_data(crypto)
57
+ crypto.auth_data = ""
58
+ crypto
59
+ end
60
+
61
+ def prepare_encrypt(cipher)
62
+ crypto = OpenSSL::Cipher.new(cipher)
63
+ crypto.encrypt
64
+ crypto
65
+ end
66
+
67
+ def prepare_decrypt(cipher)
68
+ crypto = OpenSSL::Cipher.new(cipher)
69
+ crypto.decrypt
70
+ crypto
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module Mappers
5
+ class EncryptionMapper < PipelineMapper
6
+ def initialize(key_repository, serializer: YAML, forgotten_data: ForgottenData.new)
7
+ super(Pipeline.new(
8
+ transformations: [
9
+ Transformation::Encryption.new(key_repository, serializer: serializer, forgotten_data: forgotten_data),
10
+ Transformation::Serialization.new(serializer: serializer),
11
+ ]
12
+ ))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module Mappers
5
+ class ForgottenData
6
+ FORGOTTEN_DATA = 'FORGOTTEN_DATA'.freeze
7
+
8
+ def initialize(string = FORGOTTEN_DATA)
9
+ @string = string
10
+ end
11
+
12
+ def to_s
13
+ @string
14
+ end
15
+
16
+ def ==(other)
17
+ @string == other
18
+ end
19
+ alias_method :eql?, :==
20
+
21
+ def method_missing(*)
22
+ self
23
+ end
24
+
25
+ def respond_to_missing?(*)
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end