From 4f9c43f417156f58fe9412fbd8fd53fef50387f2 Mon Sep 17 00:00:00 2001 From: pjanek Date: Fri, 1 Mar 2019 18:15:30 +0100 Subject: [PATCH 1/3] ErrorCollector for Pipes::Context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Przemek BieliƄski --- lib/codequest_pipes/context.rb | 53 +++++++++++++------ .../context/error_collector.rb | 20 +++++++ lib/codequest_pipes/pipe.rb | 2 +- spec/context_spec.rb | 18 ++++++- spec/matcher_spec.rb | 2 +- 5 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 lib/codequest_pipes/context/error_collector.rb diff --git a/lib/codequest_pipes/context.rb b/lib/codequest_pipes/context.rb index 0fcc7ba..18719ec 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,23 @@ def add(values) end end - # Quietly fail the pipe, allowing the error to be saved and accessed from - # the Context. - # - # @param error [Any] Error to be set. - def halt(error = 'Execution stopped') - @error = error + # Quietly fail the pipe. + def halt + @halted = true end - # Explicitly fail the pipe, allowing the error to be saved and accessed from - # the Context. + # Check if the Context is halted. # - # @param error [Any] Error to be set. + # @return [Boolean] halt status. + def halted? + @halted + end + + # Explicitly fail the pipe. # # @raise [ExecutionTerminated] - def terminate(error) - halt(error) - fail ExecutionTerminated, error + def terminate + fail ExecutionTerminated end # Check if the Context finished successfully. @@ -56,7 +56,7 @@ def terminate(error) # # @return [Boolean] Success status. def success? - @error.nil? + errors.empty? end # Check if the Context failed. @@ -73,9 +73,28 @@ 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 + + # 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..d0ff7fd 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.halted? _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..aaa397c 100644 --- a/spec/context_spec.rb +++ b/spec/context_spec.rb @@ -18,13 +18,27 @@ 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' 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 From e385266f0cf3701f0977291146b41796e01477de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemek=20Bielin=CC=81ski?= Date: Mon, 4 Mar 2019 11:55:55 +0100 Subject: [PATCH 2/3] Store error passed to 'halt' in the error collectors errors hash in the :base key. Add 'error' method returning this error for backwards compatibility. --- lib/codequest_pipes/context.rb | 26 ++++++++++++++++---------- lib/codequest_pipes/pipe.rb | 2 +- spec/context_spec.rb | 7 +++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/codequest_pipes/context.rb b/lib/codequest_pipes/context.rb index 18719ec..155c1e1 100644 --- a/lib/codequest_pipes/context.rb +++ b/lib/codequest_pipes/context.rb @@ -32,22 +32,19 @@ def add(values) end end - # Quietly fail the pipe. - def halt - @halted = true - end - - # Check if the Context is halted. + # Quietly fail the pipe. The error will be passed to the error_collector + # and stored in the :base errors collection. # - # @return [Boolean] halt status. - def halted? - @halted + ## @param error [String] + def halt(error = 'Execution stopped') + add_errors(base: error) end # Explicitly fail the pipe. # # @raise [ExecutionTerminated] - def terminate + def terminate(error) + halt(error) fail ExecutionTerminated end @@ -85,6 +82,15 @@ 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. # diff --git a/lib/codequest_pipes/pipe.rb b/lib/codequest_pipes/pipe.rb index d0ff7fd..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.halted? + 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 aaa397c..0982f67 100644 --- a/spec/context_spec.rb +++ b/spec/context_spec.rb @@ -41,4 +41,11 @@ ) 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 From 94d8d5edf94bc0289f3ebdb877dde10299afaf69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemek=20Bielin=CC=81ski?= Date: Mon, 4 Mar 2019 12:36:19 +0100 Subject: [PATCH 3/3] Update gem version --- codequest_pipes.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'