web_pipe 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ ```