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.
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