puppetlabs/puppet · error · Puppet::HTTP::SerializationError

HTTP REST queries cannot handle values of type '%{klass}'

Error message

HTTP REST queries cannot handle values of type '%{klass}'

What it means

When Puppet::HTTP::Client encodes URL query parameters, arrays at the top level are expanded into repeated keys, but every leaf value must be nil, true/false, String, Symbol, Integer, or Float (client.rb:444). Any other class — Hash, Time, Date, Struct — raises Puppet::HTTP::SerializationError naming the class. Note nested Hashes are not expanded: a hash value goes straight to the primitive check.

Source

Thrown at lib/puppet/http/client.rb:447

                         value.collect { |val| [key, val] }
                       else
                         [key_value]
                       end

      params.concat(expand_primitive_types_into_parameters(expanded_value))
    end
  end

  def expand_primitive_types_into_parameters(data)
    data.inject([]) do |params, key_value|
      key, value = key_value
      case value
      when nil
        params
      when true, false, String, Symbol, Integer, Float
        params << [key, value]
      else
        raise Puppet::HTTP::SerializationError, _("HTTP REST queries cannot handle values of type '%{klass}'") % { klass: value.class }
      end
    end
  end

  def encode_params(params)
    params = expand_into_parameters(params)
    params.map do |key, value|
      "#{key}=#{Puppet::Util.uri_query_encode(value.to_s)}"
    end.join('&')
  end

  def elapsed(start)
    (Time.now - start).to_f.round(3)
  end

  def raise_error(message, cause, connected)
    if connected
      raise Puppet::HTTP::HTTPError.new(message, cause)

View on GitHub (pinned to e227c27540)

Solutions

  1. Convert values to primitives yourself: since: Time.now.utc.iso8601.
  2. Flatten or serialize structured filters into strings before the call (e.g. JSON in a single param if the endpoint accepts it).
  3. For fully custom query strings, encode them into the URL yourself and pass params: {}.

Example fix

# before
client.get(url, params: { since: Time.now, environment: 'production' })

# after
client.get(url, params: { since: Time.now.utc.iso8601, environment: 'production' })
Defensive patterns

Strategy: type-guard

Validate before calling

params = params.transform_values { |v| case v when nil, true, false, String, Symbol, Integer, Float then v else v.to_s end }
client.get(url, params: params)

Type guard

PRIMITIVES = [NilClass, TrueClass, FalseClass, String, Symbol, Integer, Float].freeze
param_safe = ->(v) { v.nil? || PRIMITIVES.any? { |c| v.is_a?(c) } }
params.each { |k, v| raise ArgumentError, "#{k} is not URL-encodable" unless param_safe.call(v) }

Try / catch

begin
  client.get(url, params: params)
rescue Puppet::HTTP::SerializationError => e
  retry_with = params.transform_values(&:to_s)
  client.get(url, params: retry_with)
end

Prevention

When it happens

Trigger: client.get(url, params: { since: Time.now }); params: { filter: { kind: 'apply' } } (Hash value); params: { tags: [{ a: 1 }] } (hash inside an array); passing OpenStruct/Struct/BigDecimal objects from fact or report data.

Common situations: Query endpoints with timestamp filters where a Time object feels natural; structured filter hashes that the developer expects to be JSON-encoded; data flowing from YAML config where dates auto-parse into Date objects.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/2af37f30ed213d6d. Report an issue: GitHub.