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



147
148
149
150
# File 'lib/puppet/resource_api/data_type_handling.rb', line 147

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



130
131
132
133
134
135
136
137
138
139
# File 'lib/puppet/resource_api/data_type_handling.rb', line 130

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)
  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
# File 'lib/puppet/resource_api/data_type_handling.rb', line 45

def self.mungify_core(type, value, error_msg_prefix, unpack_strings = false)
  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



198
199
200
201
202
203
204
205
206
# File 'lib/puppet/resource_api/data_type_handling.rb', line 198

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



71
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
122
123
124
125
# File 'lib/puppet/resource_api/data_type_handling.rb', line 71

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
      if conversions.all? { |c| c[1].nil? }
        value = conversions.map { |c| c[0] }
      end
    end
  when Puppet::Pops::Types::PBooleanType
    value = boolean_munge(value)
  when Puppet::Pops::Types::PIntegerType,
       Puppet::Pops::Types::PFloatType,
       Puppet::Pops::Types::PNumericType
    if value.is_a?(String) && (value.match?(%r{^-?\d+$}) || value.match?(Puppet::Pops::Patterns::NUMERIC))
      value = Puppet::Pops::Utils.to_n(value)
    end
  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
    if conversion_results.length > 1
      return [nil, ambiguous_error_msg(error_msg_prefix, value, type)]
    end

    # 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.



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/puppet/resource_api/data_type_handling.rb', line 169

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)
  error_msg = Puppet::Pops::Types::TypeMismatchDescriber.new.describe_mismatch(
    error_msg_prefix,
    type,
    inferred_type,
  )
  error_msg
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`



158
159
160
161
# File 'lib/puppet/resource_api/data_type_handling.rb', line 158

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)


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/puppet/resource_api/data_type_handling.rb', line 182

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