Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- require 'rubygems'
- require 'ruote'
- require 'flexmock'
- require 'spec'
- # Workitem fields validation
- # This participant validates the fields of the current workitem
- # Especially useful to validate the initial workitem
- # An input definition may include the following:
- # - name: Input name, compulsory
- # - kind: Input type, compulsory
- # - obligation: Whether input is required or optional, required by default.
- # - default: Input default value, optional.
- #
- # The kind of an input consists of a string corresponding to the Ruby class
- # name, one of:
- # - String
- # - Fixnum
- # - Bool (note: not a ruby class per se)
- # - DateTime
- # - Array<KIND> (KIND must recursively take on the values in this list)
- # - Hash<KIND, KIND) (same note as above)
- #
- # The participant expects the 'fields_definitions' field to contain the
- # definitions as hashes indexed by field name
- #
- # e.g.:
- # fields_definitions 'names' => [ 'Array<String>', :required ],
- # 'operation' => [ 'String', :default => 'run' ],
- # 'operation_timeout' => [ 'Fixnum', :default => 300 ],
- # 'arguments' => [ 'Hash<String, String>', :default => {} ],
- # 'result_field' => [ 'String', :default => '__result__' ]
- # error '${f:fields_errors}', :if => '${f:fields_errors}'
- #
- class FieldsValidationParticipant
- include Ruote::LocalParticipant
- # Lookup fields validations and iterate through to validate
- #
- # === Input Workitem Fields
- # fields_definitions<Hash>:: Hash of fields definition indexed by name
- #
- # === Ouput Workitem Fields
- # fields_errors<Array>:: List of error messages, empty if no error
- def consume(workitem)
- defs = workitem.fields['fields_definitions'] || {}
- errors = []
- # First validate
- defs.each { |name, val| errors += validate(name, val, workitem.lookup(name)) }
- workitem.set_field('fields_errors', errors)
- # Then set default values
- defaults = defs.select { |_, val| val.is_a?(Hash) && val.include?(:default) }
- defaults.each { |name, val| workitem.set_field(name, val[:default]) unless workitem.lookup(name) }
- reply_to_engine(workitem)
- end
- protected
- # Validate a given workitem field against its input definition
- #
- # === Argumnets
- # name<String>:: Name of field
- # definition<Array>:: Input definition
- # field<Object>:: Actual field value
- #
- # === Return
- # errors<Array>:: List of error messages or empty array if no error
- def validate(name, definition, field)
- errors = []
- if field.nil?
- if definition[1] == :required
- errors << "Missing required workitem field #{name.inspect}"
- end
- else
- errors += check_kind(field, definition[0], name)
- end
- errors
- end
- # Recursively check that the kind of given field matches given kind
- #
- # === Arguments
- # field<Object>:: Actual workitem field being checked
- # kind<String>:: Kind 'field' should match
- # name<String>:: Name of field for error message
- #
- # === Return
- # errors<Array>:: List of errors or empty array if no error
- def check_kind(field, kind, name)
- errors = []
- if kind =~ /^Array<(.*)>$/
- inner = Regexp.last_match[1]
- errors += check_kind(field, 'Array', name)
- if errors.empty?
- field.each_index do |i|
- errors += check_kind(field[i], inner, "element at index #{i} of #{name}")
- end
- end
- elsif kind =~ /^Hash<(.*), *(.*)>$/
- inner_key = Regexp.last_match[1]
- inner_val = Regexp.last_match[2]
- errors += check_kind(field, 'Hash', name)
- if errors.empty?
- field.each do |k ,v|
- errors += check_kind(k, inner_key, "key #{k.inspect} of #{name}")
- errors += check_kind(v, inner_val, "value at key #{k.inspect} of #{name}")
- end
- end
- else
- if field.class.to_s != kind
- errors << "Workitem field #{name.inspect} kind is invalid (should be " +
- "#{kind.inspect} but is #{field.class.to_s.inspect})"
- end
- end
- errors
- end
- end
- config = Spec::Runner.configuration
- config.mock_with :flexmock
- describe Maestro::FieldsValidationParticipant do
- # Create test workitem
- def new_workitem
- workitem = Ruote::Workitem.new('fields' =>
- { 'fields_definitions' => { 'required_field' => [ 'String', :required ],
- 'optional_field' => [ 'String', :optional ],
- 'array_field' => [ 'Array<String>', :required ],
- 'hash_field' => [ 'Hash<String, Fixnum>', :required ] },
- 'required_field' => 'I\'m here',
- 'array_field' => [ 'I\'m a string' ],
- 'hash_field' => { 'key' => 42 }})
- end
- before(:each) do
- @workitem = nil
- @validator = Maestro::FieldsValidationParticipant.new
- flexmock(@validator).should_receive(:reply_to_engine).and_return { |wi| @workitem = wi }
- end
- it 'should work with no definition' do
- workitem = Ruote::Workitem.new('fields' => {'a' => 'b'})
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.to_h.should == workitem.to_h
- end
- it 'should validate required inputs' do
- workitem = new_workitem
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.to_h.should == workitem.to_h
- end
- it 'should invalidate missing inputs' do
- workitem = new_workitem
- workitem.set_field('required_field', nil)
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.lookup('fields_errors').should_not be_nil
- @workitem.lookup('fields_errors').class.should == Array
- @workitem.lookup('fields_errors').size.should == 1
- @workitem.lookup('fields_errors')[0].should =~ /^Missing required workitem field/
- end
- it 'should invalidate incorrect kinds' do
- workitem = new_workitem
- workitem.set_field('optional_field', 42)
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.lookup('fields_errors').should_not be_nil
- @workitem.lookup('fields_errors').class.should == Array
- @workitem.lookup('fields_errors').size.should == 1
- @workitem.lookup('fields_errors')[0].should =~ /kind is invalid/
- end
- it 'should invalidate multiple errors' do
- workitem = new_workitem
- workitem.set_field('optional_field', 42)
- workitem.set_field('required_field', 42)
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.lookup('fields_errors').should_not be_nil
- @workitem.lookup('fields_errors').class.should == Array
- @workitem.lookup('fields_errors').size.should == 2
- @workitem.lookup('fields_errors').all? { |e| e.should =~ /kind is invalid/ }
- end
- it 'should invalidate incorrect array kind' do
- workitem = new_workitem
- workitem.set_field('array_field', [ 42 ])
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.lookup('fields_errors').should_not be_nil
- @workitem.lookup('fields_errors').class.should == Array
- @workitem.lookup('fields_errors').size.should == 1
- @workitem.lookup('fields_errors')[0].should =~ /kind is invalid/
- end
- it 'should invalidate incorrect hash key kind' do
- workitem = new_workitem
- workitem.set_field('hash_field', [ 42 => 42 ])
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.lookup('fields_errors').should_not be_nil
- @workitem.lookup('fields_errors').class.should == Array
- @workitem.lookup('fields_errors').size.should == 1
- @workitem.lookup('fields_errors')[0].should =~ /kind is invalid/
- end
- it 'should invalidate incorrect hash value kind' do
- workitem = new_workitem
- workitem.set_field('hash_field', [ 'key' => 'not_an_int' ])
- @validator.consume(workitem)
- @workitem.should_not be_nil
- @workitem.lookup('fields_errors').should_not be_nil
- @workitem.lookup('fields_errors').class.should == Array
- @workitem.lookup('fields_errors').size.should == 1
- @workitem.lookup('fields_errors')[0].should =~ /kind is invalid/
- end
- end
Add Comment
Please, Sign In to add comment