substation 0.0.10.beta2 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. data/.travis.yml +7 -3
  2. data/Changelog.md +99 -24
  3. data/Gemfile +8 -1
  4. data/Gemfile.devtools +37 -21
  5. data/Guardfile +1 -1
  6. data/README.md +118 -46
  7. data/TODO.md +1 -0
  8. data/config/flay.yml +2 -2
  9. data/config/flog.yml +1 -1
  10. data/config/reek.yml +41 -16
  11. data/config/rubocop.yml +44 -0
  12. data/config/yardstick.yml +1 -1
  13. data/lib/substation.rb +41 -5
  14. data/lib/substation/chain.rb +73 -84
  15. data/lib/substation/chain/definition.rb +147 -0
  16. data/lib/substation/chain/dsl.rb +150 -112
  17. data/lib/substation/chain/dsl/config.rb +55 -0
  18. data/lib/substation/chain/dsl/module_builder.rb +84 -0
  19. data/lib/substation/dispatcher.rb +20 -229
  20. data/lib/substation/dsl/guard.rb +96 -0
  21. data/lib/substation/dsl/registry.rb +181 -0
  22. data/lib/substation/environment.rb +126 -23
  23. data/lib/substation/environment/dsl.rb +31 -12
  24. data/lib/substation/processor.rb +238 -7
  25. data/lib/substation/processor/builder.rb +26 -0
  26. data/lib/substation/processor/config.rb +24 -0
  27. data/lib/substation/processor/evaluator.rb +66 -42
  28. data/lib/substation/processor/evaluator/handler.rb +54 -0
  29. data/lib/substation/processor/evaluator/result.rb +24 -0
  30. data/lib/substation/processor/executor.rb +46 -0
  31. data/lib/substation/processor/nest.rb +40 -0
  32. data/lib/substation/processor/transformer.rb +46 -0
  33. data/lib/substation/processor/wrapper.rb +16 -5
  34. data/lib/substation/request.rb +29 -27
  35. data/lib/substation/response.rb +19 -34
  36. data/lib/substation/response/api.rb +37 -0
  37. data/lib/substation/response/exception.rb +20 -0
  38. data/lib/substation/response/exception/output.rb +59 -0
  39. data/lib/substation/response/failure.rb +11 -0
  40. data/lib/substation/response/success.rb +11 -0
  41. data/lib/substation/version.rb +3 -1
  42. data/spec/demo/core.rb +64 -0
  43. data/spec/demo/core/action.rb +49 -0
  44. data/spec/demo/core/action/create_person.rb +28 -0
  45. data/spec/demo/core/errors.rb +16 -0
  46. data/spec/demo/core/facade.rb +38 -0
  47. data/spec/demo/core/handler.rb +21 -0
  48. data/spec/demo/core/handler/acceptor.rb +47 -0
  49. data/spec/demo/core/handler/authenticator.rb +36 -0
  50. data/spec/demo/core/handler/authorizer.rb +38 -0
  51. data/spec/demo/core/input.rb +15 -0
  52. data/spec/demo/core/observers.rb +10 -0
  53. data/spec/demo/core/validator.rb +21 -0
  54. data/spec/demo/domain/actor.rb +29 -0
  55. data/spec/demo/domain/dto/person.rb +18 -0
  56. data/spec/demo/domain/environment.rb +55 -0
  57. data/spec/demo/domain/storage.rb +49 -0
  58. data/spec/demo/web.rb +26 -0
  59. data/spec/demo/web/errors.rb +9 -0
  60. data/spec/demo/web/facade.rb +60 -0
  61. data/spec/demo/web/handler/deserializer.rb +36 -0
  62. data/spec/demo/web/presenter.rb +38 -0
  63. data/spec/demo/web/presenter/person.rb +19 -0
  64. data/spec/demo/web/renderer.rb +45 -0
  65. data/spec/demo/web/sanitizer.rb +35 -0
  66. data/spec/demo/web/sanitizer/person.rb +20 -0
  67. data/spec/demo/web/views.rb +28 -0
  68. data/spec/integration/demo/core_spec.rb +97 -0
  69. data/spec/integration/demo/web_spec.rb +114 -0
  70. data/spec/shared/context/integration/demo.rb +33 -0
  71. data/spec/shared/context/unit/chain.rb +13 -0
  72. data/spec/shared/context/unit/processor.rb +58 -0
  73. data/spec/shared/context/unit/request.rb +8 -0
  74. data/spec/shared/examples/integration/demo.rb +35 -0
  75. data/spec/shared/examples/unit/processor.rb +72 -0
  76. data/spec/spec_helper.rb +52 -23
  77. data/spec/unit/substation/chain/definition_spec.rb +141 -0
  78. data/spec/unit/substation/chain/dsl/config/dsl_module_spec.rb +13 -0
  79. data/spec/unit/substation/chain/dsl/config/registry_spec.rb +13 -0
  80. data/spec/unit/substation/chain/dsl/config_spec.rb +18 -0
  81. data/spec/unit/substation/chain/dsl/module_builder_spec.rb +77 -0
  82. data/spec/unit/substation/chain/dsl_spec.rb +175 -0
  83. data/spec/unit/substation/chain_spec.rb +303 -0
  84. data/spec/unit/substation/dispatcher_spec.rb +68 -0
  85. data/spec/unit/substation/dsl/guard_spec.rb +72 -0
  86. data/spec/unit/substation/dsl/registry_spec.rb +181 -0
  87. data/spec/unit/substation/environment/dsl_spec.rb +156 -0
  88. data/spec/unit/substation/environment_spec.rb +259 -0
  89. data/spec/unit/substation/processor/builder_spec.rb +21 -0
  90. data/spec/unit/substation/processor/config_spec.rb +40 -0
  91. data/spec/unit/substation/processor/evaluator/handler_spec.rb +20 -0
  92. data/spec/unit/substation/processor/evaluator/pivot_spec.rb +42 -0
  93. data/spec/unit/substation/processor/evaluator/request_spec.rb +11 -0
  94. data/spec/unit/substation/processor/evaluator/result/failure_spec.rb +14 -0
  95. data/spec/unit/substation/processor/evaluator/result/success_spec.rb +14 -0
  96. data/spec/unit/substation/processor/evaluator/result_spec.rb +13 -0
  97. data/spec/unit/substation/processor/evaluator_spec.rb +18 -0
  98. data/spec/unit/substation/processor/executor/null_spec.rb +25 -0
  99. data/spec/unit/substation/processor/executor_spec.rb +32 -0
  100. data/spec/unit/substation/processor/fallible_spec.rb +24 -0
  101. data/spec/unit/substation/processor/incoming_spec.rb +17 -0
  102. data/spec/unit/substation/processor/nest/incoming_spec.rb +56 -0
  103. data/spec/unit/substation/processor/nest_spec.rb +6 -0
  104. data/spec/unit/substation/processor/outgoing_spec.rb +47 -0
  105. data/spec/unit/substation/processor/transformer/incoming_spec.rb +17 -0
  106. data/spec/unit/substation/processor/transformer/outgoing_spec.rb +17 -0
  107. data/spec/unit/substation/processor/wrapper/incoming_spec.rb +15 -0
  108. data/spec/unit/substation/processor/wrapper/outgoing_spec.rb +15 -0
  109. data/spec/unit/substation/processor/wrapper_spec.rb +24 -0
  110. data/spec/unit/substation/processor_spec.rb +68 -0
  111. data/spec/unit/substation/request_spec.rb +70 -0
  112. data/spec/unit/substation/response/api_spec.rb +22 -0
  113. data/spec/unit/substation/response/exception/output_spec.rb +46 -0
  114. data/spec/unit/substation/response/exception_spec.rb +25 -0
  115. data/spec/unit/substation/response/failure_spec.rb +25 -0
  116. data/spec/unit/substation/response/success_spec.rb +24 -0
  117. data/spec/unit/substation/response_spec.rb +73 -0
  118. data/substation.gemspec +7 -6
  119. metadata +157 -67
  120. checksums.yaml +0 -7
  121. data/TODO +0 -0
  122. data/lib/substation/observer.rb +0 -66
  123. data/lib/substation/processor/pivot.rb +0 -25
  124. data/lib/substation/utils.rb +0 -68
  125. data/spec/integration/substation/dispatcher/call_spec.rb +0 -260
  126. data/spec/unit/substation/chain/call_spec.rb +0 -63
  127. data/spec/unit/substation/chain/dsl/builder/class_methods/call_spec.rb +0 -19
  128. data/spec/unit/substation/chain/dsl/builder/dsl_spec.rb +0 -21
  129. data/spec/unit/substation/chain/dsl/builder/failure_chain_spec.rb +0 -30
  130. data/spec/unit/substation/chain/dsl/chain_spec.rb +0 -15
  131. data/spec/unit/substation/chain/dsl/class_methods/processors_spec.rb +0 -24
  132. data/spec/unit/substation/chain/dsl/initialize_spec.rb +0 -19
  133. data/spec/unit/substation/chain/dsl/processors_spec.rb +0 -42
  134. data/spec/unit/substation/chain/dsl/use_spec.rb +0 -14
  135. data/spec/unit/substation/chain/each_spec.rb +0 -46
  136. data/spec/unit/substation/chain/incoming/result_spec.rb +0 -21
  137. data/spec/unit/substation/chain/outgoing/call_spec.rb +0 -25
  138. data/spec/unit/substation/chain/outgoing/result_spec.rb +0 -21
  139. data/spec/unit/substation/dispatcher/action/call_spec.rb +0 -23
  140. data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +0 -61
  141. data/spec/unit/substation/dispatcher/action_names_spec.rb +0 -14
  142. data/spec/unit/substation/dispatcher/call_spec.rb +0 -47
  143. data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +0 -20
  144. data/spec/unit/substation/environment/chain_spec.rb +0 -50
  145. data/spec/unit/substation/environment/class_methods/build_spec.rb +0 -11
  146. data/spec/unit/substation/environment/dsl/class_methods/registry_spec.rb +0 -18
  147. data/spec/unit/substation/environment/dsl/register_spec.rb +0 -14
  148. data/spec/unit/substation/environment/dsl/registry_spec.rb +0 -19
  149. data/spec/unit/substation/observer/chain/call_spec.rb +0 -26
  150. data/spec/unit/substation/observer/class_methods/coerce_spec.rb +0 -33
  151. data/spec/unit/substation/observer/null/call_spec.rb +0 -12
  152. data/spec/unit/substation/processor/evaluator/call_spec.rb +0 -49
  153. data/spec/unit/substation/processor/pivot/call_spec.rb +0 -17
  154. data/spec/unit/substation/processor/wrapper/call_spec.rb +0 -20
  155. data/spec/unit/substation/request/env_spec.rb +0 -14
  156. data/spec/unit/substation/request/error_spec.rb +0 -15
  157. data/spec/unit/substation/request/input_spec.rb +0 -14
  158. data/spec/unit/substation/request/success_spec.rb +0 -15
  159. data/spec/unit/substation/response/env_spec.rb +0 -16
  160. data/spec/unit/substation/response/failure/success_predicate_spec.rb +0 -15
  161. data/spec/unit/substation/response/input_spec.rb +0 -16
  162. data/spec/unit/substation/response/output_spec.rb +0 -16
  163. data/spec/unit/substation/response/request_spec.rb +0 -16
  164. data/spec/unit/substation/response/success/success_predicate_spec.rb +0 -15
  165. data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +0 -46
  166. data/spec/unit/substation/utils/class_methods/const_get_spec.rb +0 -46
  167. data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +0 -20
