cloudenvoy 0.1.0.dev → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +41 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -0
- data/Appraisals +25 -0
- data/CHANGELOG.md +32 -0
- data/Gemfile.lock +215 -1
- data/README.md +581 -7
- data/app/controllers/cloudenvoy/application_controller.rb +8 -0
- data/app/controllers/cloudenvoy/subscriber_controller.rb +59 -0
- data/cloudenvoy.gemspec +15 -2
- data/config/routes.rb +5 -0
- data/examples/rails/.ruby-version +1 -0
- data/examples/rails/Gemfile +15 -0
- data/examples/rails/Gemfile.lock +207 -0
- data/examples/rails/Procfile +1 -0
- data/examples/rails/README.md +31 -0
- data/examples/rails/Rakefile +8 -0
- data/examples/rails/app/assets/config/manifest.js +2 -0
- data/examples/rails/app/assets/images/.keep +0 -0
- data/examples/rails/app/assets/stylesheets/application.css +15 -0
- data/examples/rails/app/channels/application_cable/channel.rb +6 -0
- data/examples/rails/app/channels/application_cable/connection.rb +6 -0
- data/examples/rails/app/controllers/application_controller.rb +4 -0
- data/examples/rails/app/controllers/concerns/.keep +0 -0
- data/examples/rails/app/helpers/application_helper.rb +4 -0
- data/examples/rails/app/javascript/packs/application.js +15 -0
- data/examples/rails/app/jobs/application_job.rb +9 -0
- data/examples/rails/app/mailers/application_mailer.rb +6 -0
- data/examples/rails/app/models/application_record.rb +5 -0
- data/examples/rails/app/models/concerns/.keep +0 -0
- data/examples/rails/app/publishers/hello_publisher.rb +34 -0
- data/examples/rails/app/subscribers/hello_subscriber.rb +16 -0
- data/examples/rails/app/views/layouts/application.html.erb +14 -0
- data/examples/rails/app/views/layouts/mailer.html.erb +13 -0
- data/examples/rails/app/views/layouts/mailer.text.erb +1 -0
- data/examples/rails/bin/rails +6 -0
- data/examples/rails/bin/rake +6 -0
- data/examples/rails/bin/setup +35 -0
- data/examples/rails/config.ru +7 -0
- data/examples/rails/config/application.rb +19 -0
- data/examples/rails/config/boot.rb +7 -0
- data/examples/rails/config/cable.yml +10 -0
- data/examples/rails/config/credentials.yml.enc +1 -0
- data/examples/rails/config/database.yml +25 -0
- data/examples/rails/config/environment.rb +7 -0
- data/examples/rails/config/environments/development.rb +65 -0
- data/examples/rails/config/environments/production.rb +114 -0
- data/examples/rails/config/environments/test.rb +50 -0
- data/examples/rails/config/initializers/application_controller_renderer.rb +9 -0
- data/examples/rails/config/initializers/assets.rb +14 -0
- data/examples/rails/config/initializers/backtrace_silencers.rb +8 -0
- data/examples/rails/config/initializers/cloudenvoy.rb +22 -0
- data/examples/rails/config/initializers/content_security_policy.rb +29 -0
- data/examples/rails/config/initializers/cookies_serializer.rb +7 -0
- data/examples/rails/config/initializers/filter_parameter_logging.rb +6 -0
- data/examples/rails/config/initializers/inflections.rb +17 -0
- data/examples/rails/config/initializers/mime_types.rb +5 -0
- data/examples/rails/config/initializers/wrap_parameters.rb +16 -0
- data/examples/rails/config/locales/en.yml +33 -0
- data/examples/rails/config/master.key +1 -0
- data/examples/rails/config/puma.rb +37 -0
- data/examples/rails/config/routes.rb +4 -0
- data/examples/rails/config/spring.rb +8 -0
- data/examples/rails/config/storage.yml +34 -0
- data/examples/rails/db/development.sqlite3 +0 -0
- data/examples/rails/db/test.sqlite3 +0 -0
- data/examples/rails/lib/assets/.keep +0 -0
- data/examples/rails/log/.keep +0 -0
- data/examples/rails/public/404.html +67 -0
- data/examples/rails/public/422.html +67 -0
- data/examples/rails/public/500.html +66 -0
- data/examples/rails/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/rails/public/apple-touch-icon.png +0 -0
- data/examples/rails/public/favicon.ico +0 -0
- data/examples/rails/storage/.keep +0 -0
- data/gemfiles/rails_5.2.gemfile +7 -0
- data/gemfiles/rails_5.2.gemfile.lock +251 -0
- data/gemfiles/rails_6.0.gemfile +7 -0
- data/gemfiles/rails_6.0.gemfile.lock +267 -0
- data/gemfiles/semantic_logger_3.4.gemfile +7 -0
- data/gemfiles/semantic_logger_3.4.gemfile.lock +265 -0
- data/gemfiles/semantic_logger_4.6.gemfile +7 -0
- data/gemfiles/semantic_logger_4.6.gemfile.lock +265 -0
- data/gemfiles/semantic_logger_4.7.0.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.0.gemfile.lock +265 -0
- data/gemfiles/semantic_logger_4.7.2.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.2.gemfile.lock +265 -0
- data/lib/cloudenvoy.rb +96 -2
- data/lib/cloudenvoy/authentication_error.rb +6 -0
- data/lib/cloudenvoy/authenticator.rb +57 -0
- data/lib/cloudenvoy/backend/google_pub_sub.rb +146 -0
- data/lib/cloudenvoy/backend/memory_pub_sub.rb +89 -0
- data/lib/cloudenvoy/config.rb +165 -0
- data/lib/cloudenvoy/engine.rb +20 -0
- data/lib/cloudenvoy/invalid_subscriber_error.rb +6 -0
- data/lib/cloudenvoy/logger_wrapper.rb +167 -0
- data/lib/cloudenvoy/message.rb +96 -0
- data/lib/cloudenvoy/middleware/chain.rb +250 -0
- data/lib/cloudenvoy/pub_sub_client.rb +76 -0
- data/lib/cloudenvoy/publisher.rb +211 -0
- data/lib/cloudenvoy/publisher_logger.rb +32 -0
- data/lib/cloudenvoy/subscriber.rb +222 -0
- data/lib/cloudenvoy/subscriber_logger.rb +26 -0
- data/lib/cloudenvoy/subscription.rb +19 -0
- data/lib/cloudenvoy/testing.rb +106 -0
- data/lib/cloudenvoy/topic.rb +19 -0
- data/lib/cloudenvoy/version.rb +1 -1
- data/lib/tasks/cloudenvoy.rake +61 -0
- metadata +263 -6
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudenvoy
|
|
4
|
+
# CLoudenvoy Rails engine
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
isolate_namespace Cloudenvoy
|
|
7
|
+
|
|
8
|
+
initializer 'cloudenvoy', before: :load_config_initializers do
|
|
9
|
+
Rails.application.routes.append do
|
|
10
|
+
mount Cloudenvoy::Engine, at: '/cloudenvoy'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
config.generators do |g|
|
|
15
|
+
g.test_framework :rspec, fixture: false
|
|
16
|
+
g.assets false
|
|
17
|
+
g.helper false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudenvoy
|
|
4
|
+
# Add contextual information to logs generated
|
|
5
|
+
# by subscribers/publishers.
|
|
6
|
+
#
|
|
7
|
+
# This class is a base class which aims at being inherited by
|
|
8
|
+
# object-specific logger wrappers. See Cloudenvoy::SubscriberLogger
|
|
9
|
+
# and Cloudenvoy::PublisherLogger.
|
|
10
|
+
class LoggerWrapper
|
|
11
|
+
attr_accessor :loggable
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_accessor :log_context_processor
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# The default context processor. Aims at being overriden by
|
|
19
|
+
# child classes.
|
|
20
|
+
#
|
|
21
|
+
# @return [Proc] The context processor proc.
|
|
22
|
+
#
|
|
23
|
+
def self.default_context_processor
|
|
24
|
+
@default_context_processor ||= ->(_) { {} }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Build a new instance of the class.
|
|
29
|
+
#
|
|
30
|
+
# @param [Any] loggable The loggable to wrap for logging.
|
|
31
|
+
#
|
|
32
|
+
def initialize(loggable)
|
|
33
|
+
@loggable = loggable
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Return the Proc responsible for formatting the log payload.
|
|
38
|
+
#
|
|
39
|
+
# @return [Proc] The context processor.
|
|
40
|
+
#
|
|
41
|
+
def context_processor
|
|
42
|
+
@context_processor ||= loggable.class.cloudenvoy_options_hash[:log_context_processor] ||
|
|
43
|
+
self.class.log_context_processor ||
|
|
44
|
+
self.class.default_context_processor
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# The block to pass to log messages.
|
|
49
|
+
#
|
|
50
|
+
# @return [Proc] The log block.
|
|
51
|
+
#
|
|
52
|
+
def log_block
|
|
53
|
+
@log_block ||= proc { context_processor.call(loggable) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Return the Cloudenvoy logger.
|
|
58
|
+
#
|
|
59
|
+
# @return [Logger, any] The cloudenvoy logger.
|
|
60
|
+
#
|
|
61
|
+
def logger
|
|
62
|
+
Cloudenvoy.logger
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Format main log message.
|
|
67
|
+
#
|
|
68
|
+
# @param [String] msg The message to log.
|
|
69
|
+
#
|
|
70
|
+
# @return [String] The formatted log message
|
|
71
|
+
#
|
|
72
|
+
def formatted_message(msg)
|
|
73
|
+
"[Cloudenvoy][#{loggable.class}] #{msg}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
# Log an info message.
|
|
78
|
+
#
|
|
79
|
+
# @param [String] msg The message to log.
|
|
80
|
+
# @param [Proc] &block Optional context block.
|
|
81
|
+
#
|
|
82
|
+
def info(msg, &block)
|
|
83
|
+
log_message(:info, msg, &block)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# Log an error message.
|
|
88
|
+
#
|
|
89
|
+
# @param [String] msg The message to log.
|
|
90
|
+
# @param [Proc] &block Optional context block.
|
|
91
|
+
#
|
|
92
|
+
def error(msg, &block)
|
|
93
|
+
log_message(:error, msg, &block)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
#
|
|
97
|
+
# Log an fatal message.
|
|
98
|
+
#
|
|
99
|
+
# @param [String] msg The message to log.
|
|
100
|
+
# @param [Proc] &block Optional context block.
|
|
101
|
+
#
|
|
102
|
+
def fatal(msg, &block)
|
|
103
|
+
log_message(:fatal, msg, &block)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
#
|
|
107
|
+
# Log an debut message.
|
|
108
|
+
#
|
|
109
|
+
# @param [String] msg The message to log.
|
|
110
|
+
# @param [Proc] &block Optional context block.
|
|
111
|
+
#
|
|
112
|
+
def debug(msg, &block)
|
|
113
|
+
log_message(:debug, msg, &block)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
#
|
|
117
|
+
# Delegate all methods to the underlying logger.
|
|
118
|
+
#
|
|
119
|
+
# @param [String, Symbol] name The method to delegate.
|
|
120
|
+
# @param [Array<any>] *args The list of method arguments.
|
|
121
|
+
# @param [Proc] &block Block passed to the method.
|
|
122
|
+
#
|
|
123
|
+
# @return [Any] The method return value
|
|
124
|
+
#
|
|
125
|
+
def method_missing(name, *args, &block)
|
|
126
|
+
if logger.respond_to?(name)
|
|
127
|
+
logger.send(name, *args, &block)
|
|
128
|
+
else
|
|
129
|
+
super
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Check if the class respond to a certain method.
|
|
135
|
+
#
|
|
136
|
+
# @param [String, Symbol] name The name of the method.
|
|
137
|
+
# @param [Boolean] include_private Whether to check private methods or not. Default to false.
|
|
138
|
+
#
|
|
139
|
+
# @return [Boolean] Return true if the class respond to this method.
|
|
140
|
+
#
|
|
141
|
+
def respond_to_missing?(name, include_private = false)
|
|
142
|
+
logger.respond_to?(name) || super
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
#
|
|
148
|
+
# Log a message for the provided log level.
|
|
149
|
+
#
|
|
150
|
+
# @param [String, Symbol] level The log level
|
|
151
|
+
# @param [String] msg The message to log.
|
|
152
|
+
# @param [Proc] &block Optional context block.
|
|
153
|
+
#
|
|
154
|
+
def log_message(level, msg, &block)
|
|
155
|
+
# Merge log-specific context into object-specific context
|
|
156
|
+
payload_block = ->(*_args) { log_block.call.merge(block&.call || {}) }
|
|
157
|
+
|
|
158
|
+
# ActiveSupport::Logger does not support passing a payload through a block on top
|
|
159
|
+
# of a message.
|
|
160
|
+
if defined?(ActiveSupport::Logger) && logger.is_a?(ActiveSupport::Logger)
|
|
161
|
+
logger.send(level) { "#{formatted_message(msg)} -- #{payload_block.call}" }
|
|
162
|
+
else
|
|
163
|
+
logger.send(level, formatted_message(msg), &payload_block)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudenvoy
|
|
4
|
+
# Represents a Pub/Sub message
|
|
5
|
+
class Message
|
|
6
|
+
attr_writer :topic
|
|
7
|
+
attr_accessor :id, :payload, :metadata, :sub_uri
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Return an instantiated message from a Pub/Sub webhook
|
|
11
|
+
# payload.
|
|
12
|
+
#
|
|
13
|
+
# @param [Hash] input_payload The Pub/Sub webhook hash describing
|
|
14
|
+
# the message to process.
|
|
15
|
+
#
|
|
16
|
+
# @return [Cloudenvoy::Message] The instantiated message.
|
|
17
|
+
#
|
|
18
|
+
def self.from_descriptor(input_payload)
|
|
19
|
+
# Build new message
|
|
20
|
+
new(
|
|
21
|
+
id: input_payload.dig('message', 'message_id'),
|
|
22
|
+
payload: JSON.parse(Base64.decode64(input_payload.dig('message', 'data'))),
|
|
23
|
+
metadata: input_payload.dig('message', 'attributes'),
|
|
24
|
+
sub_uri: input_payload['subscription']
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# Constructor
|
|
30
|
+
#
|
|
31
|
+
# @param [String] id The message ID
|
|
32
|
+
# @param [Hash, String] payload The message payload
|
|
33
|
+
# @param [Hash] metadata The message attributes
|
|
34
|
+
# @param [String] topic The topic - will be inferred from sub_uri if left blank
|
|
35
|
+
# @param [String] sub_uri The sub_uri this message was sent for
|
|
36
|
+
#
|
|
37
|
+
def initialize(id: nil, payload: nil, metadata: nil, topic: nil, sub_uri: nil)
|
|
38
|
+
@id = id
|
|
39
|
+
@payload = payload
|
|
40
|
+
@topic = topic
|
|
41
|
+
@metadata = metadata || {}
|
|
42
|
+
@sub_uri = sub_uri
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Return the message topic.
|
|
47
|
+
#
|
|
48
|
+
# @return [String] The message topic.
|
|
49
|
+
#
|
|
50
|
+
def topic
|
|
51
|
+
return @topic if @topic
|
|
52
|
+
return nil unless sub_uri
|
|
53
|
+
|
|
54
|
+
Subscriber.parse_sub_uri(sub_uri)[1]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# Return the instantiated Subscriber designated to process this message.
|
|
59
|
+
#
|
|
60
|
+
# @return [Subscriber] The instantiated subscriber.
|
|
61
|
+
#
|
|
62
|
+
def subscriber
|
|
63
|
+
@subscriber ||= begin
|
|
64
|
+
return nil unless sub_uri && (klass = Subscriber.from_sub_uri(sub_uri))
|
|
65
|
+
|
|
66
|
+
klass.new(message: self)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
# Return a hash description of the message.
|
|
72
|
+
#
|
|
73
|
+
# @return [Hash] The message description
|
|
74
|
+
#
|
|
75
|
+
def to_h
|
|
76
|
+
{
|
|
77
|
+
id: id,
|
|
78
|
+
payload: payload,
|
|
79
|
+
metadata: metadata,
|
|
80
|
+
topic: topic,
|
|
81
|
+
sub_uri: sub_uri
|
|
82
|
+
}.compact
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Equality operator.
|
|
87
|
+
#
|
|
88
|
+
# @param [Any] other The object to compare.
|
|
89
|
+
#
|
|
90
|
+
# @return [Boolean] True if the object is equal.
|
|
91
|
+
#
|
|
92
|
+
def ==(other)
|
|
93
|
+
other.is_a?(self.class) && other.id == id
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudenvoy
|
|
4
|
+
module Middleware
|
|
5
|
+
# The class below was originally taken from Sidekiq.
|
|
6
|
+
# See: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/middleware/chain.rb
|
|
7
|
+
#
|
|
8
|
+
# Middleware are callables configured to run before/after a message is processed.
|
|
9
|
+
# Middlewares can be configured to run on the client side (when jobs are pushed
|
|
10
|
+
# to Cloud Tasks) as well as on the server side (when jobs are processed by
|
|
11
|
+
# your application)
|
|
12
|
+
#
|
|
13
|
+
# To add a middleware on publishers:
|
|
14
|
+
#
|
|
15
|
+
# Cloudenvoy.configure do |config|
|
|
16
|
+
# config.publisher_middleware do |chain|
|
|
17
|
+
# chain.add MyPublisherHook
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# To modify middlewares on subscribers, just call
|
|
22
|
+
# with another block:
|
|
23
|
+
#
|
|
24
|
+
# Cloudenvoy.configure do |config|
|
|
25
|
+
# config.subscriber_middleware do |chain|
|
|
26
|
+
# chain.add MySubscriberHook
|
|
27
|
+
# chain.remove ActiveRecord
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# To insert immediately preceding another entry:
|
|
32
|
+
#
|
|
33
|
+
# Cloudenvoy.configure do |config|
|
|
34
|
+
# config.publisher_middleware do |chain|
|
|
35
|
+
# chain.insert_before ActiveRecord, MyPublisherHook
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# To insert immediately after another entry:
|
|
40
|
+
#
|
|
41
|
+
# Cloudenvoy.configure do |config|
|
|
42
|
+
# config.publisher_middleware do |chain|
|
|
43
|
+
# chain.insert_after ActiveRecord, MyPublisherHook
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# This is an example of a minimal server middleware:
|
|
48
|
+
#
|
|
49
|
+
# class MySubscriberHook
|
|
50
|
+
# def call(subscriber, msg, queue)
|
|
51
|
+
# puts "Before work"
|
|
52
|
+
# yield
|
|
53
|
+
# puts "After work"
|
|
54
|
+
# end
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# This is an example of a minimal client middleware, note
|
|
58
|
+
# the method must return the result or the job will not push
|
|
59
|
+
# to Redis:
|
|
60
|
+
#
|
|
61
|
+
# class MyPublisherHook
|
|
62
|
+
# def call(publisher, msg, queue, redis_pool)
|
|
63
|
+
# puts "Before push"
|
|
64
|
+
# result = yield
|
|
65
|
+
# puts "After push"
|
|
66
|
+
# result
|
|
67
|
+
# end
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
class Chain
|
|
71
|
+
include Enumerable
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# Build a new middleware chain.
|
|
75
|
+
#
|
|
76
|
+
def initialize
|
|
77
|
+
@entries = nil
|
|
78
|
+
yield self if block_given?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#
|
|
82
|
+
# Iterate over the list middlewares and execute the block on each item.
|
|
83
|
+
#
|
|
84
|
+
# @param [Proc] &block The block to execute on each item.
|
|
85
|
+
#
|
|
86
|
+
def each(&block)
|
|
87
|
+
entries.each(&block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#
|
|
91
|
+
# Return the list of middlewares.
|
|
92
|
+
#
|
|
93
|
+
# @return [Array<Cloudenvoy::Middleware::Chain::Entry>] The list of middlewares
|
|
94
|
+
#
|
|
95
|
+
def entries
|
|
96
|
+
@entries ||= []
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# Remove a middleware from the list.
|
|
101
|
+
#
|
|
102
|
+
# @param [Class] klass The middleware class to remove.
|
|
103
|
+
#
|
|
104
|
+
def remove(klass)
|
|
105
|
+
entries.delete_if { |entry| entry.klass == klass }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
#
|
|
109
|
+
# Add a middleware at the end of the list.
|
|
110
|
+
#
|
|
111
|
+
# @param [Class] klass The middleware class to add.
|
|
112
|
+
# @param [Arry<any>] *args The list of arguments to the middleware.
|
|
113
|
+
#
|
|
114
|
+
# @return [Array<Cloudenvoy::Middleware::Chain::Entry>] The updated list of middlewares
|
|
115
|
+
#
|
|
116
|
+
def add(klass, *args)
|
|
117
|
+
remove(klass) if exists?(klass)
|
|
118
|
+
entries << Entry.new(klass, *args)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
#
|
|
122
|
+
# Add a middleware at the beginning of the list.
|
|
123
|
+
#
|
|
124
|
+
# @param [Class] klass The middleware class to add.
|
|
125
|
+
# @param [Arry<any>] *args The list of arguments to the middleware.
|
|
126
|
+
#
|
|
127
|
+
# @return [Array<Cloudenvoy::Middleware::Chain::Entry>] The updated list of middlewares
|
|
128
|
+
#
|
|
129
|
+
def prepend(klass, *args)
|
|
130
|
+
remove(klass) if exists?(klass)
|
|
131
|
+
entries.insert(0, Entry.new(klass, *args))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
#
|
|
135
|
+
# Add a middleware before another middleware.
|
|
136
|
+
#
|
|
137
|
+
# @param [Class] oldklass The middleware class before which the new middleware should be inserted.
|
|
138
|
+
# @param [Class] newklass The middleware class to insert.
|
|
139
|
+
# @param [Arry<any>] *args The list of arguments for the inserted middleware.
|
|
140
|
+
#
|
|
141
|
+
# @return [Array<Cloudenvoy::Middleware::Chain::Entry>] The updated list of middlewares
|
|
142
|
+
#
|
|
143
|
+
def insert_before(oldklass, newklass, *args)
|
|
144
|
+
i = entries.index { |entry| entry.klass == newklass }
|
|
145
|
+
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
|
146
|
+
i = entries.index { |entry| entry.klass == oldklass } || 0
|
|
147
|
+
entries.insert(i, new_entry)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
#
|
|
151
|
+
# Add a middleware after another middleware.
|
|
152
|
+
#
|
|
153
|
+
# @param [Class] oldklass The middleware class after which the new middleware should be inserted.
|
|
154
|
+
# @param [Class] newklass The middleware class to insert.
|
|
155
|
+
# @param [Arry<any>] *args The list of arguments for the inserted middleware.
|
|
156
|
+
#
|
|
157
|
+
# @return [Array<Cloudenvoy::Middleware::Chain::Entry>] The updated list of middlewares
|
|
158
|
+
#
|
|
159
|
+
def insert_after(oldklass, newklass, *args)
|
|
160
|
+
i = entries.index { |entry| entry.klass == newklass }
|
|
161
|
+
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
|
162
|
+
i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
|
|
163
|
+
entries.insert(i + 1, new_entry)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# Checks if middleware has been added to the list.
|
|
168
|
+
#
|
|
169
|
+
# @param [Class] klass The middleware class to check.
|
|
170
|
+
#
|
|
171
|
+
# @return [Boolean] Return true if the middleware is in the list.
|
|
172
|
+
#
|
|
173
|
+
def exists?(klass)
|
|
174
|
+
any? { |entry| entry.klass == klass }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
#
|
|
178
|
+
# Checks if the middlware list is empty
|
|
179
|
+
#
|
|
180
|
+
# @return [Boolean] Return true if the middleware list is empty.
|
|
181
|
+
#
|
|
182
|
+
def empty?
|
|
183
|
+
@entries.nil? || @entries.empty?
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
#
|
|
187
|
+
# Return a list of instantiated middlewares. Each middleware gets
|
|
188
|
+
# initialize with the args originally passed to `add`, `insert_before` etc.
|
|
189
|
+
#
|
|
190
|
+
# @return [Array<any>] The list of instantiated middlewares.
|
|
191
|
+
#
|
|
192
|
+
def retrieve
|
|
193
|
+
map(&:make_new)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
#
|
|
197
|
+
# Empty the list of middlewares.
|
|
198
|
+
#
|
|
199
|
+
# @return [Array<Cloudenvoy::Middleware::Chain::Entry>] The updated list of middlewares
|
|
200
|
+
#
|
|
201
|
+
def clear
|
|
202
|
+
entries.clear
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
# Invoke the chain of middlewares.
|
|
207
|
+
#
|
|
208
|
+
# @param [Array<any>] *args The args to pass to each middleware.
|
|
209
|
+
#
|
|
210
|
+
def invoke(*args)
|
|
211
|
+
return yield if empty?
|
|
212
|
+
|
|
213
|
+
chain = retrieve.dup
|
|
214
|
+
traverse_chain = lambda do
|
|
215
|
+
if chain.empty?
|
|
216
|
+
yield
|
|
217
|
+
else
|
|
218
|
+
chain.shift.call(*args, &traverse_chain)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
traverse_chain.call
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Middleware list item.
|
|
226
|
+
class Entry
|
|
227
|
+
attr_reader :klass, :args
|
|
228
|
+
|
|
229
|
+
#
|
|
230
|
+
# Build a new entry.
|
|
231
|
+
#
|
|
232
|
+
# @param [Class] klass The middleware class.
|
|
233
|
+
# @param [Array<any>] *args The list of arguments for the middleware.
|
|
234
|
+
#
|
|
235
|
+
def initialize(klass, *args)
|
|
236
|
+
@klass = klass
|
|
237
|
+
@args = args
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
#
|
|
241
|
+
# Return an instantiated middleware.
|
|
242
|
+
#
|
|
243
|
+
# @return [Any] The instantiated middleware.
|
|
244
|
+
#
|
|
245
|
+
def make_new
|
|
246
|
+
@klass.new(*@args)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|