ibsciss-middleware 0.3.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 19c42633d1c333e95219851b18162d3dd8962401
4
+ data.tar.gz: 862b2a2464ff93be9ce68f9cc6c48ba215d610cf
5
+ SHA512:
6
+ metadata.gz: 8b1a8954e9053c362c10b8fe3ad606cdb22a294e8fcbfb8588d37ddeb589763d54af33d0c41e19eb529e6f3ef92840df38f58ec893816c94280bfbe84e37a215
7
+ data.tar.gz: b4a05f87ea90f0c1d2051df9057278ad2c54a0ff4a86f0eee57fdcd41535e09992b5ef0cb421879c011a95af015b926f4c8fcc4627a7d0bbb8b33a360c116dd2
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.iml
2
+ .idea
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - rbx-18mode
8
+ - rbx-19mode
9
+ - ruby-head
10
+ - jruby-head
11
+ - ree
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ ## 0.3.0
2
+ - Apply https://github.com/Ibsciss/ruby-middleware/commit/01f75d8e4137b39ea907f13756f21cba4edffaa7
3
+ - Apply https://github.com/Ibsciss/ruby-middleware/commit/00aa09353f7ee2b801bdb446418a936640ae56d7
4
+ - Refactor test suite to use the new rspec expect syntaxe
5
+ - Update dependencies version
6
+
7
+ ## 0.2.0 (unreleased)
8
+
9
+
10
+
11
+ ## 0.1.0 (March 16, 2012)
12
+
13
+ - Initial release, almost directly extracted from [Vagrant](http://vagrantup.com)
14
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in middleware.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Mitchell Hashimoto
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # Middleware
2
+
3
+ `middleware` is a library which provides a generalized implementation
4
+ of the middleware pattern for Ruby. The middleware pattern is a useful
5
+ abstraction tool in various cases, but is specifically useful for splitting
6
+ large sequential chunks of logic into small pieces.
7
+
8
+ This library was created by Mitchell Hashimoto, original repository: https://github.com/mitchellh/middleware
9
+
10
+ ## Installing
11
+
12
+ Middleware is distributed as a RubyGem, so simply gem install:
13
+
14
+ ```console
15
+ $ gem install ibsciss-middleware
16
+ ```
17
+
18
+ ## A Basic Example
19
+
20
+ Below is a basic example of the library in use. If you don't understand
21
+ what middleware is, please read below. This example is simply meant to give
22
+ you a quick idea of what the library looks like.
23
+
24
+ ```ruby
25
+ # Basic middleware that just prints the inbound and
26
+ # outbound steps.
27
+ class Trace
28
+ def initialize(app, value)
29
+ @app = app
30
+ @value = value
31
+ end
32
+
33
+ def call(env)
34
+ puts "--> #{@value}"
35
+ @app.call(env)
36
+ puts "<-- #{@value}"
37
+ end
38
+ end
39
+
40
+ # Build the actual middleware stack which runs a sequence
41
+ # of slightly different versions of our middleware.
42
+ stack = Middleware::Builder.new do |b|
43
+ b.use Trace, "A"
44
+ b.use Trace, "B"
45
+ b.use Trace, "C"
46
+ end
47
+
48
+ # Run it!
49
+ stack.call(nil)
50
+ ```
51
+
52
+ And the output:
53
+
54
+ ```
55
+ --> A
56
+ --> B
57
+ --> C
58
+ <-- C
59
+ <-- B
60
+ <-- A
61
+ ```
62
+
63
+
64
+
65
+ ## Middleware
66
+
67
+ ### What is it?
68
+
69
+ Middleware is a reusable chunk of logic that is called to perform some
70
+ action. The middleware itself is responsible for calling up the next item
71
+ in the middleware chain using a recursive-like call. This allows middleware
72
+ to perform logic both _before_ and _after_ something is done.
73
+
74
+ The canonical middleware example is in web request processing, and middleware
75
+ is used heavily by both [Rack](#) and [Rails](#).
76
+ In web processing, the first middleware is called with some information about
77
+ the web request, such as HTTP headers, request URL, etc. The middleware is
78
+ responsible for calling the next middleware, and may modify the request along
79
+ the way. When the middlewares begin returning, the state now has the HTTP
80
+ response, so that the middlewares can then modify the response.
81
+
82
+ Cool? Yeah! And this pattern is generally usable in a wide variety of
83
+ problems.
84
+
85
+ ### Middleware Classes
86
+
87
+ One method of creating middleware, and by far the most common, is to define
88
+ a class that duck types to the following interface:
89
+
90
+ ```ruby
91
+ class MiddlewareExample
92
+ def initialize(app); end
93
+ def call(env); end
94
+ end
95
+ ```
96
+
97
+ Therefore, a basic middleware example follows:
98
+
99
+ ```ruby
100
+ class Trace
101
+ def initialize(app)
102
+ @app = app
103
+ end
104
+
105
+ def call(env)
106
+ puts "Trace up"
107
+ @app.call(env)
108
+ puts "Trace down"
109
+ end
110
+ end
111
+ ```
112
+
113
+ A basic description of the two methods that a middleware must implement:
114
+
115
+ * **initialize(app)** - The first argument sent will always be the next middleware to call, called
116
+ `app` for historical reasons. This should be stored away for later.
117
+
118
+ * **call(env)** - This is what is actually invoked to do work. `env` is just some
119
+ state sent in (defined by the caller, but usually a Hash). This call should also
120
+ call `app.call(env)` at some point to move on.
121
+
122
+ ### Middleware Lambdas
123
+
124
+ A middleware can also be a simple lambda. The downside of using a lambda is that
125
+ it only has access to the state on the initial call, there is no "post" step for
126
+ lambdas. A basic example, in the context of a web request:
127
+
128
+ ```ruby
129
+ lambda { |env| puts "You requested: #{env["http.request_url"]}" }
130
+ ```
131
+
132
+ ## Middleware Stacks
133
+
134
+ Middlewares on their own are useful as small chunks of logic, but their real
135
+ power comes from building them up into a _stack_. A stack of middlewares are
136
+ executed in the order given.
137
+
138
+ ### Basic Building and Running
139
+
140
+ The middleware library comes with a `Builder` class which provides a nice DSL
141
+ for building a stack of middlewares:
142
+
143
+ ```ruby
144
+ stack = Middleware::Builder.new do |d|
145
+ d.use Trace
146
+ d.use lambda { |env| puts "LAMBDA!" }
147
+ end
148
+ ```
149
+
150
+ This `stack` variable itself is now a valid middleware and has the same interface,
151
+ so to execute the stack, just call `call` on it:
152
+
153
+ ```ruby
154
+ stack.call
155
+ ```
156
+
157
+ The call method takes an optional parameter which is the state to pass into the
158
+ initial middleware.
159
+
160
+ ### Manipulating a Stack
161
+
162
+ Stacks also provide a set of methods for manipulating the middleware stack. This
163
+ lets you insert, replace, and delete middleware after a stack has already been
164
+ created. Given the `stack` variable created above, we can manipulate it as
165
+ follows. Please imagine that each example runs with the original `stack` variable,
166
+ so that the order of the examples doesn't actually matter:
167
+
168
+ ```ruby
169
+ # Insert a new item after the Trace middleware
170
+ stack.insert_after(Trace, SomeOtherMiddleware)
171
+
172
+ # Replace the lambda
173
+ stack.replace(1, SomeOtherMiddleware)
174
+
175
+ # Delete the lambda
176
+ stack.delete(1)
177
+ ```
178
+
179
+ ### Passing Additional Constructor Arguments
180
+
181
+ When using middleware in a stack, you can also pass in additional constructor
182
+ arguments. Given the following middleware:
183
+
184
+ ```ruby
185
+ class Echo
186
+ def initialize(app, message)
187
+ @app = app
188
+ @message = message
189
+ end
190
+
191
+ def call(env)
192
+ puts @message
193
+ @app.call(env)
194
+ end
195
+ end
196
+ ```
197
+
198
+ We can initialize `Echo` with a proper message as follows:
199
+
200
+ ```ruby
201
+ Middleware::Builder.new do
202
+ use Echo, "Hello, World!"
203
+ end
204
+ ```
205
+
206
+ Then when the stack is called, it will output "Hello, World!"
207
+
208
+ Note that you can also pass blocks in using the `use` method.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ # RSpec test task
6
+ RSpec::Core::RakeTask.new
7
+
8
+ # Make sure the default is to run RSpec
9
+ task :default => "spec"
data/lib/middleware.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "middleware/builder"
2
+ require "middleware/runner"
@@ -0,0 +1,147 @@
1
+ module Middleware
2
+ # This provides a DSL for building up a stack of middlewares.
3
+ #
4
+ # This code is based heavily off of `Rack::Builder` and
5
+ # `ActionDispatch::MiddlewareStack` in Rack and Rails, respectively.
6
+ #
7
+ # # Usage
8
+ #
9
+ # Building a middleware stack is very easy:
10
+ #
11
+ # app = Middleware::Builder.new do |b|
12
+ # b.use A
13
+ # b.use B
14
+ # end
15
+ #
16
+ # # Call the middleware
17
+ # app.call(7)
18
+ #
19
+ class Builder
20
+ # Initializes the builder. An optional block can be passed which
21
+ # will either yield the builder or be evaluated in the context of the instance.
22
+ #
23
+ # Example:
24
+ #
25
+ # Builder.new do |b|
26
+ # b.use A
27
+ # b.use B
28
+ # end
29
+ #
30
+ # Builder.new do
31
+ # use A
32
+ # use B
33
+ # end
34
+ #
35
+ # @param [Hash] opts Options hash
36
+ # @option opts [Class] :runner_class The class to wrap the middleware stack
37
+ # in which knows how to run them.
38
+ # @yield [] Evaluated in this instance which allows you to use methods
39
+ # like {#use} and such.
40
+ def initialize(opts=nil, &block)
41
+ opts ||= {}
42
+ @runner_class = opts[:runner_class] || Runner
43
+
44
+ if block_given?
45
+ if block.arity == 1
46
+ yield self
47
+ else
48
+ instance_eval(&block)
49
+ end
50
+ end
51
+ end
52
+
53
+ # Returns a mergeable version of the builder. If `use` is called with
54
+ # the return value of this method, then the stack will merge, instead
55
+ # of being treated as a separate single middleware.
56
+ def flatten
57
+ lambda do |env|
58
+ self.call(env)
59
+ end
60
+ end
61
+
62
+ # Adds a middleware class to the middleware stack. Any additional
63
+ # args and a block, if given, are saved and passed to the initializer
64
+ # of the middleware.
65
+ #
66
+ # @param [Class] middleware The middleware class
67
+ def use(middleware, *args, &block)
68
+ if middleware.kind_of?(Builder)
69
+ # Merge in the other builder's stack into our own
70
+ self.stack.concat(middleware.stack)
71
+ else
72
+ self.stack << [middleware, args, block]
73
+ end
74
+
75
+ self
76
+ end
77
+
78
+ # Inserts a middleware at the given index or directly before the
79
+ # given middleware object.
80
+ def insert(index, middleware, *args, &block)
81
+ index = self.index(index) unless index.is_a?(Integer)
82
+ raise "no such middleware to insert before: #{index.inspect}" unless index
83
+ stack.insert(index, [middleware, args, block])
84
+ end
85
+
86
+ alias_method :insert_before, :insert
87
+
88
+ # Inserts a middleware after the given index or middleware object.
89
+ def insert_after(index, middleware, *args, &block)
90
+ index = self.index(index) unless index.is_a?(Integer)
91
+ raise "no such middleware to insert after: #{index.inspect}" unless index
92
+ insert(index + 1, middleware, *args, &block)
93
+ end
94
+
95
+ # Replaces the given middleware object or index with the new
96
+ # middleware.
97
+ def replace(index, middleware, *args, &block)
98
+ if index.is_a?(Integer)
99
+ delete(index)
100
+ insert(index, middleware, *args, &block)
101
+ else
102
+ insert_before(index, middleware, *args, &block)
103
+ delete(index)
104
+ end
105
+ end
106
+
107
+ # Deletes the given middleware object or index
108
+ def delete(index)
109
+ index = self.index(index) unless index.is_a?(Integer)
110
+ stack.delete_at(index)
111
+ end
112
+
113
+ # Runs the builder stack with the given environment.
114
+ def call(env=nil)
115
+ to_app.call(env)
116
+ end
117
+
118
+ protected
119
+
120
+ # Returns the numeric index for the given middleware object.
121
+ #
122
+ # @param [Object] object The item to find the index for
123
+ # @return [Integer]
124
+ def index(object)
125
+ stack.each_with_index do |item, i|
126
+ return i if item[0] == object
127
+ end
128
+
129
+ nil
130
+ end
131
+
132
+ # Returns the current stack of middlewares. You probably won't
133
+ # need to use this directly, and it's recommended that you don't.
134
+ #
135
+ # @return [Array]
136
+ def stack
137
+ @stack ||= []
138
+ end
139
+
140
+ # Converts the builder stack to a runnable action sequence.
141
+ #
142
+ # @return [Object] A callable object
143
+ def to_app
144
+ @runner_class.new(stack.dup)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,69 @@
1
+ module Middleware
2
+ # This is a basic runner for middleware stacks. This runner does
3
+ # the default expected behavior of running the middleware stacks
4
+ # in order, then reversing the order.
5
+ class Runner
6
+ # A middleware which does nothing
7
+ EMPTY_MIDDLEWARE = lambda { |env| env }
8
+
9
+ # Build a new middleware runner with the given middleware
10
+ # stack.
11
+ #
12
+ # Note: This class usually doesn't need to be used directly.
13
+ # Instead, take a look at using the {Builder} class, which is
14
+ # a much friendlier way to build up a middleware stack.
15
+ #
16
+ # @param [Array] stack An array of the middleware to run.
17
+ def initialize(stack)
18
+ # We need to take the stack of middleware and initialize them
19
+ # all so they call the proper next middleware.
20
+ @kickoff = build_call_chain(stack)
21
+ end
22
+
23
+ # Run the middleware stack with the given state bag.
24
+ #
25
+ # @param [Object] env The state to pass into as the initial
26
+ # environment data. This is usual a hash of some sort.
27
+ def call(env)
28
+ # We just call the kickoff middleware, which is responsible
29
+ # for properly calling the next middleware, and so on and so
30
+ # forth.
31
+ @kickoff.call(env)
32
+ end
33
+
34
+ protected
35
+
36
+ # This takes a stack of middlewares and initializes them in a way
37
+ # that each middleware properly calls the next middleware.
38
+ def build_call_chain(stack)
39
+ # We need to instantiate the middleware stack in reverse
40
+ # order so that each middleware can have a reference to
41
+ # the next middleware it has to call. The final middleware
42
+ # is always the empty middleware, which does nothing but return.
43
+ stack.reverse.inject(EMPTY_MIDDLEWARE) do |next_middleware, current_middleware|
44
+ # Unpack the actual item
45
+ klass, args, block = current_middleware
46
+
47
+ # Default the arguments to an empty array. Otherwise in Ruby 1.8
48
+ # a `nil` args will actually pass `nil` into the class. Not what
49
+ # we want!
50
+ args ||= []
51
+
52
+ if klass.is_a?(Class)
53
+ # If the klass actually is a class, then instantiate it with
54
+ # the app and any other arguments given.
55
+ klass.new(next_middleware, *args, &block)
56
+ elsif klass.respond_to?(:call)
57
+ # Make it a lambda which calls the item then forwards up
58
+ # the chain.
59
+ lambda do |env|
60
+ klass.call(env)
61
+ next_middleware.call(env)
62
+ end
63
+ else
64
+ raise "Invalid middleware, doesn't respond to `call`: #{klass.inspect}"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Mitchell Hashimoto", "Arnaud Lemaire"]
5
+ gem.email = ["mitchell.hashimoto@gmail.com", "alemaire@ibsciss.com"]
6
+ gem.description = %q{Generalized implementation of the rack middleware abstraction for Ruby.}
7
+ gem.summary = %q{Generalized implementation of the rack middleware abstraction for Ruby (chain of responsibility design pattern).}
8
+ gem.homepage = "https://github.com/ibsciss/ruby-middleware"
9
+ gem.license = "MIT"
10
+
11
+ gem.add_development_dependency "rake", "~> 1.6"
12
+ gem.add_development_dependency "rspec-core", "~> 3.2"
13
+ gem.add_development_dependency "rspec-expectations", "~> 3.2"
14
+ gem.add_development_dependency "rspec-mocks", "~> 3.2"
15
+
16
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ gem.files = `git ls-files`.split("\n")
18
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ gem.name = "ibsciss-middleware"
20
+ gem.require_paths = ["lib"]
21
+ gem.version = '0.3.0'
22
+ end
@@ -0,0 +1,191 @@
1
+ require "middleware"
2
+
3
+ describe Middleware::Builder do
4
+ let(:data) { { :data => [] } }
5
+ let(:instance) { described_class.new }
6
+
7
+ # This returns a proc that can be used with the builder
8
+ # that simply appends data to an array in the env.
9
+ def appender_proc(data)
10
+ Proc.new { |env| env[:data] << data }
11
+ end
12
+
13
+ context "initialized with a block" do
14
+ context "without explicit receiver" do
15
+ it "instance evals the block" do
16
+ data = {}
17
+ proc = Proc.new { |env| env[:data] = true }
18
+
19
+ app = described_class.new do
20
+ use proc
21
+ end
22
+
23
+ app.call(data)
24
+
25
+ expect(data[:data]).to be_truthy
26
+ end
27
+ end
28
+
29
+ context "with explicit receiver" do
30
+ it "yields self to the block" do
31
+ data = {}
32
+ proc = Proc.new { |env| env[:data] = true }
33
+
34
+ app = described_class.new do |b|
35
+ b.use proc
36
+ end
37
+
38
+ app.call(data)
39
+
40
+ expect(data[:data]).to be_truthy
41
+ end
42
+ end
43
+ end
44
+
45
+ context "basic `use`" do
46
+ it "should add items to the stack and make them callable" do
47
+ data = {}
48
+ proc = Proc.new { |env| env[:data] = true }
49
+
50
+ instance.use proc
51
+ instance.call(data)
52
+
53
+ expect(data[:data]).to be_truthy
54
+ end
55
+
56
+ it "should be able to add multiple items" do
57
+ data = {}
58
+ proc1 = Proc.new { |env| env[:one] = true }
59
+ proc2 = Proc.new { |env| env[:two] = true }
60
+
61
+ instance.use proc1
62
+ instance.use proc2
63
+ instance.call(data)
64
+
65
+ expect(data[:one]).to be_truthy
66
+ expect(data[:two]).to be_truthy
67
+ end
68
+
69
+ it "should be able to add another builder" do
70
+ data = {}
71
+ proc1 = Proc.new { |env| env[:one] = true }
72
+
73
+ # Build the first builder
74
+ one = described_class.new
75
+ one.use proc1
76
+
77
+ # Add it to this builder
78
+ two = described_class.new
79
+ two.use one
80
+
81
+ # Call the 2nd and verify results
82
+ two.call(data)
83
+ expect(data[:one]).to be_truthy
84
+ end
85
+
86
+ it "should default the env to `nil` if not given" do
87
+ result = false
88
+ proc = Proc.new { |env| result = env.nil? }
89
+
90
+ instance.use proc
91
+ instance.call
92
+
93
+ expect(result).to be_truthy
94
+ end
95
+ end
96
+
97
+ context "inserting" do
98
+ it "can insert at an index" do
99
+ instance.use appender_proc(1)
100
+ instance.insert(0, appender_proc(2))
101
+ instance.call(data)
102
+
103
+ expect(data[:data]).to eq [2, 1]
104
+ end
105
+
106
+ it "can insert next to a previous object" do
107
+ proc2 = appender_proc(2)
108
+ instance.use appender_proc(1)
109
+ instance.use proc2
110
+ instance.insert(proc2, appender_proc(3))
111
+ instance.call(data)
112
+
113
+ expect(data[:data]).to eq [1, 3, 2]
114
+ end
115
+
116
+ it "can insert before" do
117
+ instance.use appender_proc(1)
118
+ instance.insert_before 0, appender_proc(2)
119
+ instance.call(data)
120
+
121
+ expect(data[:data]).to eq [2, 1]
122
+ end
123
+
124
+ it "raises an exception if attempting to insert before an invalid object" do
125
+ expect { instance.insert "object", appender_proc(1) }.
126
+ to raise_error(RuntimeError)
127
+ end
128
+
129
+ it "can insert after" do
130
+ instance.use appender_proc(1)
131
+ instance.use appender_proc(3)
132
+ instance.insert_after 0, appender_proc(2)
133
+ instance.call(data)
134
+
135
+ expect(data[:data]).to eq [1, 2, 3]
136
+ end
137
+
138
+ it "raises an exception if attempting to insert after an invalid object" do
139
+ expect { instance.insert_after "object", appender_proc(1) }.
140
+ to raise_error(RuntimeError)
141
+ end
142
+ end
143
+
144
+ context "replace" do
145
+ it "can replace an object" do
146
+ proc1 = appender_proc(1)
147
+ proc2 = appender_proc(2)
148
+
149
+ instance.use proc1
150
+ instance.replace proc1, proc2
151
+ instance.call(data)
152
+
153
+ expect(data[:data]).to eq [2]
154
+ end
155
+
156
+ it "can replace by index" do
157
+ proc1 = appender_proc(1)
158
+ proc2 = appender_proc(2)
159
+
160
+ instance.use proc1
161
+ instance.replace 0, proc2
162
+ instance.call(data)
163
+
164
+ expect(data[:data]).to eq [2]
165
+ end
166
+ end
167
+
168
+ context "deleting" do
169
+ it "can delete by object" do
170
+ proc1 = appender_proc(1)
171
+
172
+ instance.use proc1
173
+ instance.use appender_proc(2)
174
+ instance.delete proc1
175
+ instance.call(data)
176
+
177
+ expect(data[:data]).to eq [2]
178
+ end
179
+
180
+ it "can delete by index" do
181
+ proc1 = appender_proc(1)
182
+
183
+ instance.use proc1
184
+ instance.use appender_proc(2)
185
+ instance.delete 0
186
+ instance.call(data)
187
+
188
+ expect(data[:data]).to eq [2]
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,192 @@
1
+ require "middleware"
2
+
3
+ describe Middleware::Runner do
4
+ it "should work with an empty stack" do
5
+ instance = described_class.new([])
6
+ expect { instance.call({}) }.to_not raise_error
7
+ end
8
+
9
+ it "should call classes in the proper order" do
10
+ a = Class.new do
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ env[:result] << "A"
17
+ @app.call(env)
18
+ env[:result] << "A"
19
+ end
20
+ end
21
+
22
+ b = Class.new do
23
+ def initialize(app)
24
+ @app = app
25
+ end
26
+
27
+ def call(env)
28
+ env[:result] << "B"
29
+ @app.call(env)
30
+ env[:result] << "B"
31
+ end
32
+ end
33
+
34
+ env = { :result => [] }
35
+ instance = described_class.new([a, b])
36
+ instance.call(env)
37
+ expect(env[:result]).to eq ["A", "B", "B", "A"]
38
+ end
39
+
40
+ it "should call lambdas in the proper order" do
41
+ data = []
42
+ a = lambda { |env| data << "A" }
43
+ b = lambda { |env| data << "B" }
44
+
45
+ instance = described_class.new([a, b])
46
+ instance.call({})
47
+
48
+ expect(data).to eq ["A", "B"]
49
+ end
50
+
51
+ it "passes in arguments if given" do
52
+ a = Class.new do
53
+ def initialize(app, value)
54
+ @app = app
55
+ @value = value
56
+ end
57
+
58
+ def call(env)
59
+ env[:result] = @value
60
+ end
61
+ end
62
+
63
+ env = {}
64
+ instance = described_class.new([[a, 42]])
65
+ instance.call(env)
66
+
67
+ expect(env[:result]).to eq 42
68
+ end
69
+
70
+ it "passes in a block if given" do
71
+ a = Class.new do
72
+ def initialize(app, &block)
73
+ @block = block
74
+ end
75
+
76
+ def call(env)
77
+ env[:result] = @block.call
78
+ end
79
+ end
80
+
81
+ block = Proc.new { 42 }
82
+ env = {}
83
+ instance = described_class.new([[a, nil, block]])
84
+ instance.call(env)
85
+
86
+ expect(env[:result]).to eq 42
87
+ end
88
+
89
+ it "should raise an error if an invalid middleware is given" do
90
+ expect { described_class.new([27]) }.to raise_error(/Invalid middleware/)
91
+ end
92
+
93
+ it "should not call middlewares which aren't called" do
94
+ # A does not call B, so B should never execute
95
+ data = []
96
+ a = Class.new do
97
+ def initialize(app); @app = app; end
98
+
99
+ define_method :call do |env|
100
+ data << "a"
101
+ end
102
+ end
103
+
104
+ b = lambda { |env| data << "b" }
105
+
106
+ env = {}
107
+ instance = described_class.new([a, b])
108
+ instance.call(env)
109
+
110
+ expect(data).to eq ["a"]
111
+ end
112
+
113
+ describe "exceptions" do
114
+ it "should propagate the exception up the middleware chain" do
115
+ # This tests a few important properties:
116
+ # * Exceptions propagate multiple middlewares
117
+ # - C raises an exception, which raises through B to A.
118
+ # * Rescuing exceptions works
119
+ data = []
120
+ a = Class.new do
121
+ def initialize(app)
122
+ @app = app
123
+ end
124
+
125
+ define_method :call do |env|
126
+ data << "a"
127
+ begin
128
+ @app.call(env)
129
+ data << "never"
130
+ rescue Exception => e
131
+ data << "e"
132
+ raise
133
+ end
134
+ end
135
+ end
136
+
137
+ b = Class.new do
138
+ def initialize(app); @app = app; end
139
+
140
+ define_method :call do |env|
141
+ data << "b"
142
+ @app.call(env)
143
+ end
144
+ end
145
+
146
+ c = lambda { |env| raise "ERROR" }
147
+
148
+ env = {}
149
+ instance = described_class.new([a, b, c])
150
+ expect { instance.call(env) }.to raise_error
151
+
152
+ expect(data).to eq ["a", "b", "e"]
153
+ end
154
+
155
+ it "should stop propagation if rescued" do
156
+ # This test mainly tests that if there is a sequence A, B, C, and
157
+ # an exception is raised in C, that if B rescues this, then the chain
158
+ # continues fine backwards.
159
+ data = []
160
+ a = Class.new do
161
+ def initialize(app); @app = app; end
162
+
163
+ define_method :call do |env|
164
+ data << "in_a"
165
+ @app.call(env)
166
+ data << "out_a"
167
+ end
168
+ end
169
+
170
+ b = Class.new do
171
+ def initialize(app); @app = app; end
172
+
173
+ define_method :call do |env|
174
+ data << "in_b"
175
+ @app.call(env) rescue nil
176
+ data << "out_b"
177
+ end
178
+ end
179
+
180
+ c = lambda do |env|
181
+ data << "in_c"
182
+ raise "BAD"
183
+ end
184
+
185
+ env = {}
186
+ instance = described_class.new([a, b, c])
187
+ instance.call(env)
188
+
189
+ expect(data).to eq ["in_a", "in_b", "in_c", "out_b", "out_a"]
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,89 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, consider making
10
+ # a separate helper file that requires the additional dependencies and performs
11
+ # the additional setup, and require it from the spec files that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # rspec-expectations config goes here. You can use an alternate
19
+ # assertion/expectation library such as wrong or the stdlib/minitest
20
+ # assertions if you prefer.
21
+ config.expect_with :rspec do |expectations|
22
+ # This option will default to `true` in RSpec 4. It makes the `description`
23
+ # and `failure_message` of custom matchers include text for helper methods
24
+ # defined using `chain`, e.g.:
25
+ # be_bigger_than(2).and_smaller_than(4).description
26
+ # # => "be bigger than 2 and smaller than 4"
27
+ # ...rather than:
28
+ # # => "be bigger than 2"
29
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30
+ end
31
+
32
+ # rspec-mocks config goes here. You can use an alternate test double
33
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
34
+ config.mock_with :rspec do |mocks|
35
+ # Prevents you from mocking or stubbing a method that does not exist on
36
+ # a real object. This is generally recommended, and will default to
37
+ # `true` in RSpec 4.
38
+ mocks.verify_partial_doubles = true
39
+ end
40
+
41
+ # The settings below are suggested to provide a good initial experience
42
+ # with RSpec, but feel free to customize to your heart's content.
43
+ =begin
44
+ # These two settings work together to allow you to limit a spec run
45
+ # to individual examples or groups you care about by tagging them with
46
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
47
+ # get run.
48
+ config.filter_run :focus
49
+ config.run_all_when_everything_filtered = true
50
+
51
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
52
+ # For more details, see:
53
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
54
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
55
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
56
+ config.disable_monkey_patching!
57
+
58
+ # This setting enables warnings. It's recommended, but in some cases may
59
+ # be too noisy due to issues in dependencies.
60
+ config.warnings = true
61
+
62
+ # Many RSpec users commonly either run the entire suite or an individual
63
+ # file, and it's useful to allow more verbose output when running an
64
+ # individual spec file.
65
+ if config.files_to_run.one?
66
+ # Use the documentation formatter for detailed output,
67
+ # unless a formatter has already been configured
68
+ # (e.g. via a command-line flag).
69
+ config.default_formatter = 'doc'
70
+ end
71
+
72
+ # Print the 10 slowest examples and example groups at the
73
+ # end of the spec run, to help surface which specs are running
74
+ # particularly slow.
75
+ config.profile_examples = 10
76
+
77
+ # Run specs in random order to surface order dependencies. If you find an
78
+ # order dependency and want to debug it, you can fix the order by providing
79
+ # the seed, which is printed after each run.
80
+ # --seed 1234
81
+ config.order = :random
82
+
83
+ # Seed global randomization in this process using the `--seed` CLI option.
84
+ # Setting this allows you to use `--seed` to deterministically reproduce
85
+ # test failures related to randomization by passing the same `--seed` value
86
+ # as the one that triggered the failure.
87
+ Kernel.srand config.seed
88
+ =end
89
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ibsciss-middleware
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Mitchell Hashimoto
8
+ - Arnaud Lemaire
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-04-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.6'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.6'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec-core
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '3.2'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec-expectations
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.2'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.2'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec-mocks
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.2'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.2'
70
+ description: Generalized implementation of the rack middleware abstraction for Ruby.
71
+ email:
72
+ - mitchell.hashimoto@gmail.com
73
+ - alemaire@ibsciss.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".rspec"
80
+ - ".travis.yml"
81
+ - CHANGELOG.md
82
+ - Gemfile
83
+ - LICENSE
84
+ - README.md
85
+ - Rakefile
86
+ - lib/middleware.rb
87
+ - lib/middleware/builder.rb
88
+ - lib/middleware/runner.rb
89
+ - middleware.gemspec
90
+ - spec/middleware/builder_spec.rb
91
+ - spec/middleware/runner_spec.rb
92
+ - spec/spec_helper.rb
93
+ homepage: https://github.com/ibsciss/ruby-middleware
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.4.3
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Generalized implementation of the rack middleware abstraction for Ruby (chain
117
+ of responsibility design pattern).
118
+ test_files:
119
+ - spec/middleware/builder_spec.rb
120
+ - spec/middleware/runner_spec.rb
121
+ - spec/spec_helper.rb