web_pipe 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +10 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +91 -0
- data/README.md +279 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/dry/monads/result/extensions/either.rb +42 -0
- data/lib/web_pipe.rb +16 -0
- data/lib/web_pipe/app.rb +106 -0
- data/lib/web_pipe/conn.rb +397 -0
- data/lib/web_pipe/conn_support/builder.rb +36 -0
- data/lib/web_pipe/conn_support/errors.rb +16 -0
- data/lib/web_pipe/conn_support/headers.rb +97 -0
- data/lib/web_pipe/conn_support/types.rb +50 -0
- data/lib/web_pipe/dsl/builder.rb +38 -0
- data/lib/web_pipe/dsl/class_context.rb +62 -0
- data/lib/web_pipe/dsl/dsl_context.rb +53 -0
- data/lib/web_pipe/dsl/instance_methods.rb +60 -0
- data/lib/web_pipe/plug.rb +103 -0
- data/lib/web_pipe/rack/app_with_middlewares.rb +61 -0
- data/lib/web_pipe/rack/middleware.rb +33 -0
- data/lib/web_pipe/types.rb +31 -0
- data/lib/web_pipe/version.rb +3 -0
- data/web_pipe.gemspec +51 -0
- metadata +244 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 617c24131c1091c3ec7fb214f8f90af0f6fd199d2684ee8b71e08ac373f9795a
|
4
|
+
data.tar.gz: 68a73cfff54e49580ea8426265579ece63e3abbe78dd93b84862cee86017ba19
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aee8034e0eff973a91df4bed368f459cd841a3ea7de448b584bfef7f7d995f519ea3201bfd3f16290fb6692bf5fe8ab9bfe5d37d443eeb75581127adc3de3cee
|
7
|
+
data.tar.gz: 452773b224c0affce8aeed40251f25835cfea045208d4ba92aeb076d2a3b888df6171cfb7d5323c8d16b5ab8eb830d649d8add14fc17b1c24339957266d7de65
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
|
+
|
7
|
+
## [0.0.1] - 2019-05-07
|
8
|
+
### Added
|
9
|
+
- Initial release.
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
web_pipe (0.1.0)
|
5
|
+
dry-initializer (~> 3.0)
|
6
|
+
dry-monads (~> 1.2)
|
7
|
+
dry-struct (~> 1.0)
|
8
|
+
dry-types (~> 1.0)
|
9
|
+
rack (~> 2.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
byebug (11.0.0)
|
15
|
+
coderay (1.1.2)
|
16
|
+
concurrent-ruby (1.1.5)
|
17
|
+
diff-lcs (1.3)
|
18
|
+
dry-configurable (0.8.2)
|
19
|
+
concurrent-ruby (~> 1.0)
|
20
|
+
dry-core (~> 0.4, >= 0.4.7)
|
21
|
+
dry-container (0.7.0)
|
22
|
+
concurrent-ruby (~> 1.0)
|
23
|
+
dry-configurable (~> 0.1, >= 0.1.3)
|
24
|
+
dry-core (0.4.7)
|
25
|
+
concurrent-ruby (~> 1.0)
|
26
|
+
dry-equalizer (0.2.2)
|
27
|
+
dry-inflector (0.1.2)
|
28
|
+
dry-initializer (3.0.1)
|
29
|
+
dry-logic (1.0.0)
|
30
|
+
concurrent-ruby (~> 1.0)
|
31
|
+
dry-core (~> 0.2)
|
32
|
+
dry-equalizer (~> 0.2)
|
33
|
+
dry-monads (1.2.0)
|
34
|
+
concurrent-ruby (~> 1.0)
|
35
|
+
dry-core (~> 0.4, >= 0.4.4)
|
36
|
+
dry-equalizer
|
37
|
+
dry-struct (1.0.0)
|
38
|
+
dry-core (~> 0.4, >= 0.4.3)
|
39
|
+
dry-equalizer (~> 0.2)
|
40
|
+
dry-types (~> 1.0)
|
41
|
+
ice_nine (~> 0.11)
|
42
|
+
dry-types (1.0.0)
|
43
|
+
concurrent-ruby (~> 1.0)
|
44
|
+
dry-container (~> 0.3)
|
45
|
+
dry-core (~> 0.4, >= 0.4.4)
|
46
|
+
dry-equalizer (~> 0.2, >= 0.2.2)
|
47
|
+
dry-inflector (~> 0.1, >= 0.1.2)
|
48
|
+
dry-logic (~> 1.0)
|
49
|
+
ice_nine (0.11.2)
|
50
|
+
method_source (0.9.2)
|
51
|
+
pry (0.12.2)
|
52
|
+
coderay (~> 1.1.0)
|
53
|
+
method_source (~> 0.9.0)
|
54
|
+
pry-byebug (3.7.0)
|
55
|
+
byebug (~> 11.0)
|
56
|
+
pry (~> 0.10)
|
57
|
+
rack (2.0.6)
|
58
|
+
rack-test (1.1.0)
|
59
|
+
rack (>= 1.0, < 3)
|
60
|
+
rake (10.5.0)
|
61
|
+
redcarpet (3.4.0)
|
62
|
+
rspec (3.8.0)
|
63
|
+
rspec-core (~> 3.8.0)
|
64
|
+
rspec-expectations (~> 3.8.0)
|
65
|
+
rspec-mocks (~> 3.8.0)
|
66
|
+
rspec-core (3.8.0)
|
67
|
+
rspec-support (~> 3.8.0)
|
68
|
+
rspec-expectations (3.8.2)
|
69
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
70
|
+
rspec-support (~> 3.8.0)
|
71
|
+
rspec-mocks (3.8.0)
|
72
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
73
|
+
rspec-support (~> 3.8.0)
|
74
|
+
rspec-support (3.8.0)
|
75
|
+
yard (0.9.19)
|
76
|
+
|
77
|
+
PLATFORMS
|
78
|
+
ruby
|
79
|
+
|
80
|
+
DEPENDENCIES
|
81
|
+
bundler (~> 1.17)
|
82
|
+
pry-byebug
|
83
|
+
rack-test (~> 1.1)
|
84
|
+
rake (~> 10.0)
|
85
|
+
redcarpet (~> 3.4)
|
86
|
+
rspec (~> 3.0)
|
87
|
+
web_pipe!
|
88
|
+
yard (~> 0.9)
|
89
|
+
|
90
|
+
BUNDLED WITH
|
91
|
+
1.17.2
|
data/README.md
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
[](https://badge.fury.io/rb/web_pipe)
|
2
|
+
[](https://travis-ci.com/waiting-for-dev/web_pipe)
|
3
|
+
|
4
|
+
# WebPipe
|
5
|
+
|
6
|
+
`web_pipe` is a rack application builder through a pipe of operations
|
7
|
+
applied to an immutable struct.
|
8
|
+
|
9
|
+
You can also think of it as a web controllers builder (the C in `MVC`)
|
10
|
+
totally declouped from the web routing (which you can still do with
|
11
|
+
something like [`hanami-router`](https://github.com/hanami/router),
|
12
|
+
[`http_router`](https://github.com/joshbuddy/http_router) or plain
|
13
|
+
[rack's `map`
|
14
|
+
method](https://www.rubydoc.info/github/rack/rack/Rack/Builder#map-instance_method)).
|
15
|
+
|
16
|
+
If you are familiar with rack you know that it models a two-way pipe,
|
17
|
+
where each middleware in the stack has the chance to modify the
|
18
|
+
request before it arrives to the actual application, and the response
|
19
|
+
once it comes back from the application:
|
20
|
+
|
21
|
+
```
|
22
|
+
|
23
|
+
---------------------> request ----------------------->
|
24
|
+
|
25
|
+
Middleware 1 Middleware 2 Application
|
26
|
+
|
27
|
+
<--------------------- response <-----------------------
|
28
|
+
|
29
|
+
|
30
|
+
```
|
31
|
+
|
32
|
+
`web_pipe` follows a simpler but equally powerful model of a one-way
|
33
|
+
pipe and abstracts it on top of rack. A struct that contains all the
|
34
|
+
data from a web request is piped trough a stack of operations which
|
35
|
+
take it as argument and return a new instance of it where response
|
36
|
+
data can be added at any step.
|
37
|
+
|
38
|
+
```
|
39
|
+
|
40
|
+
Operation 1 Operation 2 Operation 3
|
41
|
+
|
42
|
+
--------------------- request/response ---------------->
|
43
|
+
|
44
|
+
```
|
45
|
+
|
46
|
+
In addition to that, any operation in the stack has the power to stop
|
47
|
+
the propagation of the pipe, leaving any downstream operation
|
48
|
+
unexecuted. This is mainly useful to unauthorize a request while being
|
49
|
+
sure that nothing else will be done to the response.
|
50
|
+
|
51
|
+
As you may know, this is the same model used by Elixir's
|
52
|
+
[`plug`](https://hexdocs.pm/plug/readme.html), from which `web_pipe`
|
53
|
+
takes inspiration.
|
54
|
+
|
55
|
+
This library has been designed to work frictionless along the
|
56
|
+
[`dry-rb`]( https://dry-rb.org/) ruby ecosystem and it uses some of
|
57
|
+
its libraries internally.
|
58
|
+
|
59
|
+
## Usage
|
60
|
+
|
61
|
+
This is a sample `config.ru` for a contrived application built with
|
62
|
+
`web_pipe`. It simply fetches a user from an `id` request
|
63
|
+
parameter. If the user is not found, it returns a not found
|
64
|
+
response. If it is found, it will unauthorize when it is a non `admin`
|
65
|
+
user or greet it otherwise:
|
66
|
+
|
67
|
+
```
|
68
|
+
rackup --port 4000
|
69
|
+
# http://localhost:4000?id=1 => Hello Alice
|
70
|
+
# http://localhost:4000?id=2 => Unauthorized
|
71
|
+
# http://localhost:4000?id=3 => Not found
|
72
|
+
```
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# config.ru
|
76
|
+
require "web_pipe"
|
77
|
+
|
78
|
+
UsersRepo = {
|
79
|
+
1 => { name: 'Alice', admin: true },
|
80
|
+
2 => { name: 'Joe', admin: false }
|
81
|
+
}
|
82
|
+
|
83
|
+
class GreetingAdminApp
|
84
|
+
include WebPipe
|
85
|
+
|
86
|
+
plug :set_content_type
|
87
|
+
plug :fetch_user
|
88
|
+
plug :authorize
|
89
|
+
plug :greet
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def set_content_type(conn)
|
94
|
+
conn.add_response_header(
|
95
|
+
'Content-Type', 'text/html'
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def fetch_user(conn)
|
100
|
+
user = UsersRepo[conn.params['id'].to_i]
|
101
|
+
if user
|
102
|
+
conn.
|
103
|
+
put(:user, user)
|
104
|
+
else
|
105
|
+
conn.
|
106
|
+
set_status(404).
|
107
|
+
set_response_body('<h1>Not foud</h1>').
|
108
|
+
taint
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def authorize(conn)
|
113
|
+
if conn.fetch(:user)[:admin]
|
114
|
+
conn
|
115
|
+
else
|
116
|
+
conn.
|
117
|
+
set_status(401).
|
118
|
+
set_response_body('<h1>Unauthorized</h1>').
|
119
|
+
taint
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def greet(conn)
|
124
|
+
conn.
|
125
|
+
set_response_body("<h1>Hello #{conn.fetch(:user)[:name]}</h1>")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
run GreetingAdminApp.new
|
130
|
+
```
|
131
|
+
|
132
|
+
As you see, steps required are:
|
133
|
+
|
134
|
+
- Include `WebPipe` in a class.
|
135
|
+
- Specify the stack of operations with `plug`.
|
136
|
+
- Implement those operations.
|
137
|
+
- Initialize the class to obtain resulting rack application.
|
138
|
+
|
139
|
+
`WebPipe::Conn` is a struct of request and response date, seasoned
|
140
|
+
with methods that act on its data. These methods are designed to
|
141
|
+
return a new instance of the struct each time, so they encourage
|
142
|
+
immutability and make method chaining possible.
|
143
|
+
|
144
|
+
Each operation in the pipe must accept a single argument of a
|
145
|
+
`WebPipe::Conn` instance and it must also return an instance of it.
|
146
|
+
In fact, what the first operation in the pipe takes is a
|
147
|
+
`WebPipe::Conn::Clean` subclass instance. When one of your operations
|
148
|
+
calls `#taint` on it, a `WebPipe::Conn::Dirty` is returned and the pipe
|
149
|
+
is halted. This one or the 'clean' instance that reaches the end of
|
150
|
+
the pipe will be in command of the web response.
|
151
|
+
|
152
|
+
Operations have the chance to prepare data to be consumed by
|
153
|
+
downstream operations. Data can be added to the struct through
|
154
|
+
`#put(key, value)`, while it can be consumed with `#fetch(key)`.
|
155
|
+
|
156
|
+
Attributes and methods in `WebPipe::Conn` are [fully
|
157
|
+
documented](https://www.rubydoc.info/github/waiting-for-dev/web_pipe/master/WebPipe/Conn).
|
158
|
+
|
159
|
+
### Specifying operations
|
160
|
+
|
161
|
+
There are several ways you can `plug` operations to the pipe:
|
162
|
+
|
163
|
+
#### Instance methods
|
164
|
+
|
165
|
+
Operations can be just methods defined in the pipe class. This is what
|
166
|
+
you saw in the previous example:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class App
|
170
|
+
include WebPipe
|
171
|
+
|
172
|
+
plug :hello
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def hello(conn)
|
177
|
+
# ...
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
181
|
+
#### Proc (or anything responding to `#call`)
|
182
|
+
|
183
|
+
Operations can also be defined inline, through the `with:` keyword, as
|
184
|
+
anything that responds to `#call`, like a `Proc`:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
class App
|
188
|
+
include WebPipe
|
189
|
+
|
190
|
+
plug :hello, with: ->(conn) { conn }
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
#### Container
|
195
|
+
|
196
|
+
When `with:` is a `String` or a `Symbol`, it can be used as the key to
|
197
|
+
resolve an operation from a container. A container is just anything
|
198
|
+
responding to `#[]`.
|
199
|
+
|
200
|
+
The container to be used is configured when you include `WebPipe`:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
class App
|
204
|
+
Container = Hash[
|
205
|
+
'plugs.hello' => ->(conn) { conn }
|
206
|
+
]
|
207
|
+
|
208
|
+
include WebPipe.(container: Container)
|
209
|
+
|
210
|
+
plug :hello, with: 'plugs.hello'
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
### Operations injection
|
215
|
+
|
216
|
+
Operations can be injected when the application is initialized,
|
217
|
+
overriding those configured through `plug`:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
class App
|
221
|
+
include WebPipe
|
222
|
+
|
223
|
+
plug :hello, with: ->(conn) { conn.set_response_body('Hello') }
|
224
|
+
end
|
225
|
+
|
226
|
+
run App.new(
|
227
|
+
hello: ->(conn) { conn.set_response_body('Injected') }
|
228
|
+
)
|
229
|
+
```
|
230
|
+
|
231
|
+
In the previous example, resulting response body would be `Injected`.
|
232
|
+
|
233
|
+
### Rack middlewares
|
234
|
+
|
235
|
+
Rack middlewares can be added to the generated application through
|
236
|
+
`use`. They will be executed in declaration order before the pipe of
|
237
|
+
plugs:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
class App
|
241
|
+
include WebPipe
|
242
|
+
|
243
|
+
use Middleware1
|
244
|
+
use Middleware2, option_1: value_1
|
245
|
+
|
246
|
+
plug :hello, with: ->(conn) { conn }
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
250
|
+
### Standalone usage
|
251
|
+
|
252
|
+
If you prefer, you can use the application builder without the
|
253
|
+
DSL. For that, you just have to initialize a `WebPipe::App` with an
|
254
|
+
array of all the operations to be performed:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
require 'web_pipe/app`
|
258
|
+
|
259
|
+
op_1 = ->(conn) { conn.set_status(200) }
|
260
|
+
op_2 = ->(conn) { conn.set_response_body('Hello') }
|
261
|
+
|
262
|
+
WebPipe::App.new([op_1, op_2])
|
263
|
+
```
|
264
|
+
|
265
|
+
## Current status
|
266
|
+
|
267
|
+
`web_pipe` is in active development. The very basic features to build
|
268
|
+
a rack application are all available. However, very necessary
|
269
|
+
conveniences to build a production application, for example a session
|
270
|
+
mechanism, are still missing.
|
271
|
+
|
272
|
+
## Contributing
|
273
|
+
|
274
|
+
Bug reports and pull requests are welcome on GitHub at
|
275
|
+
https://github.com/waiting-for-dev/web_pipe.
|
276
|
+
|
277
|
+
## Release Policy
|
278
|
+
|
279
|
+
`web_pipe` follows the principles of [semantic versioning](http://semver.org/).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "web_pipe"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "pry"
|
14
|
+
Pry.start
|