undefined method `strip' for an instance of Hash
Opened this issue · 4 comments
After much, much, much debugging, while getting the mysterious error undefined method
strip' for an instance of Hash` (btw, whoever decided to catch that error and print it out and carry on without providing the stack trace... I hope you take a good long look at yourself in the mirror some day)...
I ended up diagnosing it as an issue with a parameter has being passed into net/http
.
The full stack trace:
undefined method `strip' for an instance of Hash
======== /Users/danieltenner/.rvm/rubies/ruby-3.3.1/lib/ruby/3.3.0/net/http/header.rb:194:in `block in initialize_http_header'
/Users/danieltenner/.rvm/rubies/ruby-3.3.1/lib/ruby/3.3.0/net/http/header.rb:189:in `each'
/Users/danieltenner/.rvm/rubies/ruby-3.3.1/lib/ruby/3.3.0/net/http/header.rb:189:in `initialize_http_header'
/Users/danieltenner/.rvm/rubies/ruby-3.3.1/lib/ruby/3.3.0/net/http/generic_request.rb:51:in `initialize'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-net_http-3.1.1/lib/faraday/adapter/net_http.rb:79:in `new'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-net_http-3.1.1/lib/faraday/adapter/net_http.rb:79:in `create_request'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-net_http-3.1.1/lib/faraday/adapter/net_http.rb:112:in `block in request_with_wrapped_block'
/Users/danieltenner/.rvm/rubies/ruby-3.3.1/lib/ruby/3.3.0/net/http.rb:1570:in `start'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-net_http-3.1.1/lib/faraday/adapter/net_http.rb:111:in `request_with_wrapped_block'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-net_http-3.1.1/lib/faraday/adapter/net_http.rb:101:in `perform_request'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-net_http-3.1.1/lib/faraday/adapter/net_http.rb:65:in `block in call'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-2.10.1/lib/faraday/adapter.rb:45:in `connection'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-net_http-3.1.1/lib/faraday/adapter/net_http.rb:64:in `call'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-2.10.1/lib/faraday/middleware.rb:56:in `call'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-2.10.1/lib/faraday/rack_builder.rb:152:in `build_response'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-2.10.1/lib/faraday/connection.rb:444:in `run_request'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/faraday-2.10.1/lib/faraday/connection.rb:200:in `get'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/xero-ruby-9.2.0/lib/xero-ruby/api_client.rb:319:in `public_send'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/xero-ruby-9.2.0/lib/xero-ruby/api_client.rb:319:in `call_api'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/xero-ruby-9.2.0/lib/xero-ruby/api/accounting_api.rb:6339:in `get_accounts_with_http_info'
/Users/danieltenner/.rvm/gems/ruby-3.3.1/gems/xero-ruby-9.2.0/lib/xero-ruby/api/accounting_api.rb:6275:in `get_accounts'
/Users/danieltenner/dev/[...]/app/apis/xero_api.rb:60:in `get_accounts'
This is happening because the initheader
parameter passed to net/http/header.rb
's initialize_http_header
method is:
{"Content-Type"=>"application/json", "User-Agent"=>"xero-ruby-9.2.0",
"Accept"=>"application/json", "Xero-tenant-id"=>{"id"=>"[snip]", "authEventId"=>"[snip]",
"tenantId"=>"[snip]", "tenantType"=>"ORGANISATION", "tenantName"=>"[snip]",
"createdDateUtc"=>"2024-09-04T15:17:17.9469550", "updatedDateUtc"=>"2024-09-04T15:17:17.9483740"}, "Authorization"=>"Bearer [snip]",
"accept-encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}
Notice Xero-tenant-id
is a hash.
initialize_http_header
then dutifully chokes on it because it expects string parameters:
def initialize_http_header(initheader) #:nodoc:
@header = {}
return unless initheader
initheader.each do |key, value|
warn "net/http: duplicated HTTP header: #{key}", uplevel: 3 if key?(key) and $VERBOSE
if value.nil?
warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE
else
value = value.strip # raise error for invalid byte sequences
if key.to_s.bytesize > MAX_KEY_LENGTH
raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..."
end
if value.to_s.bytesize > MAX_FIELD_LENGTH
raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}"
end
if value.count("\r\n") > 0
raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF"
end
@header[key.downcase.to_s] = [value]
end
end
end
Haven't got a solution or workaround yet, but thought i'd put this here in case it helps someone. Will report back once I do have a solution, whatever it is.
PETOSS-568
Thanks for raising an issue, a ticket has been created to track your request
I believe I have figured this out! Documenting here as it may help others.
The README
suggests that xero_client.last_connection
"returns the xero-tenant-id of the most recently connected Xero org".
But that's not true. It returns a hash. And inside that is ["id"]
- which is the thing you need to pass to, e.g., get_accounts
.
So this bug is fixed by changing:
@api_client.accounting_api.get_accounts(@api_client.last_connection)
To
@api_client.accounting_api.get_accounts(@api_client.last_connection["id"])
Easy fix but fiendishly hard to debug. Hope this helps someone!
Actually perhaps worth reopening as this is an easy documentation fix.