ruby_slime 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 016751bd1a7ff4786175ba1de34a94c8358c71145f6b24bed359e54a5b2f64d9
4
+ data.tar.gz: 2a2f73d7f2fd439badd416d433879ef28097aabe1de92a8f92177fc1fe331707
5
+ SHA512:
6
+ metadata.gz: 484554d5c87bbac120624f97029a35a80937aa05aa3ee70ee186f60431679e368c404d638466022e269696351634dd1327fc9c21bb2b4ed1d7bdce008291f8b2
7
+ data.tar.gz: 4b3ff146857e90975dd948937f09b1b01b4aca5457b561f4cd23cccb131a398499cbd552b7c770b2c73ba543d08d76dbef29cba4310604c231399cc36fe0b871
data/LICENSE.md ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+ ====================
3
+
4
+ _Copyright © `2023`, `Sarun Rattanasiri`_
5
+ _All rights reserved._
6
+
7
+ Redistribution and use in source and binary forms, with or without modification,
8
+ are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice,
11
+ this list of conditions and the following disclaimer.
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+ * Neither the name of the copyright holder nor the names of its contributors
16
+ may be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # RubySlime
2
+ ## What is it?
3
+ It is a collection of infrastructure utilities utilized by [Slime Systems](https://slime.systems/).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ~~~ruby
10
+ gem 'ruby_slime'
11
+ ~~~
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ ## License
18
+ Currently, we use the library privately; we don't think it will gain any attention as we never explain anything to anyone.
19
+ However, RubySlime is released under the [3-clause BSD License](LICENSE.md) anyway.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module Command
5
+ extend ::ActiveSupport::Concern
6
+ extend ::Forwardable
7
+ include ::ActiveModel::Model
8
+ include ::ActiveModel::Attributes
9
+
10
+ class_methods do
11
+ attr_reader :_applied_schema
12
+
13
+ private
14
+
15
+ def apply_schema(schema)
16
+ @_applied_schema = schema
17
+ end
18
+ end
19
+
20
+ def attributes
21
+ @attributes__immutable_cache ||= begin
22
+ normalized = super.compact
23
+ schema = self.class._applied_schema
24
+ return normalized unless schema
25
+ result = schema.call(normalized)
26
+ raise SchemaViolation.new(result.errors) unless result.success?
27
+ result.to_h
28
+ end
29
+ end
30
+
31
+ def_delegators :attributes, :[], :values_at
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module ConcurrentWriteDetected
5
+ end
6
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module Event
5
+ extend ::ActiveSupport::Concern
6
+
7
+ attr_reader :data, :metadata
8
+
9
+ def initialize(data: {}, metadata: nil)
10
+ @data = data
11
+ @metadata = metadata
12
+ end
13
+
14
+ class_methods do
15
+ def strict(data: {}, metadata: nil)
16
+ new(
17
+ data: _ensure_schema(data),
18
+ metadata: metadata,
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def apply_schema(schema)
25
+ @_applied_schema = schema
26
+ end
27
+
28
+ def _ensure_schema(data)
29
+ return data unless @_applied_schema
30
+ result = @_applied_schema.call(data)
31
+ raise SchemaViolation.new(result.errors) unless result.success?
32
+ result.to_h
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ class EventDispatcher
5
+ include Internals::EventMetadataHandler
6
+
7
+ def initialize(*handlers)
8
+ @handlers = handlers
9
+ freeze
10
+ end
11
+
12
+ def call(events, **metadata)
13
+ @handlers.each do |handler|
14
+ handle_handler_arguments(handler, events, metadata)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ class EventQueue < EventDispatcher
5
+ def call(events, **metadata)
6
+ events.each do |event|
7
+ super(event, **metadata)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ class EventRouter
5
+ include Internals::EventMetadataHandler
6
+
7
+ def initialize(handlers_map = {})
8
+ @handlers_map = handlers_map
9
+ freeze
10
+ end
11
+
12
+ def call(event, **metadata)
13
+ handler = @handlers_map[event.class]
14
+ return unless handler
15
+ handle_handler_arguments(handler, event, metadata)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module Internals
5
+ module EventMetadataHandler
6
+ private
7
+
8
+ def handle_handler_arguments(handler, events, metadata)
9
+ handler.call(events, **metadata)
10
+ rescue ::ArgumentError => e
11
+ cache = EventMetadataHandler_SingletonCache.instance
12
+ should_retry = cache.meta_patterns.any? do |pattern|
13
+ pattern =~ e.message
14
+ end
15
+ raise e unless should_retry
16
+ handler.call(events)
17
+ end
18
+
19
+ class EventMetadataHandler_SingletonCache
20
+ include ::Singleton
21
+ attr_reader :meta_patterns
22
+
23
+ def initialize
24
+ @meta_patterns = [
25
+ /wrong\s+number\s+of\s+arguments/i,
26
+ /unknown\s+keywords/i,
27
+ ]
28
+ super
29
+ freeze
30
+ end
31
+ end
32
+
33
+ private_constant :EventMetadataHandler_SingletonCache
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module Internals
5
+ end
6
+ private_constant :Internals
7
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module JWTKit
5
+ class APIVerifier
6
+ extend ::Forwardable
7
+
8
+ def initialize(
9
+ fetch_secret:,
10
+ handle_result:
11
+ )
12
+ @fetch_secret = fetch_secret
13
+ @handle_result = handle_result
14
+ end
15
+
16
+ def call(token)
17
+ token_result = token_schema.call({
18
+ token: token,
19
+ })
20
+ raise TokenError.new(token_result.errors) unless token_result.success?
21
+ ::JWT.decode(token_result[:token], nil, true, {
22
+ algorithm: 'HS256',
23
+ required_claims: %w[aud iat sub],
24
+ }) do |header|
25
+ header_result = header_schema.call(header)
26
+ raise TokenError.new(header_result.errors) unless header_result.success?
27
+ @fetch_secret.call(header_result.to_h)
28
+ end.then do |sections|
29
+ payload_section, header_section = sections
30
+ header_result = header_schema.call(header_section)
31
+ raise TokenError.new(header_result.errors) unless header_result.success?
32
+ payload_result = payload_schema.call(payload_section)
33
+ raise TokenError.new(payload_result.errors) unless payload_result.success?
34
+ [payload_result, header_result].map(&:to_h)
35
+ end.then do |sections|
36
+ @handle_result.call(*sections)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def_delegators :singleton_cache,
43
+ :token_schema,
44
+ :header_schema,
45
+ :payload_schema
46
+
47
+ def singleton_cache
48
+ APIVerifier_SingletonCache.instance
49
+ end
50
+
51
+ class APIVerifier_SingletonCache
52
+ include ::Singleton
53
+ attr_reader \
54
+ :token_schema,
55
+ :header_schema,
56
+ :payload_schema
57
+
58
+ def initialize
59
+ @token_schema = ::Dry::Schema.Params do
60
+ required(:token).filled(:string)
61
+ end
62
+ @header_schema = ::Dry::Schema.Params do
63
+ optional(:typ).filled(:string).value(eql?: 'JWT')
64
+ required(:alg).filled(:string).value(eql?: 'HS256')
65
+ required(:kid).filled(:string)
66
+ end
67
+ @payload_schema = ::Dry::Schema.Params do
68
+ required(:aud).filled(:string)
69
+ required(:iat).value(:integer, gt?: 0)
70
+ required(:sub).filled(:string)
71
+ optional(:data).filled(:hash)
72
+ end
73
+ super
74
+ freeze
75
+ end
76
+ end
77
+
78
+ private_constant :APIVerifier_SingletonCache
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module JWTKit
5
+ module HandlingHelper
6
+ private
7
+
8
+ def_delegators :@controller,
9
+ :params,
10
+ :redirect_to,
11
+ :render
12
+
13
+ def initialize_helper(controller, payload, header = nil)
14
+ @controller = controller
15
+ @payload = payload
16
+ @header = header
17
+ end
18
+
19
+ def jwt_params(*permit)
20
+ @payload.fetch(:data, {}).slice(*permit)
21
+ end
22
+
23
+ def render_data(data_hash)
24
+ render json: {
25
+ data: data_hash,
26
+ }
27
+ end
28
+
29
+ def render_exception(code, message = nil, metadata: nil)
30
+ render json: {
31
+ exception: {
32
+ code: code,
33
+ message: message.presence,
34
+ **{
35
+ metadata: metadata,
36
+ }.compact,
37
+ }.compact,
38
+ }, status: :unprocessable_entity
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module JWTKit
5
+ class TokenError < ::StandardError
6
+ attr_reader :errors
7
+
8
+ def initialize(errors)
9
+ @errors = errors
10
+ super()
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-schema'
4
+ require 'jwt'
5
+
6
+ module RubySlime
7
+ module JWTKit
8
+ end
9
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module Mongoid
5
+ class Interactor
6
+ def initialize(
7
+ state_key:,
8
+ load_process:,
9
+ event_handler: nil,
10
+ mongoid_model: State
11
+ )
12
+ @state_key = state_key
13
+ @load_process = load_process
14
+ @event_handler = event_handler
15
+ @mongoid_model = mongoid_model
16
+ freeze
17
+ end
18
+
19
+ def call(*commands)
20
+ lazy_key = @state_key.respond_to?(:call)
21
+ state_record = if lazy_key
22
+ @mongoid_model.new
23
+ else
24
+ @mongoid_model.where({
25
+ _id: @state_key,
26
+ }).first_or_initialize
27
+ end
28
+ process = @load_process.call(
29
+ state: state_record.state,
30
+ )
31
+ metadata_buffer = {
32
+ next_position: state_record.next_position,
33
+ }
34
+ pending_events = commands.each do |command|
35
+ process.call(command)
36
+ end.then do
37
+ process.pending_events
38
+ end
39
+ return pending_events unless pending_events.present?
40
+ state_record.state = process.state
41
+ state_record.next_position += pending_events.size
42
+ state_record._id = @state_key.call if lazy_key
43
+ begin
44
+ pending_events
45
+ ensure
46
+ if state_record.new_record?
47
+ begin
48
+ state_record.save!
49
+ rescue ::Mongo::Error::OperationFailure => e
50
+ raise e unless e.code == 11000 # E11000 duplicate key error
51
+ raise StaleRecord
52
+ end
53
+ else
54
+ update_result = @mongoid_model.where({
55
+ _id: state_record._id,
56
+ lock_version: state_record.lock_version,
57
+ }).update_all({
58
+ '$set': {
59
+ state: state_record.state,
60
+ next_position: state_record.next_position,
61
+ }.compact,
62
+ '$inc': {
63
+ lock_version: 1,
64
+ },
65
+ })
66
+ raise StaleRecord unless 0 < update_result.matched_count
67
+ end
68
+ metadata_buffer[:state_key] = state_record._id
69
+ @event_handler&.call(
70
+ pending_events,
71
+ **metadata_buffer,
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module Mongoid
5
+ class StaleRecord < ::StandardError
6
+ include ConcurrentWriteDetected
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module Mongoid
5
+ class State
6
+ include ::Mongoid::Document
7
+ store_in collection: 'process_states'
8
+
9
+ field :state, type: ::Hash, default: {}
10
+ field :next_position, type: ::Integer, default: 1
11
+ field :lock_version, type: ::Integer, default: 0
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid'
4
+
5
+ module RubySlime
6
+ module Mongoid
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ module ProcessHelper
5
+ extend ::ActiveSupport::Concern
6
+
7
+ # alias for convenient access
8
+ class UnknownCommand < ::RubySlime::UnknownCommand
9
+ end
10
+
11
+ def state
12
+ @state ||= {}
13
+ end
14
+
15
+ def pending_events
16
+ @pending_events ||= []
17
+ end
18
+
19
+ class_methods do
20
+ attr_reader :_event_handlers
21
+
22
+ private def on(event_class, &block)
23
+ @_event_handlers ||= {}
24
+ @_event_handlers[event_class] = block
25
+ end
26
+ end
27
+
28
+ private def apply(*events)
29
+ handlers = self.class._event_handlers || {}
30
+ events.each do |event|
31
+ handler = handlers[event.class]
32
+ instance_exec(event, &handler) if handler
33
+ pending_events << event
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ class SchemaViolation < ::StandardError
5
+ attr_reader :errors
6
+
7
+ def initialize(errors)
8
+ @errors = errors
9
+ super()
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ class UnknownCommand < ::NotImplementedError
5
+ attr_reader :command
6
+
7
+ def initialize(command)
8
+ @command = command
9
+ super()
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubySlime
4
+ VERSION = '0.0.1'
5
+ end
data/lib/ruby_slime.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model'
4
+ require 'active_support'
5
+ require 'active_support/core_ext/hash/indifferent_access'
6
+ require 'active_support/core_ext/object/blank'
7
+ require 'forwardable'
8
+ require 'singleton'
9
+ require 'zeitwerk'
10
+
11
+ loader = ::Zeitwerk::Loader.for_gem
12
+ loader.inflector.inflect(
13
+ 'api_verifier' => 'APIVerifier',
14
+ 'jwt_kit' => 'JWTKit',
15
+ )
16
+ loader.setup
17
+
18
+ module RubySlime
19
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_slime
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sarun Rattanasiri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-schema
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: jwt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mongoid
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 7.3.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 7.3.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: zeitwerk
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email: midnight_w@gmx.tw
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - LICENSE.md
104
+ - README.md
105
+ - lib/ruby_slime.rb
106
+ - lib/ruby_slime/command.rb
107
+ - lib/ruby_slime/concurrent_write_detected.rb
108
+ - lib/ruby_slime/event.rb
109
+ - lib/ruby_slime/event_dispatcher.rb
110
+ - lib/ruby_slime/event_queue.rb
111
+ - lib/ruby_slime/event_router.rb
112
+ - lib/ruby_slime/internals.rb
113
+ - lib/ruby_slime/internals/event_metadata_handler.rb
114
+ - lib/ruby_slime/jwt_kit.rb
115
+ - lib/ruby_slime/jwt_kit/api_verifier.rb
116
+ - lib/ruby_slime/jwt_kit/handling_helper.rb
117
+ - lib/ruby_slime/jwt_kit/token_error.rb
118
+ - lib/ruby_slime/mongoid.rb
119
+ - lib/ruby_slime/mongoid/interactor.rb
120
+ - lib/ruby_slime/mongoid/stale_record.rb
121
+ - lib/ruby_slime/mongoid/state.rb
122
+ - lib/ruby_slime/process_helper.rb
123
+ - lib/ruby_slime/schema_violation.rb
124
+ - lib/ruby_slime/unknown_command.rb
125
+ - lib/ruby_slime/version.rb
126
+ homepage: https://slime.systems/
127
+ licenses:
128
+ - BSD-3-Clause
129
+ metadata:
130
+ homepage_uri: https://slime.systems/
131
+ source_code_uri: https://gitlab.com/slime-systems/ruby-slime
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: 3.0.0
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubygems_version: 3.4.10
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: The ruby-type slime made by Slime Systems.
151
+ test_files: []