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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +10 -0
  4. data/README.md +13 -10
  5. data/docs/building_a_rack_application.md +1 -1
  6. data/docs/composing_applications.md +4 -4
  7. data/docs/connection_struct/configuring_the_connection_struct.md +4 -4
  8. data/docs/connection_struct/halting_the_pipe.md +17 -19
  9. data/docs/connection_struct/sharing_data_downstream.md +9 -8
  10. data/docs/connection_struct.md +22 -19
  11. data/docs/design_model.md +10 -9
  12. data/docs/dsl_free_usage.md +85 -14
  13. data/docs/extensions/container.md +9 -10
  14. data/docs/extensions/cookies.md +4 -2
  15. data/docs/extensions/dry_schema.md +5 -4
  16. data/docs/extensions/flash.md +9 -11
  17. data/docs/extensions/hanami_view.md +10 -14
  18. data/docs/extensions/params.md +6 -4
  19. data/docs/extensions/rails.md +28 -34
  20. data/docs/extensions/redirect.md +5 -4
  21. data/docs/extensions/router_params.md +5 -5
  22. data/docs/extensions/session.md +4 -4
  23. data/docs/extensions/url.md +6 -6
  24. data/docs/extensions.md +5 -6
  25. data/docs/introduction.md +7 -7
  26. data/docs/plugging_operations/composing_operations.md +3 -3
  27. data/docs/plugging_operations/injecting_operations.md +4 -4
  28. data/docs/plugging_operations/inspecting_operations.md +1 -2
  29. data/docs/plugging_operations/resolving_operations.md +3 -3
  30. data/docs/plugging_operations.md +3 -3
  31. data/docs/plugs/config.md +1 -1
  32. data/docs/plugs/content_type.md +2 -1
  33. data/docs/plugs.md +6 -7
  34. data/docs/recipes/hanami_2_and_dry_rb_integration.md +12 -0
  35. data/docs/recipes/hanami_router_integration.md +3 -1
  36. data/docs/recipes/using_all_restful_methods.md +6 -5
  37. data/docs/using_rack_middlewares/composing_middlewares.md +2 -3
  38. data/docs/using_rack_middlewares/injecting_middlewares.md +6 -6
  39. data/docs/using_rack_middlewares/inspecting_middlewares.md +7 -6
  40. data/docs/using_rack_middlewares.md +6 -6
  41. data/lib/web_pipe/app.rb +22 -25
  42. data/lib/web_pipe/conn.rb +0 -1
  43. data/lib/web_pipe/conn_support/builder.rb +0 -7
  44. data/lib/web_pipe/conn_support/composition.rb +3 -26
  45. data/lib/web_pipe/conn_support/errors.rb +5 -5
  46. data/lib/web_pipe/conn_support/headers.rb +1 -50
  47. data/lib/web_pipe/conn_support/types.rb +3 -3
  48. data/lib/web_pipe/dsl/builder.rb +10 -19
  49. data/lib/web_pipe/dsl/class_context.rb +15 -40
  50. data/lib/web_pipe/dsl/instance_context.rb +53 -0
  51. data/lib/web_pipe/extensions/container/container.rb +2 -15
  52. data/lib/web_pipe/extensions/cookies/cookies.rb +2 -31
  53. data/lib/web_pipe/extensions/dry_schema/dry_schema.rb +2 -56
  54. data/lib/web_pipe/extensions/flash/flash.rb +2 -32
  55. data/lib/web_pipe/extensions/hanami_view/hanami_view.rb +2 -93
  56. data/lib/web_pipe/extensions/not_found/not_found.rb +2 -40
  57. data/lib/web_pipe/extensions/params/params.rb +2 -63
  58. data/lib/web_pipe/extensions/rails/rails.rb +2 -119
  59. data/lib/web_pipe/extensions/redirect/redirect.rb +2 -20
  60. data/lib/web_pipe/extensions/router_params/router_params.rb +1 -39
  61. data/lib/web_pipe/extensions/session/session.rb +2 -25
  62. data/lib/web_pipe/extensions/url/url.rb +2 -5
  63. data/lib/web_pipe/pipe.rb +229 -0
  64. data/lib/web_pipe/plug.rb +30 -75
  65. data/lib/web_pipe/plugs/config.rb +0 -2
  66. data/lib/web_pipe/plugs/content_type.rb +0 -2
  67. data/lib/web_pipe/rack_support/app_with_middlewares.rb +3 -26
  68. data/lib/web_pipe/rack_support/middleware.rb +2 -2
  69. data/lib/web_pipe/rack_support/middleware_specification.rb +17 -55
  70. data/lib/web_pipe/types.rb +1 -3
  71. data/lib/web_pipe/version.rb +1 -1
  72. data/lib/web_pipe.rb +77 -21
  73. data/web_pipe.gemspec +1 -0
  74. metadata +7 -6
  75. data/docs/recipes/dry_rb_integration.md +0 -17
  76. data/lib/web_pipe/dsl/dsl_context.rb +0 -85
  77. data/lib/web_pipe/dsl/instance_methods.rb +0 -114
@@ -1,46 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #:nodoc:
3
+ # :nodoc:
4
4
  module WebPipe
5
- # Generates a not-found response
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
- #:nodoc:
6
+ # :nodoc:
7
7
  module WebPipe
8
- # Adds a {Conn#params} method which can perform any number of
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
- #:nodoc:
5
+ # :nodoc:
6
6
  module WebPipe
7
- # Integrates with Rails framework.
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
- #:nodoc:
5
+ # :nodoc:
6
6
  module WebPipe
7
- # Helper method to create redirect responses.
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
- # Adds a transformation to merge router params into {Conn#params}.
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
- #:nodoc:
7
+ # :nodoc:
8
8
  module WebPipe
9
- # Wrapper around Rack::Session middlewares.
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
- #:nodoc:
3
+ # :nodoc:
4
4
  module WebPipe
5
- # Adds helper methods related to the request URL.
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