spec_forge 0.7.1 → 1.0.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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -1
  3. data/README.md +124 -202
  4. data/bin/spec_forge +1 -1
  5. data/flake.lock +76 -4
  6. data/flake.nix +5 -4
  7. data/lib/spec_forge/attribute/chainable.rb +6 -6
  8. data/lib/spec_forge/attribute/environment.rb +45 -0
  9. data/lib/spec_forge/attribute/factory.rb +26 -17
  10. data/lib/spec_forge/attribute/faker.rb +6 -1
  11. data/lib/spec_forge/attribute/generate.rb +114 -0
  12. data/lib/spec_forge/attribute/literal.rb +1 -14
  13. data/lib/spec_forge/attribute/matcher.rb +6 -2
  14. data/lib/spec_forge/attribute/parameterized.rb +20 -22
  15. data/lib/spec_forge/attribute/resolvable_array.rb +16 -16
  16. data/lib/spec_forge/attribute/resolvable_hash.rb +17 -16
  17. data/lib/spec_forge/attribute/resolvable_struct.rb +67 -0
  18. data/lib/spec_forge/attribute/template.rb +118 -0
  19. data/lib/spec_forge/attribute/transform.rb +14 -19
  20. data/lib/spec_forge/attribute/variable.rb +31 -31
  21. data/lib/spec_forge/attribute.rb +54 -100
  22. data/lib/spec_forge/blueprint.rb +27 -0
  23. data/lib/spec_forge/cli/docs/generate.rb +28 -8
  24. data/lib/spec_forge/cli/docs.rb +5 -2
  25. data/lib/spec_forge/cli/init.rb +4 -4
  26. data/lib/spec_forge/cli/new.rb +78 -27
  27. data/lib/spec_forge/cli/run.rb +84 -52
  28. data/lib/spec_forge/cli/serve.rb +5 -0
  29. data/lib/spec_forge/cli.rb +6 -14
  30. data/lib/spec_forge/configuration.rb +209 -79
  31. data/lib/spec_forge/documentation/{loader → builder}/cache.rb +26 -23
  32. data/lib/spec_forge/documentation/builder/compiler.rb +373 -0
  33. data/lib/spec_forge/documentation/builder/extractor.rb +75 -0
  34. data/lib/spec_forge/documentation/builder.rb +77 -329
  35. data/lib/spec_forge/documentation/document/operation.rb +4 -4
  36. data/lib/spec_forge/documentation/document.rb +0 -6
  37. data/lib/spec_forge/documentation/generator.rb +88 -0
  38. data/lib/spec_forge/documentation/{generators/openapi → openapi/v3_0}/error_formatter.rb +2 -2
  39. data/lib/spec_forge/documentation/openapi/v3_0/example.rb +1 -1
  40. data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +1 -1
  41. data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +21 -5
  42. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +28 -6
  43. data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +20 -2
  44. data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +1 -1
  45. data/lib/spec_forge/documentation/openapi/v3_0.rb +116 -0
  46. data/lib/spec_forge/documentation/openapi.rb +40 -12
  47. data/lib/spec_forge/documentation.rb +1 -7
  48. data/lib/spec_forge/error.rb +215 -41
  49. data/lib/spec_forge/factory.rb +38 -18
  50. data/lib/spec_forge/forge/action.rb +41 -0
  51. data/lib/spec_forge/forge/actions/call.rb +33 -0
  52. data/lib/spec_forge/forge/actions/debug.rb +47 -0
  53. data/lib/spec_forge/forge/actions/expect.rb +44 -0
  54. data/lib/spec_forge/forge/actions/request.rb +65 -0
  55. data/lib/spec_forge/forge/actions/store.rb +31 -0
  56. data/lib/spec_forge/forge/callbacks.rb +80 -0
  57. data/lib/spec_forge/forge/context.rb +41 -0
  58. data/lib/spec_forge/forge/display.rb +503 -0
  59. data/lib/spec_forge/forge/hooks.rb +131 -0
  60. data/lib/spec_forge/forge/runner/array_io.rb +81 -0
  61. data/lib/spec_forge/forge/runner/content_validator.rb +92 -0
  62. data/lib/spec_forge/forge/runner/header_validator.rb +66 -0
  63. data/lib/spec_forge/forge/runner/reporter.rb +56 -0
  64. data/lib/spec_forge/forge/runner/schema_validator.rb +113 -0
  65. data/lib/spec_forge/forge/runner.rb +118 -0
  66. data/lib/spec_forge/forge/timer.rb +94 -0
  67. data/lib/spec_forge/forge/variables.rb +38 -0
  68. data/lib/spec_forge/forge.rb +207 -133
  69. data/lib/spec_forge/http/backend.rb +49 -146
  70. data/lib/spec_forge/http/client.rb +14 -17
  71. data/lib/spec_forge/http/request.rb +37 -84
  72. data/lib/spec_forge/http/verb.rb +4 -0
  73. data/lib/spec_forge/http.rb +0 -5
  74. data/lib/spec_forge/loader/filter.rb +85 -0
  75. data/lib/spec_forge/loader/step_processor.rb +282 -0
  76. data/lib/spec_forge/loader.rb +105 -220
  77. data/lib/spec_forge/normalizer/default.rb +1 -1
  78. data/lib/spec_forge/normalizer/structure.rb +140 -0
  79. data/lib/spec_forge/normalizer/transformers.rb +168 -0
  80. data/lib/spec_forge/normalizer/validators.rb +50 -8
  81. data/lib/spec_forge/normalizer.rb +76 -119
  82. data/lib/spec_forge/normalizers/callback.yml +38 -0
  83. data/lib/spec_forge/normalizers/configuration.yml +59 -9
  84. data/lib/spec_forge/normalizers/factory.yml +53 -2
  85. data/lib/spec_forge/normalizers/factory_reference.yml +63 -2
  86. data/lib/spec_forge/normalizers/json_schema.yml +79 -0
  87. data/lib/spec_forge/normalizers/step.yml +506 -0
  88. data/lib/spec_forge/step/call.rb +36 -0
  89. data/lib/spec_forge/step/expect.rb +110 -0
  90. data/lib/spec_forge/step/source.rb +22 -0
  91. data/lib/spec_forge/step.rb +129 -0
  92. data/lib/spec_forge/type.rb +115 -66
  93. data/lib/spec_forge/version.rb +1 -1
  94. data/lib/spec_forge.rb +44 -106
  95. data/lib/templates/forge_helper.rb.tt +43 -22
  96. data/lib/templates/new_blueprint.yml.tt +54 -0
  97. metadata +75 -44
  98. data/lib/spec_forge/attribute/global.rb +0 -96
  99. data/lib/spec_forge/attribute/store.rb +0 -65
  100. data/lib/spec_forge/backtrace_formatter.rb +0 -50
  101. data/lib/spec_forge/callbacks.rb +0 -88
  102. data/lib/spec_forge/context/callbacks.rb +0 -91
  103. data/lib/spec_forge/context/global.rb +0 -72
  104. data/lib/spec_forge/context/store.rb +0 -131
  105. data/lib/spec_forge/context/variables.rb +0 -91
  106. data/lib/spec_forge/context.rb +0 -36
  107. data/lib/spec_forge/core_ext/rspec.rb +0 -55
  108. data/lib/spec_forge/core_ext.rb +0 -5
  109. data/lib/spec_forge/documentation/generators/base.rb +0 -81
  110. data/lib/spec_forge/documentation/generators/openapi/base.rb +0 -100
  111. data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +0 -65
  112. data/lib/spec_forge/documentation/generators/openapi.rb +0 -59
  113. data/lib/spec_forge/documentation/generators.rb +0 -17
  114. data/lib/spec_forge/documentation/loader.rb +0 -159
  115. data/lib/spec_forge/documentation/openapi/base.rb +0 -33
  116. data/lib/spec_forge/filter.rb +0 -86
  117. data/lib/spec_forge/normalizer/definition.rb +0 -248
  118. data/lib/spec_forge/normalizers/_shared.yml +0 -76
  119. data/lib/spec_forge/normalizers/constraint.yml +0 -8
  120. data/lib/spec_forge/normalizers/expectation.yml +0 -47
  121. data/lib/spec_forge/normalizers/global_context.yml +0 -28
  122. data/lib/spec_forge/normalizers/spec.yml +0 -50
  123. data/lib/spec_forge/runner/adapter.rb +0 -181
  124. data/lib/spec_forge/runner/callbacks.rb +0 -246
  125. data/lib/spec_forge/runner/debug_proxy.rb +0 -215
  126. data/lib/spec_forge/runner/listener.rb +0 -54
  127. data/lib/spec_forge/runner/metadata.rb +0 -58
  128. data/lib/spec_forge/runner/state.rb +0 -98
  129. data/lib/spec_forge/runner.rb +0 -75
  130. data/lib/spec_forge/spec/expectation/constraint.rb +0 -127
  131. data/lib/spec_forge/spec/expectation.rb +0 -68
  132. data/lib/spec_forge/spec.rb +0 -68
  133. data/lib/templates/new_spec.yml.tt +0 -43
