{"record":{"id":"996eb18d46640627","repo":"puppetlabs/puppet","slug":"put-requires-a-string-body-argument","errorCode":null,"errorMessage":"'put' requires a string 'body' argument","messagePattern":"'put' requires a string 'body' argument","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/http/client.rb","lineNumber":239,"sourceCode":"\n    execute_streaming(request, options: options)\n  end\n\n  # Submits a PUT HTTP request to the given url\n  #\n  # @param [URI] url the location to submit the http request\n  # @param [String] body the body of the PUT request\n  # @param [Hash] headers merged with the default headers defined by the client. The\n  #   `Content-Type` header is required and should correspond to the type of data passed\n  #   as the `body` argument.\n  # @param [Hash] params encoded and set as the url query\n  # @!macro request_options\n  #\n  # @return [Puppet::HTTP::Response] the response\n  #\n  # @api public\n  def put(url, body, headers: {}, params: {}, options: {})\n    raise ArgumentError, \"'put' requires a string 'body' argument\" unless body.is_a?(String)\n\n    url = encode_query(url, params)\n\n    request = Net::HTTP::Put.new(url, @default_headers.merge(headers))\n    request.body = body\n    request.content_length = body.bytesize\n\n    raise ArgumentError, \"'put' requires a 'content-type' header\" unless request['Content-Type']\n\n    execute_streaming(request, options: options)\n  end\n\n  # Submits a POST HTTP request to the given url\n  #\n  # @param [URI] url the location to submit the http request\n  # @param [String] body the body of the POST request\n  # @param [Hash] headers merged with the default headers defined by the client. The\n  #   `Content-Type` header is required and should correspond to the type of data passed","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/http/client.rb#L221-L257","documentation":"Puppet::HTTP::Client#put validates up front that the request body is a String and raises ArgumentError otherwise, before any network I/O. The API intentionally has no implicit serialization — callers must render payloads (JSON, PSON, text) to a string themselves and pair it with a matching Content-Type.","triggerScenarios":"client.put(url, nil, headers: { 'Content-Type' => 'text/plain' }); client.put(url, { 'key' => 'value' }, ...) with a Hash; passing an IO or template object instead of its read content.","commonSituations":"Migrating from Puppet::Network::HttpPool or RestClient where the library serialized for you; forgetting JSON.generate when sending a report or catalog fragment; reading a file body but passing the File object.","solutions":["Render the payload to a String before the call: body = JSON.generate(data).","For file content, use File.read(path) so you pass the string (and set bytesize-derived length is handled internally).","If the value may legitimately be absent, pass an empty string '' rather than nil."],"exampleFix":"# before\nclient.put(url, { status: 'done' }, headers: { 'Content-Type' => 'application/json' })\n\n# after\nclient.put(url, JSON.generate(status: 'done'), headers: { 'Content-Type' => 'application/json' })","handlingStrategy":"type-guard","validationCode":"body = JSON.generate(data) unless body.is_a?(String)\nclient.put(url, body, headers: { 'Content-Type' => 'application/json' })","typeGuard":"string_body = ->(b) { b.is_a?(String) }\nraise ArgumentError, 'body must be a String' unless string_body.call(body)","tryCatchPattern":null,"preventionTips":["Always serialize payloads explicitly (JSON.generate / to_pem / File.read).","Never forward nil bodies; substitute '' for 'no body'."],"tags":["puppet","http","put","request-validation","ruby"],"backgroundTag":"wrong-argument-type","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}