diff --git a/codequest_pipes.gemspec b/codequest_pipes.gemspec index 0cdda07..e312bcb 100644 --- a/codequest_pipes.gemspec +++ b/codequest_pipes.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = 'codequest_pipes' - spec.version = '0.3.1.1' + spec.version = '0.3.2' spec.author = 'codequest' spec.email = 'hello@codequest.com' diff --git a/lib/codequest_pipes/context.rb b/lib/codequest_pipes/context.rb index 0fcc7ba..155c1e1 100644 --- a/lib/codequest_pipes/context.rb +++ b/lib/codequest_pipes/context.rb @@ -1,10 +1,10 @@ +require 'codequest_pipes/context/error_collector' + module Pipes # Context is an object used to pass data between Pipes. It behaves like an # OpenStruct except you can write a value only once - this way we prevent # context keys from being overwritten. class Context - attr_reader :error - # Override is an exception raised when an attempt is made to override an # existing Context property. class Override < ::StandardError; end @@ -18,7 +18,7 @@ class ExecutionTerminated < ::StandardError; end # @param values [Hash] def initialize(values = {}) add(values) - @error = nil + @error_collector = ErrorCollector.new end # Method `add` allows adding new properties (as a Hash) to the Context. @@ -32,23 +32,20 @@ def add(values) end end - # Quietly fail the pipe, allowing the error to be saved and accessed from - # the Context. + # Quietly fail the pipe. The error will be passed to the error_collector + # and stored in the :base errors collection. # - # @param error [Any] Error to be set. + ## @param error [String] def halt(error = 'Execution stopped') - @error = error + add_errors(base: error) end - # Explicitly fail the pipe, allowing the error to be saved and accessed from - # the Context. - # - # @param error [Any] Error to be set. + # Explicitly fail the pipe. # # @raise [ExecutionTerminated] def terminate(error) halt(error) - fail ExecutionTerminated, error + fail ExecutionTerminated end # Check if the Context finished successfully. @@ -56,7 +53,7 @@ def terminate(error) # # @return [Boolean] Success status. def success? - @error.nil? + errors.empty? end # Check if the Context failed. @@ -73,9 +70,37 @@ def failure? def inspect keys = methods - Object.methods - Pipes::Context.instance_methods fields = keys.map { |key| "#{key}=#{public_send(key).inspect}" } - fields << "@error=#{@error.inspect}" + fields << "@errors=#{@errors.inspect}" object_id_hex = '%x' % (object_id << 1) "#" end + + # Return errors from ErrorCollector object. + # + # @return [Hash] + def errors + error_collector.errors + end + + # This method is added to maintain backwards compatibility - previous + # versions implemented a single @error instance variable of String for error + # storage. + # + # @return [String] + def error + errors[:base]&.first + end + + # Add errors to ErrorCollector object. + # It doesn't fail the pipe as opposed to `halt` and `terminate` methods. + # + # @param collectable_errors [Hash] + def add_errors(collectable_errors) + error_collector.add(collectable_errors) + end + + private + + attr_reader :error_collector end # class Context end # module Pipes diff --git a/lib/codequest_pipes/context/error_collector.rb b/lib/codequest_pipes/context/error_collector.rb new file mode 100644 index 0000000..60a048c --- /dev/null +++ b/lib/codequest_pipes/context/error_collector.rb @@ -0,0 +1,20 @@ +module Pipes + class Context + # ErrorCollector is Context's companion object for storing non-critical + # errors. + class ErrorCollector + attr_reader :errors + + def initialize + @errors = {} + end + + def add(errors_hash) + errors_hash.map do |key, errors| + @errors[key] ||= [] + @errors[key] = @errors[key] | Array(errors) + end + end + end # class ErrorColletor + end # class Context +end # module Pipes diff --git a/lib/codequest_pipes/pipe.rb b/lib/codequest_pipes/pipe.rb index d2eff11..4a75b24 100644 --- a/lib/codequest_pipes/pipe.rb +++ b/lib/codequest_pipes/pipe.rb @@ -20,7 +20,7 @@ def self.|(other) end def self.call(ctx) - return ctx if ctx.error + return ctx if ctx.errors.any? _validate_ctx(_required_context_elements, ctx) new(ctx).call _validate_ctx(_provided_context_elements, ctx) diff --git a/spec/context_spec.rb b/spec/context_spec.rb index bd632cc..0982f67 100644 --- a/spec/context_spec.rb +++ b/spec/context_spec.rb @@ -18,13 +18,34 @@ it 'lists all fields' do subject.add(bacon: 'yum', raisins: 'bleh') expect(subject.inspect) - .to match(/bacon=\"yum\", raisins=\"bleh\", @error=nil/) + .to match(/bacon=\"yum\", raisins=\"bleh\", @errors=nil/) end it 'lists nested contexts' do subject.add(nested: Pipes::Context.new(foo: 'bar')) expect(subject.inspect) - .to match(/nested=#,/) + .to match(/nested=#,/) end end # describe '#inspect' + + describe '#add_errors' do + it 'adds error to error_collector' do + subject.add_errors(base: 'Error message') + subject.add_errors( + base: ['Another error message'], + user: 'User error message' + ) + expect(subject.errors).to eq( + base: ['Error message', 'Another error message'], + user: ['User error message'] + ) + end + end # describe '#add_errors' + + describe '#halt' do + it 'adds error to error collector :base' do + subject.halt('Some error') + expect(subject.error).to eq('Some error') + end + end # describe '#halt' end # describe Pipes::Context diff --git a/spec/matcher_spec.rb b/spec/matcher_spec.rb index 71f2c2d..3ad7b55 100644 --- a/spec/matcher_spec.rb +++ b/spec/matcher_spec.rb @@ -13,7 +13,7 @@ shared_examples_for 'fails_with_message' do |message| it 'fails' do expected_message = - message || /expected # to match/ + message || /expected # to match/ expect { expect(ctx).to match(pipe_context(expected)) } .to fail_with(expected_message) end