web_pipe 0.8.0 → 0.9.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +4 -2
  4. data/README.md +60 -310
  5. data/Rakefile +5 -3
  6. data/bin/console +4 -3
  7. data/docs/_config.yml +1 -0
  8. data/docs/building_a_rack_application.md +25 -0
  9. data/docs/composing_applications.md +43 -0
  10. data/docs/connection_struct/configuring_the_connection_struct.md +25 -0
  11. data/docs/connection_struct/halting_the_pipe.md +71 -0
  12. data/docs/connection_struct/sharing_data_downstream.md +46 -0
  13. data/docs/connection_struct.md +77 -0
  14. data/docs/design_model.md +44 -0
  15. data/docs/dsl_free_usage.md +26 -0
  16. data/docs/extensions/container.md +41 -0
  17. data/docs/extensions/cookies.md +47 -0
  18. data/docs/extensions/dry_schema.md +51 -0
  19. data/docs/extensions/dry_view.md +113 -0
  20. data/docs/extensions/flash.md +41 -0
  21. data/docs/extensions/params.md +117 -0
  22. data/docs/extensions/redirect.md +25 -0
  23. data/docs/extensions/router_params.md +40 -0
  24. data/docs/extensions/session.md +39 -0
  25. data/docs/extensions/url.md +11 -0
  26. data/docs/extensions.md +13 -0
  27. data/docs/introduction.md +73 -0
  28. data/docs/plugging_operations/composing_operations.md +36 -0
  29. data/docs/plugging_operations/injecting_operations.md +32 -0
  30. data/docs/plugging_operations/resolving_operations.md +71 -0
  31. data/docs/plugging_operations.md +21 -0
  32. data/docs/plugs/config.md +18 -0
  33. data/docs/plugs/content_type.md +16 -0
  34. data/docs/plugs.md +13 -0
  35. data/docs/recipes/dry_rb_integration.md +18 -0
  36. data/docs/recipes/hanami_router_integration.md +25 -0
  37. data/docs/recipes/using_all_restful_methods.md +25 -0
  38. data/docs/using_rack_middlewares/composing_middlewares.md +26 -0
  39. data/docs/using_rack_middlewares/injecting_middlewares.md +47 -0
  40. data/docs/using_rack_middlewares.md +22 -0
  41. data/lib/web_pipe/app.rb +4 -2
  42. data/lib/web_pipe/conn.rb +5 -3
  43. data/lib/web_pipe/conn_support/builder.rb +2 -0
  44. data/lib/web_pipe/conn_support/composition.rb +9 -7
  45. data/lib/web_pipe/conn_support/errors.rb +3 -1
  46. data/lib/web_pipe/conn_support/headers.rb +12 -10
  47. data/lib/web_pipe/conn_support/types.rb +11 -9
  48. data/lib/web_pipe/dsl/builder.rb +5 -3
  49. data/lib/web_pipe/dsl/class_context.rb +5 -3
  50. data/lib/web_pipe/dsl/dsl_context.rb +7 -5
  51. data/lib/web_pipe/dsl/instance_methods.rb +7 -5
  52. data/lib/web_pipe/extensions/container/container.rb +2 -0
  53. data/lib/web_pipe/extensions/cookies/cookies.rb +4 -3
  54. data/lib/web_pipe/extensions/dry_schema/dry_schema.rb +2 -0
  55. data/lib/web_pipe/extensions/dry_schema/plugs/sanitize_params.rb +4 -2
  56. data/lib/web_pipe/extensions/dry_view/dry_view.rb +13 -9
  57. data/lib/web_pipe/extensions/flash/flash.rb +7 -7
  58. data/lib/web_pipe/extensions/params/params/transf.rb +3 -1
  59. data/lib/web_pipe/extensions/params/params.rb +7 -5
  60. data/lib/web_pipe/extensions/redirect/redirect.rb +8 -6
  61. data/lib/web_pipe/extensions/router_params/router_params.rb +4 -2
  62. data/lib/web_pipe/extensions/session/session.rb +6 -4
  63. data/lib/web_pipe/extensions/url/url.rb +3 -1
  64. data/lib/web_pipe/plug.rb +7 -5
  65. data/lib/web_pipe/plugs.rb +7 -1
  66. data/lib/web_pipe/rack_support/app_with_middlewares.rb +3 -1
  67. data/lib/web_pipe/rack_support/middleware.rb +2 -0
  68. data/lib/web_pipe/rack_support/middleware_specification.rb +5 -3
  69. data/lib/web_pipe/types.rb +3 -1
  70. data/lib/web_pipe/version.rb +3 -1
  71. data/lib/web_pipe.rb +5 -3
  72. data/web_pipe.gemspec +34 -34
  73. metadata +82 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09151400e351c909383b2cc9308910d209c6207fc978a55c59eb02997d29b3c7'
