puppetlabs/puppet · error · Puppet::Network::HTTP::Error::HTTPBadRequestError

No support for plurality %{indirection} for %{http_method} o

Error message

No support for plurality %{indirection} for %{http_method} operations

What it means

After the verb check, the handler computes plurality(indirection) (plural for search-style paths such as facts or statuses) and looks up METHOD_MAP[http_method][plurality]. In this METHOD_MAP, GET supports both plural (search) and singular (find), while POST, PUT and DELETE define only singular entries. A verb applied to the wrong plurality therefore returns HTTP 400 naming the plurality and verb.

Source

Thrown at lib/puppet/network/http/api/indirected_routes.rb:244

        return model_class.convert_from(formatter.name.to_s, data)
      rescue => e
        raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _("The request body is invalid: %{message}") % { message: e.message }
      end
    end

    # TRANSLATORS "mime-type" is a keyword and should not be translated
    raise Puppet::Network::HTTP::Error::HTTPUnsupportedMediaTypeError.new(
      _("Client sent a mime-type (%{header}) that doesn't correspond to a format we support") % { header: request.headers['content-type'] },
      Puppet::Network::HTTP::Issues::UNSUPPORTED_MEDIA_TYPE
    )
  end

  def indirection_method(http_method, indirection)
    raise Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError, _("No support for http method %{http_method}") % { http_method: http_method } unless METHOD_MAP[http_method]

    method = METHOD_MAP[http_method][plurality(indirection)]
    unless method
      raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _("No support for plurality %{indirection} for %{http_method} operations") % { indirection: plurality(indirection), http_method: http_method }
    end

    method
  end

  def self.pluralize(indirection)
    (indirection == "status" ? "statuses" : indirection + "s")
  end
  private_class_method :pluralize

  def plurality(indirection)
    # NOTE These specific hooks for paths are ridiculous, but it's a *many*-line
    # fix to not need this, and our goal is to move away from the complication
    # that leads to the fix being too long.
    return :singular if indirection == "facts"
    return :singular if indirection == "status"
    return :singular if indirection == "certificate_status"

View on GitHub (pinned to e227c27540)

Solutions

  1. Save data against the singular form: PUT /puppet/v3/facts/<certname>
  2. Use GET on plural paths only where METHOD_MAP defines :search (facts, statuses)
  3. Read METHOD_MAP at the top of lib/puppet/network/http/api/indirected_routes.rb for the authoritative verb/plurality pairs

Example fix

# before: saving to the plural form
curl -X POST -H 'Content-Type: application/json' -d @facts.json \
  "https://puppet:8140/puppet/v3/facts?environment=production"

# after: save via PUT on the singular form
curl -X PUT -H 'Content-Type: application/json' -d @facts.json \
  "https://puppet:8140/puppet/v3/facts/mynode?environment=production"
Defensive patterns

Strategy: validation

Validate before calling

METHOD_MAP = {
  'GET'    => { plural: :search, singular: :find },
  'POST'   => { singular: :find },
  'PUT'    => { singular: :save },
  'DELETE' => { singular: :destroy }
}.freeze

def verb_supported?(verb, plural)
  !METHOD_MAP.dig(verb, plural ? :plural : :singular).nil?
end

raise ArgumentError, "#{verb} not valid for #{'im' unless plural}plural form" unless verb_supported?(verb, plural)

Prevention

When it happens

Trigger: PUT or POST to /puppet/v3/facts (facts is plural; submission must be PUT /puppet/v3/facts/<certname>); DELETE against /puppet/v3/statuses; any save/destroy attempt against a plural/search path.

Common situations: Developers applying generic REST conventions where POST targets a collection; custom fact submitters using POST on the collection URL; adapting examples from other REST APIs without checking METHOD_MAP.

Related errors


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