web_pipe 0.15.1 → 0.16.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 +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
|