web_pipe 0.15.1 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +10 -0
- data/README.md +13 -10
- data/docs/building_a_rack_application.md +1 -1
- data/docs/composing_applications.md +4 -4
- data/docs/connection_struct/configuring_the_connection_struct.md +4 -4
- data/docs/connection_struct/halting_the_pipe.md +17 -19
- data/docs/connection_struct/sharing_data_downstream.md +9 -8
- data/docs/connection_struct.md +22 -19
- data/docs/design_model.md +10 -9
- data/docs/dsl_free_usage.md +85 -14
- data/docs/extensions/container.md +9 -10
- data/docs/extensions/cookies.md +4 -2
- data/docs/extensions/dry_schema.md +5 -4
- data/docs/extensions/flash.md +9 -11
- data/docs/extensions/hanami_view.md +10 -14
- data/docs/extensions/params.md +6 -4
- data/docs/extensions/rails.md +28 -34
- data/docs/extensions/redirect.md +5 -4
- data/docs/extensions/router_params.md +5 -5
- data/docs/extensions/session.md +4 -4
- data/docs/extensions/url.md +6 -6
- data/docs/extensions.md +5 -6
- data/docs/introduction.md +7 -7
- data/docs/plugging_operations/composing_operations.md +3 -3
- data/docs/plugging_operations/injecting_operations.md +4 -4
- data/docs/plugging_operations/inspecting_operations.md +1 -2
- data/docs/plugging_operations/resolving_operations.md +3 -3
- data/docs/plugging_operations.md +3 -3
- data/docs/plugs/config.md +1 -1
- data/docs/plugs/content_type.md +2 -1
- data/docs/plugs.md +6 -7
- data/docs/recipes/hanami_2_and_dry_rb_integration.md +12 -0
- data/docs/recipes/hanami_router_integration.md +3 -1
- data/docs/recipes/using_all_restful_methods.md +6 -5
- data/docs/using_rack_middlewares/composing_middlewares.md +2 -3
- data/docs/using_rack_middlewares/injecting_middlewares.md +6 -6
- data/docs/using_rack_middlewares/inspecting_middlewares.md +7 -6
- data/docs/using_rack_middlewares.md +6 -6
- data/lib/web_pipe/app.rb +22 -25
- data/lib/web_pipe/conn.rb +0 -1
- data/lib/web_pipe/conn_support/builder.rb +0 -7
- data/lib/web_pipe/conn_support/composition.rb +3 -26
- data/lib/web_pipe/conn_support/errors.rb +5 -5
- data/lib/web_pipe/conn_support/headers.rb +1 -50
- data/lib/web_pipe/conn_support/types.rb +3 -3
- data/lib/web_pipe/dsl/builder.rb +10 -19
- data/lib/web_pipe/dsl/class_context.rb +15 -40
- data/lib/web_pipe/dsl/instance_context.rb +53 -0
- data/lib/web_pipe/extensions/container/container.rb +2 -15
- data/lib/web_pipe/extensions/cookies/cookies.rb +2 -31
- data/lib/web_pipe/extensions/dry_schema/dry_schema.rb +2 -56
- data/lib/web_pipe/extensions/flash/flash.rb +2 -32
- data/lib/web_pipe/extensions/hanami_view/hanami_view.rb +2 -93
- data/lib/web_pipe/extensions/not_found/not_found.rb +2 -40
- data/lib/web_pipe/extensions/params/params.rb +2 -63
- data/lib/web_pipe/extensions/rails/rails.rb +2 -119
- data/lib/web_pipe/extensions/redirect/redirect.rb +2 -20
- data/lib/web_pipe/extensions/router_params/router_params.rb +1 -39
- data/lib/web_pipe/extensions/session/session.rb +2 -25
- data/lib/web_pipe/extensions/url/url.rb +2 -5
- data/lib/web_pipe/pipe.rb +229 -0
- data/lib/web_pipe/plug.rb +30 -75
- data/lib/web_pipe/plugs/config.rb +0 -2
- data/lib/web_pipe/plugs/content_type.rb +0 -2
- data/lib/web_pipe/rack_support/app_with_middlewares.rb +3 -26
- data/lib/web_pipe/rack_support/middleware.rb +2 -2
- data/lib/web_pipe/rack_support/middleware_specification.rb +17 -55
- data/lib/web_pipe/types.rb +1 -3
- data/lib/web_pipe/version.rb +1 -1
- data/lib/web_pipe.rb +77 -21
- data/web_pipe.gemspec +1 -0
- metadata +7 -6
- data/docs/recipes/dry_rb_integration.md +0 -17
- data/lib/web_pipe/dsl/dsl_context.rb +0 -85
- data/lib/web_pipe/dsl/instance_methods.rb +0 -114
@@ -1,46 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# :nodoc:
|
4
4
|
module WebPipe
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# This extension helps to build a not-found response in a single method
|
8
|
-
# invocation. The {#not_found} method will:
|
9
|
-
#
|
10
|
-
# - Set 404 as response status.
|
11
|
-
# - Set 'Not found' as the response body, or instead run a step configured in
|
12
|
-
# a `:not_found_body_step` config key.
|
13
|
-
# - Halt the connection struct.
|
14
|
-
#
|
15
|
-
# @example
|
16
|
-
# require 'web_pipe'
|
17
|
-
# require 'web_pipe/plugs/config'
|
18
|
-
#
|
19
|
-
# WebPipe.load_extensions(:params, :not_found)
|
20
|
-
#
|
21
|
-
# class ShowItem
|
22
|
-
# include 'web_pipe'
|
23
|
-
#
|
24
|
-
# plug :config, WebPipe::Plugs::Config.(
|
25
|
-
# not_found_body_step: ->(conn) { conn.set_response_body('Nothing') }
|
26
|
-
# )
|
27
|
-
#
|
28
|
-
# plug :fetch_item do |conn|
|
29
|
-
# conn.add(:item, Item[params['id']])
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# plug :check_item do |conn|
|
33
|
-
# if conn.fetch(:item)
|
34
|
-
# conn
|
35
|
-
# else
|
36
|
-
# conn.not_found
|
37
|
-
# end
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# plug :render do |conn|
|
41
|
-
# conn.set_response_body(conn.fetch(:item).name)
|
42
|
-
# end
|
43
|
-
# end
|
5
|
+
# See the docs for the extension linked from the README.
|
44
6
|
module NotFound
|
45
7
|
# @api private
|
46
8
|
RESPONSE_BODY_STEP_CONFIG_KEY = :not_found_body_step
|
@@ -3,70 +3,9 @@
|
|
3
3
|
require 'web_pipe/types'
|
4
4
|
require 'web_pipe/extensions/params/params/transf'
|
5
5
|
|
6
|
-
|
6
|
+
# :nodoc:
|
7
7
|
module WebPipe
|
8
|
-
#
|
9
|
-
# transformations to the request parameters.
|
10
|
-
#
|
11
|
-
# When no transformations are given, {#params} just returns request
|
12
|
-
# parameters (both GET and POST) as a hash:
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
# # http://www.example.com?foo=bar
|
16
|
-
# conn.params #=> { 'foo' => 'bar' }
|
17
|
-
#
|
18
|
-
# Further processing can be specified thanks to `dry-transformer` gem (you
|
19
|
-
# need to add it yourself to the Gemfile). All hash transformations
|
20
|
-
# in `dry-transformer` are available:
|
21
|
-
#
|
22
|
-
# @example
|
23
|
-
# # http://www.example.com?foo=bar
|
24
|
-
# conn.params([:deep_symbolize_keys]) #=> { foo: 'bar' }
|
25
|
-
#
|
26
|
-
# Extra needed arguments can be provided as an array:
|
27
|
-
#
|
28
|
-
# @example
|
29
|
-
# # http://www.example.com?foo=bar&zoo=zoo
|
30
|
-
# conn.params([:deep_symbolize_keys, [:reject_keys, [:zoo]]) #=> { foo: 'bar' }
|
31
|
-
#
|
32
|
-
# Instead of injecting transformations at the moment `#params` is
|
33
|
-
# called, you can configure them to be automatically used.
|
34
|
-
#
|
35
|
-
# @example
|
36
|
-
# # http://www.example.com?foo=bar
|
37
|
-
# conn.
|
38
|
-
# add_config(:param_transformations, [:deep_symbolize_keys]).
|
39
|
-
# params #=> { foo: 'bar' }
|
40
|
-
#
|
41
|
-
# You can register your own transformation functions:
|
42
|
-
#
|
43
|
-
# @example
|
44
|
-
# # http://www.example.com?foo=bar
|
45
|
-
# fake = ->(_params) { { fake: :params } }
|
46
|
-
# WebPipe::Params::Transf.register(:fake, fake)
|
47
|
-
# conn.params([:fake]) #=> { fake: :params }
|
48
|
-
#
|
49
|
-
# Your own transformation functions can depend on the {Conn}
|
50
|
-
# instance at the moment of execution. For that, just place it as the
|
51
|
-
# last argument of the function and it will be curried automatically:
|
52
|
-
#
|
53
|
-
# @example
|
54
|
-
# # http://www.example.com?foo=bar
|
55
|
-
# add_name = ->(params, conn) { params.merge(name: conn.fetch(:name)) }
|
56
|
-
# WebPipe::Params::Transf.register(:add_name, add_name)
|
57
|
-
# conn.
|
58
|
-
# add(:name, 'Joe').
|
59
|
-
# params([:deep_symbolize_keys, :add_name]) #=> { foo: 'bar', name: 'Joe' }
|
60
|
-
#
|
61
|
-
# Inline transformations can also be provided:
|
62
|
-
#
|
63
|
-
# @example
|
64
|
-
# # http://www.example.com?foo=bar
|
65
|
-
# fake = ->(_params) { { fake: :params } }
|
66
|
-
# conn.
|
67
|
-
# params(fake) #=> { fake: :params }
|
68
|
-
#
|
69
|
-
# @see https://github.com/dry-rb/dry-transformer
|
8
|
+
# See the docs for the extension linked from the README.
|
70
9
|
module Params
|
71
10
|
# Key where configured transformations are set
|
72
11
|
PARAM_TRANSFORMATION_KEY = :param_transformations
|
@@ -2,126 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'web_pipe/conn'
|
4
4
|
|
5
|
-
|
5
|
+
# :nodoc:
|
6
6
|
module WebPipe
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# The first two things to keep in mind in order to integrate with Rails is
|
10
|
-
# that {WebPipe} instances are Rack applications and that rails router can
|
11
|
-
# perfectly dispatch to a rack application. For example:
|
12
|
-
#
|
13
|
-
# ```ruby
|
14
|
-
# # config/routes.rb
|
15
|
-
# get '/my_route', to: MyRoute.new
|
16
|
-
#
|
17
|
-
# # app/controllers/my_route.rb
|
18
|
-
# class MyRoute
|
19
|
-
# include WebPipe
|
20
|
-
#
|
21
|
-
# plug :set_response_body
|
22
|
-
#
|
23
|
-
# private
|
24
|
-
#
|
25
|
-
# def set_response_body(conn)
|
26
|
-
# conn.set_response_body('Hello, World!')
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
# ```
|
30
|
-
#
|
31
|
-
# In order to do something like the previous example you don't need to enable
|
32
|
-
# this extension. Notice that rails took care of dispatching the request to
|
33
|
-
# our {WebPipe} rack application, which was then responsible for generating the
|
34
|
-
# response. In this case, it used a simple call to
|
35
|
-
# {WebPipe::Conn#set_response_body}.
|
36
|
-
#
|
37
|
-
# It's quite possible that you don't need more than that in terms of rails
|
38
|
-
# integration. Of course, surely you want something more elaborate to generate
|
39
|
-
# responses. For that, you can use the view or template system you like. One
|
40
|
-
# option that will play specially well here is `hanami-view`. Furthermore, we
|
41
|
-
# have a tailored `hanami_view` extension.
|
42
|
-
#
|
43
|
-
# You need to use `:rails` extension if:
|
44
|
-
#
|
45
|
-
# - You want to use `action_view` as rendering system.
|
46
|
-
# - You want to use rails url helpers from your {WebPipe} application.
|
47
|
-
# - You want to use controller helpers from your {WebPipe} application.
|
48
|
-
#
|
49
|
-
# Rails responsibilities for controlling the request/response cycle and the
|
50
|
-
# rendering process are a little bit tangled. For this reason, even if you
|
51
|
-
# want to use {WebPipe} applications instead of Rails controller actions you
|
52
|
-
# still have to use the typical top `ApplicationController` in order to define
|
53
|
-
# some behaviour for the view layer:
|
54
|
-
#
|
55
|
-
# - Which layout is applied to the template.
|
56
|
-
# - Which helpers will become available to the templates.
|
57
|
-
#
|
58
|
-
# By default, the controller in use is `ActionController::Base`, which means
|
59
|
-
# that no layout is applied and only built-in helpers (for example,
|
60
|
-
# `number_as_currency`) are available. You can change it via the
|
61
|
-
# `:rails_controller` configuration option.
|
62
|
-
#
|
63
|
-
# The main method that this extension adds to {WebPipe::Conn} is `#render`,
|
64
|
-
# which just delegates to the Rails implementation as you'd do in a typical
|
65
|
-
# rails controller. Remember that you can provide template instance variables
|
66
|
-
# through the keyword `:assigns`.
|
67
|
-
#
|
68
|
-
# ```ruby
|
69
|
-
# # config/routes.rb
|
70
|
-
# get '/articles', to: ArticlesIndex.new
|
71
|
-
#
|
72
|
-
# # app/controllers/application_controller.rb
|
73
|
-
# class ApplicationController < ActionController::Base
|
74
|
-
# # By default uses the layout in `layouts/application`
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# # app/controllers/articles_index.rb
|
78
|
-
# require 'web_pipe/plugs/config'
|
79
|
-
#
|
80
|
-
#
|
81
|
-
# WebPipe.load_extensions(:rails) # You can put it in an initializer
|
82
|
-
#
|
83
|
-
# class ArticlesIndex
|
84
|
-
# include WebPipe
|
85
|
-
#
|
86
|
-
# plug :config, WebPipe::Plugs::Config.(
|
87
|
-
# rails_controller: ApplicationController
|
88
|
-
# )
|
89
|
-
#
|
90
|
-
# def render(conn)
|
91
|
-
# conn.render(
|
92
|
-
# template: 'articles/index',
|
93
|
-
# assigns: { articles: Article.all }
|
94
|
-
# )
|
95
|
-
# end
|
96
|
-
# end
|
97
|
-
# ```
|
98
|
-
#
|
99
|
-
# Notice that we used the keyword `template:` instead of taking advantage of
|
100
|
-
# automatic template lookup. We did that way so that we don't have to create
|
101
|
-
# also an `ArticlesController`, but it's up to you. In the case of having an
|
102
|
-
# `ArticlesController` we could just do `conn.render(:index, assigns: {
|
103
|
-
# articles: Article.all })`.
|
104
|
-
#
|
105
|
-
# Besides, this extension provides with two other methods:
|
106
|
-
#
|
107
|
-
# - `url_helpers` returns Rails router url helpers.
|
108
|
-
# - `helpers` returns the associated controller helpers.
|
109
|
-
#
|
110
|
-
# In all the examples we have supposed that we are putting {WebPipe}
|
111
|
-
# applications within `app/controllers/` directory. However, remember you can
|
112
|
-
# put them wherever you like as long as you respect rails `autoload_paths`.
|
113
|
-
#
|
114
|
-
# Here you have a link to a very simple and contrived example of a rails
|
115
|
-
# application integrating `web_pipe`:
|
116
|
-
#
|
117
|
-
# https://github.com/waiting-for-dev/rails-web_pipe
|
118
|
-
#
|
119
|
-
# @see https://guides.rubyonrails.org/routing.html#routing-to-rack-applications
|
120
|
-
# @see https://api.rubyonrails.org/v6.0.1/classes/ActionController/Renderer.html
|
121
|
-
# @see https://api.rubyonrails.org/v6.0.1/classes/ActionController/Renderer.html
|
122
|
-
# @see https://api.rubyonrails.org/v6.0.1/classes/ActionView/Helpers/UrlHelper.html
|
123
|
-
# @see https://api.rubyonrails.org/classes/ActionController/Helpers.html
|
124
|
-
# @see https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoload-paths
|
7
|
+
# See the docs for the extension linked from the README.
|
125
8
|
module Rails
|
126
9
|
def render(*args)
|
127
10
|
set_response_body(
|
@@ -2,27 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'web_pipe/types'
|
4
4
|
|
5
|
-
|
5
|
+
# :nodoc:
|
6
6
|
module WebPipe
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# This extensions adds a {#redirect} method to {Conn} which helps
|
10
|
-
# setting the `Location` header and the status code needed to
|
11
|
-
# instruct the browser to perform a redirect. By default, `302`
|
12
|
-
# status code is used.
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
# require 'web_pipe'
|
16
|
-
#
|
17
|
-
# WebPipe.load_extensions(:redirect)
|
18
|
-
#
|
19
|
-
# class MyApp
|
20
|
-
# include WebPipe
|
21
|
-
#
|
22
|
-
# plug(:redirect) do |conn|
|
23
|
-
# conn.redirect('/', 301)
|
24
|
-
# end
|
25
|
-
# end
|
7
|
+
# See the docs for the extension linked from the README.
|
26
8
|
module Redirect
|
27
9
|
# Location header
|
28
10
|
LOCATION_HEADER = 'Location'
|
@@ -6,45 +6,7 @@ require 'web_pipe/types'
|
|
6
6
|
WebPipe.load_extensions(:params)
|
7
7
|
|
8
8
|
module WebPipe
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# This extension gives an opportunity for rack routers to modify
|
12
|
-
# {Conn#params} hash. This is useful so that they can provide *route
|
13
|
-
# parameters*, which are typically rendered as variables in routes
|
14
|
-
# definitions (e.g.: `/user/:id/edit`).
|
15
|
-
#
|
16
|
-
# It adds a `:router_params` transformation that, when used, will
|
17
|
-
# merged env's `router.params` in {Conn#params} hash. Choosing this
|
18
|
-
# name automatically integrates with `hanami-router`.
|
19
|
-
#
|
20
|
-
# When using this extension, `:params` extension is automatically enabled.
|
21
|
-
#
|
22
|
-
# @example
|
23
|
-
# require 'web_pipe'
|
24
|
-
#
|
25
|
-
# WebPipe.load_extensions(:router_params)
|
26
|
-
#
|
27
|
-
# class MyApp
|
28
|
-
# include WebPipe
|
29
|
-
#
|
30
|
-
# plug :config
|
31
|
-
# plug :get_params
|
32
|
-
#
|
33
|
-
# private
|
34
|
-
#
|
35
|
-
# def config(conn)
|
36
|
-
# conn.add_config(:param_transformation, [:router_params])
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# def get_params(conn)
|
40
|
-
# # http://example.com/users/1/edit
|
41
|
-
# conn.params #=> { id: 1 }
|
42
|
-
# conn
|
43
|
-
# end
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# @see WebPipe::Params
|
47
|
-
# @see https://github.com/hanami/router#string-matching-with-variables
|
9
|
+
# See the docs for the extension linked from the README.
|
48
10
|
module RouterParams
|
49
11
|
ROUTER_PARAM_KEY = 'router.params'
|
50
12
|
|
@@ -4,32 +4,9 @@ require 'web_pipe/conn'
|
|
4
4
|
require 'web_pipe/types'
|
5
5
|
require 'rack'
|
6
6
|
|
7
|
-
|
7
|
+
# :nodoc:
|
8
8
|
module WebPipe
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# This extension provides with helper methods to retrieve rack
|
12
|
-
# session and work with it while still being able to chain {Conn}
|
13
|
-
# method calls.
|
14
|
-
#
|
15
|
-
# It requires one of `Rack::Session` middlewares to be present.
|
16
|
-
#
|
17
|
-
# @example
|
18
|
-
# require 'web_pipe'
|
19
|
-
# require 'rack/session'
|
20
|
-
#
|
21
|
-
# WebPipe.load_extensions(:session)
|
22
|
-
#
|
23
|
-
# class MyApp
|
24
|
-
# include WebPipe
|
25
|
-
#
|
26
|
-
# use Rack::Session::Cookie, secret: 'top_secret'
|
27
|
-
#
|
28
|
-
# plug :add_session, ->(conn) { conn.add_session('foo', 'bar') }
|
29
|
-
# plug :fetch_session, ->(conn) { conn.add(:foo, conn.fetch_session('foo')) }
|
30
|
-
# plug :delete_session, ->(conn) { conn.delete_session('foo') }
|
31
|
-
# plug :clear_session, ->(conn) { conn.clear_session }
|
32
|
-
# end
|
9
|
+
# See the docs for the extension linked from the README.
|
33
10
|
module Session
|
34
11
|
# Type for session keys.
|
35
12
|
SESSION_KEY = Types::Strict::String
|
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# :nodoc:
|
4
4
|
module WebPipe
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# This methods are in fact redundant with the information already
|
8
|
-
# present in {Conn} struct but, of course, they are very useful.
|
5
|
+
# See the docs for the extension linked from the README.
|
9
6
|
module Url
|
10
7
|
# Base part of the URL.
|
11
8
|
#
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'web_pipe/conn_support/composition'
|
4
|
+
require 'web_pipe/app'
|
5
|
+
require 'web_pipe/plug'
|
6
|
+
require 'web_pipe/rack_support/middleware_specification'
|
7
|
+
require 'web_pipe/rack_support/app_with_middlewares'
|
8
|
+
require 'web_pipe/types'
|
9
|
+
|
10
|
+
module WebPipe
|
11
|
+
# Composable rack application builder.
|
12
|
+
#
|
13
|
+
# An instance of this class helps build rack applications that can compose.
|
14
|
+
# Besides the DSL, which only adds a convenience layer, this is the higher
|
15
|
+
# abstraction on the library.
|
16
|
+
#
|
17
|
+
# Applications are built by plugging functions that take and return a
|
18
|
+
# {WebPipe::Conn} instance. That's an immutable struct that contains all the
|
19
|
+
# request information alongside methods to build the response. See {#plug} for
|
20
|
+
# details.
|
21
|
+
#
|
22
|
+
# Middlewares can also be added to the resulting application thanks to {#use}.
|
23
|
+
#
|
24
|
+
# Be aware that instances of this class are immutable, so methods return new
|
25
|
+
# objects every time.
|
26
|
+
#
|
27
|
+
# The instance itself is the final rack application.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# # config.ru
|
31
|
+
# require 'web_pipe/pipe'
|
32
|
+
#
|
33
|
+
# app = WebPipe::Pipe.new
|
34
|
+
# .use(:runtime, Rack::Runtime)
|
35
|
+
# .plug(:content_type) do |conn|
|
36
|
+
# conn.add_response_header('Content-Type', 'text/plain')
|
37
|
+
# end
|
38
|
+
# .plug(:render) do |conn|
|
39
|
+
# conn.set_response_body('Hello, World!')
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# run app
|
43
|
+
class Pipe
|
44
|
+
# Container that resolves nothing
|
45
|
+
EMPTY_CONTAINER = {}.freeze
|
46
|
+
|
47
|
+
# @!attribute [r] container
|
48
|
+
# Container from where resolve operations. See {#plug}.
|
49
|
+
attr_reader :container
|
50
|
+
|
51
|
+
# @!attribute [r] context
|
52
|
+
# Object from where resolve operations. See {#plug}.
|
53
|
+
attr_reader :context
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
EMPTY_PLUGS = [].freeze
|
57
|
+
|
58
|
+
# @api private
|
59
|
+
EMPTY_MIDDLEWARE_SPECIFICATIONS = [].freeze
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
Container = Types.Interface(:[])
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
attr_reader :plugs
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
attr_reader :middleware_specifications
|
69
|
+
|
70
|
+
# @param container [#to_h] Container from where resolve plug's operations
|
71
|
+
# (see {#plug}).
|
72
|
+
# @param context [Any] Object from where resolve plug's operations (see
|
73
|
+
# {#plug})
|
74
|
+
def initialize(
|
75
|
+
container: EMPTY_CONTAINER,
|
76
|
+
context: nil,
|
77
|
+
plugs: EMPTY_PLUGS,
|
78
|
+
middleware_specifications: EMPTY_MIDDLEWARE_SPECIFICATIONS
|
79
|
+
)
|
80
|
+
@plugs = plugs
|
81
|
+
@middleware_specifications = middleware_specifications
|
82
|
+
@container = Container[container]
|
83
|
+
@context = context
|
84
|
+
end
|
85
|
+
|
86
|
+
# Names and adds a plug operation to the application.
|
87
|
+
#
|
88
|
+
# The operation can be provided in several ways:
|
89
|
+
#
|
90
|
+
# - Through the `spec` parameter as:
|
91
|
+
# - Anything responding to `#call` (like a {Proc}).
|
92
|
+
# - As a string or symbol key for something registered in {#container}.
|
93
|
+
# - Anything responding to `#to_proc` (like another {WebPipe::Pipe}
|
94
|
+
# instance or an instance of a class including {WebPipe}).
|
95
|
+
# - As `nil` (default), meaning that the operation is a method in
|
96
|
+
# {#context} matching the `name` parameter.
|
97
|
+
# - Through a block, if the `spec` parameter is `nil`.
|
98
|
+
#
|
99
|
+
# @param name [Symbol]
|
100
|
+
# @param spec [#call, #to_proc, String, Symbol, nil]
|
101
|
+
# @yieldparam [WebPipe::Conn]
|
102
|
+
#
|
103
|
+
# @return [WebPipe::Pipe] A fresh new instance with the added plug.
|
104
|
+
def plug(name, spec = nil, &block_spec)
|
105
|
+
with(
|
106
|
+
plugs: [
|
107
|
+
*plugs,
|
108
|
+
Plug.new(name: name, spec: spec || block_spec)
|
109
|
+
]
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Names and adds a rack middleware to the final application.
|
114
|
+
#
|
115
|
+
# The middleware can be given in three forms:
|
116
|
+
#
|
117
|
+
# - As one or two arguments, the first one being a
|
118
|
+
# rack middleware class, and optionally a second one with its initialization
|
119
|
+
# options.
|
120
|
+
# - As something responding to `#to_middlewares` with an array of
|
121
|
+
# {WebPipe::RackSupport::Middleware} (like another {WebPipe::Pipe} instance
|
122
|
+
# or a class including {WebPipe}), case in which all middlewares are used.
|
123
|
+
#
|
124
|
+
# @overload use(name, middleware_class)
|
125
|
+
# @param name [Symbol]
|
126
|
+
# @param middleware_class [Class]
|
127
|
+
# @overload use(name, middleware_class, middleware_options)
|
128
|
+
# @param name [Symbol]
|
129
|
+
# @param middleware_class [Class]
|
130
|
+
# @param middleware_options [Any]
|
131
|
+
# @overload use(name, to_middlewares)
|
132
|
+
# @param name [Symbol]
|
133
|
+
# @param middleware_class [#to_middlewares]
|
134
|
+
#
|
135
|
+
# @return [WebPipe::Pipe] A fresh new instance with the added middleware.
|
136
|
+
def use(name, *spec)
|
137
|
+
with(
|
138
|
+
middleware_specifications: [
|
139
|
+
*middleware_specifications,
|
140
|
+
RackSupport::MiddlewareSpecification.new(name: name, spec: spec)
|
141
|
+
]
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Shortcut for {#plug} and {#use} a pipe at once.
|
146
|
+
#
|
147
|
+
# @param name [#to_sym]
|
148
|
+
# @param spec [#to_proc#to_middlewares]
|
149
|
+
def compose(name, spec)
|
150
|
+
use(name, spec)
|
151
|
+
.plug(name, spec)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Operations {#plug}ged to the app, mapped by their names.
|
155
|
+
#
|
156
|
+
# @return [Hash{Symbol => Proc}]
|
157
|
+
def operations
|
158
|
+
@operations ||= Hash[
|
159
|
+
plugs.map { |plug| [plug.name, plug.call(container, context)] }
|
160
|
+
]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Middlewares {#use}d in the app, mapped by their names.
|
164
|
+
#
|
165
|
+
# Returns them wrapped within {WebPipe::RackSupport::Middleware} instances,
|
166
|
+
# from where you can access their classes and options.
|
167
|
+
#
|
168
|
+
# @return [Hash{Symbol=>Array<WebPipe::RackSupport::Middleware>}]
|
169
|
+
def middlewares
|
170
|
+
@middlewares ||= Hash[
|
171
|
+
middleware_specifications.map { |mw_spec| [mw_spec.name, mw_spec.call] }
|
172
|
+
]
|
173
|
+
end
|
174
|
+
|
175
|
+
# @api private
|
176
|
+
def to_proc
|
177
|
+
ConnSupport::Composition
|
178
|
+
.new(operations.values)
|
179
|
+
.method(:call)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @api private
|
183
|
+
def to_middlewares
|
184
|
+
middlewares.values.flatten
|
185
|
+
end
|
186
|
+
|
187
|
+
# @api private
|
188
|
+
def inject(plugs: {}, middleware_specifications: {})
|
189
|
+
res_mw_specs = RackSupport::MiddlewareSpecification.inject(
|
190
|
+
self.middleware_specifications, middleware_specifications
|
191
|
+
)
|
192
|
+
res_plugs = Plug.inject(
|
193
|
+
self.plugs, plugs
|
194
|
+
)
|
195
|
+
with(
|
196
|
+
plugs: res_plugs,
|
197
|
+
middleware_specifications: res_mw_specs
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
# @api private
|
202
|
+
def call(env)
|
203
|
+
rack_app.call(env)
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def app
|
209
|
+
App.new(operations.values).freeze
|
210
|
+
end
|
211
|
+
|
212
|
+
def rack_app
|
213
|
+
RackSupport::AppWithMiddlewares.new(
|
214
|
+
to_middlewares,
|
215
|
+
app
|
216
|
+
).freeze
|
217
|
+
end
|
218
|
+
|
219
|
+
def with(plugs: nil, middleware_specifications: nil)
|
220
|
+
self.class.new(
|
221
|
+
container: container,
|
222
|
+
context: context,
|
223
|
+
middleware_specifications: middleware_specifications ||
|
224
|
+
self.middleware_specifications,
|
225
|
+
plugs: plugs || self.plugs
|
226
|
+
)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|