data/TODO.md ADDED
@@ -0,0 +1 @@
1
+ * Introduce DSL::Data and DSL::Data::Mutable to simplify DSL implementations
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 6
3
- total_score: 87
2
+ threshold: 12 # TODO bring it back to 11 by collapsing the dsl registries
3
+ total_score: 207
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 16.4
2
+ threshold: 16.7
@@ -10,24 +10,32 @@ ClassVariable:
10
10
  exclude: []
11
11
  ControlParameter:
12
12
  enabled: true
13
- exclude: []
13
+ exclude:
14
+ - Substation::DSL::Guard#raise_if_already_registered
14
15
  DataClump:
15
16
  enabled: true
16
- exclude: []
17
+ exclude:
18
+ - Substation::Processor::Evaluator # FIXME
17
19
  max_copies: 2
18
20
  min_clump_size: 2
19
21
  DuplicateMethodCall:
20
22
  enabled: true
21
- exclude: []
23
+ exclude:
24
+ - !ruby/regexp /^Substation::DSL::Registry#\[\]=$/ # reek won't emit this anymore soon
25
+ - Substation::Chain::Definition#prepend # reek won't emit this anymore soon
26
+ - Substation::Chain::Definition#replace_failure_chain # replaces object at given idx inside an array
27
+ - Substation::Processor::Nest::Incoming#call # calls a private method in a case expression
22
28
  max_calls: 1
