Module: Puppet::ResourceApi::DataTypeHandling

Defined in:
lib/puppet/resource_api/data_type_handling.rb

Overview

This module is used to handle data inside types, contains methods for munging and validation of the type values.

Class Method Summary collapse

Class Method Details

.ambiguous_error_msg(error_msg_prefix, value, type) ⇒ Object

Returns ambiguous error message based on error_msg_prefix, value and type.

Parameters:

  • type (Puppet::Pops::Types::TypedModelObject)

    the type to check against

  • value

    the value to clean

  • error_msg_prefix (String)

    a prefix for the error messages



143
144
145
146
# File 'lib/puppet/resource_api/data_type_handling.rb', line 143

def self.ambiguous_error_msg(error_msg_prefix, value, type)
  "#{error_msg_prefix} #{value.inspect} is not unabiguously convertable to " \
    "#{type}"
end

.boolean_munge(value) ⇒ Object

Returns correct boolean value based on one specified in type.

Parameters:

  • value

    the value to boolean munge



126
127
128
129
130
131
132
133
134
135
# File 'lib/puppet/resource_api/data_type_handling.rb', line 126

def self.boolean_munge(value)
  case value
  when 'true', :true # rubocop:disable Lint/BooleanSymbol
    true
  when 'false', :false # rubocop:disable Lint/BooleanSymbol
    false
  else
    value
  end
end

.mungify(type, value, error_msg_prefix, unpack_strings = false) ⇒ type

This method handles translating values from the runtime environment to the expected types for the provider with validation. When being called from ‘puppet resource`, it tries to transform the strings from the command line into their expected ruby representations, e.g. `“2”` (a string), will be transformed to 2 (the number) if (and only if) the target type is Integer. Additionally this function also validates that the passed in (and optionally transformed) value matches the specified type. against. legacy type

Parameters:

  • type (Puppet::Pops::Types::TypedModelObject)

    the type to check/clean

  • value

    the value to clean

  • error_msg_prefix (String)

    a prefix for the error messages

  • unpack_strings (Boolean) (defaults to: false)

    unpacking of strings for migrating off

Returns:

  • (type)

    the cleaned value



23
24
25
26
27
28
29
30
31
32
# File 'lib/puppet/resource_api/data_type_handling.rb', line 23

def self.mungify(type, value, error_msg_prefix, unpack_strings = false) # rubocop:disable Style/OptionalBooleanParameter
  cleaned_value = mungify_core(
    type,
    value,
    error_msg_prefix,
    unpack_strings
  )
  validate(type, cleaned_value, error_msg_prefix)
  cleaned_value
end

.mungify_core(type, value, error_msg_prefix, unpack_strings = false) ⇒ type

This is core method used in mungify which handles translating values to expected cleaned type values, result is not validated. against. legacy type

Parameters:

  • type (Puppet::Pops::Types::TypedModelObject)

    the type to check/clean

  • value

    the value to clean

  • error_msg_prefix (String)

    a prefix for the error messages

  • unpack_strings (Boolean) (defaults to: false)

    unpacking of strings for migrating off

Returns:

  • (type)

    the cleaned value

Raises:

  • (Puppet::ResourceError)

    if value could not be parsed into type



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/puppet/resource_api/data_type_handling.rb', line 45

def self.mungify_core(type, value, error_msg_prefix, unpack_strings = false) # rubocop:disable Style/OptionalBooleanParameter
  if unpack_strings
    # When the provider is exercised from the `puppet resource` CLI, we need
    # to unpack strings into the correct types, e.g. "1" (a string)
    # to 1 (an integer)
    cleaned_value, error_msg = try_mungify(type, value, error_msg_prefix)
    raise Puppet::ResourceError, error_msg if error_msg

    cleaned_value
  elsif value == :false # rubocop:disable Lint/BooleanSymbol
    # work around https://tickets.puppetlabs.com/browse/PUP-2368
    false
  elsif value == :true # rubocop:disable Lint/BooleanSymbol
    # work around https://tickets.puppetlabs.com/browse/PUP-2368
    true
  else
    # Every other time, we can use the values as is
    value
  end
end

.parse_puppet_type(attr_name, type) ⇒ Object



194
195
196
197
198
199
200
201
202
# File 'lib/puppet/resource_api/data_type_handling.rb', line 194

def self.parse_puppet_type(attr_name, type)
  Puppet::Pops::Types::TypeParser.singleton.parse(type)
rescue Puppet::ParseErrorWithIssue => e
  raise Puppet::DevError, "The type of the `#{attr_name}` attribute " \
                          "`#{type}` could not be parsed: #{e.message}"
rescue Puppet::ParseError => e
  raise Puppet::DevError, "The type of the `#{attr_name}` attribute " \
                          "`#{type}` is not recognised: #{e.message}"
end