@@ -25,6 +25,8 @@ module SpecForge
25
25
  # Loads factory definitions from YAML files
26
26
  # Creates Factory instances but doesn't register them with FactoryBot
27
27
  #
28
+ # Factory names are derived from the filename (e.g., user.yml -> :user)
29
+ #
28
30
  # @return [Array<Factory>] Array of loaded factory instances
29
31
  #
30
32
  def self.load_from_files
@@ -35,11 +37,11 @@ module SpecForge
35
37
  Dir[path].each do |file_path|
36
38
  hash = YAML.load_file(file_path, symbolize_names: true)
37
39
 
38
- hash.each do |factory_name, factory_hash|
39
- factory_hash[:name] = factory_name
40
+ # Extract factory name from filename (e.g., "user.yml" -> :user)
41
+ factory_name = File.basename(file_path, ".yml").to_sym
42
+ hash[:name] = factory_name
40
43
 
41
- factories << new(**factory_hash)
42
- end
44
+ factories << new(**hash)
43
45
  end
44
46
 
45
47
  factories
@@ -62,6 +64,9 @@ module SpecForge
62
64
  # @return [Hash<Symbol, Attribute>] The attributes that define this factory
63
65
  attr_reader :attributes
64
66
 
67
+ # @return [Hash<Symbol, Hash<Symbol, Attribute>>] Traits defined for this factory
68
+ attr_reader :traits
69
+
65
70
  #