4
- data.tar.gz: c2fff9105cc8cecc5aad8e2a7232f1a902bb3ec5d30e3a6b4e130db6128ba57b
3
+ metadata.gz: fc647eb09c8b7eb1d3bcfb910cc55e840285c6b1ab9bd9c69ea9b630f7a116fd
4
+ data.tar.gz: 6b667b8e71f1fac8d7c46a8160cbff273c70042bf1cc9b06da60c82a83fea5b2
5
5
  SHA512:
6
- metadata.gz: da96a1e7cdd4278db92e14692248046dbcbeb04c1592ae01dd326b3958f42873f0564080ba70379690ec390bf37160885bbe20e591b6281b97617bac0cc402e5
7
- data.tar.gz: e429d891a90cd7c90d9ccf11baca675c508906891c07f833a937c90d3ba751a7f85d73b03823be037e80a77cc20ba729c75115e61f6891829774614813045804
6
+ metadata.gz: f52d9d4c5b381d13de8f27ae03e8fc8bfb2cfe7e020d507f934341c9823f7c2cb2d94ebddc500d9337a59c2437c8ae3899e1b7ef942ed5e1cfc073591f8ac56f
7
+ data.tar.gz: 0be15319332d738f2b8ff07d7cb9d73583d00bf907e8787a2b8ab0fadc88d235f441b08e546bf162a6fb6fca8953ab68c34077cf300c0d6c1bb26aa37fe8f964
data/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [0.9.0] - 2019-08-31
8
+ ### Added
9
+ - Comprehensive documentation.
10
+ [[#35]](https://github.com/waiting-for-dev/web_pipe/pull/35)
11
+
7
12
  ## [0.8.0] - 2019-08-30
8
13
  ### Added
9
14
  - **BREAKING**. Rename `Rack` module to `RackSupport`.
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
6
 
5
7
  # Specify your gem's dependencies in web_pipe.gemspec
6
8
  gemspec
data/README.md CHANGED
@@ -3,339 +3,89 @@
3
3
 
4
4
  # WebPipe
5
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
- ```
6
+ `web_pipe` is a modular rack application builder through a pipe of
7
+ operations on an immutable struct.
8
+
9
+ In order to use in conjunction with [dry-rb](https://dry-rb.org/)
10
+ ecosystem, see also
11
+ [`dry-web-web_pipe`](https://github.com/waiting-for-dev/dry-web-web_pipe).
12
+
13
+ 1. [Introduction](/docs/introduction.md)
14
+ 1. [Design model](/docs/design_model.md)
15
+ 1. [Building a rack application](/docs/building_a_rack_application.md)
16
+ 1. [Plugging operations](/docs/plugging_operations.md)
17
+ 1. [Resolving operations](/docs/plugging_operations/resolving_operations.md)
18
+ 1. [Injecting operations](/docs/plugging_operations/injecting_operations.md)
19
+ 1. [Composing operations](/docs/plugging_operations/composing_operations.md)
20
+ 1. [Using rack middlewares](/docs/using_rack_middlewares.md)
21
+ 1. [Injecting middlewares](/docs/using_rack_middlewares/injecting_middlewares.md)
22
+ 1. [Composing middlewares](/docs/using_rack_middlewares/composing_middlewares.md)
23
+ 1. [Composing applications](/docs/composing_applications.md)
24
+ 1. [Connection struct](/docs/connection_struct.md)
25
+ 1. [Sharing data downstream](/docs/connection_struct/sharing_data_downstream.md)
26
+ 1. [Halting the pipe](/docs/connection_struct/halting_the_pipe.md)
27
+ 1. [Configuring the connection struct](/docs/connection_struct/configuring_the_connection_struct.md)
28
+ 1. [DSL free usage](/docs/dsl_free_usage.md)
29
+ 1. [Plugs](/docs/plugs.md)
30
+ 1. [Config](/docs/plugs/config.md)
31
+ 1. [ContentType](/docs/plugs/content_type.md)
32
+ 1. [Extensions](/docs/extensions.md)
33
+ 1. [Container](/docs/extensions/container.md)
34
+ 1. [Cookies](/docs/extensions/cookies.md)
35
+ 1. [Flash](/docs/extensions/flash.md)
36
+ 1. [Dry Schema](/docs/extensions/dry_schema.md)
37
+ 1. [Dry View](/docs/extensions/dry_view.md)
38
+ 1. [Params](/docs/extensions/params.md)
39
+ 1. [Redirect](/docs/extensions/redirect.md)
40
+ 1. [Router params](/docs/extensions/router_params.md)
41
+ 1. [Session](/docs/extensions/session.md)
42
+ 1. [URL](/docs/extensions/url.md)
43
+ 1. Recipes
44
+ 1. [dry-rb integration](/docs/recipes/dry_rb_integration.md)
45
+ 1. [hanami-router integration](/docs/recipes/hanami_router_integration.md)
46
+ 1. [Using all RESTful methods](/docs/recipes/using_all_restful_methods.mb)
73
47
 
74
48
  ```ruby
75
49
  # config.ru
76
- require "web_pipe"
50
+ require 'web_pipe'
77
51
 
78
- UsersRepo = {
79
- 1 => { name: 'Alice', admin: true },
80
- 2 => { name: 'Joe', admin: false }
81
- }
52
+ WebPipe.load_extensions(:params)
82
53
 
83
- class GreetingAdminApp
54
+ class HelloApp
84
55
  include WebPipe
85
-
86
- plug :set_content_type
87
- plug :fetch_user
56
+
57
+ AUTHORIZED_USERS = %w[Alice Joe]
58
+
59
+ plug :html
88
60
  plug :authorize
89
61
  plug :greet
90
-
62
+
91
63
  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
- add(:user, user)
104
- else
105
- conn.
106
- set_status(404).
107
- set_response_body('<h1>Not foud</h1>').
108
- halt
109
- end
64
+
65
+ def html(conn)
66
+ conn.add_response_header('Content-Type', 'text/html')
110
67
  end
111
-
68
+
112
69
  def authorize(conn)
113
- if conn.fetch(:user)[:admin]
114
- conn
70
+ user = conn.params['user']
71
+ if AUTHORIZED_USERS.include?(user)
72
+ conn.add(:user, user)
115
73
  else
116
74
  conn.
117
75
  set_status(401).
118
- set_response_body('<h1>Unauthorized</h1>').
76
+ set_response_body('<h1>Not authorized</h1>').
119
77
  halt
120
78
  end
121
79
  end
122
80
 
123
81
  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::Ongoing` subclass instance. When one of your operations
148
- calls `#halt` on it, a `WebPipe::Conn::Halted` is returned and the pipe
149
- is halted. This one or the 'ongoing' 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
- `#add(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 as anything that responds to
184
- `#call`, like a `Proc`, or also like a block:
185
-
186
- ```ruby
187
- class App
188
- include WebPipe
189
-
190
- plug :hello, ->(conn) { conn }
191
- plug(:hello2) { |conn| conn }
192
- end
193
- ```
194
-
195
- The representation of a `WebPipe` as a Proc is itself an operation
196
- accepting a `Conn` and returning a `Conn`: the composition of all its
197
- plugs. Therefore, it can be plugged to any other `WebPipe`:
198
-
199
- ```ruby
200
- class HtmlApp
201
- include WebPipe
202
-
203
- plug :html
204
-
205
- private
206
-
207
- def html(conn)
208
- conn.add_response_header('Content-Type', 'text/html')
82
+ conn.set_response_body("<h1>Hello #{conn.fetch(:user)}</h1>")
209
83
  end
210
84
  end
211
85
 
212
- class App
213
- include WebPipe
214
-
215
- plug :html, HtmlApp.new
216
- plug :body
217
-
218
- private
219
-
220
- def body(conn)
221
- conn.set_response_body('Hello, world!')
222
- end
223
- end
224
- ```
225
-
226
- #### Container
227
-
228
- When a `String` or a `Symbol` is given, it can be used as the key to
229
- resolve an operation from a container. A container is just anything
230
- responding to `#[]`.
231
-
232
- The container to be used is configured when you include `WebPipe`:
233
-
234
- ```ruby
235
- class App
236
- Container = Hash[
237
- 'plugs.hello' => ->(conn) { conn }
238
- ]
239
-
240
- include WebPipe.(container: Container)
241
-
242
- plug :hello, 'plugs.hello'
243
- end
86
+ run HelloApp.new
244
87
  ```
245
88
 
246
- ### Operations injection
247
-
248
- Operations can be injected when the application is initialized,
249
- overriding those configured through `plug`:
250
-
251
- ```ruby
252
- class App
253
- include WebPipe
254
-
255
- plug :hello, ->(conn) { conn.set_response_body('Hello') }
256
- end
257
-
258
- run App.new(plugs: {
259
- hello: ->(conn) { conn.set_response_body('Injected') }
260
- }
261
- )
262
- ```
263
-
264
- In the previous example, resulting response body would be `Injected`.
265
-
266
- ### Rack middlewares
267
-
268
- Rack middlewares can be added to the generated application through
269
- `use`. They will be executed in declaration order before the pipe of
270
- plugs:
271
-
272
- ```ruby
273
- class App
274
- include WebPipe
275
-
276
- use :middleware_1, Middleware1
277
- use :middleware_1, Middleware2, option_1: value_1
278
-
279
- plug :hello, ->(conn) { conn }
280
- end
281
- ```
282
-
283
- It is also possible to compose all the middlewares from another pipe
284
- class. Extending from previous example:
285
-
286
- ```ruby
287
- class App2
288
- include WebPipe
289
-
290
- use :app, App.new # it will also use Middleware1 and Middleware2
291
-
292
- plug :hello, ->(conn) { conn }
293
- end
294
- ```
295
-
296
- Middlewares can also be injected on initialization:
297
-
298
- ```ruby
299
- App.new(middlewares: {
300
- middleware_1: [AnotherMiddleware, options]
301
- })
302
- ```
303
-
304
- ### Standalone usage
305
-
306
- If you prefer, you can use the application builder without the
307
- DSL. For that, you just have to initialize a `WebPipe::App` with an
308
- array of all the operations to be performed:
309
-
310
- ```ruby
311
- require 'web_pipe/app`
312
-
313
- op_1 = ->(conn) { conn.set_status(200) }
314
- op_2 = ->(conn) { conn.set_response_body('Hello') }
315
-
316
- WebPipe::App.new([op_1, op_2])
317
- ```
318
-
319
- ## Plugs
320
-
321
- `web_pipe` ships with a series of common operations you can take
322
- advantage in order to build your application:
323
-
324
- - [content_type](lib/web_pipe/plugs/content_type.rb): Sets
325
- `Content-Type` response header.
326
-
327
- ## Extensions
328
-
329
- By default, `web_pipe` behavior is the very minimal you need to build
330
- a web application. However, you can extend it with the following
331
- extensions (click on each name for details on the usage):
332
-
333
- - [container](lib/web_pipe/plugs/container.rb): Allows
334
- configuring a container to resolve dependencies.
335
- - [dry-view](lib/web_pipe/extensions/dry_view/dry_view.rb):
336
- Integration with [`dry-view`](https://dry-rb.org/gems/dry-view/)
337
- rendering system.
338
-
339
89
  ## Current status
340
90
 
341
91
  `web_pipe` is in active development. The very basic features to build
@@ -350,4 +100,4 @@ https://github.com/waiting-for-dev/web_pipe.
350
100
 
351
101
  ## Release Policy
352
102
 
353
- `web_pipe` follows the principles of [semantic versioning](http://semver.org/).
103
+ `web_pipe` follows the principles of [semantic versioning](http://semver.org/).
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "web_pipe"
4
+ require 'bundler/setup'
5
+ require 'web_pipe'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "web_pipe"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "pry"
14
+ require 'pry'
14
15
  Pry.start
data/docs/_config.yml ADDED
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-slate
@@ -0,0 +1,25 @@
1
+ # Building a rack application
2
+
3
+ In order to build a rack application with `web_pipe` you have to include
4
+ `WebPipe` module in a class:
5
+
6
+ ```ruby
7
+ require 'web_pipe'
8
+
9
+ class MyApp
10
+ include WebPipe
11
+
12
+ # ...
13
+ end
14
+ ```
15
+
16
+ Then, you can plug the operations and add the rack middlewares you need.
17
+
18
+ The instance of that class will be the rack application:
19
+
20
+ ```ruby
21
+ # config.ru
22
+ require 'my_app'
23
+
24
+ run MyApp.new
25
+ ```
@@ -0,0 +1,43 @@
1
+ # Composing applications
2
+
3
+ Previously, we have seen how to [compose plugged
4
+ operations](/docs/plugging_operations/composing_operations.md) and how to [compose
5
+ rack middlewares](/docs/using_rack_middlewares/composing_middlewares.md). The logical
6
+ next step is thinking about composing `web_pipe` applications, which is exactly
7
+ the same as composing both operations and middlewares at the same time.
8
+
9
+ The DSL method `compose` does exactly that:
10
+
11
+ ```ruby
12
+ class HtmlApp
13
+ include WebPipe
14
+
15
+ use :session, Rack::Session::Cookie, key: 'my_app.session', secret: 'long'
16
+ use :csrf, Rack::Csrf, raise: true
17
+
18
+ plug :content_type
19
+ plug :default_status
20
+
21
+ private
22
+
23
+ def content_type(conn)
24
+ conn.add_response_header('Content-Type' => 'text/html')
25
+ end
26
+
27
+ def default_status(conn)
28
+ conn.set_status(404)
29
+ end
30
+ end
31
+
32
+ class MyApp
33
+ include WebPipe
34
+
35
+ compose :web, HtmlApp.new
36
+ # It does exactly the same than:
37
+ # use :web, HtmlApp.new
38
+ # plug :web, HtmlApp.new
39
+
40
+ # use ...
41
+ # plug ...
42
+ end
43
+ ```
@@ -0,0 +1,25 @@
1
+ # Configuring the connection struct
2
+
3
+ [Extensions](/docs/extensions.md) add extra behaviour to the connection struct.
4
+ Sometimes they need some user provided value to work properly or they may allow
5
+ some tweak depending on user needs.
6
+
7
+ For this reason, you can add configuration data to a `WebPipe::Conn` instance
8
+ so that extensions can fetch it. This shared place where extensions look for
9
+ what they need is `#config` attribute, which is very similar to `#bag` except
10
+ for its more private intention.
11
+
12
+ In order to interact with `#config`, you can use the method `#add_config(key,
13
+ value)` or [`Config` plug](/docs/plugs/config.md).
14
+
15
+ ```ruby
16
+ class MyApp
17
+ include WebPipe
18
+
19
+ plug(:config) do |conn|
20
+ conn.add_config(
21
+ foo: :bar
22
+ )
23
+ end
24
+ end
25
+ ```
@@ -0,0 +1,71 @@
1
+ # Halting the pipe
2
+
3
+ Each operation in a pipe takes a single `WebPipe::Conn` instance as argument
4
+ and returns another (or same) instance of it. In this way, a series of
5
+ operations on the connection struct is propagated until final response is sent
6
+ to the client.
7
+
8
+ More often than not, you may need to conditionally stop the propagation of a
9
+ pipe at a given operation. A lot of times the requirement to do something like
10
+ that will be authorization policies. For example, you could fetch the user
11
+ requesting a resource. In the case she was granted to perform required action
12
+ you would go on. However, if she wasn't you would like to halt the connection
13
+ and respond with a 4xx http status code.
14
+
15
+ In order to stop the pipe, you simply have to call `#halt` on the connection
16
+ struct.
17
+
18
+ At implementation level, we must admit that we've not been 100% accurate until
19
+ now. We said that the first operation in the pipe recerived a `WebPipe::Conn`
20
+ instance. That's true. However, it is more precise saying that it gets a
21
+ `WebPipe::Conn::Ongoing` instance (`WebPipe::Conn::Ongoing` being a subclass of
22
+ `WebPipe::Conn`).
23
+
24
+ As long as an operation responds with a `WebPipe::Conn::Ongoing`
25
+ instance, the propagation will go on. However, when an operation
26
+ returns a `WebPipe::Conn::Halted` instance (another subclass of
27
+ `WebPipe::Conn`) then any operation downstream will be ignored.
28
+ Calling `#halt` simply copies all attributes to a `WebPipe::Conn::Halted`
29
+ instance and returns it.
30
+
31
+ This made-up example checks if the user in the request has an admin role. If
32
+ she has, it returns solicited resource. Otherwise she is unauthorized and never
33
+ gets the resource.
34
+
35
+ ```ruby
36
+ WebPipe.load_extensions(:params)
37
+
38
+ class ShowTaskApp
39
+ include WebPipe
40
+
41
+ plug :fetch_user
42
+ plug :authorize
43
+ plug :render_task
44
+
45
+ private
46
+
47
+ def fetch_user(conn)
48
+ conn.add(
49
+ :user, UserRepo.find(conn.params[:user_id])
50
+ )
51
+ end
52
+
53
+ def authorize(conn)
54
+ if conn.fetch(:user).admin?
55
+ conn
56
+ else
57
+ conn.
58
+ set_status(401).
59
+ halt
60
+ end
61
+ end
62
+
63
+ def render_task(conn)
64
+ conn.set_response_body(
65
+ TaskRepo.find(conn.params[:id]).to_json
66
+ )
67
+ end
68
+ end
69
+
70
+ run ShowTaskApp.new
71
+ ```
@@ -0,0 +1,46 @@
1
+ # Sharing data downstream
2
+
3
+ Usually you'll find the need to prepare some data in one operation with the
4
+ intention for it to be consumed by another downstream operation. The connection
5
+ struct has a `#bag` attribute which is useful for this purpose.
6
+
7
+ `WebPipe::Conn#bag` is a `Hash` with `Symbol` keys where values can
8
+ be anything you need to share. To help with the process we have following methods:
9
+
10
+ - `#add(key, value)`: Assigns a value to a key.
11
+ - `#fetch(key)`, `#fetch(key, default)`: Retrieves value associated
12
+ to given key. If it is not found, `default` is returned when
13
+ provided.
14
+
15
+ This is a simple example of a web application which reads a `name`
16
+ parameter and normalizes it before using in the response body.
17
+
18
+ ```ruby
19
+ # config.ru
20
+ require 'web_pipe'
21
+
22
+ WebPipe.load_extensions(:params)
23
+
24
+ class NormalizeNameApp
25
+ include WebPipe
26
+
27
+ plug :normalize_name
28
+ plug :respond
29
+
30
+ private
31
+
32
+ def normalize_name(conn)
33
+ conn.add(
34
+ :name, conn.params[:name].downcase.capitalize
35
+ )
36
+ end
37
+
38
+ def respond(conn)
39
+ conn.set_response_body(
40
+ conn.fetch(:name)
41
+ )
42
+ end
43
+ end
44
+
45
+ run NormalizeNameApp.new
46
+ ```