23
29
  allow_calls: []
24
30
  FeatureEnvy:
25
31
  enabled: true
26
32
  exclude:
27
33
  - Substation::Chain#call # loops over instance state
28
- - Substation::Chain::Incoming#result # defined in a module
29
- - Substation::Chain::Outgoing#respond_with #defined in a module
34
+ - Substation::Processor::Outgoing#respond_with #defined in a module
30
35
  - Substation::Processor::Evaluator#call # method object
36
+ - Substation::Processor::Evaluator#on_success # method object
37
+ - Substation::Processor#success? # in a module to be included in a method object
38
+ - Substation::Chain::DSL#__call__ # relies heavily on #new(other)
31
39
  IrresponsibleModule:
32
40
  enabled: true
33
41
  exclude: []
@@ -35,8 +43,7 @@ LongParameterList:
35
43
  enabled: true
36
44
  exclude:
37
45
  - Substation::Dispatcher#call
38
- - Substation::Chain::DSL::Builder#define_dsl_method
39
- max_params: 2
46
+ max_params: 3
40
47
  LongYieldList:
41
48
  enabled: true
42
49
  exclude: []
@@ -44,7 +51,7 @@ LongYieldList:
44
51
  NestedIterators:
45
52
  enabled: true
46
53
  exclude:
