ibsciss-middleware 0.3.0

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
+ 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