66
71
  # Creates a new Factory instance
67
72
  #
@@ -77,8 +82,9 @@ module SpecForge
77
82
  @input = input
78
83
  @model_class = input[:model_class]
79
84
 
80
- @variables = extract_variables(input)
81
- @attributes = extract_attributes(input)
85
+ @variables = Attribute.from(input[:variables])
86
+ @attributes = Attribute.from(input[:attributes], context: @variables)
87
+ @traits = extract_traits(input[:traits], context: @variables)
82
88
  end
83
89
 
84
90
  #
@@ -91,13 +97,23 @@ module SpecForge
91
97
  dsl = FactoryBot::Syntax::Default::DSL.new
92
98
 
93
99
  options = {}
94
- options[:class] = model_class if model_class
100
+ options[:class] = model_class if model_class.present?
95
101
 
96
102
  # This creates the factory in FactoryBot
97
103
  factory_forge = self
98
104
  dsl.factory(name, options) do
99
- factory_forge.attributes.each do |name, attribute|
100
- add_attribute(name) { attribute.resolve }
105
+ # Register base attributes
106
+ factory_forge.attributes.each do |attr_name, attribute|
107
+ add_attribute(attr_name) { attribute.resolve }
108
+ end
109
+
110
+ # Register traits
111
+ factory_forge.traits.each do |trait_name, trait_attributes|
112
+ trait(trait_name) do
113
+ trait_attributes.each do |attr_name, attribute|
114
+ add_attribute(attr_name) { attribute.resolve }
115
+ end
116
+ end
101
117
  end
