diff --git a/lib/puppet/functions/assert_type.rb b/lib/puppet/functions/assert_type.rb index 4e9f33fb1..7ff216a5e 100644 --- a/lib/puppet/functions/assert_type.rb +++ b/lib/puppet/functions/assert_type.rb @@ -1,42 +1,56 @@ # Returns the given value if it is an instance of the given type, and raises an error otherwise. +# Optionally, if a block is given (accepting two parameters), it will be called instead of raising +# an error. This to enable giving the user richer feedback, or to supply a default value. # # @example how to assert type # # assert that `$b` is a non empty `String` and assign to `$a` # $a = assert_type(String[1], $b) # +# @example using custom error message +# $a = assert_type(String[1], $b) |$expected, $actual| { fail("The name cannot be empty") } +# +# @example, using a warning and a default +# $a = assert_type(String[1], $b) |$expected, $actual| { warning("Name is empty, using default") 'anonymous' } +# # See the documentation for "The Puppet Type System" for more information about types. # Puppet::Functions.create_function(:assert_type) do dispatch :assert_type do param 'Type', 'type' param 'Optional[Object]', 'value' + optional_block_param 'Callable[Optional[Object],Optional[Object]]', 'block' end dispatch :assert_type_s do param 'String', 'type_string' param 'Optional[Object]', 'value' + optional_block_param 'Callable[Optional[Object], Optional[Object]]', 'block' end # @param type [Type] the type the value must be an instance of # @param value [Optional[Object]] the value to assert # - def assert_type(type, value) + def assert_type(type, value, block=nil) unless Puppet::Pops::Types::TypeCalculator.instance?(type,value) inferred_type = Puppet::Pops::Types::TypeCalculator.infer(value) # Do not give all the details - i.e. format as Integer, instead of Integer[n, n] for exact value, which # is just confusing. (OTOH: may need to revisit, or provide a better "type diff" output. # actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type) - raise Puppet::ParseError, "assert_type(): Expected type #{type} does not match actual: #{actual}" + if block + value = block.call(nil, type, actual) + else + raise Puppet::ParseError, "assert_type(): Expected type #{type} does not match actual: #{actual}" + end end value end # @param type_string [String] the type the value must be an instance of given in String form # @param value [Optional[Object]] the value to assert # def assert_type_s(type_string, value) t = Puppet::Pops::Types::TypeParser.new.parse(type_string) assert_type(t, value) end end diff --git a/spec/unit/functions/assert_type_spec.rb b/spec/unit/functions/assert_type_spec.rb index d47f47e30..612d33d9d 100644 --- a/spec/unit/functions/assert_type_spec.rb +++ b/spec/unit/functions/assert_type_spec.rb @@ -1,59 +1,78 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe 'the assert_type function' do after(:all) { Puppet::Pops::Loaders.clear } around(:each) do |example| loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) Puppet.override({:loaders => loaders}, "test-example") do example.run end end let(:func) do Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'assert_type') end it 'asserts compliant type by returning the value' do expect(func.call({}, type(String), 'hello world')).to eql('hello world') end it 'accepts type given as a String' do expect(func.call({}, 'String', 'hello world')).to eql('hello world') end it 'asserts non compliant type by raising an error' do expect do func.call({}, type(Integer), 'hello world') end.to raise_error(Puppet::ParseError, /does not match actual/) end it 'checks that first argument is a type' do expect do func.call({}, 10, 10) end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'assert_type' called with mis-matched arguments expected one of: - assert_type(Type type, Optional[Object] value) - arg count {2} - assert_type(String type_string, Optional[Object] value) - arg count {2} + assert_type(Type type, Optional[Object] value, Callable[Optional[Object], Optional[Object]] block {0,1}) - arg count {2,3} + assert_type(String type_string, Optional[Object] value, Callable[Optional[Object], Optional[Object]] block {0,1}) - arg count {2,3} actual: assert_type(Integer, Integer) - arg count {2}"))) end it 'allows the second arg to be undef/nil)' do expect do func.call({}, optional(String), nil) end.to_not raise_error(ArgumentError) end + it 'can be called with a callable' do + expected, actual = func.call({}, optional(String), 1, create_callable_2_args_unit) + expect(expected.to_s).to eql('Optional[String]') + expect(actual.to_s).to eql('Integer') + end + def optional(type_ref) Puppet::Pops::Types::TypeFactory.optional(type(type_ref)) end def type(type_ref) Puppet::Pops::Types::TypeFactory.type_of(type_ref) end + + def create_callable_2_args_unit() + Puppet::Functions.create_function(:func) do + dispatch :func do + param 'Type', 'expected' + param 'Type', 'actual' + end + + def func(expected, actual) + [expected, actual] + end + end.new({}, nil) + end end