ruby_slime 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []