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.
@@ -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
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,10 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4
5
+ - 2.5
6
+ - 2.6
7
+ before_install:
8
+ - gem update --system --no-doc
9
+ script:
10
+ - bundle exec rspec
@@ -0,0 +1,8 @@
1
+ --title 'web_pipe'
2
+ --embed-mixins
3
+ --output doc
4
+ --readme README.md
5
+ --files CHANGELOG.md
6
+ --markup markdown
7
+ --markup-provider=redcarpet
8
+ lib/**/*.rb
@@ -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
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in web_pipe.gemspec
6
+ gemspec
@@ -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
@@ -0,0 +1,279 @@
1
+ [![Gem Version](https://badge.fury.io/rb/web_pipe.svg)](https://badge.fury.io/rb/web_pipe)
2
+ [![Build Status](https://travis-ci.com/waiting-for-dev/web_pipe.svg?branch=master)](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/).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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