47
- - Substation::Chain::DSL::Builder#define_dsl_method
54
+ - Substation::Chain::DSL::ModuleBuilder#define_dsl_method
48
55
  max_allowed_nesting: 1
49
56
  ignore_iterators: []
50
57
  NilCheck:
@@ -52,27 +59,41 @@ NilCheck:
52
59
  exclude: []
53
60
  RepeatedConditional:
54
61
  enabled: true
55
- exclude: []
62
+ exclude:
63
+ - Substation::Chain::DSL
56
64
  max_ifs: 1
57
65
  TooManyInstanceVariables:
58
66
  enabled: true
59
67
  exclude:
60
68
  - Substation::Response
61
69
  - Substation::Processor::Evaluator
70
+ - Substation::Environment # 4
71
+ - Substation::Chain::Definition # 4
72
+ - Substation::Chain::DSL # 4
62
73
  max_instance_variables: 3
63
74
  TooManyMethods:
64
75
  enabled: true
65
- exclude: []
76
+ exclude:
77
+ - Substation::Chain::DSL # 7
78
+ - Substation::Chain::Definition # 8
79
+ - Substation::Environment # 5
80
+ - Substation::DSL::Registry # 10
66
81
  max_methods: 4
67
82
  TooManyStatements:
68
83
  enabled: true
69
84
  exclude:
70
- - Substation::Utils#self.const_get
71
- - Substation::Utils#self.symbolize_keys
72
- - Substation::Dispatcher::Action#self.coerce
73
85
  - Substation::Chain#call
74
- - Substation::Utils#self.coerce_callable
75
86
  - Substation::Processor::Evaluator#call
87
+ - Substation::Dispatcher::DSL#dispatch
88
+ - Substation::Environment#self.build # ~5
89
+ - Substation::Environment::DSL#register # 4
90
+ - Substation::Chain::Definition#<< # 4
91
+ - Substation::Chain::Definition#initialize # 4
92
+ - Substation::Chain::DSL#initialize # 5
93
+ - Substation::Environment#initialize # 4
94
+ - Substation::Processor#initialize # 6
95
+ - Substation::Processor::Nest::Incoming#call # 4
96
+ - Substation::Response#initialize # 4
76
97
  max_statements: 3
