flows 0.1.0 → 0.5.1

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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +38 -0
  3. data/.gitignore +9 -1
  4. data/.mdlrc +1 -0
  5. data/.reek.yml +54 -0
  6. data/.rubocop.yml +44 -2
  7. data/.ruby-version +1 -1
  8. data/.yardopts +1 -0
  9. data/CHANGELOG.md +65 -0
  10. data/README.md +186 -256
  11. data/Rakefile +35 -1
  12. data/bin/.rubocop.yml +5 -0
  13. data/bin/all_the_errors +55 -0
  14. data/bin/benchmark +69 -78
  15. data/bin/benchmark_cli/compare.rb +118 -0
  16. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  17. data/bin/benchmark_cli/compare/base.rb +45 -0
  18. data/bin/benchmark_cli/compare/command.rb +47 -0
  19. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  20. data/bin/benchmark_cli/examples.rb +23 -0
  21. data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
  22. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  23. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  30. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  31. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  32. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  33. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  37. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  38. data/bin/benchmark_cli/helpers.rb +12 -0
  39. data/bin/benchmark_cli/ruby.rb +15 -0
  40. data/bin/benchmark_cli/ruby/command.rb +38 -0
  41. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  42. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  43. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  44. data/bin/console +1 -0
  45. data/bin/docserver +7 -0
  46. data/bin/errors +130 -0
  47. data/bin/errors_cli/contract_error_demo.rb +49 -0
  48. data/bin/errors_cli/di_error_demo.rb +38 -0
  49. data/bin/errors_cli/flow_error_demo.rb +22 -0
  50. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  51. data/bin/errors_cli/oc_error_demo.rb +40 -0
  52. data/bin/errors_cli/railway_error_demo.rb +10 -0
  53. data/bin/errors_cli/result_error_demo.rb +13 -0
  54. data/bin/errors_cli/scp_error_demo.rb +17 -0
  55. data/docs/.nojekyll +0 -0
  56. data/docs/README.md +13 -0
  57. data/docs/_sidebar.md +2 -0
  58. data/docs/index.html +30 -0
  59. data/flows.gemspec +27 -2
  60. data/forspell.dict +17 -0
  61. data/lefthook.yml +21 -0
  62. data/lib/flows.rb +13 -5
  63. data/lib/flows/contract.rb +402 -0
  64. data/lib/flows/contract/array.rb +55 -0
  65. data/lib/flows/contract/case_eq.rb +43 -0
  66. data/lib/flows/contract/compose.rb +77 -0
  67. data/lib/flows/contract/either.rb +53 -0
  68. data/lib/flows/contract/error.rb +25 -0
  69. data/lib/flows/contract/hash.rb +75 -0
  70. data/lib/flows/contract/hash_of.rb +70 -0
  71. data/lib/flows/contract/helpers.rb +22 -0
  72. data/lib/flows/contract/predicate.rb +34 -0
  73. data/lib/flows/contract/transformer.rb +50 -0
  74. data/lib/flows/contract/tuple.rb +70 -0
  75. data/lib/flows/flow.rb +96 -7
  76. data/lib/flows/flow/errors.rb +29 -0
  77. data/lib/flows/flow/node.rb +132 -0
  78. data/lib/flows/flow/router.rb +29 -0
  79. data/lib/flows/flow/router/custom.rb +59 -0
  80. data/lib/flows/flow/router/errors.rb +11 -0
  81. data/lib/flows/flow/router/simple.rb +25 -0
  82. data/lib/flows/plugin.rb +14 -0
  83. data/lib/flows/plugin/dependency_injector.rb +159 -0
  84. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  85. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  86. data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
  87. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  88. data/lib/flows/plugin/implicit_init.rb +45 -0
  89. data/lib/flows/plugin/output_contract.rb +85 -0
  90. data/lib/flows/plugin/output_contract/dsl.rb +48 -0
  91. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  92. data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
  93. data/lib/flows/plugin/profiler.rb +114 -0
  94. data/lib/flows/plugin/profiler/injector.rb +35 -0
  95. data/lib/flows/plugin/profiler/report.rb +48 -0
  96. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  97. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  98. data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
  99. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  100. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  101. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  102. data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
  103. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  104. data/lib/flows/railway.rb +154 -0
  105. data/lib/flows/railway/dsl.rb +18 -0
  106. data/lib/flows/railway/errors.rb +17 -0
  107. data/lib/flows/railway/step.rb +24 -0
  108. data/lib/flows/railway/step_list.rb +38 -0
  109. data/lib/flows/result.rb +189 -2
  110. data/lib/flows/result/do.rb +172 -0
  111. data/lib/flows/result/err.rb +12 -6
  112. data/lib/flows/result/errors.rb +29 -17
  113. data/lib/flows/result/helpers.rb +25 -3
  114. data/lib/flows/result/ok.rb +12 -6
  115. data/lib/flows/shared_context_pipeline.rb +299 -0
  116. data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
  117. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
  118. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  119. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  120. data/lib/flows/shared_context_pipeline/mutation_step.rb +29 -0
  121. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  122. data/lib/flows/shared_context_pipeline/step.rb +44 -0
  123. data/lib/flows/shared_context_pipeline/track.rb +54 -0
  124. data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
  125. data/lib/flows/shared_context_pipeline/wrap.rb +74 -0
  126. data/lib/flows/util.rb +17 -0
  127. data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
  128. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +98 -0
  129. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
  130. data/lib/flows/util/prepend_to_class.rb +179 -0
  131. data/lib/flows/version.rb +1 -1
  132. metadata +288 -20
  133. data/.travis.yml +0 -8
  134. data/Gemfile.lock +0 -119
  135. data/bin/demo +0 -66
  136. data/bin/examples.rb +0 -159
  137. data/bin/profile_10steps +0 -64
  138. data/bin/ruby_benchmarks +0 -26
  139. data/lib/flows/node.rb +0 -27
  140. data/lib/flows/operation.rb +0 -54
  141. data/lib/flows/operation/builder.rb +0 -130
  142. data/lib/flows/operation/builder/build_router.rb +0 -37
  143. data/lib/flows/operation/dsl.rb +0 -72
  144. data/lib/flows/operation/errors.rb +0 -75
  145. data/lib/flows/operation/executor.rb +0 -78
  146. data/lib/flows/result_router.rb +0 -14
  147. data/lib/flows/router.rb +0 -22