102
118
  end
103
119
 
@@ -106,16 +122,20 @@ module SpecForge
106
122
 
107
123
  private
108
124
 
109
- def extract_variables(input)
110
- variables = Attribute.from(input[:variables])
111
-
112
- # Update the variables that reference other variables lol
113
- Attribute.bind_variables(variables, variables)
114
- end
125
+ #
126
+ # Extracts and processes trait definitions from the input hash
127
+ # Trait definitions are flat hashes of attributes (no nested "attributes:" key)
128
+ #
129
+ # @param traits_hash [Hash] Raw trait definitions from YAML
130
+ # @param context [Hash] Variable context for attribute resolution
131
+ # @return [Hash<Symbol, Hash<Symbol, Attribute>>] Processed traits with Attribute values
132
+ #
133
+ def extract_traits(traits_hash, context: nil)
134
+ return {} if traits_hash.blank?
115
135
 
116
- def extract_attributes(input)
117
- attributes = Attribute.from(input[:attributes])
118
- Attribute.bind_variables(attributes, variables)
136
+ traits_hash.transform_values do |trait_attributes|
137
+ Attribute.from(trait_attributes || {}, context:)
138
+ end
119
139
  end
120
140
  end
121
141
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Base class for step actions
7
+ #
8
+ # Actions are the executable units within a step. Each action type
9
+ # (Call, Debug, Expect, Request, Store) inherits from this base class
10
+ # and implements the run method to perform its specific behavior.
11
+ #
12
+ class Action
13
+ # @return [Step] The step being executed
14
+ attr_reader :step
15
+
16
+ #
17
+ # Creates a new action for the given step
18
+ #
19
+ # @param step [Step] The step this action belongs to
20
+ #
21
+ # @return [Action] A new action instance
22
+ #
23
+ def initialize(step)
24
+ @step = step
25
+ end
26
+
27
+ #
28
+ # Executes the action
29
+ #
30
+ # @param _forge [Forge] The forge instance executing this action
31
+ #
32
+ # @return [void]
33
+ #
34
+ # @raise [RuntimeError] If not implemented by subclass
35
+ #
36
+ def run(_forge)
37
+ raise "not implemented"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Action for the `call:` step attribute
7
+ #
8
+ # Executes registered callbacks defined in forge_helper.rb during step processing.
9
+ # Callbacks can receive the current context and optional arguments.
10
+ #
11
+ class Call < Action
12
+ #
13
+ # Executes all callbacks defined in the step's call: attribute
14
+ #
15
+ # @param forge [Forge] The forge instance for accessing callbacks and display
16
+ #
17
+ # @return [void]
18
+ #
19
+ def run(forge)
20
+ context = SpecForge::Forge.context
21
+
22
+ step.calls.each do |call|
23
+ callback_name = call.callback_name
24
+ arguments = call.arguments
25
+
26
+ forge.display.action("Call #{callback_name}", symbol: :checkmark, symbol_styles: :yellow)
27
+
28
+ forge.callbacks.run(callback_name, context, arguments)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Action for the `debug:` step attribute
7
+ #
8
+ # Triggers a debug breakpoint when a step has debug: true, invoking the
9
+ # configured debug handler (e.g., binding.pry) to allow interactive debugging.
10
+ #
11
+ class Debug < Action
12
+ #
13
+ # Triggers the debug breakpoint
14
+ #
15
+ # @param forge [Forge] The forge instance
16
+ #
17
+ # @return [void]
18
+ #
19
+ # @raise [Error] If no debug handler is configured
20
+ #
21
+ def run(forge, blueprint)
22
+ forge.display.action("Debug breakpoint triggered", symbol: :flag, symbol_styles: :yellow)
23
+
24
+ callback = SpecForge.configuration.on_debug_proc
25
+ if callback.nil?
26
+ raise Error, <<~STRING
27
+ Debug breakpoint triggered but no debug handler is configured.
28
+
29
+ Add a debug handler in your forge_helper.rb:
30
+
31
+ SpecForge.configure do |config|
32
+ config.on_debug { binding.pry } # or byebug, debug, etc.
33
+ end
34
+
35
+ STRING
36
+ end
37
+
38
+ if callback.arity == 1
39
+ context = SpecForge::Forge.context.with(forge:, blueprint:, step:)
40
+ callback.call(context)
41
+ else
42
+ callback.call
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Action for the `expect:` step attribute
7
+ #
8
+ # Runs expectations against the current response, validating status codes,
9
+ # headers, and JSON body against expected values using RSpec matchers.
10
+ #
11
+ class Expect < Action
12
+ #
13
+ # Runs all expectations for the step against the current response
14
+ #
15
+ # @param forge [Forge] The forge instance containing response data
16
+ #
17
+ # @return [void]
18
+ #
19
+ # @raise [Error::ExpectationFailure] If any expectations fail
20
+ #
21
+ def run(forge)
22
+ show_expectation_count = step.expects.size > 1
23
+
24
+ failed_examples =
25
+ step.expects.flat_map.with_index do |expectation, index|
26
+ failed = forge.runner.run(forge, step, expectation)
27
+
28
+ forge.display.expectation_finished(
29
+ failed_examples: failed,
30
+ total_count: expectation.size,
31
+ index: index + 1,
32
+ show_index: show_expectation_count
33
+ )
34
+
35
+ failed
36
+ end
37
+
38
+ return if failed_examples.empty?
39
+
40
+ raise Error::ExpectationFailure.new(failed_examples)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Action for the `request:` step attribute
7
+ #
8
+ # Builds and executes an HTTP request based on the step's configuration,
9
+ # then stores both the request and response in the forge's variables for
10
+ # use by subsequent actions.
11
+ #
12
+ class Request < Action
13
+ #
14
+ # Executes the HTTP request and stores the response
15
+ #
16
+ # @param forge [Forge] The forge instance
17
+ #
18
+ # @return [void]
19
+ #
20
+ def run(forge)
21
+ sendable_request, resolved_request = build_requests
22
+
23
+ forge.display.action(
24
+ "#{sendable_request.http_verb} #{sendable_request.url}",
25
+ symbol: :right_arrow, symbol_styles: :yellow
26
+ )
27
+
28
+ response = forge.http_client.perform(sendable_request)
29
+ response = parse_response(response)
30
+
31
+ # Only store the original resolved request before we modify it
32
+ forge.variables[:request] = resolved_request
33
+ forge.variables[:response] = response
34
+ end
35
+
36
+ private
37
+
38
+ def build_requests
39
+ resolved_request = step.request.to_h.transform_values { |v| v.respond_to?(:resolved) ? v.resolved : v }
40
+ resolved_request[:base_url] = SpecForge.configuration.base_url if resolved_request[:base_url].blank?
41
+
42
+ request = resolved_request.deep_dup
43
+ request[:body] =
44
+ if step.request.json?
45
+ request[:body].to_json
46
+ else
47
+ request[:body].to_s
48
+ end
49
+
50
+ [HTTP::Request.new(**request), resolved_request]
51
+ end
52
+
53
+ def parse_response(response)
54
+ response.to_hash.tap do |response|
55
+ response[:headers] = response.delete(:response_headers)
56
+
57
+ case response[:headers]["content-type"]
58
+ when "application/json"
59
+ response[:body] = response[:body].to_h
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Action for the `store:` step attribute
7
+ #
8
+ # Resolves attribute values and stores them as variables for use by
9
+ # subsequent steps. Can store literal values or extract values from
10
+ # the response using template syntax.
11
+ #
12
+ class Store < Action
13
+ #
14
+ # Stores all configured values in the forge's variables
15
+ #
16
+ # @param forge [Forge] The forge instance
17
+ #
18
+ # @return [void]
19
+ #
20
+ def run(forge)
21
+ step.store.each do |name, value|
22
+ event = forge.variables.key?(name) ? "Update" : "Store"
23
+
24
+ forge.display.action("#{event} #{name.in_quotes}", symbol: :flag, symbol_styles: :bright_cyan)
25
+
26
+ forge.variables[name] = value.resolved
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Manages registered callbacks that can be invoked from blueprints
7
+ #
8
+ # Callbacks are Ruby blocks registered in forge_helper.rb that can be
9
+ # called during step execution using the call: attribute.
10
+ #
11
+ class Callbacks
12
+ #
13
+ # Creates a new empty callback registry
14
+ #
15
+ # @return [Callbacks] A new callbacks instance
16
+ #
17
+ def initialize
18
+ @callbacks = {}
19
+ end
20
+
21
+ #
22
+ # Registers a callback with the given name
23
+ #
24
+ # @param name [String, Symbol] The name to register the callback under
25
+ #
26
+ # @yield The block to execute when the callback is invoked
27
+ #
28
+ # @raise [ArgumentError] If no block is provided
29
+ #
30
+ def register(name, &block)
31
+ raise ArgumentError, "A block must be provided" unless block.is_a?(Proc)
32
+
33
+ if registered?(name)
34
+ warn("Callback #{name.in_quotes} is already registered. It will be overwritten")
35
+ end
36
+
37
+ @callbacks[name.to_sym] = block
38
+ end
39
+
40
+ #
41
+ # Checks if a callback with the given name has been registered
42
+ #
43
+ # @param name [String, Symbol] The callback name to check
44
+ #
45
+ # @return [Boolean] Whether the callback is registered
46
+ #
47
+ def registered?(name)
48
+ @callbacks.key?(name.to_sym)
49
+ end
50
+
51
+ #
52
+ # Executes a registered callback by name
53
+ #
54
+ # @param name [String, Symbol] The callback name to execute
55
+ # @param context [Forge::Context, nil] The current execution context
56
+ # @param arguments [Array, Hash] Arguments to pass to the callback
57
+ #
58
+ # @return [Object] The return value of the callback
59
+ #
60
+ # @raise [Error::UndefinedCallbackError] If the callback is not registered
61
+ #
62
+ def run(name, context = nil, arguments = [])
63
+ raise Error::UndefinedCallbackError.new(name, @callbacks.keys) unless registered?(name)
64
+
65
+ callback = @callbacks[name.to_sym]
66
+
67
+ # No arguments? Just call
68
+ return callback.call if callback.arity == 0
69
+ return callback.call(context) if callback.arity == 1
70
+
71
+ case arguments
72
+ when Array
73
+ callback.call(context, *arguments)
74
+ when Hash
75
+ callback.call(context, **arguments)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Forge
5
+ #
6
+ # Immutable execution context passed to callbacks and hooks
7
+ #
8
+ # Contains the current state during forge execution including
9
+ # variables, the current blueprint and step, and any error that occurred.
10
+ #
11
+ class Context < Data.define(:variables, :forge, :blueprint, :step, :error)
12
+ def initialize(**context)
13
+ context[:variables] ||= nil
14
+ context[:forge] ||= nil
15
+ context[:blueprint] ||= nil
16
+ context[:step] ||= nil
17
+ context[:error] ||= nil
18
+
19
+ super(context)
20
+ end
21
+
22
+ #
23
+ # Returns whether the context represents a successful state
24
+ #
25
+ # @return [Boolean] True if no error is present
26
+ #
27
+ def success?
28
+ error.nil?
29
+ end
30
+
31
+ #
32
+ # Returns whether the context represents a failed state
33
+ #
34
+ # @return [Boolean] True if an error is present
35
+ #
36
+ def failure?
37
+ !success?
38
+ end
39
+ end
40
+ end
41
+ end