77
98
  UncommunicativeMethodName:
78
99
  enabled: true
@@ -111,6 +132,10 @@ UnusedParameters:
111
132
  UtilityFunction:
112
133
  enabled: true
113
134
  exclude:
114
- - Substation::Chain::Incoming#result # defined in a module
115
- - Substation::Chain::Outgoing#respond_with # defined in a module
135
+ - Substation::Processor::Outgoing#respond_with # defined in a module
136
+ - Substation::Processor::Evaluator::Handler#respond_with # defined in a module
137
+ - Substation::Processor::Evaluator#on_success # inside a method object
138
+ - Substation::Processor#success? # in a module to be included in a method object
139
+ - Substation::Environment#action # DSL method
140
+ - Substation::Environment#dispatcher # DSL method
116
141
  max_helper_calls: 0
@@ -0,0 +1,44 @@
1
+ AllCops:
2
+ Includes:
3
+ - '../**/*.rake'
4
+ Excludes:
5
+ - '../spec/spec_helper.rb'
6
+
7
+ # Avoid parameter lists longer than five parameters.
8
+ ParameterLists:
9
+ Max: 3
10
+ CountKeywordArgs: true
11
+
12
+ # Avoid more than `Max` levels of nesting.
13
+ BlockNesting:
14
+ Max: 3
15
+
16
+ HashSyntax:
17
+ Enabled: false
18
+
19
+ Blocks:
20
+ Enabled: false
21
+
22
+ SpaceInsideBrackets:
23
+ Enabled: false
24
+
25
+ Documentation:
26
+ Enabled: false # reek already checks this and rubocop requires duplicate docs
27
+
28
+ SingleLineMethods:
29
+ Enabled: false
30
+
31
+ LineLength:
32
+ Max: 114 # the offending lines are in specs, sadly this means global disabling for now
33
+
34
+ MethodLength:
35
+ Max: 12 # reek performs these checks anyway
36
+
37
+ CaseIndentation:
38
+ Enabled: false
39
+
40
+ Lambda:
41
+ Enabled: false # i personally like the look of multiline ->(arg) {} lambdas
42
+
43
+ IfUnlessModifier:
44
+ Enabled: false # i have no strong opinion but i won't let it force me to go over the col limit
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 100.0
2
+ threshold: 99.0
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'set'
2
4
  require 'forwardable'
3
5
 
@@ -35,18 +37,52 @@ module Substation
35
37
  # Represent an undefined argument
36
38
  Undefined = Object.new.freeze
37
39
 
40
+ # An empty frozen array useful for (default) parameters
41
+ EMPTY_ARRAY = [].freeze
42
+
43
+ # An empty frozen hash useful for (default) parameters
44
+ EMPTY_HASH = {}.freeze
45
+
46
+ # Error raised when trying to access an unknown processor
47
+ UnknownProcessor = Class.new(StandardError)
48
+
49
+ # Raised when trying to dispatch to an unregistered action
50
+ UnknownActionError = Class.new(StandardError)
51
+
52
+ # Raised when an object is already registered under the a given name
53
+ AlreadyRegisteredError = Class.new(StandardError)
54
+
55
+ # Raised when a reserved method is being given
56
+ ReservedNameError = Class.new(StandardError)
57
+
58
+ # Raised when a duplicate {Processor} should be registered within a {Chain}
59
+ DuplicateProcessorError = Class.new(StandardError)
38
60
  end
39
61
 
40
- require 'substation/utils'
41
62
  require 'substation/request'
42
63
  require 'substation/response'
43
- require 'substation/observer'
44
- require 'substation/chain'
45
- require 'substation/chain/dsl'
64
+ require 'substation/response/api'
65
+ require 'substation/response/success'
66
+ require 'substation/response/failure'
67
+ require 'substation/response/exception'
68
+ require 'substation/response/exception/output'
46
69
  require 'substation/processor'
70
+ require 'substation/processor/builder'
71
+ require 'substation/processor/config'
72
+ require 'substation/processor/executor'
47
73
  require 'substation/processor/evaluator'