@@ -0,0 +1,11 @@
1
+ module Flows
2
+ class Flow
3
+ class Router
4
+ # Base class for {Flows::Router} error.
5
+ class Error < Flows::Error; end
6
+
7
+ # Raised when no route found basing on provided data.
8
+ class NoRouteError < Error; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module Flows
2
+ class Flow
3
+ class Router
4
+ # Router with static paths for successful and failure results.
5
+ class Simple < Router
6
+ # @param success_route [Symbol] route for any successful results.
7
+ # @param failure_route [Symbol] route for any failure results.
8
+ def initialize(success_route, failure_route)
9
+ @success_route = success_route
10
+ @failure_route = failure_route
11
+ end
12
+
13
+ # @see Flows::Flow::Router#call
14
+ def call(result)
15
+ result.ok? ? @success_route : @failure_route
16
+ end
17
+
18
+ # @see Flows::Flow::Router#destinations
19
+ def destinations
20
+ [@success_route, @failure_route]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ module Flows
2
+ # Namespace for class behaviour extensions.
3
+ #
4
+ # Feel free to use it to empower your abstractions.
5
+ #
6
+ # @since 0.4.0
7
+ module Plugin
8
+ end
9
+ end
10
+
11
+ require_relative 'plugin/dependency_injector'
12
+ require_relative 'plugin/implicit_init'
13
+ require_relative 'plugin/output_contract'
14
+ require_relative 'plugin/profiler'
@@ -0,0 +1,159 @@
1
+ require_relative 'dependency_injector/errors'
2
+ require_relative 'dependency_injector/dependency'
3
+ require_relative 'dependency_injector/dependency_definition'
4
+ require_relative 'dependency_injector/dependency_list'
5
+
6
+ module Flows
7
+ module Plugin
8
+ # Allows to inject dependencies on the initialization step.
9
+ #
10
+ # After including this module you inject dependencies by providing `:dependencies` key
11
+ # to your initializer:
12
+ #
13
+ # x = MyClass.new(dependencies: { my_dep: -> { 'Hi' } })
14
+ # x.my_dep
15
+ # # => 'Hi'
16
+ #
17
+ # Keys are dependency names. Dependency will be injected as
18
+ # a public method with dependency name. Values are dependencies itself.
19
+ #
20
+ # You can also require some dependencies to be present.
21
+ # If required dependency is missed - {MissingDependencyError} will be raised.
22
+ #
23
+ # If an optional dependency has no default - {MissingDependencyDefaultError} will be raised.
24
+ #
25
+ # For an optional dependency default value must be provided.
26
+ #
27
+ # You can provide a type for the dependency.
28
+ # Type check uses case equality (`===`).
29
+ # So, it works like Ruby's `case`.
30
+ # In case of type mismatch {UnexpectedDependencyTypeError} will be raised.
31
+ #
32
+ # dependency :name, type: String # name should be a string
33
+ #
34
+ # # by the way, you can use lambdas like in Ruby's `case`
35
+ # dependency :age, type: ->(x) { x.is_a?(Number) && x > 0 && x < 100 }
36
+ #
37
+ # If you're trying to inject undeclared dependency - {UnexpectedDependencyError} will be raised.
38
+ #
39
+ # Inheritance is supported and dependency definitions will be inherited into child classes.
40
+ #
41
+ # @example
42
+ #
43
+ # class MyClass
44
+ # include Flows::Plugin::DependencyInjector
45
+ #
46
+ # dependency :logger, required: true
47
+ # dependency :name, default: 'Boris', type: String # by default dependency is optional.
48
+ #
49
+ # attr_reader :data
50
+ #
51
+ # def initializer(data)
52
+ # @data = data
53
+ # end
54
+ #
55
+ # def log_the_name
56
+ # logger.call(name)
57
+ # end
58
+ # end
59
+ #
60
+ # class Logger
61
+ # def self.call(msg)
62
+ # puts msg
63
+ # end
64
+ # end
65
+ #
66
+ # x = MyClass.new('DATA', dependencies: {
67
+ # logger: Logger
68
+ # })
69
+ #
70
+ # x.data
71
+ # # => 'DATA'
72
+ #
73
+ # x.name
74
+ # # => 'Boris'
75
+ #
76
+ # x.logger.call('Hello')
77
+ # # prints 'Hello'
78
+ #
79
+ # x.log_the_name
80
+ # # prints 'Boris'
81
+ module DependencyInjector
82
+ # Placeholder for empty type. We cannot use `nil` because value can be `nil`.
83
+ NO_TYPE = :__no_type__
84
+
85
+ # Placeholder for empty default. We cannot use `nil` because value can be `nil`.
86
+ NO_DEFAULT = :__no_default__
87
+
88
+ # Placeholder for empty value. We cannot use `nil` because value can be `nil`.
89
+ NO_VALUE = :__no_value__
90
+
91
+ SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
92
+ '@dependencies' => {}
93
+ )
94
+
95
+ include SingletonVarsSetup
96
+
97
+ # @api private
98
+ module DSL
99
+ attr_reader :dependencies
100
+
101
+ # `:reek:BooleanParameter` disabled here because it's not applicable for DSLs
102
+ def dependency(name, required: false, default: NO_DEFAULT, type: NO_TYPE)
103
+ dependencies[name] = DependencyDefinition.new(
104
+ name: name,
105
+ required: required,
106
+ default: default,
107
+ type: type,
108
+ klass: self
109
+ )
110
+ end
111
+ end
112
+
113
+ # @api private
114
+ #
115
+ # `:reek:UtilityFunction` and `:reek:FeatureEnvy` are disabled here because Reek does not
116
+ # know about inheritance callback stuff.
117
+ module InheritanceCallback
118
+ def included(mod)
119
+ mod.extend(DSL)
120
+
121
+ mod.singleton_class.prepend(InheritanceCallback) if mod.class == Module
122
+
123
+ super
124
+ end
125
+
126
+ def extended(mod)
127
+ mod.extend(DSL)
128
+
129
+ mod.singleton_class.prepend(InheritanceCallback) if mod.class == Module
130
+
131
+ super
132
+ end
133
+ end
134
+
135
+ singleton_class.prepend InheritanceCallback
136
+
137
+ InitializerWrapper = Util::PrependToClass.make_module do
138
+ def initialize(*args, **kwargs, &block) # rubocop:disable Metrics/MethodLength
139
+ klass = self.class
140
+ DependencyList.new(
141
+ klass: klass,
142
+ definitions: klass.dependencies,
143
+ provided_values: kwargs[:dependencies].dup || {}
144
+ ).inject_to(self)
145
+
146
+ filtered_kwargs = kwargs.reject { |key, _| key == :dependencies }
147
+
148
+ if filtered_kwargs.empty? # https://bugs.ruby-lang.org/issues/14415
149
+ super(*args, &block)
150
+ else
151
+ super(*args, **filtered_kwargs, &block)
152
+ end
153
+ end
154
+ end
155
+
156
+ include InitializerWrapper
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,24 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Resolves dependency on initialization and can inject it into class instance.
5
+ #
6
+ # @api private
7
+ Dependency = Struct.new(:name, :definition, :provided_value, :value, :klass, keyword_init: true) do
8
+ def initialize(*)
9
+ super
10
+
11
+ self.value = provided_value == NO_VALUE ? definition.default : provided_value
12
+ type = definition.type
13
+
14
+ raise UnexpectedDependencyTypeError.new(klass, name, value, type) if type != NO_TYPE && !(type === value) # rubocop:disable Style/CaseEquality
15
+ end
16
+
17
+ def inject_to(instance)
18
+ value = self.value
19
+ instance.define_singleton_method(name) { value }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Struct for storing dependency definitions.
5
+ #
6
+ # @api private
7
+ DependencyDefinition = Struct.new(:name, :required, :default, :type, :klass, keyword_init: true) do
8
+ def initialize(*)
9
+ super
10
+
11
+ raise MissingDependencyDefaultError.new(klass, name) if !required && (default == NO_DEFAULT)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Resolves dependencies on initialization and can inject it into class instance.
5
+ #
6
+ # @api private
7
+ class DependencyList
8
+ attr_reader :definitions
9
+ attr_reader :provided_values
10
+ attr_reader :dependencies
11
+
12
+ def initialize(klass:, definitions:, provided_values:)
13
+ @klass = klass
14
+ @definitions = definitions
15
+ @provided_values = provided_values.dup.tap { |pv| pv.default = NO_VALUE }
16
+
17
+ check_missing_dependencies
18
+ check_unexpected_dependencies
19
+ resolve_dependencies
20
+ end
21
+
22
+ def inject_to(instance)
23
+ dependencies.each { |dep| dep.inject_to(instance) }
24
+ end
25
+
26
+ private
27
+
28
+ def required_dependencies
29
+ definitions.select { |_, definition| definition.required }.keys
30
+ end
31
+
32
+ def check_missing_dependencies
33
+ missing = required_dependencies - provided_values.keys
34
+
35
+ raise MissingDependencyError.new(@klass, missing) if missing.any?
36
+ end
37
+
38
+ def check_unexpected_dependencies
39
+ unexpected = provided_values.keys - definitions.keys
40
+
41
+ raise UnexpectedDependencyError.new(@klass, unexpected) if unexpected.any?
42
+ end
43
+
44
+ def resolve_dependencies
45
+ @dependencies = definitions.map do |name, definition|
46
+ Dependency.new(
47
+ klass: @klass,
48
+ name: name,
49
+ definition: definition,
50
+ provided_value: provided_values[name]
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,58 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Base error class for dependency injection errors.
5
+ class Error < ::Flows::Error; end
6
+
7
+ # Raised when you're missed some dependency.
8
+ class MissingDependencyError < Error
9
+ def initialize(klass, names)
10
+ @klass = klass
11
+ @names = names
12
+ end
13
+
14
+ def message
15
+ "Missing dependency(ies) for #{@klass}: #{@names.map(&:to_s).join(', ')}"
16
+ end
17
+ end
18
+
19
+ # Raised when you're providing undeclared dependency.
20
+ class UnexpectedDependencyError < Error
21
+ def initialize(klass, names)
22
+ @klass = klass
23
+ @names = names
24
+ end
25
+
26
+ def message
27
+ "Unexpected dependency(ies) for #{@klass}: #{@names.map(&:to_s).join(', ')}"
28
+ end
29
+ end
30
+
31
+ # Raised when dependency has unexpected type.
32
+ class UnexpectedDependencyTypeError < Error
33
+ def initialize(klass, name, value, type)
34
+ @klass = klass
35
+ @_name = name
36
+ @value = value
37
+ @_type = type
38
+ end
39
+
40
+ def message
41
+ "#{@_name} dependency for #{@klass} has wrong type, must conform `#{@_type.inspect}`: `#{@value.inspect}`"
42
+ end
43
+ end
44
+
45
+ # Raised when an optional dependency has no default value.
46
+ class MissingDependencyDefaultError < Error
47
+ def initialize(klass, name)
48
+ @klass = klass
49
+ @_name = name
50
+ end
51
+
52
+ def message
53
+ "Optional dependency #{@_name} for #{@klass} has no default value"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ module Flows
2
+ module Plugin
3
+ # Class extension with method `MyClass.call` which works like `MyClass.new.call`.
4
+ #
5
+ # @note This module must be injected into target class using `extend`, not `include`.
6
+ #
7
+ # @note Class inheritance is supported: each child class will inherit behaviour, but not data.
8
+ #
9
+ # @example Extending a class
10
+ # class SomeClass
11
+ # extend Flows::Plugin::ImplicitInit
12
+ #
13
+ # def initialize(param: 'default')
14
+ # @param = param
15
+ # end
16
+ #
17
+ # def call
18
+ # @param
19
+ # end
20
+ # end
21
+ #
22
+ # SomeClass.call
23
+ # # => 'default'
24
+ #
25
+ # SomeClass.default_instance.call
26
+ # # => 'default'
27
+ # @since 0.4.0
28
+ module ImplicitInit
29
+ # Contains memoized instance of a host class or `nil`.
30
+ attr_reader :default_instance
31
+
32
+ # Creates an instance of a host class by calling `new` without arguments and
33
+ # calls `#call` method on the instance with provided parameters and block.
34
+ #
35
+ # After first invocation the instance will be memoized in {.default_instance}.
36
+ #
37
+ # Child classes have separate default instances.
38
+ def call(*args, **kwargs, &block)
39
+ @default_instance ||= new
40
+
41
+ default_instance.call(*args, **kwargs, &block)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,85 @@
1
+ require_relative 'output_contract/errors'
2
+ require_relative 'output_contract/dsl'
3
+ require_relative 'output_contract/wrapper'
4
+
5
+ module Flows
6
+ module Plugin
7
+ # Allows to make a contract check and transformation for `#call` method execution in any class.
8
+ #
9
+ # Plugin applies a wrapper to a `#call` instance method.
10
+ # This wrapper will do the following:
11
+ #
12
+ # * check that {Result} instance is returned by `#call`
13
+ # * check that returned {Result#status} is expected
14
+ # * check that returned result data conforms {Contract} assigned
15
+ # to a particular result type and status
16
+ # * applies contract transform to the returned data
17
+ # * returns {Result} with the same status and type,
18
+ # wraps transformed data inside.
19
+ #
20
+ # Plugin provides DSL to express expected result statuses and assigned contracts.
21
+ # Contracts definition reuses {Contract.make} to execute block and get a contract.
22
+ #
23
+ # * `success_with(status, &block)` - defines contract for a successful result with status `status`.
24
+ # * `failure_with(status, &block)` - defines contract for a failure result with status `status`.
25
+ # * `skip_output_contract` - disables contract check and transformation for current class and children.
26
+ #
27
+ # @example with one possible output contract
28
+ # class DoJob
29
+ # include Flows::Result::Helpers
30
+ # include Flows::Plugin::OutputContract
31
+ #
32
+ # success_with :ok do
33
+ # Integer
34
+ # end
35
+ #
36
+ # def call(a, b)
37
+ # ok_data(a + b)
38
+ # end
39
+ # end
40
+ #
41
+ # DoJob.new.call(1, 2).unwrap
42
+ # # => 3
43
+ #
44
+ # DoJob.new.call('a', 'b')
45
+ # # Flows::Contract::Error exception raised
46
+ #
47
+ # @example with multiple contracts
48
+ # class DoJob
49
+ # include Flows::Result::Helpers
50
+ # include Flows::Plugin::OutputContract
51
+ #
52
+ # success_with :int_sum do
53
+ # Integer
54
+ # end
55
+ #
56
+ # success_with :float_sum do
57
+ # Float
58
+ # end
59
+ #
60
+ # failure_with :err do
61
+ # hash_of(
62
+ # key: Symbol,
63
+ # msg: String
64
+ # )
65
+ # end
66
+ #
67
+ # def call(a, b)
68
+ # if a.is_a?(Float) || b.is_a?(Float)
69
+ # ok_data(a + b, status: :float_sum)
70
+ # elsif a.is_a?(Integer) && b.is_a?(Integer)
71
+ # ok_data(a + b, status: :int_sum)
72
+ # else
73
+ # err(key: :unexpected_type, msg: "Unexpected argument types")
74
+ # end
75
+ # end
76
+ # end
77
+ module OutputContract
78
+ # @api private
79
+ def self.included(mod)
80
+ mod.extend(DSL)
81
+ mod.prepend(Wrapper)
82
+ end
83
+ end
84
+ end
85
+ end