.try_mungify(type, value, error_msg_prefix) ⇒ Array

Recursive implementation part of #mungify_core. Uses a multi-valued return value to avoid excessive exception throwing for regular usage.

Returns:

  • (Array)

    if the mungify worked, the first element is the cleaned value, and the second element is nil. If the mungify failed, the first element is nil, and the second element is an error message



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/puppet/resource_api/data_type_handling.rb', line 72

def self.try_mungify(type, value, error_msg_prefix)
  case type
  when Puppet::Pops::Types::PArrayType
    if value.is_a? Array
      conversions = value.map do |v|
        try_mungify(type.element_type, v, error_msg_prefix)
      end
      # only convert the values if none failed. otherwise fall through and
      # rely on puppet to render a proper error
      value = conversions.map { |c| c[0] } if conversions.all? { |c| c[1].nil? }
    end
  when Puppet::Pops::Types::PBooleanType
    value = boolean_munge(value)
  when Puppet::Pops::Types::PIntegerType,
       Puppet::Pops::Types::PFloatType,
       Puppet::Pops::Types::PNumericType
    value = Puppet::Pops::Utils.to_n(value) if value.is_a?(String) && (value.match?(/^-?\d+$/) || value.match?(Puppet::Pops::Patterns::NUMERIC))
  when Puppet::Pops::Types::PEnumType,
       Puppet::Pops::Types::PStringType,
       Puppet::Pops::Types::PPatternType
    value = value.to_s if value.is_a? Symbol
  when Puppet::Pops::Types::POptionalType
    return value.nil? ? [nil, nil] : try_mungify(type.type, value, error_msg_prefix)
  when Puppet::Pops::Types::PVariantType
    # try converting to anything except string first
    string_type = type.types.find { |t| t.is_a? Puppet::Pops::Types::PStringType }
    conversion_results = (type.types - [string_type]).map do |t|
      try_mungify(t, value, error_msg_prefix)
    end

    # only consider valid results
    conversion_results = conversion_results.select { |r| r[1].nil? }.to_a

    # use the conversion result if unambiguous
    return conversion_results[0] if conversion_results.length == 1

    # return an error if ambiguous
    return [nil, ambiguous_error_msg(error_msg_prefix, value, type)] if conversion_results.length > 1

    # try to interpret as string
    return try_mungify(string_type, value, error_msg_prefix) if string_type

    # fall through to default handling
  end

  error_msg = try_validate(type, value, error_msg_prefix)
  return [nil, error_msg] if error_msg # an error

  [value, nil]                         # match
end

.try_validate(type, value, error_msg_prefix) ⇒ String?

Tries to validate the value against the specified type.

Parameters:

  • type (Puppet::Pops::Types::TypedModelObject)

    the type to check against

  • value

    the value to clean

  • error_msg_prefix (String)

    a prefix for the error messages

Returns:

  • (String, nil)

    a error message indicating the problem, or nil if the value was valid.



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/puppet/resource_api/data_type_handling.rb', line 165

def self.try_validate(type, value, error_msg_prefix)
  return nil if type.instance?(value)

  # an error :-(
  inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value)
  Puppet::Pops::Types::TypeMismatchDescriber.new.describe_mismatch(
    error_msg_prefix,
    type,
    inferred_type
  )
end

.validate(type, value, error_msg_prefix) ⇒ Object

Validates the value against the specified type.

Parameters:

  • type (Puppet::Pops::Types::TypedModelObject)

    the type to check against

  • value

    the value to clean

  • error_msg_prefix (String)

    a prefix for the error messages

Raises:

  • (Puppet::ResourceError)

    if value is not of type type



154
155
156
157
# File 'lib/puppet/resource_api/data_type_handling.rb', line 154

def self.validate(type, value, error_msg_prefix)
  error_msg = try_validate(type, value, error_msg_prefix)
  raise Puppet::ResourceError, error_msg if error_msg
end

.validate_ensure(definition) ⇒ Object

Raises:

  • (Puppet::DevError)


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/puppet/resource_api/data_type_handling.rb', line 177

def self.validate_ensure(definition)
  return unless definition[:attributes].key? :ensure

  options = definition[:attributes][:ensure]
  type = parse_puppet_type(:ensure, options[:type])

  # If the type is wrapped in optional, then check it unwrapped:
  type = type.type if type.is_a?(Puppet::Pops::Types::POptionalType)
  return if type.is_a?(Puppet::Pops::Types::PEnumType) && type.values.sort == %w[absent present].sort

  # If Variant was used instead, then construct one and use the built-in type comparison:
  variant_type = Puppet::Pops::Types::TypeParser.singleton.parse('Variant[Undef, Enum[present, absent]]')
  return if variant_type == type

  raise Puppet::DevError, '`:ensure` attribute must have a type of: `Enum[present, absent]` or `Optional[Enum[present, absent]]`'
end