48
- require 'substation/processor/pivot'
74
+ require 'substation/processor/evaluator/result'
75
+ require 'substation/processor/evaluator/handler'
76
+ require 'substation/processor/transformer'
49
77
  require 'substation/processor/wrapper'
78
+ require 'substation/processor/nest'
79
+ require 'substation/dsl/guard'
80
+ require 'substation/dsl/registry'
81
+ require 'substation/chain/definition'
82
+ require 'substation/chain'
83
+ require 'substation/chain/dsl'
84
+ require 'substation/chain/dsl/config'
85
+ require 'substation/chain/dsl/module_builder'
50
86
  require 'substation/environment'
51
87
  require 'substation/environment/dsl'
52
88
  require 'substation/dispatcher'
@@ -1,20 +1,19 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # Implements a chain of responsibility for an action
4
6
  #
5
7
  # An instance of this class will typically contain (in that order)
6
- # a few handlers that process the incoming {Request} object, one
7
- # handler that calls an action ({Chain::Pivot}), and some handlers
8
+ # a few processors that process the incoming {Request} object, one
9
+ # processor that calls an action ({Processor::Pivot}), and some processors
8
10
  # that process the outgoing {Response} object.
9
11
  #
10
- # Both {Chain::Incoming} and {Chain::Outgoing} handlers must
11
- # respond to `#call(response)` and `#result(response)`.
12
- #
13
- # @example chain handlers (used in instance method examples)
12
+ # @example chain processors (used in instance method examples)
14
13
  #
15
14
  # module App
16
15
  #
17
- # class Handler
16
+ # class Processor
18
17
  #
19
18
  # def initialize(handler = nil)
20
19
  # @handler = handler
@@ -25,11 +24,11 @@ module Substation
25
24
  # attr_reader :handler
26
25
  #
27
26
  # class Incoming < self
28
- # include Substation::Chain::Incoming
27
+ # include Substation::Processor::Incoming
29
28
  # end
30
29
  #
31
30
  # class Outgoing < self
32
- # include Substation::Chain::Outgoing
31
+ # include Substation::Processor::Outgoing
33
32
  #
34
33
  # private
35
34
  #
@@ -39,13 +38,13 @@ module Substation
39
38
  # end
40
39
  # end
41
40
  #
42
- # class Validator < Handler::Incoming
41
+ # class Validator < Processor::Incoming
43
42
  # def call(request)
44
43
  # result = handler.call(request.input)
45
- # if result.valid?
44
+ # if result.success?
46
45
  # request.success(request.input)
47
46
  # else
48
- # request.error(result.violations)
47
+ # request.error(result.output)
49
48
  # end
50
49
  # end
51
50
  # end
@@ -58,7 +57,7 @@ module Substation
58
57
  # end
59
58
  # end
60
59
  #
61
- # class Presenter < Handler::Outgoing
60
+ # class Presenter < Processor::Outgoing
62
61
  # def call(response)
63
62
  # respond_with(response, handler.new(response.output))
64
63
  # end
@@ -67,74 +66,38 @@ module Substation
67
66
  #
68
67
  class Chain
69
68
 
70
- # Supports chaining handlers processed before the {Pivot}
71
- module Incoming
72
-
73
- # The request passed on to the next handler in a {Chain}
74
- #
75
- # @param [Response] response
76
- # the response returned from the previous handler in a {Chain}
77
- #
78
- # @return [Request]
79
- # the request passed on to the next handler in a {Chain}
80
- #
81
- # @api private
82
- def result(response)
83
- Request.new(response.env, response.output)
84
- end
85
- end
86
-
87
- # Supports chaining the {Pivot} or handlers processed after the {Pivot}
88
- module Outgoing
89
-
90
- # The response passed on to the next handler in a {Chain}
91
- #
92
- # @param [Response] response
93
- # the response returned from the previous handler in a {Chain}
94
- #
95
- # @return [Response]
96
- # the response passed on to the next handler in a {Chain}
97
- #
98
- # @api private
99
- def result(response)
100
- response
101
- end
102
-
103
- private
104
-
105
- # Build a new {Response} based on +response+ and +output+
106
- #
107
- # @param [Response] response
108
- # the original response
109
- #
110
- # @param [Object] output
111
- # the data to be wrapped within the new {Response}
112
- #
113
- # @return [Response]
114
- #
115
- # @api private
116
- def respond_with(response, output)
117
- response.class.new(response.request, output)
118
- end
119
- end
120
-
121
- # Supports chaining the {Pivot} handler
122
- Pivot = Outgoing
123
-
124
69
  include Enumerable
