├── .gitignore ├── .hound.yml ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console ├── rspec └── setup ├── evil_events.gemspec ├── gemfiles ├── with_native_extensions.gemfile └── without_native_extensions.gemfile ├── lib ├── evil_events.rb └── evil_events │ ├── application.rb │ ├── config.rb │ ├── config │ ├── adapters.rb │ └── types.rb │ ├── core.rb │ ├── core │ ├── activity_logger.rb │ ├── bootstrap.rb │ ├── broadcasting.rb │ ├── broadcasting │ │ ├── adapters.rb │ │ ├── adapters │ │ │ ├── memory_async.rb │ │ │ └── memory_sync.rb │ │ ├── dispatcher.rb │ │ ├── dispatcher │ │ │ └── mixin.rb │ │ ├── emitter.rb │ │ └── emitter │ │ │ └── adapter_proxy.rb │ ├── config.rb │ ├── events.rb │ ├── events │ │ ├── abstract_event.rb │ │ ├── event_extensions │ │ │ ├── class_signature.rb │ │ │ ├── class_signature │ │ │ │ ├── equalizer.rb │ │ │ │ └── signature.rb │ │ │ ├── dispatchable.rb │ │ │ ├── hookable.rb │ │ │ ├── hookable │ │ │ │ ├── abstract_hook.rb │ │ │ │ ├── after_emit_hook.rb │ │ │ │ ├── before_emit_hook.rb │ │ │ │ └── on_error_hook.rb │ │ │ ├── manageable.rb │ │ │ ├── metadata_extendable.rb │ │ │ ├── metadata_extendable │ │ │ │ └── abstract_metadata.rb │ │ │ ├── observable.rb │ │ │ ├── payloadable.rb │ │ │ ├── payloadable │ │ │ │ └── abstract_payload.rb │ │ │ ├── serializable.rb │ │ │ └── type_aliasing.rb │ │ ├── event_factory.rb │ │ ├── manager.rb │ │ ├── manager │ │ │ └── subscriber_list.rb │ │ ├── manager_factory.rb │ │ ├── manager_registry.rb │ │ ├── manager_registry │ │ │ └── scoped_event_type_matcher.rb │ │ ├── notifier.rb │ │ ├── notifier │ │ │ ├── abstract.rb │ │ │ ├── builder.rb │ │ │ ├── logging.rb │ │ │ ├── proxy.rb │ │ │ ├── sequential.rb │ │ │ ├── worker.rb │ │ │ └── worker │ │ │ │ ├── executor.rb │ │ │ │ └── job.rb │ │ ├── serializers.rb │ │ ├── serializers │ │ │ ├── base.rb │ │ │ ├── base │ │ │ │ ├── abstract_engine.rb │ │ │ │ ├── abstract_factory.rb │ │ │ │ ├── data_transformer.rb │ │ │ │ ├── event_serialization_state.rb │ │ │ │ └── generic_config.rb │ │ │ ├── hash.rb │ │ │ ├── hash │ │ │ │ ├── config.rb │ │ │ │ ├── engines.rb │ │ │ │ ├── engines │ │ │ │ │ └── native.rb │ │ │ │ ├── factory.rb │ │ │ │ ├── packer.rb │ │ │ │ └── unpacker.rb │ │ │ ├── json.rb │ │ │ ├── json │ │ │ │ ├── config.rb │ │ │ │ ├── engines.rb │ │ │ │ ├── engines │ │ │ │ │ └── native.rb │ │ │ │ ├── factory.rb │ │ │ │ ├── packer.rb │ │ │ │ └── unpacker.rb │ │ │ ├── message_pack.rb │ │ │ ├── message_pack │ │ │ │ ├── config.rb │ │ │ │ ├── engines.rb │ │ │ │ ├── engines │ │ │ │ │ └── .keep │ │ │ │ ├── factory.rb │ │ │ │ ├── packer.rb │ │ │ │ └── unpacker.rb │ │ │ ├── xml.rb │ │ │ └── xml │ │ │ │ ├── config.rb │ │ │ │ ├── engines.rb │ │ │ │ ├── engines │ │ │ │ └── .keep │ │ │ │ ├── factory.rb │ │ │ │ ├── packer.rb │ │ │ │ └── unpacker.rb │ │ ├── subscriber.rb │ │ └── subscriber │ │ │ └── mixin.rb │ ├── system.rb │ └── system │ │ ├── broadcaster.rb │ │ ├── event_builder.rb │ │ ├── event_manager.rb │ │ ├── mock.rb │ │ ├── mocking.rb │ │ └── type_manager.rb │ ├── dispatcher_mixin.rb │ ├── emitter.rb │ ├── error.rb │ ├── event.rb │ ├── plugins.rb │ ├── plugins │ ├── mpacker_engine.rb │ ├── mpacker_engine │ │ ├── config.rb │ │ └── mpacker.rb │ ├── oj_engine.rb │ ├── oj_engine │ │ └── oj.rb │ ├── ox_engine.rb │ └── ox_engine │ │ └── ox.rb │ ├── serializer.rb │ ├── shared.rb │ ├── shared │ ├── any_config.rb │ ├── clonable_module_builder.rb │ ├── crypto.rb │ ├── delegator_resolver.rb │ ├── dependency_container.rb │ ├── extensions_mixin.rb │ ├── logger.rb │ ├── structure.rb │ ├── type_converter.rb │ ├── type_converter │ │ ├── converter.rb │ │ ├── converter_registry.rb │ │ └── type_builder.rb │ └── types.rb │ ├── subscriber_mixin.rb │ ├── types.rb │ └── version.rb └── spec ├── integration ├── configuration_spec.rb ├── event_broadcasting_spec.rb ├── event_creation_spec.rb ├── plugins │ ├── mpacker_engine_spec.rb │ ├── oj_engine_spec.rb │ └── ox_engine_spec.rb └── plugins_spec.rb ├── spec_helper.rb ├── support ├── application_state_metascopes.rb ├── shared_contexts.rb ├── shared_contexts │ └── event_system_context.rb ├── shared_examples.rb ├── shared_examples │ ├── dependency_container_interface.rb │ ├── event_dispatching_interface.rb │ ├── event_extensions │ │ ├── class_signature_interface.rb │ │ ├── dispatchable_interface.rb │ │ ├── hook_component_behaviour.rb │ │ ├── hookable_interface.rb │ │ ├── manageable_interface.rb │ │ ├── metadata_entity_component.rb │ │ ├── metadata_extendable_interface.rb │ │ ├── observable_interface.rb │ │ ├── payload_entity_component.rb │ │ ├── payloadable_interface.rb │ │ ├── serializable_interface.rb │ │ └── type_aliasing_interface.rb │ ├── event_serializers │ │ ├── generic_event_serialization_component.rb │ │ ├── hash_event_serialization_component.rb │ │ ├── json_event_serialization_component.rb │ │ ├── messagepack_event_serialization_component.rb │ │ └── xml_event_serialization_component.rb │ ├── event_subscriber_component.rb │ └── notifier │ │ ├── callbacks_invokation_interface.rb │ │ └── logging_interface.rb ├── spec_support.rb └── spec_support │ ├── dispatching_adapter_factories.rb │ ├── dumb_event_serializer.rb │ ├── event_factories.rb │ ├── event_manager_factories.rb │ ├── fake_data_generator.rb │ ├── notifier_factories.rb │ ├── null_logger.rb │ └── testing.rb └── units └── evil_events ├── application_spec.rb ├── config ├── adapters_spec.rb └── types_spec.rb ├── config_spec.rb ├── core ├── activity_logger_spec.rb ├── bootstrap_spec.rb ├── broadcasting │ ├── adapters │ │ ├── memory_async_spec.rb │ │ └── memory_sync_spec.rb │ ├── adapters_spec.rb │ ├── dispatcher │ │ └── mixin_spec.rb │ ├── dispatcher_spec.rb │ ├── emitter │ │ └── adapter_proxy_spec.rb │ └── emitter_spec.rb ├── config_spec.rb ├── events │ ├── abstract_event_spec.rb │ ├── event_extensions │ │ ├── class_signature │ │ │ ├── equalizer_spec.rb │ │ │ └── signature_spec.rb │ │ ├── class_signature_spec.rb │ │ ├── dispatchable_spec.rb │ │ ├── hookable │ │ │ ├── abstract_hook_spec.rb │ │ │ ├── after_emit_hook_spec.rb │ │ │ ├── before_emit_hook_spec.rb │ │ │ └── on_error_hook_spec.rb │ │ ├── hookable_spec.rb │ │ ├── manageable_spec.rb │ │ ├── metadata_extendable_spec.rb │ │ ├── observable_spec.rb │ │ ├── payloadable_spec.rb │ │ ├── serializable_spec.rb │ │ └── type_aliasing_spec.rb │ ├── event_factory_spec.rb │ ├── manager │ │ └── subscriber_list_spec.rb │ ├── manager_factory_spec.rb │ ├── manager_registry │ │ └── scoped_event_type_matcher_spec.rb │ ├── manager_registry_spec.rb │ ├── manager_spec.rb │ ├── notifier │ │ ├── builder_spec.rb │ │ ├── logging_spec.rb │ │ ├── proxy_spec.rb │ │ ├── sequential_spec.rb │ │ ├── worker │ │ │ ├── executor_spec.rb │ │ │ └── job_spec.rb │ │ └── worker_spec.rb │ ├── serializers │ │ ├── base │ │ │ └── event_serialization_state_spec.rb │ │ ├── hash │ │ │ └── engines │ │ │ │ └── native_spec.rb │ │ ├── hash_spec.rb │ │ ├── json │ │ │ └── engines │ │ │ │ └── native_spec.rb │ │ └── json_spec.rb │ ├── serializers_spec.rb │ ├── subscriber │ │ └── mixin_spec.rb │ └── subscriber_spec.rb ├── system │ ├── broadcaster_spec.rb │ ├── event_builder_spec.rb │ ├── event_manager_spec.rb │ ├── mock_spec.rb │ └── type_manager_spec.rb └── system_spec.rb ├── dispatcher_mixin_spec.rb ├── emitter_spec.rb ├── error_spec.rb ├── event_spec.rb ├── serializer_spec.rb ├── shared ├── any_config_spec.rb ├── clonable_module_builder_spec.rb ├── delegator_resolver_spec.rb ├── dependency_container_spec.rb ├── extensions_mixin_spec.rb ├── logger_spec.rb ├── structure_spec.rb ├── type_converter │ ├── converter_registry_spec.rb │ ├── converter_spec.rb │ └── type_builder_spec.rb ├── type_converter_spec.rb └── types_spec.rb ├── subscriber_mixin_spec.rb ├── types_spec.rb └── version_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | .rspec_status 11 | .ruby-version 12 | gemfiles/*.lock 13 | *.gem 14 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | config_file: .rubocop.yml 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | armitage-rubocop: 3 | - lib/rubocop.general.yml 4 | - lib/rubocop.rspec.yml 5 | 6 | AllCops: 7 | UseCache: true 8 | TargetRubyVersion: 2.6.2 9 | Include: 10 | - bin/console 11 | - bin/rspec 12 | - lib/**/*.rb 13 | - spec/**/*.rb 14 | - Gemfile 15 | - Rakefile 16 | - evil_events.gemspec 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | before_install: gem install bundler 4 | cache: bundler 5 | script: bundle exec rspec 6 | matrix: 7 | fast_finish: true 8 | include: 9 | - rvm: 2.3.8 10 | gemfile: gemfiles/with_native_extensions.gemfile 11 | env: TEST_NATIVE_EXTENSIONS=true 12 | - rvm: 2.4.6 13 | gemfile: gemfiles/with_native_extensions.gemfile 14 | env: TEST_NATIVE_EXTENSIONS=true 15 | - rvm: 2.5.3 16 | gemfile: gemfiles/with_native_extensions.gemfile 17 | env: TEST_NATIVE_EXTENSIONS=true 18 | - rvm: 2.6.3 19 | gemfile: gemfiles/with_native_extensions.gemfile 20 | env: TEST_NATIVE_EXTENSIONS=true 21 | - rvm: ruby-head 22 | gemfile: gemfiles/with_native_extensions.gemfile 23 | env: TEST_NATIVE_EXTENSIONS=true 24 | - rvm: 2.3.8 25 | gemfile: gemfiles/without_native_extensions.gemfile 26 | - rvm: 2.4.6 27 | gemfile: gemfiles/without_native_extensions.gemfile 28 | - rvm: 2.5.4 29 | gemfile: gemfiles/without_native_extensions.gemfile 30 | - rvm: 2.6.3 31 | gemfile: gemfiles/without_native_extensions.gemfile 32 | - rvm: ruby-head 33 | gemfile: gemfiles/without_native_extensions.gemfile 34 | - rvm: jruby-head 35 | gemfile: gemfiles/without_native_extensions.gemfile 36 | allow_failures: 37 | - rvm: ruby-head 38 | gemfile: gemfiles/without_native_extensions.gemfile 39 | - rvm: jruby-head 40 | gemfile: gemfiles/without_native_extensions.gemfile 41 | notifications: 42 | slack: 43 | secure: Uqeyyfn7s9dEoE3Af28lGxY7X/xZ45vEy5VB4gTZ+hLGhDsmK7hurpbo4sfonelVGwF0FYq7FVO82k4wbymdDZkU7L72sT+bK1Q16tpOgIewStzil07LCZ+UxXliD3l9txSAbdd26A4byk8K79wZ0SY1PWTmpSs5gaQ9QZPg9bM4Qr35u+FtJ9LGvn0YYQ6LXq5+GC+fb99CHVbzwhZSPZld6xIbRDpBawOzYUNQvQt96b6iutql03VVqN0NuGqj5UeYHEFWvPeCO0pNmVlWIL730wVfT+MOvU3QF05w2ybhgUBL8Gpsz6eQMnhNYh3rnvS8sZTSEVm3IrVNqTkSI2N6z8SfidEHcGdRo+wvtYRdolQ3SvWnQkG1SITPyA17NeIjn3Gd0vh3QKvN+3eVM9Ctiq/2KP1EYUqoalwpJsd2/H+OO3vFail/7efddQ3HzEN5fq8oXLjVpUHvaF5pWBMWmykO8o89XcyYVU7ZJQFT90Z9Dai/aiEcX9ylCrEqJBsHn9KtR0zATg88aKfYGx4wcEwykhjfXCAe92HIXWV/snP6OIoM8QdlxzPzOGswMVXrINhLvsUy4hlj7O/vlAZH3sThU+4o5yURJxqnWW/Qr5vvKCHl1ngCu1cMFRCg+3FaPuX4TPQ3G8Kz/NawWM/NQ3vC5hnU58s1Gy8kImk= 44 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --protected --private --markup markdown 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2024 Rustam Ibragimov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EvilEvents · [![Gem Version](https://badge.fury.io/rb/evil_events.svg)](https://badge.fury.io/rb/evil_events) [![Build Status](https://travis-ci.org/0exp/evil_events.svg?branch=master)](https://travis-ci.org/0exp/evil_events) [![Coverage Status](https://coveralls.io/repos/github/0exp/evil_events/badge.svg?branch=master)](https://coveralls.io/github/0exp/evil_events?branch=master) 2 | 3 | Coming soon... :smiling_imp: 4 | 5 | --- 6 | 7 | ## Installation 8 | 9 | ```ruby 10 | gem 'evil_events' 11 | ``` 12 | 13 | ```shell 14 | bundle install 15 | # --- or --- 16 | gem install evil_events 17 | ``` 18 | 19 | ```ruby 20 | require 'evil_events' 21 | ``` 22 | 23 | --- 24 | 25 | ## Contributing 26 | 27 | - Fork it ( https://github.com/0exp/evil_events/fork ) 28 | - Create your feature branch (`git checkout -b feature/my-new-feature`) 29 | - Commit your changes (`git commit -am 'Add some feature'`) 30 | - Push to the branch (`git push origin feature/my-new-feature`) 31 | - Create new Pull Request 32 | 33 | ## License 34 | 35 | Released under MIT License. 36 | 37 | ## Authors 38 | 39 | [Rustam Ibragimov](https://github.com/0exp) 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | require 'yard' 6 | 7 | RSpec::Core::RakeTask.new(:rspec) 8 | 9 | YARD::Rake::YardocTask.new(:doc) do |t| 10 | t.files = Dir[Pathname.new(__FILE__).join('../lib/**/*.rb')] 11 | t.options = %w[--protected --private] 12 | end 13 | 14 | task default: :rspec 15 | 16 | task yardoc: :doc do 17 | undocumented_code_objects = YARD::Registry.tap(&:load).select do |code_object| 18 | code_object.docstring.empty? 19 | end 20 | 21 | if undocumented_code_objects.empty? 22 | puts 'YARD COVERAGE [SUCCESS] => 100% documentation coverage!' 23 | else 24 | failing_code_objects = undocumented_code_objects.map do |code_object| 25 | "- #{code_object.class} => #{code_object}" 26 | end.join("\n") 27 | 28 | abort("YARD COVERAGE [FAILURE] => No documentation found for: \n #{failing_code_objects}") 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # frozen_string_literal: true 4 | 5 | require 'bundler/setup' 6 | require 'evil_events' 7 | 8 | # You can add fixtures and/or initialization code here to make experimenting 9 | # with your gem easier. You can also use a different console, if you like. 10 | 11 | require 'pry' 12 | Pry.start 13 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'pathname' 5 | require 'optparse' 6 | 7 | module EvilEventsSpecRunner 8 | GEMFILES = { 9 | with_native: File.expand_path( 10 | File.join('..', '..', 'gemfiles', 'with_native_extensions.gemfile'), 11 | Pathname.new(__FILE__).realpath 12 | ), 13 | 14 | without_naitve: File.expand_path( 15 | File.join('..', '..', 'gemfiles', 'without_native_extensions.gemfile'), 16 | Pathname.new(__FILE__).realpath 17 | ) 18 | }.freeze 19 | 20 | class << self 21 | def run! 22 | OptionParser.new do |opts| 23 | opts.banner = 'Usage: bin/rspec [options]' 24 | 25 | opts.on('-w', '--without-native-extensions', 'Test without native extensions') do 26 | run_without_native_extensions! 27 | end 28 | 29 | opts.on('-n', '--with-native-extensions', 'Test with native extensions') do 30 | run_with_native_extensions! 31 | end 32 | 33 | opts.on('-h', '--help', 'Show this message') do 34 | puts opts 35 | end 36 | end.parse! 37 | end 38 | 39 | private 40 | 41 | def run_with_native_extensions! 42 | ENV['TEST_NATIVE_EXTENSIONS'] = 'true' 43 | ENV['BUNDLE_GEMFILE'] = GEMFILES[:with_native] 44 | 45 | run_tests! 46 | end 47 | 48 | def run_without_native_extensions! 49 | ENV['BUNDLE_GEMFILE'] = GEMFILES[:without_native] 50 | 51 | run_tests! 52 | end 53 | 54 | def run_tests! 55 | require 'rubygems' 56 | require 'bundler/setup' 57 | load Gem.bin_path('rspec-core', 'rspec') 58 | end 59 | end 60 | end 61 | 62 | EvilEventsSpecRunner.run! 63 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /evil_events.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # frozen_string_literal: true 3 | 4 | lib = File.expand_path('lib', __dir__) 5 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 6 | require 'evil_events/version' 7 | 8 | Gem::Specification.new do |spec| 9 | spec.required_ruby_version = '>= 2.3.8' 10 | 11 | spec.name = 'evil_events' 12 | spec.version = EvilEvents::VERSION 13 | spec.authors = 'Rustam Ibragimov' 14 | spec.email = 'iamdaiver@icloud.com' 15 | spec.homepage = 'https://github.com/0exp/evil_events' 16 | spec.license = 'MIT' 17 | spec.summary = 'Event subsystem for ruby applications' 18 | spec.description = 'Ultra simple, but very flexible and fully customizable event subsystem ' \ 19 | 'for ruby applications with a wide set of customization interfaces ' \ 20 | 'and smart event definition DSL.' 21 | 22 | spec.bindir = 'bin' 23 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 24 | spec.require_paths = ['lib'] 25 | 26 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 27 | f.match(%r{^(spec|features)/}) 28 | end 29 | 30 | spec.add_dependency 'dry-monads', '~> 1.2.0' 31 | spec.add_dependency 'dry-types', '~> 0.14.0' 32 | spec.add_dependency 'dry-struct', '~> 0.6.0' 33 | spec.add_dependency 'dry-container', '~> 0.7.0' 34 | spec.add_dependency 'concurrent-ruby', '~> 1.0' 35 | spec.add_dependency 'symbiont-ruby', '~> 0.6.0' 36 | spec.add_dependency 'qonfig', '~> 0.10.0' 37 | 38 | spec.add_development_dependency 'coveralls', '~> 0.8.22' 39 | spec.add_development_dependency 'simplecov', '~> 0.16.1' 40 | spec.add_development_dependency 'rspec', '~> 3.8.0' 41 | spec.add_development_dependency 'armitage-rubocop', '~> 0.25.0' 42 | 43 | spec.add_development_dependency 'pry' 44 | spec.add_development_dependency 'rake' 45 | spec.add_development_dependency 'bundler' 46 | spec.add_development_dependency 'yard' 47 | end 48 | -------------------------------------------------------------------------------- /gemfiles/with_native_extensions.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'ox', '~> 2.10.0' 6 | gem 'oj', '~> 3.7.7' 7 | gem 'msgpack', '~> 1.2.6' 8 | 9 | gemspec path: '..' 10 | -------------------------------------------------------------------------------- /gemfiles/without_native_extensions.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec path: '..' 6 | -------------------------------------------------------------------------------- /lib/evil_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dry-container' 4 | require 'dry-struct' 5 | require 'dry-types' 6 | require 'concurrent/array' 7 | require 'concurrent/map' 8 | require 'symbiont' 9 | require 'securerandom' 10 | require 'forwardable' 11 | require 'qonfig' 12 | require 'logger' 13 | require 'json' 14 | 15 | # @api public 16 | # @since 0.1.0 17 | module EvilEvents 18 | require_relative 'evil_events/version' 19 | require_relative 'evil_events/shared' 20 | require_relative 'evil_events/types' 21 | require_relative 'evil_events/error' 22 | require_relative 'evil_events/core' 23 | require_relative 'evil_events/config' 24 | require_relative 'evil_events/event' 25 | require_relative 'evil_events/serializer' 26 | require_relative 'evil_events/emitter' 27 | require_relative 'evil_events/subscriber_mixin' 28 | require_relative 'evil_events/dispatcher_mixin' 29 | require_relative 'evil_events/application' 30 | require_relative 'evil_events/plugins' 31 | end 32 | -------------------------------------------------------------------------------- /lib/evil_events/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.2.0 6 | module Application 7 | class << self 8 | # @see EvilEvents::Core::System 9 | # @api public 10 | # @since 0.2.0 11 | def registered_events 12 | EvilEvents::Core::Bootstrap[:event_system].registered_events 13 | end 14 | 15 | # @see EvilEvents::Core::System 16 | # @api public 17 | # @since 0.3.0 18 | def restart_event_notifier 19 | EvilEvents::Core::Bootstrap[:event_system].restart_event_notifier 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/evil_events/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.1.0 6 | module Config 7 | require_relative 'config/adapters' 8 | require_relative 'config/types' 9 | 10 | class << self 11 | # @see EvilEvents::Core::Config 12 | # @api public 13 | # @since 0.1.0 14 | def options 15 | EvilEvents::Core::Bootstrap[:config].settings 16 | end 17 | 18 | # @see EvilEvents::Core::Config 19 | # @api public 20 | # @since 0.1.0 21 | def configure 22 | EvilEvents::Core::Bootstrap[:config].configure { |conf| yield(conf) if block_given? } 23 | end 24 | 25 | # @see EvilEvents::Config::Types 26 | # @api public 27 | # @since 0.2.0 28 | def setup_types 29 | yield(Config::Types) if block_given? 30 | end 31 | 32 | # @see EvilEvents::Config::Adapters 33 | # @api public 34 | # @since 0.2.0 35 | def setup_adapters 36 | yield(Config::Adapters) if block_given? 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/evil_events/config/adapters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Config 4 | # @api public 5 | # @since 0.2.0 6 | module Adapters 7 | class << self 8 | # @see EvilEvents::Core::System 9 | # @api public 10 | # @since 0.2.0 11 | def register(adapter_name, adapter_object) 12 | EvilEvents::Core::Bootstrap[:event_system].register_adapter(adapter_name, adapter_object) 13 | end 14 | 15 | # @see EvilEvents::Core::System 16 | # @api public 17 | # @since 0.2.0 18 | def resolve(adapter_name) 19 | EvilEvents::Core::Bootstrap[:event_system].resolve_adapter(adapter_name) 20 | end 21 | alias_method :[], :resolve 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/evil_events/config/types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Config 4 | # @api public 5 | # @since 0.2.0 6 | module Types 7 | class << self 8 | # @see EvilEvents::Core::System 9 | # @api public 10 | # @since 0.2.0 11 | def define_converter(type, &coercer) 12 | EvilEvents::Core::Bootstrap[:event_system].register_converter(type, coercer) 13 | end 14 | 15 | # @see EvilEvents::Core::System 16 | # @api public 17 | # @since 0.2.0 18 | def resolve_type(type, **options) 19 | EvilEvents::Core::Bootstrap[:event_system].resolve_type(type, **options) 20 | end 21 | alias_method :[], :resolve_type 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/evil_events/core.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api private 5 | # @since 0.1.0 6 | module Core 7 | require_relative 'core/activity_logger' 8 | require_relative 'core/broadcasting' 9 | require_relative 'core/events' 10 | require_relative 'core/config' 11 | require_relative 'core/system' 12 | require_relative 'core/bootstrap' 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/evil_events/core/activity_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core 4 | # @api private 5 | # @since 0.1.0 6 | class ActivityLogger 7 | class << self 8 | # @param activity [String, NilClass] 9 | # @param message [String, NilClass] 10 | # @return void 11 | # 12 | # @since 0.1.0 13 | def log(activity: nil, message: nil) 14 | progname = "[EvilEvents:#{activity}]" 15 | logger.add(logger.level, message, progname) 16 | end 17 | 18 | private 19 | 20 | # @return [Logger] 21 | # 22 | # @since 0.1.0 23 | def logger 24 | EvilEvents::Core::Bootstrap[:config].settings.logger 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/evil_events/core/bootstrap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core 4 | # @api private 5 | # @since 0.1.0 6 | class Bootstrap 7 | # @since 0.1.0 8 | extend EvilEvents::Shared::DependencyContainer::Mixin 9 | 10 | register(:event_system, memoize: true) { EvilEvents::Core::System.new } 11 | register(:config, memoize: true) { EvilEvents::Core::Config.new } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core 4 | # @api private 5 | # @since 0.1.0 6 | module Broadcasting 7 | require_relative 'broadcasting/dispatcher' 8 | require_relative 'broadcasting/dispatcher/mixin' 9 | require_relative 'broadcasting/adapters' 10 | require_relative 'broadcasting/adapters/memory_sync' 11 | require_relative 'broadcasting/adapters/memory_async' 12 | require_relative 'broadcasting/emitter' 13 | require_relative 'broadcasting/emitter/adapter_proxy' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting/adapters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Broadcasting 4 | # @api private 5 | # @since 0.1.0 6 | class Adapters 7 | include EvilEvents::Shared::DependencyContainer::Mixin 8 | 9 | # @return void 10 | # 11 | # @since 0.1.0 12 | def register_core_adapters! 13 | register(:memory_sync) { MemorySync } 14 | register(:memory_async) { MemoryAsync } 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting/adapters/memory_async.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Broadcasting 4 | class Adapters 5 | # @api public 6 | # @since 0.1.0 7 | module MemoryAsync 8 | # @since 0.1.0 9 | AsyncTask = ::Thread 10 | 11 | class << self 12 | # @since 0.1.0 13 | include Dispatcher::Mixin 14 | 15 | # @param event [EvilEvents::Core::Events::AbstractEvent] 16 | # @return void 17 | # 18 | # @since 0.1.0 19 | def call(event) 20 | AsyncTask.new { dispatch(event) } 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting/adapters/memory_sync.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Broadcasting 4 | class Adapters 5 | # @api public 6 | # @since 0.1.0 7 | module MemorySync 8 | class << self 9 | # @since 0.1.0 10 | include Dispatcher::Mixin 11 | 12 | # @param event [EvilEvents::Core::Events::AbstractEvent] 13 | # @return void 14 | # 15 | # @since 0.1.0 16 | def call(event) 17 | dispatch(event) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting/dispatcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Broadcasting 4 | # @api private 5 | # @since 0.1.0 6 | class Dispatcher # Broadcaster 7 | class << self 8 | # @param event [EvilEvents::Core::Events::AbstractEvent] 9 | # @return void 10 | # 11 | # @since 0.1.0 12 | def dispatch(event) # Broadcast 13 | EvilEvents::Core::Bootstrap[:event_system].manager_of_event(event).notify(event) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting/dispatcher/mixin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Broadcasting 4 | class Dispatcher 5 | # @api private 6 | # @since 0.1.0 7 | Mixin = EvilEvents::Shared::ClonableModuleBuilder.build do 8 | def dispatch(event) 9 | Dispatcher.dispatch(event) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting/emitter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Broadcasting 4 | # @api private 5 | # @since 0.1.0 6 | class Emitter 7 | # @param event [EvilEvents::Core::Events::AbstractEvent] 8 | # @option adapter [Symbol,NilClass] 9 | # @raise [EvilEvents::IncorrectEventForEmitError] 10 | # @return [void] 11 | # 12 | # @since 0.1.0 13 | def emit(event, adapter: nil) 14 | unless event.is_a?(EvilEvents::Core::Events::AbstractEvent) 15 | raise EvilEvents::IncorrectEventForEmitError 16 | end 17 | 18 | adapter_proxy = AdapterProxy.new(event, explicit_identifier: adapter) 19 | 20 | log_activity(event, adapter_proxy) 21 | adapter_proxy.broadcast! 22 | end 23 | 24 | # @param event_type [String] 25 | # @option id [NilClass,String] 26 | # @option payload [Hash] 27 | # @option metadata [Hash] 28 | # @option adapter [Symbol,NilClass] 29 | # @return [void] 30 | # 31 | # @since 0.1.0 32 | def raw_emit(event_type, id: nil, payload: {}, metadata: {}, adapter: nil) 33 | event_object = EvilEvents::Core::Bootstrap[:event_system].resolve_event_object( 34 | event_type, id: id, payload: payload, metadata: metadata 35 | ) 36 | 37 | emit(event_object, adapter: adapter) 38 | end 39 | 40 | private 41 | 42 | # @param event [EvilEvents::Core::Events::AbstractEvent] 43 | # @param adapter_proxy [EvilEvents::Core::Broadcasting::Emitter::AdapterProxy] 44 | # @return [void] 45 | # 46 | # @since 0.1.0 47 | def log_activity(event, adapter_proxy) 48 | activity = "EventEmitted(#{adapter_proxy.identifier})" 49 | message = "ID: #{event.id} :: " \ 50 | "TYPE: #{event.type} :: " \ 51 | "PAYLOAD: #{event.payload} :: " \ 52 | "METADATA: #{event.metadata}" 53 | 54 | EvilEvents::Core::ActivityLogger.log(activity: activity, message: message) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/evil_events/core/broadcasting/emitter/adapter_proxy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Broadcasting::Emitter 4 | # @api private 5 | # @since 0.4.0 6 | class AdapterProxy 7 | # @return [Symbol] 8 | # 9 | # @api private 10 | # @since 0.4.0 11 | attr_reader :identifier 12 | 13 | # @param event [EvilEvents::Core::Events::AbstractEvent] 14 | # @option explicit_identifier [Symbol,NilClass] 15 | # 16 | # @api private 17 | # @since 0.4.0 18 | def initialize(event, explicit_identifier: nil) 19 | @event = event 20 | @identifier = explicit_identifier || event.adapter_name 21 | @adapter = EvilEvents::Core::Bootstrap[:event_system].resolve_adapter(@identifier) 22 | end 23 | 24 | # @return [void] 25 | # 26 | # @api private 27 | # @since 0.4.0 28 | def broadcast! 29 | adapter.call(event) 30 | end 31 | 32 | private 33 | 34 | # @return [EvilEvents::Core::Events::AbstractEvent] 35 | # 36 | # @api private 37 | # @since 0.4.0 38 | attr_reader :event 39 | 40 | # @return [EvilEvents::Core::Broadcasting::Dispatcher::Mixin] 41 | # 42 | # @api private 43 | # @since 0.4.0 44 | attr_reader :adapter 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/evil_events/core/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core 4 | # @api private 5 | # @since 0.1.0 6 | class Config < Qonfig::DataSet 7 | class << self 8 | # @api private 9 | # @since 0.1.0 10 | def build_stub 11 | new 12 | end 13 | end 14 | 15 | setting :serializers do 16 | setting :json do 17 | setting :engine, :native 18 | end 19 | 20 | setting :hashing do 21 | setting :engine, :native 22 | end 23 | 24 | setting :xml do 25 | setting :engine, :ox 26 | end 27 | 28 | setting :msgpack do 29 | setting :engine 30 | end 31 | end 32 | 33 | setting :adapter do 34 | setting :default, :memory_sync 35 | end 36 | 37 | setting :subscriber do 38 | setting :default_delegator, :call 39 | end 40 | 41 | setting :logger, EvilEvents::Shared::Logger.new(STDOUT) 42 | 43 | setting :notifier do 44 | setting :type, :sequential 45 | 46 | setting :sequential do 47 | # NOTE: place future settings here 48 | end 49 | 50 | setting :worker do 51 | setting :min_threads, 0 52 | setting :max_threads, 5 53 | setting :max_queue, 1000 54 | setting :fallback_policy, :main_thread 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/abstract_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events 4 | # @abstract 5 | # @api private 6 | # @since 0.1.0 7 | class AbstractEvent 8 | # @since 0.1.0 9 | include EventExtensions::TypeAliasing 10 | # @since 0.1.0 11 | include EventExtensions::Payloadable 12 | # @since 0.1.0 13 | include EventExtensions::Manageable 14 | # @since 0.1.0 15 | include EventExtensions::Observable 16 | # @since 0.1.0 17 | include EventExtensions::Serializable 18 | # @since 0.1.0 19 | include EventExtensions::MetadataExtendable 20 | # @sicne 0.2.0 21 | include EventExtensions::ClassSignature 22 | # @since 0.3.0 23 | include EventExtensions::Hookable 24 | # @since 0.4.0 25 | include EventExtensions::Dispatchable 26 | # @since 0.4.0 27 | extend Symbiont::Context 28 | 29 | # @return [String] 30 | # 31 | # @api public 32 | # @since 0.1.0 33 | attr_reader :id 34 | 35 | # @option payload [Hash] 36 | # @option metadata [Hash] 37 | # 38 | # @since 0.1.0 39 | def initialize(id: nil, payload: {}, metadata: {}) 40 | @id = id || EvilEvents::Shared::Crypto.uuid 41 | @payload = build_payload(**payload) 42 | @metadata = build_metadata(**metadata) 43 | end 44 | 45 | # @return [Hash] 46 | # 47 | # @api public 48 | # @since 0.1.0 49 | def payload 50 | @payload.to_h 51 | end 52 | 53 | # @return [Hash] 54 | # 55 | # @api public 56 | # @since 0.1.0 57 | def metadata 58 | @metadata.to_h 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/class_signature.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.2.0 6 | module ClassSignature 7 | class << self 8 | # @param base_calss [Class{AbstractEvent}] 9 | # 10 | # @since 0.2.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | 14 | base_class.singleton_class.class_eval do 15 | attr_accessor :__creation_strategy__ 16 | end 17 | end 18 | end 19 | 20 | # @param another_event [Object] 21 | # @return [Boolean] 22 | # 23 | # @since 0.4.0 24 | def similar_to?(another_event) 25 | # rubocop:disable Layout/MultilineOperationIndentation 26 | id == another_event.id && 27 | type == another_event.type && 28 | payload == another_event.payload && 29 | metadata == another_event.metadata 30 | # rubocop:enable Layout/MultilineOperationIndentation 31 | rescue NoMethodError 32 | false 33 | end 34 | 35 | # @since 0.2.0 36 | module ClassMethods 37 | # @return [Signature] 38 | # 39 | # @since 0.2.0 40 | def signature 41 | Signature.new(self) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/class_signature/equalizer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::ClassSignature 4 | # @api private 5 | # @since 0.2.0 6 | class Equalizer 7 | # @rreturn [Signature] 8 | # 9 | # @since 0.2.0 10 | attr_reader :signature_a 11 | # @rreturn [Signature] 12 | # 13 | # @since 0.2.0 14 | attr_reader :signature_b 15 | 16 | # @param signature_a [Signature] 17 | # @param signature_b [Signature] 18 | # 19 | # @since 0.2.0 20 | def initialize(signature_a, signature_b) 21 | @signature_a = signature_a 22 | @signature_b = signature_b 23 | end 24 | 25 | # @return [Boolean] 26 | # 27 | # @since 0.2.0 28 | def equal_payload? 29 | signature_a.payload_stamp == signature_b.payload_stamp 30 | end 31 | 32 | # @return [Boolean] 33 | # 34 | # @since 0.2.0 35 | def equal_metadata? 36 | signature_a.metadata_stamp == signature_b.metadata_stamp 37 | end 38 | 39 | # @return [Boolean] 40 | # 41 | # @since 0.2.0 42 | def equal_delegator? 43 | signature_a.delegator_stamp == signature_b.delegator_stamp 44 | end 45 | 46 | # @return [Boolean] 47 | # 48 | # @since 0.2.0 49 | def equal_adapter? 50 | signature_a.adapter_stamp == signature_b.adapter_stamp 51 | end 52 | 53 | # @return [Boolean] 54 | # 55 | # @since 0.2.0 56 | def equal_type_alias? 57 | signature_a.type_alias_stamp == signature_b.type_alias_stamp 58 | end 59 | 60 | # @return [Boolean] 61 | # 62 | # @since 0.2.0 63 | def equal_class? 64 | signature_a.class_stamp == signature_b.class_stamp 65 | end 66 | 67 | # @option strict [Boolean] 68 | # @return [Boolean] 69 | # 70 | # @since 0.2.0 71 | def similar_signatures? 72 | # rubocop:disable Layout/MultilineOperationIndentation 73 | equal_type_alias? && 74 | equal_class? && 75 | equal_payload? && 76 | equal_metadata? && 77 | equal_delegator? && 78 | equal_adapter? 79 | # rubocop:enable Layout/MultilineOperationIndentation 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/class_signature/signature.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::ClassSignature 4 | # @api private 5 | # @since 0.2.0 6 | class Signature 7 | # @return [Class{EvilEvents::Core::Events::AbstractEvent}] 8 | # 9 | # @since 0.2.0 10 | attr_reader :event_class 11 | 12 | # @param event_calass [Class{EvilEvents::Core::Events::AbstractEvent}] 13 | # 14 | # @since 0.2.0 15 | def initialize(event_class) 16 | @event_class = event_class 17 | end 18 | 19 | # @return [Hash] 20 | # 21 | # @since 0.2.0 22 | def payload_stamp 23 | event_class::Payload.schema 24 | end 25 | 26 | # @return [Hash] 27 | # 28 | # @since 0.2.0 29 | def metadata_stamp 30 | event_class::Metadata.schema 31 | end 32 | 33 | # @return [Hash] 34 | # 35 | # @since 0.2.0 36 | def class_stamp 37 | { name: event_class.name, creation_strategy: event_class.__creation_strategy__ } 38 | end 39 | 40 | # @return [String] 41 | # 42 | # @since 0.2.0 43 | def type_alias_stamp 44 | event_class.type 45 | end 46 | 47 | # @return [Symbol,String] 48 | # 49 | # @since 0.2.0 50 | def delegator_stamp 51 | event_class.default_delegator 52 | end 53 | 54 | # @return [Hash] 55 | # 56 | # @since 0.2.0 57 | def adapter_stamp 58 | { event_class.adapter_name => event_class.adapter } 59 | end 60 | 61 | # @param other [Signature] 62 | # @return [Boolean] 63 | # 64 | # @since 0.2.0 65 | def ==(other) 66 | Equalizer.new(self, other).similar_signatures? 67 | end 68 | alias_method :eql?, :== 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/dispatchable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.4.0 6 | module Dispatchable 7 | class << self 8 | # @param base_class [Class{AbstractEvent}] 9 | # 10 | # @api private 11 | # @since 0.4.0 12 | def included(base_class) 13 | base_class.extend(ClassMethods) 14 | end 15 | end 16 | 17 | # @return [EvilEvents::Core::Broadcasting::Dispatcher::Mixin] 18 | # 19 | # @since 0.4.0 20 | def adapter 21 | self.class.adapter 22 | end 23 | 24 | # @return [EvilEvents::Core::Broadcasting::Dispatcher::Mixin] 25 | # 26 | # @since 0.4.0 27 | def adapter_name 28 | self.class.adapter_name 29 | end 30 | 31 | # @option adapter [Symbol,NilClass] 32 | # @return [void] 33 | # 34 | # @api public 35 | # @since 0.4.0 36 | def emit!(adapter: nil) 37 | EvilEvents::Core::Bootstrap[:event_system].emit(self, adapter: adapter) 38 | end 39 | 40 | # @since 0.4.0 41 | module ClassMethods 42 | # @param identifier [Symbol,String,NilClass] 43 | # @return [EvilEvents::Core::Broadcasting::Dispatcher::Dispatchable] 44 | # 45 | # @since 0.4.0 46 | def adapter(identifier = nil) 47 | @adapter_identifier = identifier if identifier 48 | EvilEvents::Core::Bootstrap[:event_system].resolve_adapter(adapter_name) 49 | end 50 | 51 | # @return [Symbol, String] 52 | # 53 | # @since 0.4.0 54 | def adapter_name 55 | @adapter_identifier || EvilEvents::Core::Bootstrap[:config].settings.adapter.default 56 | end 57 | 58 | # @option id [NilClass,Object] 59 | # @option payload [Hash] 60 | # @option metadata [Hash] 61 | # @option adapter [Symbol,NilClass] 62 | # @return [void] 63 | # 64 | # @see EvilEvents::Core::Events::AbstractEvent#initialize 65 | # @see EvilEvents::Core::Events::EventExtensions::Emittable#emit! 66 | # 67 | # @api public 68 | # @since 0.4.0 69 | def emit!(id: nil, payload: {}, metadata: {}, adapter: nil) 70 | new(id: id, payload: payload, metadata: metadata).emit!(adapter: adapter) 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/hookable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.3.0 6 | module Hookable 7 | class << self 8 | # @param base_class [Class] 9 | # 10 | # @since 0.3.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | end 14 | end 15 | 16 | # @param error [StandardError] 17 | # 18 | # @api private 19 | # @since 0.3.0 20 | def __call_on_error_hooks__(error) 21 | self.class.__on_error_hooks__.each do |hook| 22 | hook.call(self, error) 23 | end 24 | end 25 | 26 | # @api private 27 | # @since 0.3.0 28 | def __call_before_hooks__ 29 | self.class.__before_emit_hooks__.each do |hook| 30 | hook.call(self) 31 | end 32 | end 33 | 34 | # @api private 35 | # @since 0.3.0 36 | def __call_after_hooks__ 37 | self.class.__after_emit_hooks__.each do |hook| 38 | hook.call(self) 39 | end 40 | end 41 | 42 | # @since 0.3.0 43 | module ClassMethods 44 | # @param hook [#call] 45 | # 46 | # @api public 47 | # @since 0.3.0 48 | def before_emit(hook) 49 | __before_emit_hooks__ << BeforeEmitHook.new(hook) 50 | end 51 | 52 | # @param hook [#call] 53 | # 54 | # @api public 55 | # @since 0.3.0 56 | def after_emit(hook) 57 | __after_emit_hooks__ << AfterEmitHook.new(hook) 58 | end 59 | 60 | # @param hook [#call] 61 | # 62 | # @api public 63 | # @since 0.3.0 64 | def on_error(hook) 65 | __on_error_hooks__ << OnErrorHook.new(hook) 66 | end 67 | 68 | # @return [Concurrent::Array] 69 | # 70 | # @api private 71 | # @since 0.3.0 72 | def __before_emit_hooks__ 73 | @__before_emit_hooks__ ||= Concurrent::Array.new 74 | end 75 | 76 | # @return [Concurrent::Array] 77 | # 78 | # @api private 79 | # @since 0.3.0 80 | def __after_emit_hooks__ 81 | @__after_emit_hooks__ ||= Concurrent::Array.new 82 | end 83 | 84 | # @return [Concurrent::Array] 85 | # 86 | # @api private 87 | # @since 0.3.0 88 | def __on_error_hooks__ 89 | @__on_error_hooks__ ||= Concurrent::Array.new 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/hookable/abstract_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::Hookable 4 | # @api private 5 | # @since 0.3.0 6 | class AbstractHook 7 | # @return [#call] 8 | # 9 | # @since 0.3.0 10 | attr_reader :callable 11 | 12 | # @param callable [#call] 13 | # 14 | # @since 0.3.0 15 | def initialize(callable) 16 | @callable = callable 17 | end 18 | 19 | # @param source [Object] 20 | # @return void 21 | # 22 | # @since 0.3.0 23 | def call(source) 24 | callable.call(source) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/hookable/after_emit_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::Hookable 4 | # @api private 5 | # @since 0.3.0 6 | class AfterEmitHook < AbstractHook 7 | # @!method call(source) 8 | # @param source [EvilEvents::Core::Events::AbstractEvent] 9 | # @return void 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/hookable/before_emit_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::Hookable 4 | # @api private 5 | # @since 0.3.0 6 | class BeforeEmitHook < AbstractHook 7 | # @!method call(source) 8 | # @param source [EvilEvents::Core::Events::AbstractEvent] 9 | # @return void 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/hookable/on_error_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::Hookable 4 | # @api private 5 | # @since 0.3.0 6 | class OnErrorHook < AbstractHook 7 | # @param event [EvilEvents::Core::Events::AbstractEvent] 8 | # @param error [StandardError] 9 | # @return void 10 | # 11 | # @since 0.3.0 12 | def call(event, error) 13 | callable.call(event, error) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/manageable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.1.0 6 | module Manageable 7 | class << self 8 | # @param base_class [Class] 9 | # 10 | # @since 0.1.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | end 14 | end 15 | 16 | # @since 0.1.0 17 | module ClassMethods 18 | # @return void 19 | # 20 | # @since 0.1.0 21 | def manage! 22 | EvilEvents::Core::Bootstrap[:event_system].register_event_class(self) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/metadata_extendable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.1.0 6 | module MetadataExtendable 7 | class << self 8 | # @param base_class [Class] 9 | # 10 | # @since 0.1.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | end 14 | end 15 | 16 | private 17 | 18 | # @return [Class{AbstractMetadata}] 19 | # 20 | # @since 0.1.0 21 | def build_metadata(**metadata_attributes) 22 | self.class.metadata_class.new(**metadata_attributes) 23 | end 24 | 25 | # @since 0.1.0 26 | module ClassMethods 27 | # @param child_class [Class] 28 | # 29 | # @since 0.1.0 30 | def inherited(child_class) 31 | child_class.const_set(:Metadata, Class.new(AbstractMetadata)) 32 | super 33 | end 34 | 35 | # @return [Class{AbstractMetadata}] 36 | # 37 | # @since 0.2.0 38 | def metadata_class 39 | const_get(:Metadata) 40 | end 41 | 42 | # @param key [Symbol] 43 | # @param type [EvilEvents::Shared::Types::Any] 44 | # @param options [Hash] 45 | # @return void 46 | # 47 | # @since 0.1.0 48 | def metadata(key, type = EvilEvents::Types::Any, **options) 49 | if type.is_a?(Symbol) 50 | type = EvilEvents::Core::Bootstrap[:event_system].resolve_type(type, **options) 51 | end 52 | 53 | metadata_class.attribute(key, type) 54 | end 55 | 56 | # @return [Array] 57 | # 58 | # @since 0.1.0 59 | def metadata_fields 60 | metadata_class.attribute_names 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/metadata_extendable/abstract_metadata.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::MetadataExtendable 4 | # @api private 5 | # @since 0.1.0 6 | AbstractMetadata = Class.new(EvilEvents::Shared::Structure) 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/observable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.1.0 6 | module Observable 7 | class << self 8 | # @param base_class [Class] 9 | # 10 | # @since 0.1.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | end 14 | end 15 | 16 | # @return [Array] 17 | # 18 | # @since 0.1.0 19 | def observers 20 | self.class.observers 21 | end 22 | alias_method :subscribers, :observers 23 | 24 | # @since 0.1.0 25 | module ClassMethods 26 | # @param raw_subscriber [Object] 27 | # @param delegator [Symbol, String, NilClass] 28 | # 29 | # @since 0.1.0 30 | def observe(raw_subscriber, delegator: nil) 31 | EvilEvents::Core::Bootstrap[:event_system].observe(self, raw_subscriber, delegator) 32 | end 33 | 34 | # @param delegator [Symbol, String, NilClass] 35 | # 36 | # @since 0.1.0 37 | def default_delegator(delegator = nil) 38 | @default_delegator = delegator if delegator 39 | @default_delegator || begin 40 | EvilEvents::Core::Bootstrap[:config].settings.subscriber.default_delegator 41 | end 42 | end 43 | 44 | # @return [Array] 45 | # 46 | # @since 0.1.0 47 | def observers 48 | EvilEvents::Core::Bootstrap[:event_system].observers(self) 49 | end 50 | alias_method :subscribers, :observers 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/payloadable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.1.0 6 | module Payloadable 7 | class << self 8 | # @param base_class [Class] 9 | # 10 | # @since 0.1.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | end 14 | end 15 | 16 | private 17 | 18 | # @return [Class{AbstractPayload}] 19 | # 20 | # @since 0.1.0 21 | def build_payload(**payload_attributes) 22 | self.class.payload_class.new(**payload_attributes) 23 | end 24 | 25 | # @since 0.1.0 26 | module ClassMethods 27 | # @param child_class [Class] 28 | # 29 | # @since 0.1.0 30 | def inherited(child_class) 31 | child_class.const_set(:Payload, Class.new(AbstractPayload)) 32 | super 33 | end 34 | 35 | # @return [Class{AbstractPayload}] 36 | # 37 | # @since 0.2.0 38 | def payload_class 39 | const_get(:Payload) 40 | end 41 | 42 | # @param key [Symbol] 43 | # @param type [EvilEvents::Shared::Types::Any] 44 | # @param options [Hash] 45 | # @return void 46 | # 47 | # @since 0.1.0 48 | def payload(key, type = EvilEvents::Types::Any, **options) 49 | if type.is_a?(Symbol) 50 | type = EvilEvents::Core::Bootstrap[:event_system].resolve_type(type, **options) 51 | end 52 | 53 | payload_class.attribute(key, type) 54 | end 55 | 56 | # @return [Array] 57 | # 58 | # @since 0.1.0 59 | def payload_fields 60 | payload_class.attribute_names 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/payloadable/abstract_payload.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions::Payloadable 4 | # @api private 5 | # @since 0.1.0 6 | AbstractPayload = Class.new(EvilEvents::Shared::Structure) 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/serializable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.1.0 6 | module Serializable 7 | # @return [Hash] 8 | # 9 | # @api private 10 | # @since 0.1.0 11 | def serialize_to_hash 12 | EvilEvents::Core::Bootstrap[:event_system].serialize_to_hash(self) 13 | end 14 | alias_method :dump_to_hash, :serialize_to_hash 15 | 16 | # @return [String] 17 | # 18 | # @api private 19 | # @since 0.1.0 20 | def serialize_to_json 21 | EvilEvents::Core::Bootstrap[:event_system].serialize_to_json(self) 22 | end 23 | alias_method :dump_to_json, :serialize_to_json 24 | 25 | # @return [String] 26 | # 27 | # @api private 28 | # @since 0.4.0 29 | def serialize_to_xml 30 | EvilEvents::Core::Bootstrap[:event_system].serialize_to_xml(self) 31 | end 32 | alias_method :dump_to_xml, :serialize_to_xml 33 | 34 | # @return [String] 35 | # 36 | # @api private 37 | # @since 0.4.0 38 | def serialize_to_msgpack 39 | EvilEvents::Core::Bootstrap[:event_system].serialize_to_msgpack(self) 40 | end 41 | alias_method :dump_to_msgpack, :serialize_to_msgpack 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_extensions/type_aliasing.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::EventExtensions 4 | # @api private 5 | # @since 0.1.0 6 | module TypeAliasing 7 | class << self 8 | # @param base_class [Class] 9 | # 10 | # @since 0.1.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | end 14 | end 15 | 16 | # @return [String] 17 | # 18 | # @since 0.1.0 19 | def type 20 | self.class.type 21 | end 22 | 23 | module ClassMethods 24 | # @param type_alias [String, NilClass] 25 | # @raise [EvilEvents::IncopatibleEventTypeError] 26 | # @raise [EvilEvents::EventTypeNotDefinedError] 27 | # @raise [EvilEvents::EventTypeAlreadyDefinedError] 28 | # @return [String] 29 | # 30 | # @since 0.1.0 31 | def type(type_alias = nil) 32 | case 33 | when incompatible_type_alias_type?(type_alias) 34 | raise EvilEvents::IncopatibleEventTypeError 35 | when fetching_type_alias_when_type_alias_not_defined?(type_alias) 36 | raise EvilEvents::EventTypeNotDefinedError 37 | when providing_type_alias_when_type_alias_already_defined?(type_alias) 38 | raise EvilEvents::EventTypeAlreadyDefinedError 39 | when tries_to_define_type_alias_first_time?(type_alias) 40 | @type = type_alias 41 | end 42 | 43 | @type 44 | end 45 | 46 | private 47 | 48 | # @param type_alias [String, NilClass] 49 | # @return [Boolean] 50 | # 51 | # @since 0.1.0 52 | def incompatible_type_alias_type?(type_alias) 53 | !(type_alias.is_a?(NilClass) || type_alias.is_a?(String)) 54 | end 55 | 56 | # @param type_alias [String, NilClass] 57 | # @return [Boolean] 58 | # 59 | # @since 0.1.0 60 | def fetching_type_alias_when_type_alias_not_defined?(type_alias) 61 | !instance_variable_defined?(:@type) && type_alias.nil? 62 | end 63 | 64 | # @param type_alias [String, NilClass] 65 | # @return [Boolean] 66 | # 67 | # @since 0.1.0 68 | def providing_type_alias_when_type_alias_already_defined?(type_alias) 69 | instance_variable_defined?(:@type) && type_alias 70 | end 71 | 72 | # @param type_alias [String, NilClass] 73 | # @return [Boolean] 74 | # 75 | # @since 0.1.0 76 | def tries_to_define_type_alias_first_time?(type_alias) 77 | !instance_variable_defined?(:@type) && type_alias 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/event_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events 4 | # @api private 5 | # @since 0.1.1 6 | module EventFactory 7 | # @since 0.1.1 8 | UNDEFINED_EVENT_ID = 'unknown' 9 | CLASS_INHERITANCE_STRATEGY = :class_inheritance 10 | PROC_EVAL_STRATEGY = :proc_eval 11 | 12 | module_function 13 | 14 | # @param event_type [String] 15 | # @raise [EvilEvents::Core::Events::ManagerRegistry::AlreadyManagedEventClassError] 16 | # @return [Class{EvilEvents::Core::Events::AbstractEvent}] 17 | # 18 | # @since 0.1.1 19 | def create_abstract_class(event_type) 20 | Class.new(AbstractEvent).tap do |klass| 21 | klass.type(event_type) 22 | klass.__creation_strategy__ = CLASS_INHERITANCE_STRATEGY 23 | 24 | class << klass 25 | def inherited(child_class) 26 | child_class.type(type) 27 | child_class.__creation_strategy__ = CLASS_INHERITANCE_STRATEGY 28 | child_class.manage! 29 | rescue EvilEvents::AlreadyManagedEventClassError 30 | EvilEvents::Core::Bootstrap[:event_system].unregister_event_class(child_class) 31 | raise 32 | end 33 | end 34 | end 35 | end 36 | 37 | # @param event_type [String] 38 | # @param event_class_definitions [Proc] 39 | # @yield [AbstractEvent] 40 | # @raise [EvilEvents::Core::Events::ManagerRegistry::AlreadyManagedEventClassError] 41 | # @return [Class{EvilEvents::Core::Events::AbstractEvent}] 42 | # 43 | # @since 0.1.1 44 | def create_class(event_type, &event_class_definitions) 45 | Class.new(AbstractEvent).tap do |klass| 46 | begin # rubocop:disable Style/RedundantBegin 47 | klass.__creation_strategy__ = PROC_EVAL_STRATEGY 48 | klass.type(event_type) 49 | klass.manage! 50 | klass.evaluate(&event_class_definitions) if block_given? 51 | rescue StandardError 52 | EvilEvents::Core::Bootstrap[:event_system].unregister_event_class(klass) 53 | raise 54 | end 55 | end 56 | end 57 | 58 | # @param event_class [Class{EvilEvents::Core::Events::AbstractEvent}] 59 | # @option id [String, Object] 60 | # @option payload [Hash] 61 | # @option metadata [Hash] 62 | # @return [EvilEvents::Core::Events::AbstractEvent] 63 | # 64 | # @api private 65 | # @since 0.1.1 66 | def restore_instance(event_class, id: UNDEFINED_EVENT_ID, payload: {}, metadata: {}) 67 | event_class.new(id: id || UNDEFINED_EVENT_ID, payload: payload, metadata: metadata) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/manager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events 4 | # @api private 5 | # @since 0.1.0 6 | class Manager 7 | # @return [EvilEvents::Core::Events::AbstractEvent] 8 | # 9 | # @since 0.1.0 10 | attr_reader :event_class 11 | 12 | # @return [Concurrent::Array] 13 | # 14 | # @since 0.1.0 15 | attr_reader :subscribers 16 | 17 | # @param event_class [Class{EvilEvents::Core::Events::AbstractEvent}] 18 | # 19 | # @since 0.1.0 20 | def initialize(event_class) 21 | @event_class = event_class 22 | @subscribers = SubscriberList.new 23 | end 24 | 25 | # @param raw_subscriber [Object] 26 | # @param delegator [Symbol, String, NilClass] 27 | # @raise [EvilEvents::InvalidDelegatorTypeError] 28 | # @return void 29 | # 30 | # @since 0.1.0 31 | def observe(raw_subscriber, delegator = nil) 32 | case delegator 33 | when NilClass, Symbol, String 34 | subscribers.push(create_event_subscriber(raw_subscriber, delegator)) 35 | else 36 | raise EvilEvents::InvalidDelegatorTypeError 37 | end 38 | end 39 | 40 | # @param event [EvilEvents::Core::Events::AbstractEvent] 41 | # @raise [EvilEvents::InconsistentEventClassError] 42 | # @raise [EvilEvents::FailingSubscribersError] 43 | # 44 | # @return void 45 | # 46 | # @since 0.1.0 47 | def notify(event) 48 | raise EvilEvents::InconsistentEventClassError unless supported_event?(event) 49 | EvilEvents::Core::Bootstrap[:event_system].process_event_notification(self, event) 50 | end 51 | 52 | # @return [String, Symbol] 53 | # 54 | # @since 0.1.0 55 | def event_type 56 | event_class.type 57 | end 58 | 59 | private 60 | 61 | # @param event [EvilEvents::Core::Events::AbstractEvent] 62 | # @return [Boolean] 63 | # 64 | # @since 0.3.0 65 | def supported_event?(event) 66 | event.is_a?(event_class) 67 | end 68 | 69 | # @param raw_subscriber [Object] 70 | # @param delegator [Symbol, String, NilClass] 71 | # @return [EvilEvents::Core::Events::Subscriber] 72 | # 73 | # @since 0.1.0 74 | def create_event_subscriber(raw_subscriber, delegator) 75 | delegation = -> { delegator || event_class.default_delegator } 76 | resolver = EvilEvents::Shared::DelegatorResolver.new(delegation) 77 | EvilEvents::Core::Events::Subscriber.new(raw_subscriber, resolver) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/manager/subscriber_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Manager 4 | # @api private 5 | # @since 0.1.0 6 | class SubscriberList < Concurrent::Array 7 | # @param source_subscriber [Object] 8 | # @return [Boolean] 9 | # 10 | # @since 0.1.0 11 | def registered?(source_subscriber) 12 | any? { |subscriber| subscriber.source_object == source_subscriber } 13 | end 14 | 15 | # @param source_subscriber [Object] 16 | # @return [EvilEvents::Core::Events::Subscriber] 17 | # 18 | # @since 0.1.0 19 | def wrapper_of(source_subscriber) 20 | find { |subscriber| subscriber.source_object == source_subscriber } 21 | end 22 | 23 | # @return [Array] 24 | # 25 | # @since 0.1.0 26 | def sources 27 | map(&:source_object) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/manager_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events 4 | # @api private 5 | # @since 0.1.0 6 | module ManagerFactory 7 | class << self 8 | # @param event_class [Class{EvilEvents::Core::Events::AbstractEvent}] 9 | # @raise [EvilEvents::IncorrectEventClassError] 10 | # @return [EvilEvents::Core::Events::Manager] 11 | # 12 | # @since 0.1.0 13 | def create(event_class) 14 | unless event_class.is_a?(Class) && event_class < EvilEvents::Core::Events::AbstractEvent 15 | raise EvilEvents::IncorrectEventClassError 16 | end 17 | 18 | Manager.new(event_class) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.3.0 5 | module EvilEvents::Core::Events::Notifier 6 | end 7 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier/abstract.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.3.0 5 | class EvilEvents::Core::Events::Notifier::Abstract 6 | # @param options [Hash] 7 | # 8 | # @api private 9 | # @since 0.3.0 10 | def initialize(**options); end 11 | 12 | # @param manager [EvilEvents::Core::Events::Manager] 13 | # @param event [EvilEvents::Core::Events::AbstractEvent] 14 | # 15 | # @api private 16 | # @since 0.3.0 17 | def notify(manager, event); end 18 | 19 | # @api private 20 | # @since 0.3.0 21 | def restart!; end 22 | 23 | # @api private 24 | # @since 0.3.0 25 | def shutdown!; end 26 | end 27 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier/builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::Notifier 4 | # @api private 5 | # @sicne 0.3.0 6 | module Builder 7 | class << self 8 | # @raise EvilEvents::UnknownNotifierTypeError 9 | # @return [Notifier::Abstract, Notifier::Sequential, Notifier::Worker] 10 | # 11 | # @api private 12 | # @since 0.3.0 13 | def build_notifier! 14 | case EvilEvents::Core::Bootstrap[:config].settings.notifier.type 15 | when :sequential then build_sequential_notifier! 16 | when :worker then build_worker_notifier! 17 | else 18 | raise EvilEvents::UnknownNotifierTypeError 19 | end 20 | end 21 | 22 | private 23 | 24 | # rubocop:disable Lint/UselessAssignment 25 | # @return [Notifier::Sequential] 26 | # 27 | # @api private 28 | # @since 0.3.0 29 | def build_sequential_notifier! 30 | options = EvilEvents::Core::Bootstrap[:config].settings.notifier.sequential 31 | 32 | Sequential.new # TODO: use `options` when it will be consistent 33 | end 34 | # rubocop:enable Lint/UselessAssignment 35 | 36 | # @return [Notifier::Worker] 37 | # 38 | # @api private 39 | # @since 0.3.0 40 | def build_worker_notifier! 41 | options = EvilEvents::Core::Bootstrap[:config].settings.notifier.worker 42 | 43 | Worker.new( 44 | min_threads: options.min_threads, 45 | max_threads: options.max_threads, 46 | max_queue: options.max_queue, 47 | fallback_policy: options.fallback_policy 48 | ) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier/logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.3.0 5 | module EvilEvents::Core::Events::Notifier::Logging 6 | # @param event [EvilEvents::Core::Events::AbstractEvent] 7 | # @param subscriber [EvilEvents::Core::Events::Subscriber] 8 | # @return void 9 | # 10 | # @api private 11 | # @since 0.3.0 12 | def log_failure(event, subscriber) 13 | log_activity(event, subscriber, :failed) 14 | end 15 | 16 | # @param event [EvilEvents::Core::Events::AbstractEvent] 17 | # @param subscriber [EvilEvents::Core::Events::Subscriber] 18 | # @return void 19 | # 20 | # @api private 21 | # @since 0.3.0 22 | def log_success(event, subscriber) 23 | log_activity(event, subscriber, :successful) 24 | end 25 | 26 | private 27 | 28 | # @param event [EvilEvents::Core::Events::AbstractEvent] 29 | # @param subscriber [EvilEvents::Core::Events::Subscriber] 30 | # @param status [String, Symbol] 31 | # @return void 32 | # 33 | # @api private 34 | # @since 0.3.0 35 | def log_activity(event, subscriber, status) 36 | activity = "EventProcessed(#{event.type})" 37 | message = "EVENT_ID: #{event.id} :: " \ 38 | "STATUS: #{status} :: " \ 39 | "SUBSCRIBER: #{subscriber.source_object}" 40 | 41 | EvilEvents::Core::ActivityLogger.log(activity: activity, message: message) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier/proxy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.3.0 5 | class EvilEvents::Core::Events::Notifier::Proxy 6 | # @since 0.3.0 7 | extend Forwardable 8 | 9 | # @since 0.3.0 10 | def_delegators :notifier, :shutdown!, :restart!, :notify 11 | 12 | # @return [Mutex] 13 | # 14 | # @api private 15 | # @since 0.3.0 16 | attr_reader :initialization_mutex 17 | 18 | # @api private 19 | # @since 0.3.0 20 | def initialize 21 | @initialization_mutex = Mutex.new 22 | end 23 | 24 | # @return [Abstract, Sequential, Worker] 25 | # 26 | # @api private 27 | # @since 0.3.0 28 | def notifier 29 | initialization_mutex.synchronize do 30 | @notifier ||= EvilEvents::Core::Events::Notifier::Builder.build_notifier! 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier/sequential.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::Notifier 4 | # @api private 5 | # @since 0.3.0 6 | class Sequential < Abstract 7 | # @since 0.3.0 8 | include Logging 9 | 10 | # @param manager [EvilEvents::Core::Events::Manager] 11 | # @param event [EvilEvents::Core::Events::AbstractEvent] 12 | # @raise [EvilEvents::FailingSubscribersError] 13 | # @return void 14 | # 15 | # @api private 16 | # @since 0.3.0 17 | def notify(manager, event) 18 | errors_stack = EvilEvents::FailingSubscribersError.new 19 | 20 | event.__call_before_hooks__ 21 | 22 | manager.subscribers.each do |subscriber| 23 | begin # rubocop:disable Style/RedundantBegin 24 | subscriber.notify(event) 25 | 26 | log_success(event, subscriber) 27 | rescue StandardError => error 28 | log_failure(event, subscriber) 29 | 30 | event.__call_on_error_hooks__(error) 31 | 32 | errors_stack << error 33 | end 34 | end 35 | 36 | event.__call_after_hooks__ 37 | 38 | raise errors_stack unless errors_stack.empty? 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier/worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events::Notifier 4 | # @api private 5 | # @since 0.3.0 6 | class Worker < Abstract 7 | # @since 0.3.0 8 | extend Forwardable 9 | 10 | # @api public 11 | # @since 0.3.0 12 | MAIN_THREAD_POLICY = :main_thread 13 | 14 | # @api public 15 | # @since 0.3.0 16 | IGNORANCE_POLICY = :ignorance 17 | 18 | # @api public 19 | # @since 0.3.0 20 | EXCEPTION_POLICY = :exception 21 | 22 | # @since 0.3.0 23 | def_delegators :executor, :shutdown!, :restart! 24 | 25 | # @return [EvilEvents::Core::Events::Notifier::Worker::Executor] 26 | # 27 | # @api private 28 | # @since 0.3.0 29 | attr_reader :executor 30 | 31 | # @option min_threads [Integer] 32 | # @option max_threads [Integer] 33 | # @option max_queue [Integer] 34 | # @option fallback_policy [Symbol] 35 | # 36 | # @see EvilEvents::Core::Events::Notifier::Worker::Executor 37 | # 38 | # @api private 39 | # @since 0.3.0 40 | def initialize(min_threads:, max_threads:, max_queue:, fallback_policy: MAIN_THREAD_POLICY) 41 | @executor = Executor.new( 42 | min_threads: min_threads, 43 | max_threads: max_threads, 44 | max_queue: max_queue, 45 | fallback_policy: fallback_policy 46 | ) 47 | end 48 | 49 | # @param manager [EvilEvents::Core::Events::Manager] 50 | # @param event [EvilEvents::Core::Events::AbstractEvent] 51 | # 52 | # @api private 53 | # @since 0.3.0 54 | def notify(manager, event) 55 | event.__call_before_hooks__ 56 | manager.subscribers.each { |subscriber| schedule_job(event, subscriber) } 57 | event.__call_after_hooks__ 58 | end 59 | 60 | private 61 | 62 | # @param event [EvilEvents::Core::Events::AbstractEvent] 63 | # @param subscriber [EvilEvents::Core::Events::Subscriber] 64 | # 65 | # @api private 66 | # @since 0.3.0 67 | def schedule_job(event, subscriber) 68 | executor.execute(Job.new(event, subscriber)) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/notifier/worker/job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # since 0.3.0 5 | class EvilEvents::Core::Events::Notifier::Worker::Job 6 | # @return [EvilEvents::Core::Events::Subscriber] 7 | # 8 | # @api private 9 | # @since 0.3.0 10 | attr_reader :subscriber 11 | 12 | # @return [EvilEvents::Core::Events::AbstractEvent] 13 | # 14 | # @api private 15 | # @since 0.3.0 16 | attr_reader :event 17 | 18 | # @param event [EvilEvents::Core::Events::AbstractEvent] 19 | # @param subscriber [EvilEvents::Core::Events::Subscriber] 20 | # 21 | # @api private 22 | # @since 0.3.0 23 | def initialize(event, subscriber) 24 | @event = event 25 | @subscriber = subscriber 26 | end 27 | 28 | # @return void 29 | # 30 | # @api private 31 | # @since 0.3.0 32 | def perform 33 | subscriber.notify(event) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events 4 | # @api private 5 | # @since 0.1.0 6 | class Serializers 7 | # @since 0.4.0 8 | include EvilEvents::Shared::DependencyContainer::Mixin 9 | 10 | # @return void 11 | # 12 | # @api private 13 | # @since 0.4.0 14 | def register_core_serializers! 15 | register(:json, memoize: true) { JSON::Factory.new.create! } 16 | register(:hash, memoize: true) { Hash::Factory.new.create! } 17 | register(:msgpack, memoize: true) { MessagePack::Factory.new.create! } 18 | register(:xml, memoize: true) { XML::Factory.new.create! } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | # @api private 5 | # @since 0.4.0 6 | class Base 7 | # @param engine [Engines::Abstract] 8 | # @param config [GenericConfig] 9 | # @param packer [DataTransformer] 10 | # @param unpacker [DataTransformer] 11 | # 12 | # @api private 13 | # @since 0.4.0 14 | def initialize(engine, config, packer, unpacker) 15 | @engine = engine 16 | @config = config 17 | @packer = packer 18 | @unpacker = unpacker 19 | end 20 | 21 | # @param event [EvilEvents::Core::Events::AbstractEvent] 22 | # @return [Object] 23 | # 24 | # @api private 25 | # @since 0.4.0 26 | def serialize(event) 27 | packer.call(event) 28 | end 29 | 30 | # @param data [Object] 31 | # @return [EvilEvents::Core::Events::AbstractEvent] 32 | # 33 | # @api private 34 | # @since 0.4.0 35 | def deserialize(data) 36 | unpacker.call(data) 37 | end 38 | 39 | private 40 | 41 | # @return [DataTransformer] 42 | # 43 | # @api private 44 | # @since 0.4.0 45 | attr_reader :packer 46 | 47 | # @return [DataTransformer] 48 | # 49 | # @api private 50 | # @since 0.4.0 51 | attr_reader :unpacker 52 | 53 | # @return [Engines::Abstract] 54 | # 55 | # @api private 56 | # @since 0.4.0 57 | attr_reader :engine 58 | 59 | # @return [GenericConfig] 60 | # 61 | # @api private 62 | # @since 0.4.0 63 | attr_reader :config 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/base/abstract_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::Base 4 | # @api private 5 | # @since 0.4.0 6 | class AbstractEngine 7 | # @param config [GenericConfig] 8 | # 9 | # @api private 10 | # @since 0.4.0 11 | def initialize(config); end 12 | 13 | # @param data [EventSerializationState] 14 | # @return [Object] 15 | # 16 | # @api private 17 | # @since 0.4.0 18 | def dump(serialization_state); end 19 | 20 | # @param data [Object] 21 | # @return [EventSerializationState] 22 | # 23 | # @api private 24 | # @since 0.4.0 25 | def load(data); end 26 | 27 | private 28 | 29 | # @option id [String,Integer,Object] 30 | # @option type [String] 31 | # @option payload [::Hash] 32 | # @option metadata [::Hash] 33 | # 34 | # @return [EventSerializationState] 35 | # 36 | # @api private 37 | # @since 0.4.0 38 | def restore_serialization_state(id:, type:, payload:, metadata:) 39 | EventSerializationState.build_from_options( 40 | id: id, 41 | type: type, 42 | payload: payload, 43 | metadata: metadata 44 | ) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/base/abstract_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::Base 4 | # @api private 5 | # @since 0.4.0 6 | class AbstractFactory 7 | # @return [Base] 8 | # 9 | # @api private 10 | # @since 0.4.0 11 | def create! 12 | config = build_config 13 | engine = build_engine(config) 14 | 15 | packer = build_packer(engine, config) 16 | unpacker = build_unpacker(engine, config) 17 | 18 | create_adapter(engine, config, packer, unpacker) 19 | end 20 | 21 | # @return [Base::GenericConfig] 22 | # 23 | # @api private 24 | # @since 0.4.0 25 | def build_config; end 26 | 27 | # @param config [Base::GenericConfig] 28 | # @return [Base::Engines::Abstract] 29 | # 30 | # @api private 31 | # @since 0.4.0 32 | def build_engine(config); end 33 | 34 | # @param engine [Base::Engines::Abstract] 35 | # @param config [Base::GenericConfig] 36 | # @return [Base::Dumper] 37 | # 38 | # @api private 39 | # @since 0.4.0 40 | def build_packer(engine, config); end 41 | 42 | # @param engine [Base::Engines::Abstract] 43 | # @param config [Base::GenericConfig] 44 | # @return [Base::Dumper] 45 | # 46 | # @api private 47 | # @since 0.4.0 48 | def build_unpacker(engine, config); end 49 | 50 | # @param engine [Base::AbstractEngine] 51 | # @param config [Base::GenericConfig] 52 | # @param packer [Base::DataTransformer] 53 | # @param unpacker [Base::DataTransformer] 54 | # @return [Base] 55 | # 56 | # @api private 57 | # @since 0.4.0 58 | def create_adapter(engine, config, packer, unpacker); end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/base/data_transformer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::Base 4 | # @api private 5 | # @since 0.4.0 6 | class DataTransformer 7 | # @param engine [Base::Engine::Abstract] 8 | # 9 | # @api private 10 | # @since 0.4.0 11 | def initialize(engine) 12 | @engine = engine 13 | end 14 | 15 | # @param event [EvilEvents::Core::Events::AbstractEvent] 16 | # @return [Object] 17 | # 18 | # @api private 19 | # @since 0.4.0 20 | def call(event); end 21 | 22 | private 23 | 24 | # @return [Base::Engine::Abstract] 25 | # 26 | # @api private 27 | # @since 0.4.0 28 | attr_reader :engine 29 | 30 | # @param event [EvilEvents::Core::Events::AbstractEvent] 31 | # @return [EventSerializationState] 32 | # 33 | # @api private 34 | # @since 0.4.0 35 | def build_serialization_state(event) 36 | EventSerializationState.build_from_event(event) 37 | end 38 | 39 | # @param serialization_state [EventSerializationState] 40 | # @return [EvilEvents::Core::Events::AbstractEvent] 41 | # 42 | # @api private 43 | # @since 0.4.0 44 | def restore_event_instance(serialization_state) 45 | event_class = EvilEvents::Core::Bootstrap[:event_system].resolve_event_class( 46 | serialization_state.type 47 | ) 48 | 49 | EvilEvents::Core::Events::EventFactory.restore_instance( 50 | event_class, 51 | id: serialization_state.id, 52 | payload: serialization_state.payload, 53 | metadata: serialization_state.metadata 54 | ) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/base/event_serialization_state.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::Base 4 | # @api private 5 | # @since 0.4.0 6 | class EventSerializationState 7 | class << self 8 | # @param event [EvilEvents::Core::Event::AbstractEvent] 9 | # @return [EventSerializationState] 10 | # 11 | # @api private 12 | # @since 0.4.0 13 | def build_from_event(event) 14 | new(id: event.id, type: event.type, payload: event.payload, metadata: event.metadata) 15 | end 16 | 17 | # @option id [String,Integer,Object] 18 | # @option type [String] 19 | # @option payload [::Hash] 20 | # @option metadata [::Hash] 21 | # @return [EventSerializationState] 22 | # 23 | # @api private 24 | # @since 0.4.0 25 | def build_from_options(**options) 26 | new(**options) 27 | end 28 | end 29 | 30 | # @return [String, Integer, Object] 31 | # 32 | # @api private 33 | # @since 0.4.0 34 | attr_reader :id 35 | 36 | # @return [String] 37 | # 38 | # @api private 39 | # @since 0.4.0 40 | attr_reader :type 41 | 42 | # @return [::Hash] 43 | # 44 | # @api private 45 | # @since 0.4.0 46 | attr_reader :payload 47 | 48 | # @return [::Hash] 49 | # 50 | # @api private 51 | # @since 0.4.0 52 | attr_reader :metadata 53 | 54 | # @option id [String,Integer,Object] 55 | # @option type [String] 56 | # @option payload [::Hash] 57 | # @option metadata [::Hash] 58 | # 59 | # @api private 60 | # @since 0.4.0 61 | def initialize(id:, type:, payload:, metadata:) 62 | @id = id 63 | @type = type 64 | @payload = payload 65 | @metadata = metadata 66 | end 67 | 68 | # @return [Boolean] 69 | # 70 | # @api private 71 | # @since 0.4.0 72 | def valid? 73 | return false unless type && payload && metadata 74 | return false unless payload.is_a?(::Hash) 75 | return false unless metadata.is_a?(::Hash) 76 | return false unless type.is_a?(String) 77 | true 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/base/generic_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::Base 4 | # @api private 5 | # @since 0.4.0 6 | class GenericConfig < Qonfig::DataSet 7 | setting :options 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | # @api private 5 | # @since 0.4.0 6 | Hash = Class.new(Base) 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/hash/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class Hash 5 | # @api private 6 | # @since 0.4.0 7 | Config = Class.new(Base::GenericConfig) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/hash/engines.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::Hash 4 | # @since 0.4.0 5 | # @api private 6 | class Engines 7 | # @since 0.4.0 8 | extend EvilEvents::Shared::DependencyContainer::Mixin 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/hash/engines/native.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::Hash::Engines 4 | # @api private 5 | # @since 0.4.0 6 | class Native < EvilEvents::Core::Events::Serializers::Base::AbstractEngine 7 | # @param serialization_state [Base::EventSerializationState] 8 | # @return [::Hash] 9 | # 10 | # @since 0.4.0 11 | # @api private 12 | def dump(serialization_state) 13 | { 14 | id: serialization_state.id, 15 | type: serialization_state.type, 16 | payload: serialization_state.payload, 17 | metadata: serialization_state.metadata 18 | } 19 | end 20 | 21 | # @param hash [::Hash] 22 | # @raise [EvilEvents::SerializationEngineError] 23 | # @return [EventSerializationState] 24 | # 25 | # @since 0.4.0 26 | # @api private 27 | def load(hash) 28 | begin 29 | event_id = hash[:id] || hash['id'] 30 | event_type = hash[:type] || hash['type'] 31 | event_payload = hash[:payload] || hash['payload'] 32 | event_metadata = hash[:metadata] || hash['metadata'] 33 | rescue NoMethodError, TypeError, ArgumentError 34 | raise EvilEvents::SerializationEngineError 35 | end 36 | 37 | restore_serialization_state( 38 | id: event_id, 39 | type: event_type, 40 | payload: (symbolized_hash(event_payload) if event_payload), 41 | metadata: (symbolized_hash(event_metadata) if event_metadata) 42 | ) 43 | end 44 | 45 | private 46 | 47 | # @param hash [::Hash] 48 | # @return [::Hash] 49 | # 50 | # @since 0.1.0 51 | def symbolized_hash(hash) 52 | hash.each_pair.each_with_object({}) do |(key, value), result_hash| 53 | result_hash[key.to_sym] = value 54 | end 55 | end 56 | end 57 | 58 | # @since 0.4.0 59 | register(:native, Native) 60 | end 61 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/hash/factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class Hash 5 | # @api private 6 | # @since 0.4.0 7 | class Factory < AbstractFactory 8 | # @return [Hash::Config] 9 | # 10 | # @api private 11 | # @since 0.4.0 12 | def build_config 13 | options = EvilEvents::Core::Bootstrap[:config].settings.serializers.hashing 14 | Config.new.tap { |conf| conf.settings.options = options } 15 | end 16 | 17 | # @param config [Hash::Config] 18 | # @raise [EvilEvents::UnrecognizedSerializationEngineError] 19 | # @return [Base::AbstractEngine] 20 | # 21 | # @api private 22 | # @since 0.4.0 23 | def build_engine(config) 24 | Engines.resolve(config.settings.options[:engine]).new(config) 25 | rescue Dry::Container::Error 26 | raise EvilEvents::UnrecognizedSerializationEngineError 27 | end 28 | 29 | # @param engine [Base::AbstractEngine] 30 | # @param config [Hash::Config] 31 | # @return [Hash::Packer] 32 | # 33 | # @api private 34 | # @since 0.4.0 35 | def build_packer(engine, _config) 36 | Packer.new(engine) 37 | end 38 | 39 | # @param engine [Base::AbstractEngine] 40 | # @param config [Hash::Config] 41 | # @return [Hash::Unpacker] 42 | # 43 | # @api private 44 | # @since 0.4.0 45 | def build_unpacker(engine, _config) 46 | Unpacker.new(engine) 47 | end 48 | 49 | # @param engine [Base::AbstractEngine] 50 | # @param config [Hash::Config] 51 | # @param packer [Hash::Packer] 52 | # @param unpacker [Hash::Unpacker] 53 | # @return [Hash] 54 | # 55 | # @api private 56 | # @since 0.4.0 57 | def create_adapter(engine, config, packer, unpacker) 58 | Hash.new(engine, config, packer, unpacker) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/hash/packer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class Hash 5 | # @api private 6 | # @since 0.4.0 7 | class Packer < Base::DataTransformer 8 | # @param event [EvilEvents::Core::Events::AbstractEvent] 9 | # @raise [EvilEvents::HashSerializationError] 10 | # @return [String] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(event) 17 | unless event.is_a?(EvilEvents::Core::Events::AbstractEvent) 18 | raise EvilEvents::HashSerializationError 19 | end 20 | 21 | serialization_state = build_serialization_state(event) 22 | engine.dump(serialization_state) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/hash/unpacker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class Hash 5 | # @api private 6 | # @since 0.4.0 7 | class Unpacker < Base::DataTransformer 8 | # @param serialized_event [::Hash] 9 | # @raise [EvilEvents::HashDeserializationError] 10 | # @return [EvilEvents::Core::Events::AbstractEvent] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(serialized_event) 17 | raise EvilEvents::HashDeserializationError unless serialized_event.is_a?(::Hash) 18 | serialization_state = engine.load(serialized_event) 19 | raise EvilEvents::HashDeserializationError unless serialization_state.valid? 20 | restore_event_instance(serialization_state) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/json.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | # @api private 5 | # @since 0.4.0 6 | JSON = Class.new(Base) 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/json/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class JSON 5 | # @api private 6 | # @since 0.4.0 7 | Config = Class.new(Base::GenericConfig) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/json/engines.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::JSON 4 | # @since 0.4.0 5 | # @api private 6 | class Engines 7 | # @since 0.4.0 8 | extend EvilEvents::Shared::DependencyContainer::Mixin 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/json/engines/native.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::JSON::Engines 4 | # @api private 5 | # @since 0.4.0 6 | class Native < EvilEvents::Core::Events::Serializers::Base::AbstractEngine 7 | # @param serialization_state [Base::EventSerializationState] 8 | # @return [String] 9 | # 10 | # @since 0.4.0 11 | # @api private 12 | def dump(serialization_state) 13 | ::JSON.generate( 14 | id: serialization_state.id, 15 | type: serialization_state.type, 16 | payload: serialization_state.payload, 17 | metadata: serialization_state.metadata 18 | ) 19 | end 20 | 21 | # @param json_string [String] 22 | # @raise [EvilEvents::SerializationEngineError] 23 | # @return [EventSerializationState] 24 | # 25 | # @since 0.4.0 26 | # @api private 27 | def load(json_string) 28 | json = ::JSON.parse(json_string, symbolize_names: true) 29 | 30 | restore_serialization_state( 31 | id: json[:id], 32 | type: json[:type], 33 | payload: json[:payload], 34 | metadata: json[:metadata] 35 | ) 36 | rescue ::JSON::ParserError, TypeError 37 | raise EvilEvents::SerializationEngineError 38 | end 39 | end 40 | 41 | # @since 0.4.0 42 | register(:native, Native) 43 | end 44 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/json/factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class JSON 5 | # @api private 6 | # @since 0.4.0 7 | class Factory < AbstractFactory 8 | # @return [JSON::Config] 9 | # 10 | # @api private 11 | # @since 0.4.0 12 | def build_config 13 | options = EvilEvents::Core::Bootstrap[:config].settings.serializers.json 14 | Config.new.tap { |conf| conf.settings.options = options } 15 | end 16 | 17 | # @param config [JSON::Config] 18 | # @raise [EvilEvents::UnrecognizedSerializationEngineError] 19 | # @return [Base::AbstractEngine] 20 | # 21 | # @api private 22 | # @since 0.4.0 23 | def build_engine(config) 24 | Engines.resolve(config.settings.options[:engine]).new(config) 25 | rescue Dry::Container::Error 26 | raise EvilEvents::UnrecognizedSerializationEngineError 27 | end 28 | 29 | # @param engine [Base::AbstractEngine] 30 | # @param config [JSON::Config] 31 | # @return [JSON::Packer] 32 | # 33 | # @api private 34 | # @since 0.4.0 35 | def build_packer(engine, _config) 36 | Packer.new(engine) 37 | end 38 | 39 | # @param engine [Base::AbstractEngine] 40 | # @param config [JSON::Config] 41 | # @return [JSON::Unpacker] 42 | # 43 | # @api private 44 | # @since 0.4.0 45 | def build_unpacker(engine, _config) 46 | Unpacker.new(engine) 47 | end 48 | 49 | # @param engine [Base::AbstractEngine] 50 | # @param config [JSON::Config] 51 | # @param packer [JSON::Packer] 52 | # @param unpacker [JSON::Unpacker] 53 | # @return [JSON] 54 | # 55 | # @api private 56 | # @since 0.4.0 57 | def create_adapter(engine, config, packer, unpacker) 58 | JSON.new(engine, config, packer, unpacker) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/json/packer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class JSON 5 | # @api private 6 | # @since 0.4.0 7 | class Packer < Base::DataTransformer 8 | # @param event [EvilEvents::Core::Events::AbstractEvent] 9 | # @raise [EvilEvents::JSONSerializationError] 10 | # @return [String] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(event) 17 | unless event.is_a?(EvilEvents::Core::Events::AbstractEvent) 18 | raise EvilEvents::JSONSerializationError 19 | end 20 | 21 | serialization_state = build_serialization_state(event) 22 | engine.dump(serialization_state) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/json/unpacker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class JSON 5 | # @api private 6 | # @since 0.4.0 7 | class Unpacker < Base::DataTransformer 8 | # @param serialized_event [String] 9 | # @raise [EvilEvents::JSONDeserializationError] 10 | # @return [EvilEvents::Core::Events::AbstractEvent] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(serialized_event) 17 | raise EvilEvents::JSONDeserializationError unless serialized_event.is_a?(String) 18 | 19 | begin 20 | serialization_state = engine.load(serialized_event) 21 | rescue EvilEvents::SerializationEngineError 22 | raise EvilEvents::JSONDeserializationError 23 | end 24 | 25 | raise EvilEvents::JSONDeserializationError unless serialization_state.valid? 26 | 27 | restore_event_instance(serialization_state) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/message_pack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | # @api private 5 | # @since 0.4.0 6 | MessagePack = Class.new(Base) 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/message_pack/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class MessagePack 5 | # @api private 6 | # @since 0.4.0 7 | Config = Class.new(Base::GenericConfig) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/message_pack/engines.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::MessagePack 4 | # @since 0.4.0 5 | # @api private 6 | class Engines 7 | # @since 0.4.0 8 | extend EvilEvents::Shared::DependencyContainer::Mixin 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/message_pack/engines/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0exp/evil_events/6cb49a45e58bcdf814db72f47a761afd19b4633d/lib/evil_events/core/events/serializers/message_pack/engines/.keep -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/message_pack/factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class MessagePack 5 | # @api private 6 | # @since 0.4.0 7 | class Factory < AbstractFactory 8 | # @return [MessagePack::Config] 9 | # 10 | # @api private 11 | # @since 0.4.0 12 | def build_config 13 | options = EvilEvents::Core::Bootstrap[:config].settings.serializers.msgpack 14 | Config.new.tap { |conf| conf.settings.options = options } 15 | end 16 | 17 | # @param config [MessagePack::Config] 18 | # @raise [EvilEvents::UnrecognizedSerializationEngineError] 19 | # @return [Base::AbstractEngine] 20 | # 21 | # @api private 22 | # @since 0.4.0 23 | def build_engine(config) 24 | Engines.resolve(config.settings.options[:engine]).new(config) 25 | rescue Dry::Container::Error 26 | raise EvilEvents::UnrecognizedSerializationEngineError 27 | end 28 | 29 | # @param engine [Base::AbstractEngine] 30 | # @param config [MessagePack::Config] 31 | # @return [MessagePack::Packer] 32 | # 33 | # @api private 34 | # @since 0.4.0 35 | def build_packer(engine, _config) 36 | Packer.new(engine) 37 | end 38 | 39 | # @param engine [Base::AbstractEngine] 40 | # @param config [MessagePack::Config] 41 | # @return [MessagePack::Unpacker] 42 | # 43 | # @api private 44 | # @since 0.4.0 45 | def build_unpacker(engine, _config) 46 | Unpacker.new(engine) 47 | end 48 | 49 | # @param engine [Base::AbstractEngine] 50 | # @param config [MessagePack::Config] 51 | # @param packer [MessagePack::Packer] 52 | # @param unpacker [MessagePack::Unpacker] 53 | # 54 | # @api private 55 | # @since 0.4.0 56 | def create_adapter(engine, config, packer, unpacker) 57 | MessagePack.new(engine, config, packer, unpacker) 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/message_pack/packer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class MessagePack 5 | # @api private 6 | # @since 0.4.0 7 | class Packer < Base::DataTransformer 8 | # @param event [EvilEvents::Core::Events::AbstractEvent] 9 | # @raise [EvilEvents::MessagePackSerializationError] 10 | # @return [String] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(event) 17 | unless event.is_a?(EvilEvents::Core::Events::AbstractEvent) 18 | raise EvilEvents::MessagePackSerializationError 19 | end 20 | 21 | serialization_state = build_serialization_state(event) 22 | engine.dump(serialization_state) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/message_pack/unpacker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class MessagePack 5 | # @api private 6 | # @since 0.4.0 7 | class Unpacker < Base::DataTransformer 8 | # @param serialized_event [String] 9 | # @raise [EvilEvents::MessagePackDeserializationErro] 10 | # @return [EvilEvents::Core::Events::AbstractEvent] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(serialized_event) 17 | raise EvilEvents::MessagePackDeserializationError unless serialized_event.is_a?(String) 18 | serialization_state = engine.load(serialized_event) 19 | raise EvilEvents::MessagePackDeserializationError unless serialization_state.valid? 20 | restore_event_instance(serialization_state) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/xml.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | # @api private 5 | # @since 0.4.0 6 | XML = Class.new(Base) 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/xml/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class XML 5 | # @api private 6 | # @since 0.4.0 7 | Config = Class.new(Base::GenericConfig) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/xml/engines.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::XML 4 | # @since 0.4.0 5 | # @api private 6 | class Engines 7 | # @since 0.4.0 8 | extend EvilEvents::Shared::DependencyContainer::Mixin 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/xml/engines/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0exp/evil_events/6cb49a45e58bcdf814db72f47a761afd19b4633d/lib/evil_events/core/events/serializers/xml/engines/.keep -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/xml/factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class XML 5 | # @api private 6 | # @since 0.4.0 7 | class Factory < AbstractFactory 8 | # @return [XML::Config] 9 | # 10 | # @api private 11 | # @since 0.4.0 12 | def build_config 13 | options = EvilEvents::Core::Bootstrap[:config].settings.serializers.xml 14 | Config.new.tap { |conf| conf.settings.options = options } 15 | end 16 | 17 | # @param config [XML::Config] 18 | # @raise [EvilEvents::UnrecognizedSerializationEngineError] 19 | # @return [Base::AbstractEngine] 20 | # 21 | # @api private 22 | # @since 0.4.0 23 | def build_engine(config) 24 | Engines.resolve(config.settings.options[:engine]).new(config) 25 | rescue Dry::Container::Error 26 | raise EvilEvents::UnrecognizedSerializationEngineError 27 | end 28 | 29 | # @param engine [Base::AbstractEngine] 30 | # @param config [XML::Config] 31 | # @return [XML::Packer] 32 | # 33 | # @api private 34 | # @since 0.4.0 35 | def build_packer(engine, _config) 36 | Packer.new(engine) 37 | end 38 | 39 | # @param engine [Base::AbstractEngine] 40 | # @param config [XML::Config] 41 | # @return [XML::Unpacker] 42 | # 43 | # @api private 44 | # @since 0.4.0 45 | def build_unpacker(engine, _config) 46 | Unpacker.new(engine) 47 | end 48 | 49 | # @param engine [Base::AbstractEngine] 50 | # @param config [XML::Config] 51 | # @param packer [XML::Packer] 52 | # @param unpacker [XML::Unpacker] 53 | # @return [XML] 54 | # 55 | # @api private 56 | # @since 0.4.0 57 | def create_adapter(engine, config, packer, unpacker) 58 | XML.new(engine, config, packer, unpacker) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/xml/packer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class XML 5 | # @api private 6 | # @since 0.4.0 7 | class Packer < Base::DataTransformer 8 | # @param event [EvilEvents::Core::Events::AbstractEvent] 9 | # @raise [EvilEvents::XMLSerializationError] 10 | # @return [String] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(event) 17 | unless event.is_a?(EvilEvents::Core::Events::AbstractEvent) 18 | raise EvilEvents::XMLSerializationError 19 | end 20 | 21 | serialization_state = build_serialization_state(event) 22 | engine.dump(serialization_state) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/serializers/xml/unpacker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers 4 | class XML 5 | # @api private 6 | # @since 0.4.0 7 | class Unpacker < Base::DataTransformer 8 | # @param serialized_event [String] 9 | # @raise [EvilEvents::XMLDeserializationError] 10 | # @return [EvilEvents::Core::Events::AbstractEvent] 11 | # 12 | # @see Base::DataTransformer 13 | # 14 | # @api private 15 | # @since 0.4.0 16 | def call(serialized_event) 17 | raise EvilEvents::XMLDeserializationError unless serialized_event.is_a?(String) 18 | serialization_state = engine.load(serialized_event) 19 | raise EvilEvents::XMLDeserializationError unless serialization_state.valid? 20 | restore_event_instance(serialization_state) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/subscriber.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Core::Events 4 | # @api private 5 | # @since 0.1.0 6 | class Subscriber 7 | # @return [EvilEvents::Shared::DelegatorResolver] 8 | # 9 | # @since 0.1.0 10 | attr_reader :delegator_resolver 11 | 12 | # @param subscriber [Object] 13 | # @param delegator_resolver [EvilEvents::Shared::DelegatorResolver] 14 | # 15 | # @since 0.1.0 16 | def initialize(subscriber, delegator_resolver = default_resolver) 17 | @subscriber = subscriber 18 | @delegator_resolver = delegator_resolver 19 | end 20 | 21 | # @param event [EvilEvents::Core::Events::AbstractEvent] 22 | # @return void 23 | # 24 | # @since 0.1.0 25 | def notify(event) 26 | source_object.public_send(delegator, event) 27 | end 28 | 29 | # @return [Object] 30 | # 31 | # @since 0.1.0 32 | def source_object 33 | @subscriber 34 | end 35 | 36 | # @return [String, Symbol] 37 | # 38 | # @since 0.1.0 39 | def delegator 40 | @delegator ||= delegator_resolver.delegator 41 | end 42 | 43 | private 44 | 45 | # @return [EvilEvents::Shared::DelegatorResolver] 46 | # 47 | # @since 0.1.0 48 | def default_resolver 49 | delegation = -> { EvilEvents::Core::Bootstrap[:config].settings.subscriber.default_delegator } 50 | EvilEvents::Shared::DelegatorResolver.new(delegation) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/evil_events/core/events/subscriber/mixin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Subscriber 4 | # rubocop:disable Metrics/BlockLength 5 | 6 | # @api public 7 | # @since 0.1.0 8 | Mixin = EvilEvents::Shared::ClonableModuleBuilder.build do 9 | # @param event_types [Array] 10 | # @option delegator [String, Symbol, NilClass] 11 | # @raise [EvilEvents::ArgumentError] 12 | # @return [void] 13 | # 14 | # @since 0.2.0 15 | def subscribe_to(*event_types, delegator: nil) 16 | raise EvilEvents::ArgumentError unless event_types.all? do |event_type| 17 | event_type.is_a?(Class) || 18 | event_type.is_a?(String) || 19 | event_type.is_a?(Regexp) || 20 | event_type.is_a?(Proc) 21 | end 22 | 23 | event_system = EvilEvents::Core::Bootstrap[:event_system] 24 | 25 | event_types.each do |event_type| 26 | case event_type 27 | when Class then event_system.observe(event_type, self, delegator) 28 | when String then event_system.raw_observe(event_type, self, delegator) 29 | when Regexp then event_system.observe_list(event_type, self, delegator) 30 | when Proc then event_system.conditional_observe(event_type, self, delegator) 31 | end 32 | end 33 | end 34 | 35 | # @param event_scopes [Array] 36 | # @option delegator [String,Symbol,NilClass] 37 | # @raise [EvilEvents::ArgumentError] 38 | # @return [void] 39 | # 40 | # @api public 41 | # @since 0.4.0 42 | def subscribe_to_scope(*event_scopes, delegator: nil) 43 | raise EvilEvents::ArgumentError unless event_scopes.all? do |event_scope| 44 | event_scope.is_a?(String) 45 | end 46 | 47 | event_system = EvilEvents::Core::Bootstrap[:event_system] 48 | 49 | event_scopes.each do |event_scope| 50 | event_system.scoped_observe(event_scope, self, delegator) 51 | end 52 | end 53 | end 54 | # rubocop:enable Metrics/BlockLength 55 | end 56 | -------------------------------------------------------------------------------- /lib/evil_events/core/system/mocking.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::System 4 | # @api private 5 | # @since 0.1.0 6 | module Mocking 7 | class << self 8 | # @param base_class [EvilEvents::Core::System] 9 | # 10 | # @since 0.1.0 11 | def included(base_class) 12 | base_class.extend(ClassMethods) 13 | end 14 | end 15 | 16 | # @since 0.1.0 17 | module ClassMethods 18 | # @return [EvilEvents::Core::System::Mock] 19 | # 20 | # @since 0.1.0 21 | def build_mock 22 | Mock.new 23 | end 24 | 25 | # @return [EvilEvents::Core::System] 26 | # 27 | # @since 0.1.0 28 | def build_stub 29 | new 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/evil_events/core/system/type_manager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::System 4 | # @api private 5 | # @since 0.2.0 6 | class TypeManager 7 | # @return [EvilEvents::Shared::TypeConverter] 8 | # 9 | # @since 0.2.0 10 | attr_reader :converter 11 | 12 | # @since 0.2.0 13 | def initialize 14 | @converter = EvilEvents::Shared::TypeConverter.new 15 | end 16 | 17 | # @param type [Symbol] 18 | # @param coercer [Proc] 19 | # @return [EvilEvents::Shared::TypeConverter::Converter] 20 | # 21 | # @since 0.2.0 22 | def register_converter(type, coercer) 23 | converter.register(type, coercer) 24 | end 25 | 26 | # @param type [Symbol] 27 | # @param options [Hash] 28 | # @return [EvilEvents::Shared::Types::Any] 29 | # 30 | # @since 0.2.0 31 | def resolve_type(type, **options) 32 | converter.resolve_type(type, **options) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/evil_events/dispatcher_mixin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.1.0 6 | DispatcherMixin = EvilEvents::Core::Broadcasting::Dispatcher::Mixin.module_clone 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/emitter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.1.0 6 | module Emitter 7 | class << self 8 | # @see EvilEvents::Core::System 9 | # @api public 10 | # @since 0.1.0 11 | def emit(event_type, id: nil, payload: {}, metadata: {}, adapter: nil) 12 | EvilEvents::Core::Bootstrap[:event_system].raw_emit( 13 | event_type, id: id, payload: payload, metadata: metadata, adapter: adapter 14 | ) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/evil_events/event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.1.0 6 | module Event 7 | class << self 8 | # @see EvilEvents::Core::System 9 | # @api public 10 | # @since 0.1.0 11 | def [](event_type) 12 | EvilEvents::Core::Bootstrap[:event_system].define_abstract_event_class(event_type) 13 | end 14 | 15 | # @see EvilEvents::Core::System 16 | # @api public 17 | # @since 0.1.0 18 | def define(event_type, &event_class_definitions) 19 | EvilEvents::Core::Bootstrap[:event_system].define_event_class( 20 | event_type, &event_class_definitions 21 | ) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/evil_events/plugins.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.2.0 6 | class Plugins 7 | require_relative 'plugins/oj_engine' 8 | require_relative 'plugins/ox_engine' 9 | require_relative 'plugins/mpacker_engine' 10 | 11 | # @since 0.3.0 12 | extend Shared::ExtensionsMixin 13 | 14 | # @since 0.5.0 15 | register_extension(:oj_engine) { OjEngine.load! } 16 | # @since 0.5.0 17 | register_extension(:ox_engine) { OxEngine.load! } 18 | # @since 0.5.0 19 | register_extension(:mpacker_engine) { MpackerEngine.load! } 20 | 21 | class << self 22 | # @param plugins [Symbol,Symbol,Symbol,...] 23 | # @raise [ArgumentError] When required plugin is not registered 24 | # @return void 25 | # 26 | # @api public 27 | # @since 0.2.0 28 | def load!(*plugins) 29 | load_extensions(*(plugins.empty? ? names : plugins)) 30 | end 31 | 32 | # @return [Array] 33 | # 34 | # @api public 35 | # @since 0.2.0 36 | def names 37 | @__available_extensions__.keys 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/evil_events/plugins/mpacker_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Plugins 4 | # @api private 5 | # @since 0.5.0 6 | module MpackerEngine 7 | class << self 8 | # @return [void] 9 | # 10 | # @api private 11 | # @since 0.5.0 12 | def load! 13 | load_dependencies! 14 | load_code! 15 | end 16 | 17 | private 18 | 19 | # @return [void] 20 | # 21 | # @api private 22 | # @since 0.5.0 23 | def load_dependencies! 24 | require 'msgpack' 25 | end 26 | 27 | # @return [void] 28 | # 29 | # @api private 30 | # @since 0.5.0 31 | def load_code! 32 | require_relative 'mpacker_engine/config' 33 | require_relative 'mpacker_engine/mpacker' 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/evil_events/plugins/mpacker_engine/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.5.0 5 | class EvilEvents::Core::Config 6 | setting :serializers do 7 | setting :msgpack do 8 | setting :mpacker do 9 | setting :configurator, -> (engine) {} 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/evil_events/plugins/mpacker_engine/mpacker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::MessagePack::Engines 4 | # @api private 5 | # @since 0.5.0 6 | class Mpacker < EvilEvents::Core::Events::Serializers::Base::AbstractEngine 7 | # @param config [EvilEvents::Core::Events::Serializers::MessagePack::Config] 8 | # @raise [EvilEvents::SerializationEngineError] 9 | # 10 | # @api private 11 | # @since 0.5.0 12 | def initialize(config) 13 | configurator = config.settings.options[:mpacker][:configurator] 14 | raise EvilEvents::SerializationEngineError unless configurator.is_a?(Proc) 15 | @factory = ::MessagePack::Factory.new.tap { |factory| configurator.call(factory) } 16 | end 17 | 18 | # @param serialization_state [Base::EventSerializationState] 19 | # @return [String] 20 | # 21 | # @since 0.5.0 22 | # @api private 23 | def dump(serialization_state) 24 | packer.pack( 25 | id: serialization_state.id, 26 | type: serialization_state.type, 27 | payload: serialization_state.payload, 28 | metadata: serialization_state.metadata 29 | ).to_str 30 | end 31 | 32 | # @param message [String] 33 | # @raise [EvilEvents::SerializationEngineError] 34 | # @return [EventSerializationState] 35 | # 36 | # @since 0.5.0 37 | # @api private 38 | def load(message) 39 | begin 40 | event_data = unpacker.feed(message).unpack 41 | raise EvilEvents::SerializationEngineError unless event_data.is_a?(::Hash) 42 | rescue ::MessagePack::UnpackError, ArgumentError, TypeError 43 | raise EvilEvents::SerializationEngineError 44 | end 45 | 46 | restore_serialization_state( 47 | id: event_data[:id], 48 | type: event_data[:type], 49 | payload: event_data[:payload], 50 | metadata: event_data[:metadata] 51 | ) 52 | end 53 | 54 | private 55 | 56 | # @return [::MessagePack::Factory] 57 | # 58 | # @api private 59 | # @since 0.5.0 60 | attr_reader :factory 61 | 62 | # @return [::MessagePack::Packer] 63 | # 64 | # @api private 65 | # @since 0.5.0 66 | def packer 67 | factory.packer 68 | end 69 | 70 | # @return [::MessagePack::Unpacker] 71 | # 72 | # @api private 73 | # @since 0.5.0 74 | def unpacker 75 | factory.unpacker(symbolize_keys: true) 76 | end 77 | end 78 | 79 | # @since 0.5.0 80 | register(:mpacker, Mpacker) 81 | end 82 | -------------------------------------------------------------------------------- /lib/evil_events/plugins/oj_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Plugins 4 | # @api private 5 | # @since 0.5.0 6 | module OjEngine 7 | class << self 8 | # @return [void] 9 | # 10 | # @api private 11 | # @since 0.5.0 12 | def load! 13 | load_dependencies! 14 | load_code! 15 | end 16 | 17 | private 18 | 19 | # @return [void] 20 | # 21 | # @api private 22 | # @since 0.5.0 23 | def load_dependencies! 24 | require 'oj' 25 | end 26 | 27 | # @return [void] 28 | # 29 | # @api private 30 | # @since 0.5.0 31 | def load_code! 32 | require_relative 'oj_engine/oj' 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/evil_events/plugins/oj_engine/oj.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::JSON::Engines 4 | # @api private 5 | # @since 0.5.0 6 | class Oj < EvilEvents::Core::Events::Serializers::Base::AbstractEngine 7 | # @param serialization_state [Base::EventSerializationState] 8 | # @return [String] 9 | # 10 | # @since 0.5.0 11 | # @api private 12 | def dump(serialization_state) 13 | ::Oj.dump( 14 | id: serialization_state.id, 15 | type: serialization_state.type, 16 | payload: serialization_state.payload, 17 | metadata: serialization_state.metadata 18 | ) 19 | end 20 | 21 | # @param json_string [String] 22 | # @raise [EvilEvents::SerializationEngineError] 23 | # @return [EventSerializationState] 24 | # 25 | # @since 0.5.0 26 | # @api private 27 | def load(json_string) 28 | json = ::Oj.load(json_string, symbol_keys: true) 29 | 30 | restore_serialization_state( 31 | id: json[:id], 32 | type: json[:type], 33 | payload: json[:payload], 34 | metadata: json[:metadata] 35 | ) 36 | rescue ::Oj::Error, NoMethodError, TypeError, ArgumentError 37 | raise EvilEvents::SerializationEngineError 38 | end 39 | end 40 | 41 | # @since 0.5.0 42 | register(:oj, Oj) 43 | end 44 | -------------------------------------------------------------------------------- /lib/evil_events/plugins/ox_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Plugins 4 | # @api private 5 | # @since 0.5.0 6 | module OxEngine 7 | class << self 8 | # @return [void] 9 | # 10 | # @api private 11 | # @since 0.5.0 12 | def load! 13 | load_dependencies! 14 | load_code! 15 | end 16 | 17 | private 18 | 19 | # @return [void] 20 | # 21 | # @api private 22 | # @since 0.5.0 23 | def load_dependencies! 24 | require 'ox' 25 | end 26 | 27 | # @return [void] 28 | # 29 | # @api private 30 | # @since 0.5.0 31 | def load_code! 32 | require_relative 'ox_engine/ox' 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/evil_events/plugins/ox_engine/ox.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Core::Events::Serializers::XML::Engines 4 | # @api private 5 | # @since 0.5.0 6 | class Ox < EvilEvents::Core::Events::Serializers::Base::AbstractEngine 7 | # @param serialization_state [Base::EventSerializationState] 8 | # @return [String] 9 | # 10 | # @since 0.5.0 11 | # @api private 12 | def dump(serialization_state) 13 | ::Ox.dump(serialization_state) 14 | end 15 | 16 | # @param xml [String] 17 | # @raise [EvilEvents::SerializationEngineError] 18 | # @return [EventSerializationState] 19 | # 20 | # @since 0.5.0 21 | # @api private 22 | def load(xml) 23 | ::Ox.parse_obj(xml) 24 | rescue ::Ox::Error, NoMethodError, TypeError, ArgumentError 25 | raise EvilEvents::SerializationEngineError 26 | end 27 | end 28 | 29 | # @since 0.5.0 30 | register(:ox, Ox) 31 | end 32 | -------------------------------------------------------------------------------- /lib/evil_events/serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.1.0 6 | module Serializer 7 | class << self 8 | # @see EvilEvents::Core::System 9 | # @api public 10 | # @since 0.1.0 11 | def load_from_json(serialized_event) 12 | EvilEvents::Core::Bootstrap[:event_system].deserialize_from_json(serialized_event) 13 | end 14 | 15 | # @see EvilEvents::Core::System 16 | # @api public 17 | # @since 0.1.0 18 | def load_from_hash(serialized_event) 19 | EvilEvents::Core::Bootstrap[:event_system].deserialize_from_hash(serialized_event) 20 | end 21 | 22 | # @see EvilEvents::Core::System 23 | # @api public 24 | # @since 0.4.0 25 | def load_from_xml(serialized_event) 26 | EvilEvents::Core::Bootstrap[:event_system].deserialize_from_xml(serialized_event) 27 | end 28 | 29 | # @see EvilEvents::Core::System 30 | # @api public 31 | # @since 0.4.0 32 | def load_from_msgpack(serialized_event) 33 | EvilEvents::Core::Bootstrap[:event_system].deserialize_from_msgpack(serialized_event) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/evil_events/shared.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.1.0 6 | module Shared 7 | require_relative 'shared/types' 8 | require_relative 'shared/logger' 9 | require_relative 'shared/crypto' 10 | require_relative 'shared/structure' 11 | require_relative 'shared/delegator_resolver' 12 | require_relative 'shared/dependency_container' 13 | require_relative 'shared/extensions_mixin' 14 | require_relative 'shared/clonable_module_builder' 15 | require_relative 'shared/type_converter' 16 | require_relative 'shared/type_converter/converter' 17 | require_relative 'shared/type_converter/type_builder' 18 | require_relative 'shared/type_converter/converter_registry' 19 | require_relative 'shared/any_config' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/evil_events/shared/any_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 0.4.0 5 | class EvilEvents::Shared::AnyConfig 6 | class << self 7 | # @param configuration [Proc] 8 | # @return [Proc] 9 | # 10 | # @api public 11 | # @since 0.4.0 12 | def configure(&configuration) 13 | case 14 | when !block_given? && !instance_variable_defined?(:@setup) 15 | @setup = proc {} 16 | when block_given? 17 | @setup = configuration 18 | else 19 | @setup 20 | end 21 | end 22 | end 23 | 24 | # @api public 25 | # @since 0.4.0 26 | def initialize 27 | setup = self.class.configure 28 | 29 | @config = Module.new do 30 | extend Dry::Configurable 31 | instance_eval(&setup) 32 | end 33 | 34 | @config.configure { |conf| yield(conf) if block_given? } 35 | end 36 | 37 | # @return [Hash] 38 | # 39 | # @api public 40 | # @since 0.4.0 41 | def to_h 42 | @config.config.to_h 43 | end 44 | alias_method :to_hash, :to_h 45 | 46 | # @api private 47 | # @since 0.4.0 48 | def method_missing(method_name, *attributes, &block) 49 | return super unless @config.respond_to?(method_name) 50 | @config.public_send(method_name, *attributes, &block) 51 | end 52 | 53 | # :nocov: 54 | # @api private 55 | # @since 0.4.0 56 | def respond_to_missing?(method_name, include_private = false) 57 | @config.respond_to?(method_name, include_private) || super 58 | end 59 | # :nocov: 60 | end 61 | -------------------------------------------------------------------------------- /lib/evil_events/shared/clonable_module_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.1.0 6 | module ClonableModuleBuilder 7 | class << self 8 | # @param module_definitions [Proc] 9 | # @return [Module] 10 | # 11 | # @since 0.1.0 12 | def build(&module_definitions) 13 | Module.new do 14 | class_eval(&module_definitions) if block_given? 15 | 16 | singleton_class.instance_eval do 17 | define_method :module_clone do 18 | ClonableModuleBuilder.build(&module_definitions) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/evil_events/shared/crypto.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.1.0 6 | module Crypto 7 | module_function 8 | 9 | # @return [String] 10 | # 11 | # @api public 12 | # @since 0.1.0 13 | def uuid 14 | ::SecureRandom.uuid 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/evil_events/shared/delegator_resolver.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @example 5 | # DelegatorResolver.new(-> { 'test' }).delegator # => 'test' 6 | # DelegatorResolver.new('test') # => InvalidProcAttributeError 7 | # 8 | # @since 0.1.0 9 | # @api public 10 | class DelegatorResolver 11 | # @since 0.1.0 12 | DelegatorResolverError = Class.new(StandardError) 13 | # @since 0.1.0 14 | InvalidProcAttributeError = Class.new(DelegatorResolverError) 15 | 16 | # @return [Proc] 17 | # 18 | # @since 0.1.0 19 | attr_reader :method_name_resolver 20 | 21 | # @param method_name_resolver [Proc] 22 | # 23 | # @since 0.1.0 24 | def initialize(method_name_resolver) 25 | raise InvalidProcAttributeError unless method_name_resolver.is_a?(Proc) 26 | @method_name_resolver = method_name_resolver 27 | end 28 | 29 | # @return [String, Symbol] 30 | # 31 | # @since 0.1.0 32 | def delegator 33 | @delegator ||= method_name_resolver.call 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/evil_events/shared/dependency_container.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.1.0 6 | DependencyContainer = Dry::Container 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/shared/extensions_mixin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.3.0 6 | ExtensionsMixin = Dry::Core::Extensions 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/shared/logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.1.0 6 | class Logger < ::Logger 7 | # @since 0.1.0 8 | def initialize(*, **) 9 | super 10 | self.level = ::Logger::INFO 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/evil_events/shared/structure.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.1.0 6 | class Structure < Dry::Struct 7 | # NOTE: dry-struct API + dry-initializer API 8 | input input.strict 9 | 10 | class << self 11 | # @since 0.1.0 12 | alias_method :_native_attribute, :attribute 13 | 14 | # @param key [Symbol] 15 | # @param type [EvilEvents::Shared::Types::Any] 16 | # 17 | # @since 0.1.0 18 | def attribute(key, type = EvilEvents::Shared::Types::Any) 19 | _native_attribute(key, type) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/evil_events/shared/type_converter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.2.0 6 | class TypeConverter 7 | # @return [EvilEvents::Shared::TypeConverter::ConverterRegistry] 8 | # 9 | # @api public 10 | # @since 0.2.0 11 | attr_reader :registry 12 | 13 | # @api public 14 | # @since 0.2.0 15 | def initialize 16 | @registry = ConverterRegistry.new 17 | end 18 | 19 | # @param type_name [Symbol] 20 | # @param coercer [Proc] 21 | # @return [EvilEvents::Shared::TypeConverter::Converter] 22 | # 23 | # @see EvilEvents::Shared::TypeConverter::ConverterRegistry 24 | # 25 | # @api public 26 | # @since 0.2.0 27 | def register(type_name, coercer) 28 | registry.register(type_name, coercer) 29 | end 30 | 31 | # @param type_name [Symbol] 32 | # @param options [Hash] 33 | # @return [EvilEvents::Shared::Types::Any] 34 | # 35 | # @see EvilEvents::Shared::TypeConverter::ConverterRegistry 36 | # 37 | # @api public 38 | # @since 0.2.0 39 | def resolve_type(type_name, **options) 40 | registry.resolve(type_name).transform_to_type(**options) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/evil_events/shared/type_converter/converter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Shared::TypeConverter 4 | # @api public 5 | # @since 0.2.0 6 | class Converter 7 | # @return [Proc] 8 | # 9 | # @api public 10 | # @since 0.2.0 11 | attr_reader :coercer 12 | 13 | # @param coercer [Proc] 14 | # 15 | # @api public 16 | # @since 0.2.0 17 | def initialize(coercer) 18 | raise ArgumentError unless coercer.is_a?(Proc) 19 | 20 | @coercer = coercer 21 | end 22 | 23 | # @param value [Mixed] 24 | # @return [Mixed] 25 | # 26 | # @api public 27 | # @since 0.2.0 28 | def convert(value) 29 | coercer.call(value) 30 | end 31 | 32 | # @option :default [Mixed] 33 | # @return [EvilEvents::Shared::Types::Any] 34 | # 35 | # @see EvilEvents::Shared::TypeConverter::TypeBuilder 36 | # 37 | # @since 0.2.0 38 | def transform_to_type(**options) 39 | TypeBuilder.new.tap do |builder| 40 | builder.append(:constructor, coercer) 41 | builder.append(:default, options[:default]) if options.key?(:default) 42 | end.result 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/evil_events/shared/type_converter/converter_registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Shared::TypeConverter 4 | # @api public 5 | # @since 0.2.0 6 | class ConverterRegistry 7 | # @since 0.2.0 8 | ConverterRegistryError = Class.new(StandardError) 9 | # @since 0.2.0 10 | ConverterNotRegisteredError = Class.new(ConverterRegistryError) 11 | 12 | # @return [Coucnrrent::Map] 13 | # 14 | # @api public 15 | # @since 0.2.0 16 | attr_reader :converters 17 | 18 | # @api public 19 | # @since 0.2.0 20 | def initialize 21 | @converters = EvilEvents::Shared::DependencyContainer.new 22 | end 23 | 24 | # @param type_name [Symbol] 25 | # @param coercer [Proc] 26 | # @raise [ArgumentError] 27 | # @return [Converter] 28 | # 29 | # @api public 30 | # @since 0.2.0 31 | def register(type_name, coercer) 32 | raise ArgumentError unless type_name.is_a?(Symbol) 33 | raise ArgumentError unless coercer.is_a?(Proc) 34 | 35 | Converter.new(coercer).tap do |converter| 36 | converters.register(type_name, converter) 37 | end 38 | end 39 | 40 | # @param type [Mixed] 41 | # @raise [ConverterNotRegisteredError] 42 | # @return [Mixed] 43 | # 44 | # @api public 45 | # @since 0.2.0 46 | def resolve(type) 47 | converters[type] 48 | end 49 | alias_method :[], :resolve 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/evil_events/shared/type_converter/type_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EvilEvents::Shared::TypeConverter 4 | # @api public 5 | # @since 0.2.0 6 | class TypeBuilder 7 | # @api public 8 | # @since 0.2.0 9 | def initialize 10 | @type_atom = Concurrent::Atom.new(EvilEvents::Shared::Types::Any) 11 | end 12 | 13 | # @param option [Symbol] 14 | # @param value [Mixed] 15 | # @return self 16 | # 17 | # @api public 18 | # @since 0.2.0 19 | def append(option, value) 20 | type_atom.swap do |type| 21 | case option 22 | when :default 23 | # NOTE: Dry::Types callable wrapper (see Dry::Types::Default::Callable#evaulate) 24 | default_value = value.is_a?(Proc) ? (-> (_type) { value.call }) : (proc { value }) 25 | type.default(default_value) 26 | when :constructor 27 | type.constructor(value) 28 | else 29 | type 30 | end 31 | end 32 | 33 | self 34 | end 35 | 36 | # @return [EvilEvents::Shared::Types::Any] 37 | # 38 | # @api public 39 | # @since 0.2.0 40 | def result 41 | type_atom.value 42 | end 43 | 44 | private 45 | 46 | # @return [Concurrent::Atom] 47 | # 48 | # @since 0.2.0 49 | attr_reader :type_atom 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/evil_events/shared/types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents::Shared 4 | # @api public 5 | # @since 0.1.0 6 | module Types 7 | Dry::Types.load_extensions(:maybe) 8 | include Dry::Types.module 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/evil_events/subscriber_mixin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api piblic 5 | # @since 0.1.0 6 | SubscriberMixin = EvilEvents::Core::Events::Subscriber::Mixin.module_clone 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.1.0 6 | Types = Shared::Types 7 | end 8 | -------------------------------------------------------------------------------- /lib/evil_events/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EvilEvents 4 | # @api public 5 | # @since 0.5.0 6 | VERSION = '0.5.0' 7 | end 8 | -------------------------------------------------------------------------------- /spec/integration/plugins_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe 'plugins ecosystem' do 4 | specify 'registered plugins' do 5 | expect(EvilEvents::Plugins.names).to contain_exactly( 6 | :oj_engine, 7 | :ox_engine, 8 | :mpacker_engine 9 | ) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov' 4 | require 'coveralls' 5 | 6 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ 7 | SimpleCov::Formatter::HTMLFormatter, 8 | Coveralls::SimpleCov::Formatter 9 | ]) 10 | 11 | SimpleCov.start { add_filter 'spec' } 12 | 13 | require 'pry' 14 | require 'bundler/setup' 15 | require 'dry/container/stub' 16 | require 'evil_events' 17 | 18 | require_relative 'support/spec_support' 19 | require_relative 'support/shared_examples' 20 | require_relative 'support/shared_contexts' 21 | require_relative 'support/application_state_metascopes' 22 | 23 | RSpec.configure do |config| 24 | config.shared_context_metadata_behavior = :apply_to_host_groups 25 | config.expect_with(:rspec) { |c| c.syntax = :expect } 26 | config.order = :random 27 | Kernel.srand config.seed 28 | 29 | config.include SpecSupport::EventFactories 30 | config.include SpecSupport::NotifierFactories 31 | config.include SpecSupport::EventManagerFactories 32 | config.include SpecSupport::DispatchingAdapterFactories 33 | config.include SpecSupport::FakeDataGenerator 34 | config.extend SpecSupport::FakeDataGenerator 35 | end 36 | -------------------------------------------------------------------------------- /spec/support/application_state_metascopes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |config| 4 | config.before(:example) do 5 | EvilEvents::Core::Bootstrap.enable_stubs! 6 | EvilEvents::Core::Bootstrap.stub(:config, EvilEvents::Core::Config.build_stub) 7 | end 8 | 9 | config.after(:example) do 10 | EvilEvents::Core::Bootstrap.unstub 11 | end 12 | 13 | config.before(:example, :mock_event_system) do 14 | EvilEvents::Core::Bootstrap.stub(:event_system, EvilEvents::Core::System.build_mock) 15 | end 16 | 17 | config.before(:example, :stub_event_system) do 18 | EvilEvents::Core::Bootstrap.stub(:event_system, EvilEvents::Core::System.build_stub) 19 | end 20 | 21 | config.before(:example, :null_logger) do 22 | EvilEvents::Core::Bootstrap[:config].configure do |conf| 23 | conf.logger = SpecSupport::NullLogger 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/support/shared_contexts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'shared_contexts/event_system_context' 4 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/event_system_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_context 'event system' do 4 | let(:event_system) { EvilEvents::Core::Bootstrap[:event_system] } 5 | let(:system_config) { EvilEvents::Core::Bootstrap[:config] } 6 | end 7 | -------------------------------------------------------------------------------- /spec/support/shared_examples.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'shared_examples/event_serializers/generic_event_serialization_component' 4 | require_relative 'shared_examples/event_serializers/json_event_serialization_component' 5 | require_relative 'shared_examples/event_serializers/hash_event_serialization_component' 6 | require_relative 'shared_examples/event_serializers/xml_event_serialization_component' 7 | require_relative 'shared_examples/event_serializers/messagepack_event_serialization_component' 8 | require_relative 'shared_examples/event_subscriber_component' 9 | require_relative 'shared_examples/dependency_container_interface' 10 | require_relative 'shared_examples/event_dispatching_interface' 11 | require_relative 'shared_examples/event_extensions/dispatchable_interface' 12 | require_relative 'shared_examples/event_extensions/manageable_interface' 13 | require_relative 'shared_examples/event_extensions/observable_interface' 14 | require_relative 'shared_examples/event_extensions/serializable_interface' 15 | require_relative 'shared_examples/event_extensions/type_aliasing_interface' 16 | require_relative 'shared_examples/event_extensions/payloadable_interface' 17 | require_relative 'shared_examples/event_extensions/payload_entity_component' 18 | require_relative 'shared_examples/event_extensions/metadata_extendable_interface' 19 | require_relative 'shared_examples/event_extensions/metadata_entity_component' 20 | require_relative 'shared_examples/event_extensions/class_signature_interface' 21 | require_relative 'shared_examples/event_extensions/hookable_interface' 22 | require_relative 'shared_examples/event_extensions/hook_component_behaviour' 23 | require_relative 'shared_examples/notifier/logging_interface' 24 | require_relative 'shared_examples/notifier/callbacks_invokation_interface' 25 | -------------------------------------------------------------------------------- /spec/support/shared_examples/dependency_container_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'dependency container interface' do 4 | describe 'public container interface' do 5 | # HACK: have to reset internal registry so that doesnt interfer with other specs) 6 | specify 'registering/resolving dependencies' do 7 | rand(20).times do 8 | random_key = gen_str 9 | expect { container.resolve(random_key) }.to raise_error(Dry::Container::Error) 10 | 11 | random_object = double 12 | container.register(random_key, random_object) 13 | expect(container.resolve(random_key)).to eq(random_object) 14 | expect(container[random_key]).to eq(random_object) 15 | end 16 | end 17 | 18 | specify 'key duplication error' do 19 | repeated_key = gen_symb(only_letters: true) 20 | 21 | expect do 22 | container.register(repeated_key, double) 23 | container.register(repeated_key, double) 24 | end.to raise_error(Dry::Container::Error) 25 | end 26 | 27 | specify 'dependency memoization' do 28 | simple_object_key = gen_symb(only_letters: true) 29 | another_object_key = gen_symb(only_letters: true) 30 | 31 | # with memoization 32 | container.register(simple_object_key, memoize: true) { Object.new } 33 | first_resolve = container.resolve(simple_object_key) 34 | second_resolve = container.resolve(simple_object_key) 35 | expect(first_resolve).to eq(second_resolve) 36 | 37 | # without memoization 38 | container.register(another_object_key) { Object.new } 39 | first_resolve = container.resolve(another_object_key) 40 | second_resolve = container.resolve(another_object_key) 41 | expect(first_resolve).not_to eq(second_resolve) 42 | end 43 | 44 | specify 'mocking dependencies' do 45 | simple_dependency = double 46 | dependency_key = gen_symb(only_letters: true) 47 | 48 | container.register(dependency_key, simple_dependency) 49 | 50 | container.enable_stubs! 51 | 52 | mocked_dependency = double 53 | container.stub(dependency_key, mocked_dependency) 54 | expect(container.resolve(dependency_key)).to eq(mocked_dependency) 55 | expect(container.resolve(dependency_key)).to eq(mocked_dependency) 56 | 57 | container.unstub 58 | expect(container.resolve(dependency_key)).to eq(simple_dependency) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_dispatching_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'event dispatching interface' do 4 | describe 'event dispatching behaviour' do 5 | describe '#dispatch' do 6 | context 'when we tries to dispatch a system-managed event object' do 7 | it 'delegates event handling to the appropriate event manager' do 8 | event_system = EvilEvents::Core::Bootstrap[:event_system] 9 | 10 | first_event = EvilEvents::Event.define('first_event').new 11 | second_event = EvilEvents::Event.define('second_event').new 12 | third_event = EvilEvents::Event.define('third_event').new 13 | 14 | first_manager = event_system.manager_of_event(first_event) 15 | second_manager = event_system.manager_of_event(second_event) 16 | third_manager = event_system.manager_of_event(third_event) 17 | 18 | expect(first_manager).to receive(:notify).with(first_event).once 19 | dispatcher.dispatch(first_event) 20 | 21 | expect(second_manager).to receive(:notify).with(second_event).once 22 | dispatcher.dispatch(second_event) 23 | 24 | expect(third_manager).to receive(:notify).with(third_event).once 25 | dispatcher.dispatch(third_event) 26 | end 27 | end 28 | 29 | context 'when we tries to dispatch a system-non-managed event object' do 30 | subject(:dispatch) { dispatcher.dispatch double } 31 | 32 | it 'fails with non-managed event class error' do 33 | expect { dispatch }.to raise_error(EvilEvents::NonManagedEventClassError) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_extensions/hook_component_behaviour.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'hook component behaviour' do |base_call_method: true| 4 | specify 'intiialization and state (#callable)' do 5 | callable = double 6 | hook = described_class.new(callable) 7 | 8 | expect(hook.callable).to eq(callable) 9 | end 10 | 11 | if base_call_method 12 | specify 'invokation (#call)' do 13 | source = double 14 | callable = double 15 | hook = hook_class.new(callable) 16 | 17 | expect(callable).to receive(:call).with(source) 18 | 19 | hook.call(source) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_extensions/manageable_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'manageable interface' do 4 | describe 'manageable behavior', :mock_event_system do 5 | describe 'event class registration' do 6 | describe '.manage!' do 7 | it 'registers the current event class via delegation this process to the event system' do 8 | allow(EvilEvents::Core::Bootstrap[:event_system]).to receive(:register_event_class) 9 | 10 | manageable.manage! 11 | 12 | expect(EvilEvents::Core::Bootstrap[:event_system]).to( 13 | have_received(:register_event_class).with(manageable) 14 | ) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_extensions/payloadable_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # TODO: Dry with 4 | shared_examples 'payloadable interface' do 5 | describe 'payloadable behaviour' do 6 | describe 'payload sub-type intialization' do 7 | it 'creates a concrete sub-type of an abstract payload after inheritence' do 8 | first_event_class = Class.new(payloadable_abstraction) 9 | second_event_class = Class.new(payloadable_abstraction) 10 | 11 | expect(first_event_class::Payload).not_to eq(second_event_class::Payload) 12 | end 13 | end 14 | 15 | describe 'payload attribute definition DSL' do 16 | let(:event_class) { Class.new(payloadable_abstraction) } 17 | 18 | describe '#attribute' do 19 | it 'defines payload attribute with a custom type', :stub_event_system do 20 | # register two coercible types 21 | EvilEvents::Core::Bootstrap[:event_system].tap do |system| 22 | system.register_converter(:string, -> (value) { value.to_s }) 23 | system.register_converter(:integer, -> (value) { value.to_i }) 24 | end 25 | 26 | # define attribute without any strict type 27 | expect { event_class.payload :foo }.not_to raise_error 28 | expect(event_class::Payload.attribute_names).to contain_exactly(:foo) 29 | 30 | # define attributes with type definitions 31 | expect do 32 | # Dry::Types API 33 | event_class.payload :bar, EvilEvents::Types::Strict::String 34 | # TypeConverter API 35 | event_class.payload :baz, :string 36 | end.not_to raise_error 37 | 38 | expect(event_class::Payload.attribute_names).to contain_exactly(:foo, :bar, :baz) 39 | end 40 | 41 | it 'delegates the attribute definition process to Payload', :stub_event_system do 42 | # using Dry::Types 43 | common_attr = :amount 44 | common_type = EvilEvents::Types::Strict::Integer 45 | expect(event_class::Payload).to receive(:attribute).with(common_attr, common_type) 46 | event_class.payload common_attr, common_type 47 | 48 | # using coercible types 49 | EvilEvents::Core::Bootstrap[:event_system].tap do |system| 50 | system.register_converter(:utility, proc { |value| value.to_s }) 51 | end 52 | coercible_attr = :utility 53 | expect(event_class::Payload).to receive(:attribute).with(coercible_attr, anything) 54 | event_class.payload coercible_attr, default: -> { 0 } 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_extensions/serializable_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'serializable interface' do 4 | describe 'serailizable behaviour' do 5 | shared_examples 'serialization/deserialization logic' do |serialization_method| 6 | it 'delegates serialization logic to event system' do 7 | allow(EvilEvents::Core::Bootstrap[:event_system]).to receive(serialization_method) 8 | 9 | serializable.public_send(serialization_method) 10 | 11 | expect(EvilEvents::Core::Bootstrap[:event_system]).to( 12 | have_received(serialization_method).with(serializable) 13 | ) 14 | end 15 | end 16 | 17 | it_behaves_like 'serialization/deserialization logic', :serialize_to_hash 18 | it_behaves_like 'serialization/deserialization logic', :serialize_to_json 19 | it_behaves_like 'serialization/deserialization logic', :serialize_to_xml 20 | it_behaves_like 'serialization/deserialization logic', :serialize_to_msgpack 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_extensions/type_aliasing_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'type aliasing interface' do 4 | describe 'DSL extensions' do 5 | describe '.type' do 6 | it 'fails when a function argument (type_name) has incopatible type (non-nil/non-string)' do 7 | expect { pseudo_identifiable.type(gen_symb) }.to raise_error( 8 | EvilEvents::IncopatibleEventTypeError 9 | ) 10 | 11 | expect { pseudo_identifiable.type(gen_class) }.to raise_error( 12 | EvilEvents::IncopatibleEventTypeError 13 | ) 14 | 15 | expect { pseudo_identifiable.type(gen_int) }.to raise_error( 16 | EvilEvents::IncopatibleEventTypeError 17 | ) 18 | 19 | expect { pseudo_identifiable.type(gen_str) }.not_to raise_error 20 | expect { pseudo_identifiable.type }.not_to raise_error 21 | end 22 | 23 | it 'fails when we tries to redefine the already defined type name' do 24 | pseudo_identifiable.type gen_str 25 | expect { pseudo_identifiable.type gen_str }.to raise_error( 26 | EvilEvents::EventTypeAlreadyDefinedError 27 | ) 28 | end 29 | 30 | it 'fails when we tries to get the non-defined type name' do 31 | expect { pseudo_identifiable.type }.to raise_error( 32 | EvilEvents::EventTypeNotDefinedError 33 | ) 34 | end 35 | 36 | it 'registrates a type name with passed string if type name was not defined previously' do 37 | random_type_name = gen_str 38 | 39 | expect { pseudo_identifiable.type(random_type_name) }.not_to raise_error 40 | expect(pseudo_identifiable.type).to eq(random_type_name) 41 | end 42 | 43 | it 'returns the defined type name after the correct definition and getting operations' do 44 | random_type_name = gen_str 45 | 46 | expect(pseudo_identifiable.type(random_type_name)).to eq(random_type_name) 47 | expect(pseudo_identifiable.type).to eq(random_type_name) 48 | end 49 | end 50 | end 51 | 52 | describe 'instance extensions' do 53 | describe '#type' do 54 | it 'returns the previously defined type defined on a class' do 55 | type_name = gen_str 56 | 57 | pseudo_identifiable.type type_name 58 | 59 | # check that all instances has the same type alias 60 | expect(pseudo_identifiable.new.type).to eq(type_name) 61 | expect(pseudo_identifiable.new.type).to eq(type_name) 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_serializers/hash_event_serialization_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'hash event serialization component' do 4 | describe 'hash serializer behaviour' do 5 | it_behaves_like 'generic event serialization component' do 6 | let(:serializer) { EvilEvents::Core::Events::Serializers::Hash::Factory.new.create! } 7 | let(:serialization_error) { EvilEvents::HashSerializationError } 8 | let(:deserialization_error) { EvilEvents::HashDeserializationError } 9 | let(:serialization_type) { Hash } 10 | let(:incorrect_deserialization_objects) { gen_all } 11 | let(:invalid_serializations) do 12 | key_mappings = ( 13 | %i[type metadata payload].combination(1).to_a | 14 | %i[type metadata payload].combination(2).to_a | 15 | %i[type metadata payload].combination(3).to_a | 16 | %i[type metadata payload].combination(4).to_a 17 | ) 18 | 19 | key_mappings.map do |key_map| 20 | data = serializer.serialize(event) 21 | data.tap { |d| key_map.each { |key| d[key] = nil } } # nullify values 22 | end 23 | end 24 | 25 | specify { expect(serializer).to be_a(EvilEvents::Core::Events::Serializers::Hash) } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_serializers/json_event_serialization_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'json event serialization component' do 4 | describe 'json serializer behaviour' do 5 | it_behaves_like 'generic event serialization component' do 6 | let(:serializer) { EvilEvents::Core::Events::Serializers::JSON::Factory.new.create! } 7 | let(:serialization_error) { EvilEvents::JSONSerializationError } 8 | let(:deserialization_error) { EvilEvents::JSONDeserializationError } 9 | let(:serialization_type) { String } 10 | let(:incorrect_deserialization_objects) { gen_all } 11 | let(:invalid_serializations) do 12 | data = serializer.serialize(event) 13 | 14 | [ 15 | data.sub('type":', "\"#{gen_str}\":"), 16 | data.sub('payload":', "\"#{gen_str}\":"), 17 | data.sub('metadata":', "\"#{gen_str}\":") 18 | ] 19 | end 20 | 21 | specify { expect(serializer).to be_a(EvilEvents::Core::Events::Serializers::JSON) } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_serializers/messagepack_event_serialization_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'messagepack event serialization component' do 4 | describe 'messagepack serializer behaviour' do 5 | it_behaves_like 'generic event serialization component' do 6 | let(:serializer) { EvilEvents::Core::Events::Serializers::MessagePack::Factory.new.create! } 7 | let(:serialization_error) { EvilEvents::MessagePackSerializationError } 8 | let(:deserialization_error) { EvilEvents::MessagePackDeserializationError } 9 | let(:serialization_type) { String } 10 | let(:incorrect_deserialization_objects) { gen_all(except: :gen_str) } 11 | let(:invalid_serializations) do 12 | data = serializer.serialize(event) 13 | 14 | [ 15 | data.sub('type', gen_str), 16 | data.sub('metadata', gen_str), 17 | data.sub('payload', gen_str) 18 | ] 19 | end 20 | 21 | specify { expect(serializer).to be_a(EvilEvents::Core::Events::Serializers::MessagePack) } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/support/shared_examples/event_serializers/xml_event_serialization_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'xml event serialization component' do 4 | describe 'xml serializer behaviour' do 5 | it_behaves_like 'generic event serialization component' do 6 | let(:serializer) { EvilEvents::Core::Events::Serializers::XML::Factory.new.create! } 7 | let(:serialization_error) { EvilEvents::XMLSerializationError } 8 | let(:deserialization_error) { EvilEvents::XMLDeserializationError } 9 | let(:serialization_type) { String } 10 | let(:incorrect_deserialization_objects) { gen_all(except: :gen_str) } 11 | let(:invalid_serializations) do 12 | data = serializer.serialize(event) 13 | 14 | [ 15 | data.sub('type', gen_str), 16 | data.sub('metadata', gen_str), 17 | data.sub('payload', gen_str) 18 | ] 19 | end 20 | 21 | specify { expect(serializer).to be_a(EvilEvents::Core::Events::Serializers::XML) } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/support/shared_examples/notifier/callbacks_invokation_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: ture 2 | 3 | shared_examples 'notifier callbacks invokation interface' do 4 | describe 'callbacks invokation', :null_logger do 5 | let(:event_class) { build_event_class(gen_str(only_letters: true)) } 6 | let(:event_manager) { build_event_manager(event_class) } 7 | let(:event) { event_class.new } 8 | 9 | specify 'invokes callbacks in order BEFORE => ON ERROR => AFTER' do 10 | before_emit_hook_buffer = [] 11 | after_emit_hook_buffer = [] 12 | on_error_hook_buffer = [] 13 | 14 | hook_results = (1..10).to_a 15 | 16 | event_class.before_emit -> (event) { before_emit_hook_buffer << hook_results.shift } 17 | event_class.before_emit -> (event) { before_emit_hook_buffer << hook_results.shift } 18 | event_class.after_emit -> (event) { after_emit_hook_buffer << hook_results.shift } 19 | event_class.after_emit -> (event) { after_emit_hook_buffer << hook_results.shift } 20 | event_class.on_error -> (event, error) { on_error_hook_buffer << error } 21 | 22 | successfull_subscriber = -> (event) {} 23 | event_manager.observe(successfull_subscriber, :call) 24 | notifier.notify(event_manager, event) 25 | 26 | expect(before_emit_hook_buffer).to contain_exactly(1, 2) 27 | expect(after_emit_hook_buffer).to contain_exactly(3, 4) 28 | expect(on_error_hook_buffer).to be_empty 29 | 30 | failing_subscriber = -> (event) { raise ArgumentError } 31 | event_manager.observe(failing_subscriber, :call) 32 | 33 | begin 34 | notifier.notify(event_manager, event) 35 | rescue EvilEvents::FailingSubscribersError 36 | end 37 | 38 | expect(before_emit_hook_buffer).to contain_exactly(1, 2, 5, 6) 39 | expect(after_emit_hook_buffer).to contain_exactly(3, 4, 7, 8) 40 | expect(on_error_hook_buffer).to match([be_a(ArgumentError)]) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/support/shared_examples/notifier/logging_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'notifier logging interface' do 4 | describe 'logging interface logic', :stub_event_system do 5 | let(:event) { build_event_class('test_event').new } 6 | let(:subscriber) { build_event_subscriber } 7 | let(:silent_output) { StringIO.new } 8 | let(:silent_logger) { ::Logger.new(silent_output) } 9 | 10 | before do 11 | EvilEvents::Core::Bootstrap[:config].configure do |config| 12 | config.logger = silent_logger 13 | end 14 | end 15 | 16 | specify '#log_failure' do 17 | loggable.log_failure(event, subscriber) 18 | 19 | expect(silent_output.string).to include( 20 | "[EvilEvents:EventProcessed(#{event.type})]: " \ 21 | "EVENT_ID: #{event.id} :: " \ 22 | "STATUS: failed :: " \ 23 | "SUBSCRIBER: #{subscriber.source_object}" 24 | ) 25 | end 26 | 27 | specify '#log_success' do 28 | loggable.log_success(event, subscriber) 29 | 30 | expect(silent_output.string).to include( 31 | "[EvilEvents:EventProcessed(#{event.type})]: " \ 32 | "EVENT_ID: #{event.id} :: " \ 33 | "STATUS: successful :: " \ 34 | "SUBSCRIBER: #{subscriber.source_object}" 35 | ) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/support/spec_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport 4 | require_relative 'spec_support/fake_data_generator' 5 | require_relative 'spec_support/null_logger' 6 | require_relative 'spec_support/dumb_event_serializer' 7 | require_relative 'spec_support/event_factories' 8 | require_relative 'spec_support/event_manager_factories' 9 | require_relative 'spec_support/dispatching_adapter_factories' 10 | require_relative 'spec_support/notifier_factories' 11 | require_relative 'spec_support/testing' 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/spec_support/dispatching_adapter_factories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport::DispatchingAdapterFactories 4 | module_function 5 | 6 | def build_adapter_class 7 | Class.new do 8 | include EvilEvents::Core::Broadcasting::Dispatcher::Mixin 9 | 10 | def call(event) 11 | dispatch(event) 12 | end 13 | 14 | yield(self) if block_given? 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/spec_support/dumb_event_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport::DumbEventSerializer 4 | SERIALIZATION_RESULT = :dumb_serialization_result 5 | DESERIALIZATION_RESULT = :dumb_deserialization_result 6 | 7 | class << self 8 | def serialize(_event) 9 | SERIALIZATION_RESULT 10 | end 11 | 12 | def deserialize(_event) 13 | DESERIALIZATION_RESULT 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/support/spec_support/event_factories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport::EventFactories 4 | module_function 5 | 6 | EventFactoriesError = Class.new(StandardError) 7 | EventSubscriberError = Class.new(EventFactoriesError) 8 | 9 | def build_serialization_state(id: gen_str, type: gen_str, payload: {}, metadata: {}) 10 | EvilEvents::Core::Events::Serializers::Base::EventSerializationState.new( 11 | id: id, type: type, payload: payload, metadata: metadata 12 | ) 13 | end 14 | 15 | def build_event_class_mock 16 | Class.new do 17 | class << self 18 | def payload(*); end 19 | 20 | def metadata(*); end 21 | end 22 | 23 | attr_reader :id 24 | attr_reader :payload 25 | attr_reader :metadata 26 | 27 | def initialize(id: nil, payload: {}, metadata: {}) 28 | @id = id 29 | @payload = payload 30 | @metadata = metadata 31 | end 32 | 33 | yield(self) if block_given? 34 | end 35 | end 36 | 37 | def build_event_class_stub 38 | Class.new do 39 | attr_reader :id 40 | attr_reader :payload 41 | attr_reader :metadata 42 | 43 | def initialize(id: nil, payload: {}, metadata: {}) 44 | @id = id 45 | @payload = payload 46 | @metadata = metadata 47 | end 48 | 49 | yield(self) if block_given? 50 | end 51 | end 52 | 53 | def build_abstract_event_class(type_alias = gen_str(only_letters: true)) 54 | EvilEvents::Core::Events::EventFactory.create_abstract_class(type_alias) 55 | end 56 | 57 | def build_event_class(type_alias = gen_str(only_letters: true), &definitions) 58 | EvilEvents::Core::Events::EventFactory.create_class(type_alias, &definitions) 59 | end 60 | 61 | def build_event_subscriber(failing: false) 62 | source_subscriber = lambda do |event| 63 | raise EventSubscriberError if failing 64 | yield if block_given? 65 | end 66 | 67 | delegator = -> { :call } 68 | delegator_resolver = EvilEvents::Shared::DelegatorResolver.new(delegator) 69 | EvilEvents::Core::Events::Subscriber.new(source_subscriber, delegator_resolver) 70 | end 71 | 72 | def build_event_manager(event_class) 73 | EvilEvents::Core::Events::Manager.new(event_class) 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/support/spec_support/event_manager_factories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport::EventManagerFactories 4 | module_function 5 | 6 | def build_event_manager(event_class) 7 | EvilEvents::Core::Events::ManagerFactory.create(event_class) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/spec_support/notifier_factories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport::NotifierFactories 4 | module_function 5 | 6 | def build_job(event, subscriber) 7 | EvilEvents::Core::Events::Notifier::Worker::Job.new(event, subscriber) 8 | end 9 | 10 | def build_failing_job_stub(event = build_event_class.new, &subscriber_logic) 11 | subscriber = build_event_subscriber(failing: true, &subscriber_logic) 12 | 13 | build_job(event, subscriber) 14 | end 15 | 16 | def build_successful_job_stub(event = build_event_class.new, &subscriber_logic) 17 | subscriber = build_event_subscriber(&subscriber_logic) 18 | 19 | build_job(event, subscriber) 20 | end 21 | 22 | def build_job_executor(min_threads: 1, max_threads: 3, max_queue: 2, fallback_policy: :exception) 23 | EvilEvents::Core::Events::Notifier::Worker::Executor.new( 24 | min_threads: min_threads, 25 | max_threads: max_threads, 26 | max_queue: max_queue, 27 | fallback_policy: fallback_policy 28 | ) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/spec_support/null_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport::NullLogger 4 | class << self 5 | def method_missing(method_name, *arguments, &block) 6 | self 7 | end 8 | 9 | def respond_to_missing?(method_name, *arguments, &block) 10 | true 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/spec_support/testing.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SpecSupport::Testing 4 | module_function 5 | 6 | def test_native_extensions? 7 | !!ENV['TEST_NATIVE_EXTENSIONS'] 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/units/evil_events/application_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Application, :stub_event_system do 4 | describe '.registered_events' do 5 | it 'returns a list of the all created event classes in { type => class } form' do 6 | expect(described_class.registered_events).to eq({}) 7 | 8 | event_type = gen_str 9 | event_class = EvilEvents::Event.define(event_type) 10 | 11 | expect(described_class.registered_events).to match( 12 | event_type => event_class 13 | ) 14 | 15 | match_lost_alias = gen_str 16 | match_lost_event = Class.new(EvilEvents::Event[match_lost_alias]) 17 | 18 | expect(described_class.registered_events).to match( 19 | match_lost_alias => match_lost_event, 20 | event_type => event_class 21 | ) 22 | end 23 | 24 | it 'exceptional event initialization code doesnt affect event class list' do 25 | EvilEvents::Event.define(gen_str) { raise } rescue nil 26 | 27 | expect(described_class.registered_events).to eq({}) 28 | 29 | event_class = EvilEvents::Event.define(gen_str) 30 | EvilEvents::Event.define(gen_str) { raise } rescue nil 31 | 32 | expect(described_class.registered_events).to match( 33 | event_class.type => event_class 34 | ) 35 | 36 | another_event_class = EvilEvents::Event.define(gen_str) 37 | EvilEvents::Event.define(gen_str) { raise } rescue nil 38 | 39 | expect(described_class.registered_events).to match( 40 | event_class.type => event_class, 41 | another_event_class.type => another_event_class 42 | ) 43 | end 44 | end 45 | 46 | describe '.restart_event_notifier' do 47 | shared_examples 'restart of a notifier' do |notifier_type| 48 | specify "#{notifier_type} restarting" do 49 | expect_any_instance_of(notifier_type).to receive(:restart!) 50 | EvilEvents::Application.restart_event_notifier 51 | end 52 | end 53 | 54 | context 'sequential notifier' do 55 | before { EvilEvents::Config.configure { |config| config.notifier.type = :sequential } } 56 | 57 | it_behaves_like 'restart of a notifier', EvilEvents::Core::Events::Notifier::Sequential 58 | end 59 | 60 | context 'worker notifier' do 61 | before { EvilEvents::Config.configure { |config| config.notifier.type = :worker } } 62 | 63 | it_behaves_like 'restart of a notifier', EvilEvents::Core::Events::Notifier::Worker 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/units/evil_events/config/adapters_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Config::Adapters, :stub_event_system do 4 | include_context 'event system' 5 | 6 | describe '.register' do 7 | it 'delegates a registration action to the event system' do 8 | adapter_name = double 9 | adapter_object = double 10 | 11 | expect(event_system).to receive(:register_adapter).with(adapter_name, adapter_object).once 12 | described_class.register(adapter_name, adapter_object) 13 | end 14 | 15 | it 'registrates new adapter object with passed name' do 16 | adapter_name = :test_adapter 17 | adapter_object = double 18 | described_class.register(adapter_name, adapter_object) 19 | expect(event_system.resolve_adapter(adapter_name)).to eq(adapter_object) 20 | end 21 | 22 | it 'fails when apdater with passed name is already registered' do 23 | described_class.register(:test_adapter, double) 24 | expect { described_class.register(:test_adapter, double) }.to( 25 | raise_error(Dry::Container::Error) 26 | ) 27 | 28 | described_class.register(:another_adapter, double) 29 | expect { described_class.register(:another_adapter, double) }.to( 30 | raise_error(Dry::Container::Error) 31 | ) 32 | end 33 | end 34 | 35 | describe '.resolve' do 36 | it 'delegates a recognition action to the event system' do 37 | adapter_name = double 38 | expect(event_system).to receive(:resolve_adapter).with(adapter_name) 39 | described_class.resolve(adapter_name) 40 | end 41 | 42 | it 'returns adapter object by its name' do 43 | sidekiq_adapter = double 44 | que_adapter = double 45 | 46 | described_class.register(:sidekiq, sidekiq_adapter) 47 | described_class.register(:que, que_adapter) 48 | 49 | expect(described_class.resolve(:que)).to eq(que_adapter) 50 | expect(described_class.resolve(:sidekiq)).to eq(sidekiq_adapter) 51 | end 52 | 53 | it 'fails when event system has no registered adapter with passed name' do 54 | expect { described_class.resolve(:kek_pek) }.to raise_error(Dry::Container::Error) 55 | described_class.register(:kek_pek, double) 56 | expect { described_class.resolve(:kek_pek) }.not_to raise_error 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/units/evil_events/config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Config, :stub_event_system do 4 | include_context 'event system' 5 | 6 | let(:config) { described_class } 7 | 8 | describe '.configure' do 9 | it 'yields configuration object' do 10 | original_configuration = nil 11 | system_config.configure { |config| original_configuration = config } 12 | 13 | public_configuration = nil 14 | config.configure { |config| public_configuration = config } 15 | 16 | expect(public_configuration).to eq(original_configuration) 17 | end 18 | end 19 | 20 | specify '.options' do 21 | expect(config.options).to eq(system_config.settings) 22 | end 23 | 24 | specify '.setup_types' do 25 | expect { |b| config.setup_types(&b) }.to yield_with_args(EvilEvents::Config::Types) 26 | end 27 | 28 | specify '.setup_adapters' do 29 | expect { |b| config.setup_adapters(&b) }.to yield_with_args(EvilEvents::Config::Adapters) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/activity_logger_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::ActivityLogger do 4 | include_context 'event system' 5 | 6 | let(:activity_logger) { described_class } 7 | 8 | before do 9 | system_config.configure do |config| 10 | config.logger = ::Logger.new(StringIO.new) 11 | end 12 | end 13 | 14 | describe '#log' do 15 | it 'logs received activity (progname) and message via #logger and its log-level' do 16 | log_levels = [ 17 | ::Logger::DEBUG, 18 | ::Logger::INFO, 19 | ::Logger::WARN, 20 | ::Logger::ERROR, 21 | ::Logger::FATAL, 22 | ::Logger::UNKNOWN 23 | ] 24 | 25 | log_levels.each do |expected_level| 26 | system_config.configure do |config| 27 | config.logger.level = expected_level 28 | end 29 | 30 | activity = SecureRandom.uuid 31 | message = SecureRandom.uuid 32 | 33 | expected_progname = "[EvilEvents:#{activity}]" 34 | 35 | expect(system_config.settings.logger).to( 36 | receive(:add).with(expected_level, message, expected_progname) 37 | ) 38 | 39 | activity_logger.log(activity: activity, message: message) 40 | end 41 | end 42 | 43 | it 'activity and message attributes are optional' do 44 | activity = SecureRandom.uuid 45 | message = SecureRandom.uuid 46 | 47 | expect { activity_logger.log }.not_to raise_error 48 | expect { activity_logger.log(activity: activity) }.not_to raise_error 49 | expect { activity_logger.log(message: message) }.not_to raise_error 50 | expect { activity_logger.log(activity: activity, message: message) }.not_to raise_error 51 | end 52 | 53 | it 'uses globally pre-configured logger object inside itself' do 54 | preconfigured_logger = system_config.settings.logger 55 | 56 | level = preconfigured_logger.level 57 | activity = 'RSpec' 58 | progname = "[EvilEvents:#{activity}]" 59 | message = 'test' 60 | 61 | expect(preconfigured_logger).to receive(:add).with(level, message, progname) 62 | activity_logger.log(activity: activity, message: message) 63 | 64 | custom_preconfigured_logger = ::Logger.new(StringIO.new) 65 | system_config.configure do |config| 66 | config.logger = custom_preconfigured_logger 67 | end 68 | 69 | expect(custom_preconfigured_logger).to receive(:add).with(level, message, progname) 70 | activity_logger.log(activity: activity, message: message) 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/bootstrap_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Bootstrap do 4 | it_behaves_like 'dependency container interface' do 5 | let(:container) { described_class } 6 | end 7 | 8 | describe 'registered dependencies' do 9 | specify 'event_system' do 10 | expect(described_class.resolve(:event_system)).to be_a(EvilEvents::Core::System) 11 | 12 | # verify event system memoization 13 | first_resolve = described_class.resolve(:event_system) 14 | second_resolve = described_class.resolve(:event_system) 15 | expect(first_resolve).to eq(second_resolve) 16 | end 17 | 18 | specify 'config' do 19 | expect(described_class.resolve(:config)).to be_a(EvilEvents::Core::Config) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/broadcasting/adapters/memory_async_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Broadcasting::Adapters::MemoryAsync, :stub_event_system do 4 | let(:memory_dispatcher) { described_class } 5 | 6 | it_behaves_like 'event dispatching interface' do 7 | let(:dispatcher) { memory_dispatcher } 8 | end 9 | 10 | describe '#call' do 11 | it 'delegates the processing of the received event to the event dispatcher asynchronously' do 12 | event = build_event_class('event').new 13 | 14 | # TODO: sorry, need a global ioc container for low-level dependencies 15 | expect(described_class::AsyncTask).to eq(::Thread) 16 | stub_const( 17 | 'EvilEvents::Core::Broadcasting::Adapters::MemoryAsync::AsyncTask', 18 | Concurrent::Future 19 | ) 20 | 21 | expect(EvilEvents::Core::Broadcasting::Dispatcher).to( 22 | receive(:dispatch).with(event).exactly(2).times 23 | ) 24 | 25 | [ 26 | proc { memory_dispatcher.call(event) }, 27 | proc { memory_dispatcher.call(event) }, 28 | proc { memory_dispatcher.call(event) }, 29 | proc { memory_dispatcher.call(event).execute.value }, 30 | proc { memory_dispatcher.call(event).execute.value } 31 | ].shuffle.each(&:call) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/broadcasting/adapters/memory_sync_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Broadcasting::Adapters::MemorySync, :stub_event_system do 4 | let(:memory_dispatcher) { described_class } 5 | 6 | it_behaves_like 'event dispatching interface' do 7 | let(:dispatcher) { memory_dispatcher } 8 | end 9 | 10 | describe '#call' do 11 | it 'delegates the processing of the received event to the event dispatcher' do 12 | event = build_event_class('event').new 13 | 14 | expect(EvilEvents::Core::Broadcasting::Dispatcher).to( 15 | receive(:dispatch).with(event) 16 | ) 17 | 18 | memory_dispatcher.call(event) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/broadcasting/adapters_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Broadcasting::Adapters do 4 | let(:container) { described_class.new } 5 | 6 | it_behaves_like 'dependency container interface' 7 | 8 | describe '#register_core_adapters!' do 9 | it 'registrates core adapters from Adapters namespace' do 10 | expect { container.resolve(:memory_sync) }.to raise_error(Dry::Container::Error) 11 | expect { container.resolve(:memory_async) }.to raise_error(Dry::Container::Error) 12 | 13 | container.register_core_adapters! 14 | 15 | expect(container.resolve(:memory_sync)).to eq( 16 | EvilEvents::Core::Broadcasting::Adapters::MemorySync 17 | ) 18 | 19 | expect(container.resolve(:memory_async)).to eq( 20 | EvilEvents::Core::Broadcasting::Adapters::MemoryAsync 21 | ) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/broadcasting/dispatcher/mixin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Broadcasting::Dispatcher::Mixin, :stub_event_system do 4 | it_behaves_like 'event dispatching interface' do 5 | let(:dispatcher) { Module.new.tap { |mod| mod.extend(described_class) } } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/broadcasting/dispatcher_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Broadcasting::Dispatcher, :stub_event_system do 4 | it_behaves_like 'event dispatching interface' do 5 | let(:dispatcher) { described_class } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/broadcasting/emitter/adapter_proxy_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Broadcasting::Emitter::AdapterProxy, :stub_event_system do 4 | include_context 'event system' 5 | 6 | let(:sidekiq_adapter) { build_adapter_class.new } 7 | let(:rabbit_adapter) { build_adapter_class.new } 8 | let(:redis_adapter) { build_adapter_class.new } 9 | 10 | before do 11 | event_system.register_adapter(:sidekiq, sidekiq_adapter) 12 | event_system.register_adapter(:rabbit, rabbit_adapter) 13 | event_system.register_adapter(:redis, redis_adapter) 14 | end 15 | 16 | describe 'shared interface' do 17 | let(:rabbit_event) { build_event_class { adapter :rabbit } } 18 | let(:sidekiq_event) { build_event_class { adapter :sidekiq } } 19 | 20 | context 'without explicit adapter identifier' do 21 | specify 'broadcasting works via event\'s pre-configured adapter' do 22 | adapter = described_class.new(rabbit_event) 23 | expect(rabbit_adapter).to receive(:call).with(rabbit_event).once 24 | adapter.broadcast! 25 | 26 | adapter = described_class.new(sidekiq_event) 27 | expect(sidekiq_adapter).to receive(:call).with(sidekiq_event).once 28 | adapter.broadcast! 29 | end 30 | end 31 | 32 | context 'with explicit adapter identifier' do 33 | specify 'broadcasting works via explicitly specified adapter' do 34 | adapter = described_class.new(rabbit_event, explicit_identifier: :redis) 35 | expect(redis_adapter).to receive(:call).with(rabbit_event).once 36 | expect(rabbit_adapter).not_to receive(:call) 37 | adapter.broadcast! 38 | 39 | adapter = described_class.new(sidekiq_event, explicit_identifier: :redis) 40 | expect(redis_adapter).to receive(:call).with(sidekiq_event).once 41 | expect(sidekiq_adapter).not_to receive(:call) 42 | adapter.broadcast! 43 | end 44 | 45 | specify 'fails when adapter with passed identifier isnt registered' do 46 | expect do 47 | described_class.new(sidekiq_event, explicit_identifier: gen_symb) 48 | end.to raise_error(Dry::Container::Error) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/class_signature/equalizer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::ClassSignature::Equalizer do 4 | let(:event_class) do 5 | build_event_class(gen_str).tap do |klass| 6 | klass.default_delegator gen_symb(only_letters: true) 7 | klass.payload gen_symb(only_letters: true), gen_event_attr_type 8 | klass.metadata gen_symb(only_letters: true), gen_event_attr_type 9 | klass.adapter :memory_sync 10 | end 11 | end 12 | 13 | let(:another_event_class) do 14 | Class.new(build_abstract_event_class(gen_str)).tap do |klass| 15 | klass.default_delegator gen_symb(only_letters: true) 16 | 17 | klass.payload gen_symb(only_letters: true), gen_event_attr_type 18 | klass.payload gen_symb(only_letters: true), gen_event_attr_type(:strict) 19 | 20 | klass.metadata gen_symb(only_letters: true), gen_event_attr_type 21 | klass.metadata gen_symb(only_letters: true), gen_event_attr_type(:json) 22 | 23 | klass.adapter :memory_async 24 | end 25 | end 26 | 27 | let(:equalizer_with_same_signatures) do 28 | signature_a = event_class.signature 29 | signature_b = event_class.signature 30 | 31 | described_class.new(signature_a, signature_b) 32 | end 33 | 34 | let(:equalizer_with_different_signatures) do 35 | signature_a = event_class.signature 36 | signature_b = another_event_class.signature 37 | 38 | described_class.new(signature_a, signature_b) 39 | end 40 | 41 | shared_examples 'equality behaviour' do |equality_method| 42 | describe "##{equality_method}" do 43 | subject { equalizer.public_send(equality_method) } 44 | 45 | context 'signatures with similar stmaps' do 46 | let(:equalizer) { equalizer_with_same_signatures } 47 | 48 | it { is_expected.to eq(true) } 49 | end 50 | 51 | context 'signatures with different stamps' do 52 | let(:equalizer) { equalizer_with_different_signatures } 53 | 54 | it { is_expected.to eq(false) } 55 | end 56 | end 57 | end 58 | 59 | it_behaves_like 'equality behaviour', :equal_payload? 60 | it_behaves_like 'equality behaviour', :equal_metadata? 61 | it_behaves_like 'equality behaviour', :equal_delegator? 62 | it_behaves_like 'equality behaviour', :equal_adapter? 63 | it_behaves_like 'equality behaviour', :equal_type_alias? 64 | it_behaves_like 'equality behaviour', :equal_class? 65 | it_behaves_like 'equality behaviour', :similar_signatures? 66 | end 67 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/class_signature_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::ClassSignature do 4 | it_behaves_like 'class signature interface' do 5 | let(:event_class) { build_event_class_stub.tap { |klass| klass.include described_class } } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/dispatchable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Dispatchable do 4 | it_behaves_like 'dispatchable interface' do 5 | let(:dispatchable) do 6 | build_event_class_mock.tap do |klass| 7 | klass.include described_class 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/hookable/abstract_hook_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Hookable::AbstractHook do 4 | it_behaves_like 'hook component behaviour' do 5 | let(:hook_class) { described_class } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/hookable/after_emit_hook_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Hookable::AfterEmitHook do 4 | it_behaves_like 'hook component behaviour' do 5 | let(:hook_class) { described_class } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/hookable/before_emit_hook_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Hookable::BeforeEmitHook do 4 | it_behaves_like 'hook component behaviour' do 5 | let(:hook_class) { described_class } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/hookable/on_error_hook_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Hookable::OnErrorHook do 4 | it_behaves_like 'hook component behaviour', base_call_method: false do 5 | let(:hook_class) { described_class } 6 | end 7 | 8 | specify 'invokation (#call)' do 9 | source = double 10 | error = double 11 | callable = double 12 | hook = described_class.new(callable) 13 | 14 | expect(callable).to receive(:call).with(source, error) 15 | 16 | hook.call(source, error) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/hookable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Hookable do 4 | it_behaves_like 'hookable interface' do 5 | let(:hookable) do 6 | build_event_class_stub do |klass| 7 | klass.include described_class 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/manageable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Manageable do 4 | it_behaves_like 'manageable interface' do 5 | let(:manageable) do 6 | build_event_class_stub do |klass| 7 | klass.include described_class 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/metadata_extendable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::MetadataExtendable, :stub_event_system do 4 | it_behaves_like 'metadata extendable interface' do 5 | let(:metadata_extendable_abstraction) do 6 | build_event_class_stub do |klass| 7 | klass.include described_class 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/observable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Observable do 4 | it_behaves_like 'observable interface' do 5 | let(:observable) do 6 | build_event_class_stub do |klass| 7 | klass.include described_class 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/payloadable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Payloadable, :stub_event_system do 4 | it_behaves_like 'payloadable interface' do 5 | let(:payloadable_abstraction) do 6 | build_event_class_stub do |klass| 7 | klass.include described_class 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/serializable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::Serializable do 4 | it_behaves_like 'serializable interface' do 5 | let(:serializable) do 6 | build_event_class_stub do |klass| 7 | klass.include described_class 8 | end.new 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/event_extensions/type_aliasing_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::EventExtensions::TypeAliasing do 4 | it_behaves_like 'type aliasing interface' do 5 | let(:pseudo_identifiable) do 6 | build_event_class_stub do |klass| 7 | klass.include described_class 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/manager_factory_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::ManagerFactory do 4 | describe '#create' do 5 | it 'creates new manager object with passed event class' do 6 | event_class_1 = build_abstract_event_class('test_event_1') 7 | event_class_2 = build_abstract_event_class('test_event_2') 8 | manager_1 = described_class.create(event_class_1) 9 | manager_2 = described_class.create(event_class_2) 10 | 11 | expect(manager_1).to be_a(EvilEvents::Core::Events::Manager) 12 | expect(manager_2).to be_a(EvilEvents::Core::Events::Manager) 13 | expect(manager_1.event_class).to eq(event_class_1) 14 | expect(manager_2.event_class).to eq(event_class_2) 15 | end 16 | 17 | it 'fails when receives an inconsistent event class object' do 18 | expect { described_class.create }.to( 19 | raise_error(ArgumentError) 20 | ) 21 | 22 | expect { described_class.create(Object) }.to( 23 | raise_error(EvilEvents::IncorrectEventClassError) 24 | ) 25 | 26 | expect { described_class.create(Class) }.to( 27 | raise_error(EvilEvents::IncorrectEventClassError) 28 | ) 29 | 30 | expect { described_class.create(double) }.to( 31 | raise_error(EvilEvents::IncorrectEventClassError) 32 | ) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/notifier/logging_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Notifier::Logging do 4 | it_behaves_like 'notifier logging interface' do 5 | let(:loggable) { Class.new.tap { |klass| klass.extend described_class } } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/notifier/proxy_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Notifier::Proxy do 4 | describe 'method delegation' do 5 | let(:proxy) { described_class.new } 6 | 7 | it 'delegates shutdown! / restart! / notify to a notifier' do 8 | expect(proxy.notifier).to receive(:shutdown!) 9 | proxy.shutdown! 10 | 11 | expect(proxy.notifier).to receive(:restart!) 12 | proxy.restart! 13 | 14 | expect(proxy.notifier).to receive(:notify) 15 | proxy.notify 16 | end 17 | end 18 | 19 | describe 'initialization' do 20 | it 'initializes a notifier object lazily (proxy instance)' do 21 | expect(EvilEvents::Core::Events::Notifier::Builder).not_to receive(:build_notifier!) 22 | described_class.new 23 | end 24 | 25 | it 'initializes a notifier object lazily (notifier instance)' do 26 | expect(EvilEvents::Core::Events::Notifier::Builder).to receive(:build_notifier!) 27 | described_class.new.notifier 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/notifier/worker/job_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Notifier::Worker::Job do 4 | let(:event) { build_event_class(gen_str).new } 5 | let(:subscriber) { build_event_subscriber } 6 | 7 | specify 'instantiation and options / attributes' do 8 | expect { described_class.new }.to raise_error(ArgumentError) 9 | expect { described_class.new(double) }.to raise_error(ArgumentError) 10 | expect { described_class.new(double, double, double) }.to raise_error(ArgumentError) 11 | expect { described_class.new(double, double) }.not_to raise_error 12 | 13 | job = described_class.new(event, subscriber) 14 | 15 | expect(job.event).to eq(event) 16 | expect(job.subscriber).to eq(subscriber) 17 | end 18 | 19 | specify 'job invokation calls the subscriber notification process' do 20 | expect(subscriber).to receive(:notify).with(event).once 21 | described_class.new(event, subscriber).perform 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/serializers/hash_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Serializers::Hash, :stub_event_system do 4 | include_context 'event system' 5 | 6 | context 'native engine' do 7 | before { system_config.configure { |c| c.serializers.hashing.engine = :native } } 8 | 9 | it_behaves_like 'hash event serialization component' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/serializers/json_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Serializers::JSON, :stub_event_system do 4 | include_context 'event system' 5 | 6 | context 'native engine' do 7 | before { system_config.configure { |c| c.serializers.json.engine = :native } } 8 | 9 | it_behaves_like 'json event serialization component' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/serializers_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Serializers, :stub_event_system do 4 | include_context 'event system' 5 | 6 | let(:serializers_container) { described_class.new } 7 | 8 | it_behaves_like 'dependency container interface' do 9 | let(:container) { serializers_container } 10 | end 11 | 12 | describe 'common behaviour' do 13 | before { serializers_container.register_core_serializers! } 14 | 15 | it 'has following registered serializers' do 16 | expect(serializers_container.resolve(:json)).to be_a(described_class::JSON) 17 | expect(serializers_container.resolve(:hash)).to be_a(described_class::Hash) 18 | end 19 | 20 | specify 'serializers should be memoized' do 21 | expect(serializers_container.resolve(:json)).to eq(serializers_container.resolve(:json)) 22 | expect(serializers_container.resolve(:hash)).to eq(serializers_container.resolve(:hash)) 23 | end 24 | 25 | specify 'fails when serialization engine cant be recognized', :stub_event_system do 26 | system_config.configure { |c| c.serializers.json.engine = gen_symb } 27 | 28 | expect { serializers_container.resolve(:json) }.to raise_error( 29 | EvilEvents::UnrecognizedSerializationEngineError 30 | ) 31 | 32 | system_config.configure { |c| c.serializers.hashing.engine = gen_symb } 33 | expect { serializers_container.resolve(:hash) }.to raise_error( 34 | EvilEvents::UnrecognizedSerializationEngineError 35 | ) 36 | 37 | system_config.configure { |c| c.serializers.msgpack.engine = gen_symb } 38 | expect { serializers_container.resolve(:msgpack) }.to raise_error( 39 | EvilEvents::UnrecognizedSerializationEngineError 40 | ) 41 | 42 | system_config.configure { |c| c.serializers.xml.engine = gen_symb } 43 | expect { serializers_container.resolve(:xml) }.to raise_error( 44 | EvilEvents::UnrecognizedSerializationEngineError 45 | ) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/subscriber/mixin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Subscriber::Mixin, :stub_event_system do 4 | it_behaves_like 'event subscriber component' do 5 | let(:subscribeable) { Class.new.tap { |klass| klass.include(described_class) }.new } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/events/subscriber_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::Events::Subscriber do 4 | describe 'shared interface' do 5 | describe '#delegator' do 6 | it 'returns delegator method name via delegator resolver evaluation process' do 7 | source_subscriber = double 8 | resolver_clojure = -> { :test_method } 9 | delegator_resolver = EvilEvents::Shared::DelegatorResolver.new(resolver_clojure) 10 | subscriber = described_class.new(source_subscriber, delegator_resolver) 11 | 12 | expect(subscriber.delegator).to eq(:test_method) 13 | end 14 | 15 | it 'memoizes calculated delegator method' do 16 | source_subscriber = double 17 | method_prefix = 'test' 18 | resolver_clojure = -> { (method_prefix += '_method').to_sym } 19 | delegator_resolver = EvilEvents::Shared::DelegatorResolver.new(resolver_clojure) 20 | subscriber = described_class.new(source_subscriber, delegator_resolver) 21 | 22 | expect(subscriber.delegator).to eq(:test_method) 23 | expect(subscriber.delegator).to eq(:test_method) 24 | end 25 | end 26 | 27 | describe '#source_object' do 28 | it 'returns a source subscriber obeject received due to instantiation process' do 29 | source_subscriber = double 30 | subscriber = described_class.new(source_subscriber, double) 31 | expect(subscriber.source_object).to eq(source_subscriber) 32 | end 33 | end 34 | 35 | describe '#delegator_resolver' do 36 | it 'returns a delegator resolver received due to instantiation process' do 37 | delegator_resolver = double 38 | subscriber = described_class.new(double, delegator_resolver) 39 | expect(subscriber.delegator_resolver).to eq(delegator_resolver) 40 | end 41 | end 42 | 43 | describe '#notify' do 44 | let(:source_subscriber) { Class.new { def test_method(test_event); end }.new } 45 | let(:delegator_resolver) { EvilEvents::Shared::DelegatorResolver.new(-> { :test_method }) } 46 | let(:subscriber) { described_class.new(source_subscriber, delegator_resolver) } 47 | 48 | it 'delegates received event to the source subscriber via calculated delegator method' do 49 | event = double 50 | expect(source_subscriber).to receive(:test_method).with(event) 51 | subscriber.notify(event) 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/units/evil_events/core/system/mock_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Core::System::Mock do 4 | let(:mock) { described_class.new } 5 | 6 | shared_examples 'mocked dependency' do |dependency, excluded: [], only: []| 7 | specify "mocked #{dependency} interface" do 8 | # fetch all required method signatures 9 | method_names = only.any? ? only : dependency.instance_methods(false) - excluded 10 | 11 | original_method_signatures = method_names.map do |method_name| 12 | method_object = dependency.instance_method(method_name) 13 | { method: method_name, params: method_object.parameters } 14 | end 15 | 16 | # verify mocked method signatures 17 | original_method_signatures.each do |method_config| 18 | method_name = method_config[:method] 19 | method_params = method_config[:params] 20 | 21 | # 1. mock object should respond to the original method 22 | expect(mock).to respond_to(method_name) 23 | 24 | mocked_method_object = mock.method(method_name) 25 | mocked_method_params = mocked_method_object.parameters 26 | 27 | # 2. mocked method signature equals to original method signature 28 | expect(mocked_method_params).to match(method_params) 29 | end 30 | end 31 | end 32 | 33 | # rubocop:disable Layout/AlignParameters 34 | it_behaves_like 'mocked dependency', EvilEvents::Core::System::EventBuilder, 35 | excluded: [:serializers_container] 36 | 37 | it_behaves_like 'mocked dependency', EvilEvents::Core::System::Broadcaster, 38 | excluded: %i[event_emitter adapters_container event_notifier] 39 | 40 | it_behaves_like 'mocked dependency', EvilEvents::Core::System::EventManager, 41 | excluded: [:manager_registry] 42 | 43 | it_behaves_like 'mocked dependency', EvilEvents::Core::System::TypeManager, 44 | excluded: [:converter] 45 | 46 | it_behaves_like 'mocked dependency', EvilEvents::Core::System, 47 | only: %i[broadcaster event_manager] 48 | 49 | # rubocop:enable Layout/AlignParameters 50 | end 51 | -------------------------------------------------------------------------------- /spec/units/evil_events/dispatcher_mixin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::DispatcherMixin do 4 | it_behaves_like 'event dispatching interface' do 5 | let(:dispatcher) { Class.new.tap { |klass| klass.include(described_class) }.new } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/emitter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Emitter, :stub_event_system do 4 | include_context 'event system' 5 | 6 | describe '.emit' do 7 | it 'receives event attributes and delegates event handling process to the event system' do 8 | event_type = 'suite_event' 9 | 10 | event_attributes = { 11 | id: gen_int, 12 | payload: { a: gen_int }, 13 | metadata: { b: gen_str }, 14 | adapter: gen_symb 15 | } 16 | 17 | expect(event_system).to( 18 | receive(:raw_emit).with(event_type, hash_including(event_attributes)) 19 | ) 20 | 21 | described_class.emit(event_type, **event_attributes) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/units/evil_events/event_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Event, :stub_event_system do 4 | include_context 'event system' 5 | 6 | describe '.[]' do 7 | it 'delegates the creation of the abstract event class to the event system' do 8 | event_type = gen_str 9 | 10 | expect(event_system).to( 11 | receive(:define_abstract_event_class).with(event_type).once 12 | ) 13 | 14 | described_class[event_type] 15 | end 16 | 17 | it 'returns a result of the class creation process' do 18 | event_type = gen_str 19 | creation_result = double 20 | 21 | allow(event_system).to( 22 | receive(:define_abstract_event_class).and_return(creation_result) 23 | ) 24 | 25 | expect(described_class[event_type]).to eq(creation_result) 26 | end 27 | 28 | it 'requires an event type alias only' do 29 | expect { described_class[] }.to raise_error(ArgumentError) 30 | expect { described_class[gen_str, gen_str] }.to raise_error(ArgumentError) 31 | expect { described_class[gen_str] }.not_to raise_error 32 | end 33 | end 34 | 35 | describe '.define' do 36 | it 'delegates the creation of the full event class to the event system' do 37 | event_type = gen_str 38 | event_definitions = proc {} 39 | 40 | expect(event_system).to( 41 | receive(:define_event_class).with(event_type).once do |&received_block| 42 | expect(received_block).to eq(event_definitions) 43 | end 44 | ) 45 | 46 | described_class.define(event_type, &event_definitions) 47 | end 48 | 49 | it 'returns a result of the class creation process' do 50 | event_type = gen_str 51 | event_definitions = proc {} 52 | creation_result = double 53 | 54 | allow(event_system).to( 55 | receive(:define_event_class).and_return(creation_result) 56 | ) 57 | 58 | expect(described_class.define(event_type, &event_definitions)).to eq(creation_result) 59 | end 60 | 61 | specify 'required attributes' do 62 | expect { described_class.define }.to raise_error(ArgumentError) 63 | expect { described_class.define(gen_str, double) }.to raise_error(ArgumentError) 64 | expect { described_class.define(gen_str) }.not_to raise_error 65 | expect { described_class.define(gen_str, &(proc {})) }.not_to raise_error 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/units/evil_events/serializer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Serializer, :stub_event_system do 4 | include_context 'event system' 5 | 6 | shared_examples 'deserialization module' do |data_type, tested_method, delegated_method| 7 | let(:serialized_event) { double } 8 | 9 | it 'delegates event deserialization to the event system' do 10 | expect(event_system).to receive(delegated_method).with(serialized_event) 11 | described_class.public_send(tested_method, serialized_event) 12 | end 13 | end 14 | 15 | it_behaves_like 'deserialization module', :json, :load_from_json, :deserialize_from_json 16 | it_behaves_like 'deserialization module', :hash, :load_from_hash, :deserialize_from_hash 17 | it_behaves_like 'deserialization module', :xml, :load_from_xml, :deserialize_from_xml 18 | it_behaves_like 'deserialization module', :msgpack, :load_from_msgpack, :deserialize_from_msgpack 19 | end 20 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/any_config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::AnyConfig do 4 | specify 'empty configuration' do 5 | config_klass = Class.new(described_class) 6 | config = config_klass.new 7 | 8 | expect(config.to_hash).to eq({}) 9 | end 10 | 11 | specify 'with settings' do 12 | config_klass = Class.new(described_class) do 13 | configure do 14 | setting :my_simple_setting, 55, reader: true 15 | 16 | setting :nested, reader: true do 17 | setting(:pre_processed, 50) { |value| value * 2 } 18 | end 19 | end 20 | end 21 | 22 | config = config_klass.new 23 | 24 | # dry-configurable: hash representation 25 | expect(config.to_h).to match(my_simple_setting: 55, nested: { pre_processed: 100 }) 26 | expect(config.to_hash).to match(my_simple_setting: 55, nested: { pre_processed: 100 }) 27 | # dry-configurable: config object 28 | expect(config.config.my_simple_setting).to eq(55) 29 | expect(config.config.nested.pre_processed).to eq(100) 30 | # dry-configurable: reader option 31 | expect(config.my_simple_setting).to eq(55) 32 | expect(config.nested.pre_processed).to eq(100) 33 | 34 | # dry-configurable: change config 35 | config.configure do |c| 36 | c.my_simple_setting = 66 37 | c.nested.pre_processed = 30 38 | end 39 | # dry-configurable: hash representation after changing 40 | expect(config.to_h).to match(my_simple_setting: 66, nested: { pre_processed: 60 }) 41 | expect(config.to_hash).to match(my_simple_setting: 66, nested: { pre_processed: 60 }) 42 | # dry-configurable: config object after changing 43 | expect(config.config.my_simple_setting).to eq(66) 44 | expect(config.config.nested.pre_processed).to eq(60) 45 | # dry-configurable: reader option after changing 46 | expect(config.my_simple_setting).to eq(66) 47 | expect(config.nested.pre_processed).to eq(60) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/delegator_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::DelegatorResolver do 4 | describe 'instantiation' do 5 | it 'accepts proc objects only' do 6 | expect { described_class.new(-> {}) }.not_to raise_error 7 | expect { described_class.new(proc {}) }.not_to raise_error 8 | 9 | clojure = double 10 | expect { described_class.new(clojure) }.to raise_error( 11 | described_class::InvalidProcAttributeError 12 | ) 13 | 14 | allow(clojure).to receive(:call) 15 | expect { described_class.new(clojure) }.to raise_error( 16 | described_class::InvalidProcAttributeError 17 | ) 18 | end 19 | end 20 | 21 | describe 'shared interface' do 22 | describe '#method_name_resolver' do 23 | it 'returns the proc object which accepted on an instantiation process' do 24 | clojure = proc {} 25 | resolver = described_class.new(clojure) 26 | 27 | expect(resolver.method_name_resolver).to eq(clojure) 28 | end 29 | end 30 | 31 | describe '#delegator' do 32 | it 'returns the calculated delegation method based on proc-ivar calculation' do 33 | test_clojure_1 = proc { :test_clojure_1 } 34 | test_clojure_2 = proc { :test_clojure_2 } 35 | 36 | resolver = described_class.new(test_clojure_1) 37 | expect(resolver.delegator).to eq(:test_clojure_1) 38 | 39 | resolver = described_class.new(test_clojure_2) 40 | expect(resolver.delegator).to eq(:test_clojure_2) 41 | end 42 | 43 | it 'memoizes the calculated result value' do 44 | local_value = 1 45 | test_clojure = proc { local_value += 1 } 46 | resolver = described_class.new(test_clojure) 47 | memoized_result = resolver.delegator 48 | 49 | expect(resolver.delegator).to eq(memoized_result) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/dependency_container_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::DependencyContainer do 4 | specify { expect(described_class).to eq(Dry::Container) } 5 | end 6 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/extensions_mixin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::ExtensionsMixin do 4 | specify { expect(described_class).to eq(Dry::Core::Extensions) } 5 | end 6 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/logger_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::Logger do 4 | specify { expect(described_class).to be < ::Logger } 5 | specify { expect(described_class.new(STDOUT).level).to eq(::Logger::INFO) } 6 | end 7 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/structure_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::Structure do 4 | specify { expect(described_class).to be < Dry::Struct } 5 | end 6 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/type_converter/converter_registry_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::TypeConverter::ConverterRegistry do 4 | let(:registry) { described_class.new } 5 | 6 | describe 'public interface' do 7 | describe '#converters' do 8 | it 'returns the internal registry object (with empty state)' do 9 | internal_registry = registry.converters 10 | 11 | expect(internal_registry).to be_a(EvilEvents::Shared::DependencyContainer) 12 | expect(internal_registry._container).to be_empty 13 | end 14 | end 15 | 16 | describe '#register/#resolve' do 17 | it 'registers a new convertion object based on the passed clojure' do 18 | test_key = gen_symb 19 | another_key = gen_symb 20 | 21 | test_coercer = -> (value) { value.to_s } 22 | another_coercer = proc { |value| value.to_s } 23 | 24 | expect { registry.register(test_key, test_coercer) }.not_to raise_error 25 | expect { registry.register(another_key, another_coercer) }.not_to raise_error 26 | 27 | expect(registry.resolve(test_key)).to be_a(EvilEvents::Shared::TypeConverter::Converter) 28 | expect(registry.resolve(another_key)).to be_a(EvilEvents::Shared::TypeConverter::Converter) 29 | 30 | expect(registry.resolve(test_key).coercer).to eq(test_coercer) 31 | expect(registry.resolve(another_key).coercer).to eq(another_coercer) 32 | end 33 | 34 | it 'fails when type name isnt a symbol and/or convertion object isnt a proc' do 35 | invalid_type_names = [gen_int, gen_str, gen_obj, gen_bool] 36 | 37 | invalid_type_names.each do |type_name| 38 | expect { registry.register(type_name, -> {}) }.to raise_error(ArgumentError) 39 | end 40 | 41 | expect { registry.register(gen_symb) }.to raise_error(ArgumentError) 42 | expect { registry.register(gen_symb, -> {}) }.not_to raise_error 43 | end 44 | 45 | it 'fails when you try to register an object with already registered name' do 46 | first_key = gen_symb 47 | second_key = gen_symb 48 | 49 | expect { registry.register(first_key, -> {}) }.not_to raise_error 50 | expect { registry.register(second_key, -> {}) }.not_to raise_error 51 | 52 | expect { registry.register(first_key, -> {}) }.to raise_error(Dry::Container::Error) 53 | expect { registry.register(second_key, -> {}) }.to raise_error(Dry::Container::Error) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/type_converter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::TypeConverter do 4 | let(:type_converter) { described_class.new } 5 | 6 | describe 'public interface' do 7 | describe '#register/#resolve' do 8 | specify 'converter registration' do 9 | { 10 | gen_symb => gen_lambda, 11 | gen_symb => gen_proc 12 | }.each_pair do |type_name, coercer| 13 | expect { type_converter.resolve_type(type_name) }.to raise_error(Dry::Container::Error) 14 | expect { type_converter.register(type_name, coercer) }.not_to raise_error 15 | expect { type_converter.resolve_type(type_name) }.not_to raise_error 16 | end 17 | end 18 | 19 | specify 'converter object (after registration)' do 20 | type, coercer = gen_symb, gen_proc 21 | converter = type_converter.register(type, coercer) 22 | 23 | expect(converter).to be_a(EvilEvents::Shared::TypeConverter::Converter) 24 | expect(converter.coercer).to eq(coercer) 25 | end 26 | 27 | specify 'type resolving with type builder options' do 28 | type_key = gen_symb 29 | type_converter.register(type_key, -> (value) { value.to_s }) 30 | 31 | raw_value = gen_int 32 | raw_default = gen_str 33 | 34 | # without options 35 | custom_type = type_converter.resolve_type(type_key) 36 | 37 | expect(custom_type[raw_value]).to eq(raw_value.to_s) 38 | expect(custom_type[nil]).to eq('') 39 | 40 | # with default option as a primitive 41 | custom_type_with_default = type_converter.resolve_type( 42 | type_key, default: raw_default 43 | ) 44 | expect(custom_type_with_default[raw_value]).to eq(raw_value.to_s) 45 | expect(custom_type_with_default[]).to eq(raw_default) 46 | 47 | # with default option as a proc 48 | custom_type_with_proc_default = type_converter.resolve_type( 49 | type_key, default: -> { raw_default } 50 | ) 51 | expect(custom_type_with_proc_default[raw_value]).to eq(raw_value.to_s) 52 | expect(custom_type_with_proc_default[]).to eq(raw_default) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/units/evil_events/shared/types_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Shared::Types do 4 | specify do 5 | expect(described_class.constants).to contain_exactly( 6 | # dry-types constants 7 | :String, 8 | :Integer, 9 | :Float, 10 | :Decimal, 11 | :Array, 12 | :Hash, 13 | :Nil, 14 | :Symbol, 15 | :Class, 16 | :True, 17 | :False, 18 | :Date, 19 | :DateTime, 20 | :Time, 21 | :Strict, 22 | :Coercible, 23 | :Optional, 24 | :Bool, 25 | :Any, 26 | :Object, 27 | :Params, 28 | :JSON, 29 | :Maybe, 30 | :Range 31 | ) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/units/evil_events/subscriber_mixin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::SubscriberMixin, :stub_event_system do 4 | it_behaves_like 'event subscriber component' do 5 | let(:subscribeable) { Class.new.tap { |klass| klass.extend(described_class) } } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/types_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::Types do 4 | specify do 5 | expect(described_class).to eq(EvilEvents::Shared::Types) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/units/evil_events/version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe EvilEvents::VERSION do 4 | it { is_expected.to eq('0.5.0') } # ¯\_(ツ)_/¯ 5 | end 6 | --------------------------------------------------------------------------------