module Puppet::ResourceApi::DataTypeHandling

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

Public Class Methods

ambiguous_error_msg(error_msg_prefix, value, type) click to toggle source

Returns ambiguous error message based on ‘error_msg_prefix`, `value` and `type`. @param type the type to check against @param value the value to clean @param error_msg_prefix a prefix for the error messages @private

# 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) click to toggle source

Returns correct boolean ‘value` based on one specified in `type`. @param value the value to boolean munge @private

# 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) click to toggle source

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. @param type the type to check/clean against. @param value the value to clean @param error_msg_prefix a prefix for the error messages @param unpack_strings unpacking of strings for migrating off legacy type @return [type] the cleaned value

# 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) click to toggle source

This is core method used in mungify which handles translating values to expected cleaned type values, result is not validated. @param type the type to check/clean against. @param value the value to clean @param error_msg_prefix a prefix for the error messages @param unpack_strings unpacking of strings for migrating off legacy type @return [type] the cleaned value @raise [Puppet::ResourceError] if ‘value` could not be parsed into `type` @private

# 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) click to toggle source
# 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) click to toggle source

Recursive implementation part of mungify_core. Uses a multi-valued return value to avoid excessive exception throwing for regular usage. @return [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

@private

# 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) click to toggle source

Tries to validate the ‘value` against the specified `type`. @param type the type to check against @param value the value to clean @param error_msg_prefix a prefix for the error messages @return [String, nil] a error message indicating the problem, or `nil` if the value was valid. @private

# 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) click to toggle source

Validates the ‘value` against the specified `type`. @param type the type to check against @param value the value to clean @param error_msg_prefix a prefix for the error messages @raise [Puppet::ResourceError] if `value` is not of type `type` @private

# 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) click to toggle source
# 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