125
- include Concord.new(:handlers)
70
+ include Concord.new(:processors, :exception_chain)
126
71
  include Adamantium::Flat
127
- include Pivot # allow nesting of chains
128
72
 
129
73
  # Empty chain
130
- EMPTY = Class.new(self).new([])
74
+ EMPTY = new(Definition::EMPTY, EMPTY_ARRAY)
75
+
76
+ # Return an exception response
77
+ #
78
+ # @param [Request, Response] state
79
+ # the initial state passed into the chain
80
+ #
81
+ # @param [Object] data
82
+ # the processed data available when the exception was raised
83
+ #
84
+ # @param [Class<StandardError>] exception
85
+ # the exception instance that was raised
86
+ #
87
+ # @return [Response::Exception]
88
+ #
89
+ # @api private
90
+ def self.exception_response(state, data, exception)
91
+ output = Response::Exception::Output.new(data, exception)
92
+ Response::Exception.new(state.to_request(data), output)
93
+ end
131
94
 
132
95
  # Call the chain
133
96
  #
134
- # Invokes all handlers and returns either the first
97
+ # Invokes all processors and returns either the first
135
98
  # {Response::Failure} that it encounters, or if all
136
99
  # goes well, the {Response::Success} returned from
137
- # the last handler.
100
+ # the last processor.
138
101
  #
139
102
  # @example
140
103
  #
@@ -162,20 +125,24 @@ module Substation
162
125
  # the request to handle
163
126
  #
164
127
  # @return [Response::Success]
165
- # the response returned from the last handler
128
+ # the response returned from the last processor
166
129
  #
167
130
  # @return [Response::Failure]
168
- # the response returned from the failing handler
131
+ # the response returned from the failing processor's failure chain
169
132
  #
170
- # @raise [Exception]
171
- # any exception that isn't explicitly rescued in client code
133
+ # @return [Response::Exception]
134
+ # the response returned from invoking the {#exception_chain}
172
135
  #
173
136
  # @api public
174
137
  def call(request)
175
- handlers.inject(request) { |result, handler|
176
- response = handler.call(result)
177
- return response unless response.success?
178
- handler.result(response)
138
+ reduce(request) { |result, processor|
139
+ begin
140
+ response = processor.call(result)
141
+ return response unless processor.success?(response)
142
+ processor.result(response)
143
+ rescue => exception
144
+ return on_exception(request, result.data, exception)
145
+ end
179
146
  }
180
147
  end
181
148
 
@@ -184,18 +151,40 @@ module Substation
184
151
  # @param [Proc] block
185
152
  # a block passed to {#handlers} each method
186
153
  #
187
- # @yield [handler]
154
+ # @yield [processor]
188
155
  #
189
- # @yieldparam [#call] handler
190
- # each handler in the chain
156
+ # @yieldparam [#call] processor
157
+ # each processor in the chain
191
158
  #
192
159
  # @return [self]
193
160
  #
194
161
  # @api private
195
162
  def each(&block)
196
163
  return to_enum unless block
197
- handlers.each(&block)
164
+ processors.each(&block)
198
165
  self
199
166
  end
167
+
168
+ private
169
+
170
+ # Call the failure chain in case of an uncaught exception
171
+ #
172
+ # @param [Request] request
173
+ # the initial request passed into the chain
174
+ #
175
+ # @param [Object] data
176
+ # the processed data available when the exception was raised
177
+ #
178
+ # @param [Class<StandardError>] exception
179
+ # the exception instance that was raised
180
+ #
181
+ # @return [Response::Exception]
182
+ #
183
+ # @api private
184
+ def on_exception(state, data, exception)
185
+ response = self.class.exception_response(state, data, exception)
186
+ exception_chain.call(response)
187
+ end
188
+
200
189
  end # class Chain
201
190
  end # module Substation