Class: PortfolioManager::Client::Base

Inherits:
Logger::Application
  • Object
show all
Defined in:
lib/portfolio_manager/client.rb

Overview

Base Client

Direct Known Subclasses

Live, Test

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_uri, user = nil, pass = nil, debug = false) ⇒ PortfolioManager::Client::Base

Default constructor.

Parameters:

  • base_uri (URI)
  • user (nil, String) (defaults to: nil)
  • pass (nil, String) (defaults to: nil)
  • debug (Boolean) (defaults to: false)


66
67
68
69
70
# File 'lib/portfolio_manager/client.rb', line 66

def initialize(base_uri, user = nil, pass = nil, debug = false)
  super("PortfolioManager")

  @base_uri, @user, @pass, @debug = base_uri, user, pass, debug
end

Instance Attribute Details

#base_uriURI (readonly)

Returns:

  • (URI)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/portfolio_manager/client.rb', line 54

class Base < Logger::Application
  attr_reader :base_uri
  attr_reader :user, :pass
  attr_reader :debug

  # Default constructor.
  #
  # @param base_uri [URI]
  # @param user [nil, String]
  # @param pass [nil, String]
  # @param debug [Boolean]
  # @return [PortfolioManager::Client::Base]
  def initialize(base_uri, user = nil, pass = nil, debug = false)
    super("PortfolioManager")

    @base_uri, @user, @pass, @debug = base_uri, user, pass, debug
  end

  protected

  # Send HTTP request.
  #
  # @param verb [Class]
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @param initheader [Hash<String, String>]
  # @param obj [nil, Object]
  # @param elename [nil, String]
  # @param klass [nil, Class]
  # @param options [Hash<Symbol, Object>]
  # @option options [Boolean] :basic_auth
  # @return [Object]
  # @raise [PortfolioManager::HTTPBasicCredentialsNotFoundError]
  # @raise [PortfolioManager::HTTPResponseError]
  # @see https://portfoliomanager.energystar.gov/webservices/home/errors
  def request(verb = Net::HTTP::Get, path = "/", params = {}, initheader = {}, obj = nil, elename = nil, klass = nil, **options)
    uri = url_for(path, params)

    proxy_uri = uri.find_proxy

    request = verb.new(uri, initheader)

    request["Accept"] = "application/xml, text/xml"

    unless obj.nil?
      request["Content-Type"] = "application/xml; charset=UTF-8"

      request.body = PortfolioManager::Mapper.instance.obj2xml(obj, elename)
    end

    if options[:basic_auth]
      if @user.nil? || @pass.nil?
        raise PortfolioManager::HTTPBasicCredentialsNotFoundError.new(request)
      else
        request.basic_auth(@user, @pass)
      end
    end

    Net::HTTP.start(uri.host, uri.port, proxy_uri.nil? ? nil : proxy_uri.host, proxy_uri.nil? ? nil : proxy_uri.port, use_ssl: uri.is_a?(URI::HTTPS)) do |http|
      log(INFO, "Request:\n" + ([
        "Protocol: HTTP/#{Net::HTTP.version_1_2? ? "1.2" : "1.1"}",
        "Method: #{verb.name.split("::")[-1].upcase}",
        "Scheme: #{uri.scheme}",
        "Host: #{uri.host}",
        "Path: #{uri.path}",
        "QueryString: #{uri.query}",
        "Body: #{request.body}",
      ] + (["Accept", "Authorization", "Content-Type"] + initheader.keys).sort.uniq.collect { |key|
        request.key?(key) ? "#{key}: #{request[key]}" : nil
      }.compact).collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      response = http.request(request)

      response_body_utf8 = response.body.force_encoding("UTF-8")

      log(INFO, "Response:\n" + ([
        "Protocol: HTTP/#{response.http_version}",
        "URI: #{response.uri}",
        "Code: #{response.code}",
        "Message: #{response.message}",
        "Body: #{response_body_utf8}",
      ]).compact.collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      if response_body_utf8.start_with?("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
        PortfolioManager::Mapper.instance.xml2obj(response_body_utf8, response.code.start_with?("2") ? klass : PortfolioManager::Xml::ResponseType)
      else
        raise PortfolioManager::HTTPResponseError.new(response)
      end
    end
  end

  # Build path for the given arguments.
  #
  # @param args [Array<#to_s>]
  # @return [String]
  def path_for(*args)
    "/#{args.collect { |arg| ERB::Util.url_encode(arg.to_s) }.join("/")}"
  end

  # Build URL for given path and query parameters.
  #
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @return [URI]
  def url_for(path, params = {})
    query_string = params.each_pair.collect { |pair|
      key, value = *pair

      value.nil? ? nil : "#{ERB::Util.url_encode(key)}=#{ERB::Util.url_encode(value.to_s)}"
    }.compact.join("&")

    URI("#{@base_uri}#{path}#{query_string.empty? ? nil : "?#{query_string}"}")
  end
end

#debugBoolean (readonly)

Returns:

  • (Boolean)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/portfolio_manager/client.rb', line 54

class Base < Logger::Application
  attr_reader :base_uri
  attr_reader :user, :pass
  attr_reader :debug

  # Default constructor.
  #
  # @param base_uri [URI]
  # @param user [nil, String]
  # @param pass [nil, String]
  # @param debug [Boolean]
  # @return [PortfolioManager::Client::Base]
  def initialize(base_uri, user = nil, pass = nil, debug = false)
    super("PortfolioManager")

    @base_uri, @user, @pass, @debug = base_uri, user, pass, debug
  end

  protected

  # Send HTTP request.
  #
  # @param verb [Class]
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @param initheader [Hash<String, String>]
  # @param obj [nil, Object]
  # @param elename [nil, String]
  # @param klass [nil, Class]
  # @param options [Hash<Symbol, Object>]
  # @option options [Boolean] :basic_auth
  # @return [Object]
  # @raise [PortfolioManager::HTTPBasicCredentialsNotFoundError]
  # @raise [PortfolioManager::HTTPResponseError]
  # @see https://portfoliomanager.energystar.gov/webservices/home/errors
  def request(verb = Net::HTTP::Get, path = "/", params = {}, initheader = {}, obj = nil, elename = nil, klass = nil, **options)
    uri = url_for(path, params)

    proxy_uri = uri.find_proxy

    request = verb.new(uri, initheader)

    request["Accept"] = "application/xml, text/xml"

    unless obj.nil?
      request["Content-Type"] = "application/xml; charset=UTF-8"

      request.body = PortfolioManager::Mapper.instance.obj2xml(obj, elename)
    end

    if options[:basic_auth]
      if @user.nil? || @pass.nil?
        raise PortfolioManager::HTTPBasicCredentialsNotFoundError.new(request)
      else
        request.basic_auth(@user, @pass)
      end
    end

    Net::HTTP.start(uri.host, uri.port, proxy_uri.nil? ? nil : proxy_uri.host, proxy_uri.nil? ? nil : proxy_uri.port, use_ssl: uri.is_a?(URI::HTTPS)) do |http|
      log(INFO, "Request:\n" + ([
        "Protocol: HTTP/#{Net::HTTP.version_1_2? ? "1.2" : "1.1"}",
        "Method: #{verb.name.split("::")[-1].upcase}",
        "Scheme: #{uri.scheme}",
        "Host: #{uri.host}",
        "Path: #{uri.path}",
        "QueryString: #{uri.query}",
        "Body: #{request.body}",
      ] + (["Accept", "Authorization", "Content-Type"] + initheader.keys).sort.uniq.collect { |key|
        request.key?(key) ? "#{key}: #{request[key]}" : nil
      }.compact).collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      response = http.request(request)

      response_body_utf8 = response.body.force_encoding("UTF-8")

      log(INFO, "Response:\n" + ([
        "Protocol: HTTP/#{response.http_version}",
        "URI: #{response.uri}",
        "Code: #{response.code}",
        "Message: #{response.message}",
        "Body: #{response_body_utf8}",
      ]).compact.collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      if response_body_utf8.start_with?("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
        PortfolioManager::Mapper.instance.xml2obj(response_body_utf8, response.code.start_with?("2") ? klass : PortfolioManager::Xml::ResponseType)
      else
        raise PortfolioManager::HTTPResponseError.new(response)
      end
    end
  end

  # Build path for the given arguments.
  #
  # @param args [Array<#to_s>]
  # @return [String]
  def path_for(*args)
    "/#{args.collect { |arg| ERB::Util.url_encode(arg.to_s) }.join("/")}"
  end

  # Build URL for given path and query parameters.
  #
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @return [URI]
  def url_for(path, params = {})
    query_string = params.each_pair.collect { |pair|
      key, value = *pair

      value.nil? ? nil : "#{ERB::Util.url_encode(key)}=#{ERB::Util.url_encode(value.to_s)}"
    }.compact.join("&")

    URI("#{@base_uri}#{path}#{query_string.empty? ? nil : "?#{query_string}"}")
  end
end

#passString (readonly)

Returns:

  • (String)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/portfolio_manager/client.rb', line 54

class Base < Logger::Application
  attr_reader :base_uri
  attr_reader :user, :pass
  attr_reader :debug

  # Default constructor.
  #
  # @param base_uri [URI]
  # @param user [nil, String]
  # @param pass [nil, String]
  # @param debug [Boolean]
  # @return [PortfolioManager::Client::Base]
  def initialize(base_uri, user = nil, pass = nil, debug = false)
    super("PortfolioManager")

    @base_uri, @user, @pass, @debug = base_uri, user, pass, debug
  end

  protected

  # Send HTTP request.
  #
  # @param verb [Class]
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @param initheader [Hash<String, String>]
  # @param obj [nil, Object]
  # @param elename [nil, String]
  # @param klass [nil, Class]
  # @param options [Hash<Symbol, Object>]
  # @option options [Boolean] :basic_auth
  # @return [Object]
  # @raise [PortfolioManager::HTTPBasicCredentialsNotFoundError]
  # @raise [PortfolioManager::HTTPResponseError]
  # @see https://portfoliomanager.energystar.gov/webservices/home/errors
  def request(verb = Net::HTTP::Get, path = "/", params = {}, initheader = {}, obj = nil, elename = nil, klass = nil, **options)
    uri = url_for(path, params)

    proxy_uri = uri.find_proxy

    request = verb.new(uri, initheader)

    request["Accept"] = "application/xml, text/xml"

    unless obj.nil?
      request["Content-Type"] = "application/xml; charset=UTF-8"

      request.body = PortfolioManager::Mapper.instance.obj2xml(obj, elename)
    end

    if options[:basic_auth]
      if @user.nil? || @pass.nil?
        raise PortfolioManager::HTTPBasicCredentialsNotFoundError.new(request)
      else
        request.basic_auth(@user, @pass)
      end
    end

    Net::HTTP.start(uri.host, uri.port, proxy_uri.nil? ? nil : proxy_uri.host, proxy_uri.nil? ? nil : proxy_uri.port, use_ssl: uri.is_a?(URI::HTTPS)) do |http|
      log(INFO, "Request:\n" + ([
        "Protocol: HTTP/#{Net::HTTP.version_1_2? ? "1.2" : "1.1"}",
        "Method: #{verb.name.split("::")[-1].upcase}",
        "Scheme: #{uri.scheme}",
        "Host: #{uri.host}",
        "Path: #{uri.path}",
        "QueryString: #{uri.query}",
        "Body: #{request.body}",
      ] + (["Accept", "Authorization", "Content-Type"] + initheader.keys).sort.uniq.collect { |key|
        request.key?(key) ? "#{key}: #{request[key]}" : nil
      }.compact).collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      response = http.request(request)

      response_body_utf8 = response.body.force_encoding("UTF-8")

      log(INFO, "Response:\n" + ([
        "Protocol: HTTP/#{response.http_version}",
        "URI: #{response.uri}",
        "Code: #{response.code}",
        "Message: #{response.message}",
        "Body: #{response_body_utf8}",
      ]).compact.collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      if response_body_utf8.start_with?("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
        PortfolioManager::Mapper.instance.xml2obj(response_body_utf8, response.code.start_with?("2") ? klass : PortfolioManager::Xml::ResponseType)
      else
        raise PortfolioManager::HTTPResponseError.new(response)
      end
    end
  end

  # Build path for the given arguments.
  #
  # @param args [Array<#to_s>]
  # @return [String]
  def path_for(*args)
    "/#{args.collect { |arg| ERB::Util.url_encode(arg.to_s) }.join("/")}"
  end

  # Build URL for given path and query parameters.
  #
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @return [URI]
  def url_for(path, params = {})
    query_string = params.each_pair.collect { |pair|
      key, value = *pair

      value.nil? ? nil : "#{ERB::Util.url_encode(key)}=#{ERB::Util.url_encode(value.to_s)}"
    }.compact.join("&")

    URI("#{@base_uri}#{path}#{query_string.empty? ? nil : "?#{query_string}"}")
  end
end

#userString (readonly)

Returns:

  • (String)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/portfolio_manager/client.rb', line 54

class Base < Logger::Application
  attr_reader :base_uri
  attr_reader :user, :pass
  attr_reader :debug

  # Default constructor.
  #
  # @param base_uri [URI]
  # @param user [nil, String]
  # @param pass [nil, String]
  # @param debug [Boolean]
  # @return [PortfolioManager::Client::Base]
  def initialize(base_uri, user = nil, pass = nil, debug = false)
    super("PortfolioManager")

    @base_uri, @user, @pass, @debug = base_uri, user, pass, debug
  end

  protected

  # Send HTTP request.
  #
  # @param verb [Class]
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @param initheader [Hash<String, String>]
  # @param obj [nil, Object]
  # @param elename [nil, String]
  # @param klass [nil, Class]
  # @param options [Hash<Symbol, Object>]
  # @option options [Boolean] :basic_auth
  # @return [Object]
  # @raise [PortfolioManager::HTTPBasicCredentialsNotFoundError]
  # @raise [PortfolioManager::HTTPResponseError]
  # @see https://portfoliomanager.energystar.gov/webservices/home/errors
  def request(verb = Net::HTTP::Get, path = "/", params = {}, initheader = {}, obj = nil, elename = nil, klass = nil, **options)
    uri = url_for(path, params)

    proxy_uri = uri.find_proxy

    request = verb.new(uri, initheader)

    request["Accept"] = "application/xml, text/xml"

    unless obj.nil?
      request["Content-Type"] = "application/xml; charset=UTF-8"

      request.body = PortfolioManager::Mapper.instance.obj2xml(obj, elename)
    end

    if options[:basic_auth]
      if @user.nil? || @pass.nil?
        raise PortfolioManager::HTTPBasicCredentialsNotFoundError.new(request)
      else
        request.basic_auth(@user, @pass)
      end
    end

    Net::HTTP.start(uri.host, uri.port, proxy_uri.nil? ? nil : proxy_uri.host, proxy_uri.nil? ? nil : proxy_uri.port, use_ssl: uri.is_a?(URI::HTTPS)) do |http|
      log(INFO, "Request:\n" + ([
        "Protocol: HTTP/#{Net::HTTP.version_1_2? ? "1.2" : "1.1"}",
        "Method: #{verb.name.split("::")[-1].upcase}",
        "Scheme: #{uri.scheme}",
        "Host: #{uri.host}",
        "Path: #{uri.path}",
        "QueryString: #{uri.query}",
        "Body: #{request.body}",
      ] + (["Accept", "Authorization", "Content-Type"] + initheader.keys).sort.uniq.collect { |key|
        request.key?(key) ? "#{key}: #{request[key]}" : nil
      }.compact).collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      response = http.request(request)

      response_body_utf8 = response.body.force_encoding("UTF-8")

      log(INFO, "Response:\n" + ([
        "Protocol: HTTP/#{response.http_version}",
        "URI: #{response.uri}",
        "Code: #{response.code}",
        "Message: #{response.message}",
        "Body: #{response_body_utf8}",
      ]).compact.collect { |s|
        "\t#{s}"
      }.join("\n")) if @debug

      if response_body_utf8.start_with?("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
        PortfolioManager::Mapper.instance.xml2obj(response_body_utf8, response.code.start_with?("2") ? klass : PortfolioManager::Xml::ResponseType)
      else
        raise PortfolioManager::HTTPResponseError.new(response)
      end
    end
  end

  # Build path for the given arguments.
  #
  # @param args [Array<#to_s>]
  # @return [String]
  def path_for(*args)
    "/#{args.collect { |arg| ERB::Util.url_encode(arg.to_s) }.join("/")}"
  end

  # Build URL for given path and query parameters.
  #
  # @param path [String]
  # @param params [Hash<String, Object>]
  # @return [URI]
  def url_for(path, params = {})
    query_string = params.each_pair.collect { |pair|
      key, value = *pair

      value.nil? ? nil : "#{ERB::Util.url_encode(key)}=#{ERB::Util.url_encode(value.to_s)}"
    }.compact.join("&")

    URI("#{@base_uri}#{path}#{query_string.empty? ? nil : "?#{query_string}"}")
  end
end

Instance Method Details

#path_for(*args) ⇒ String (protected)

Build path for the given arguments.

Parameters:

  • args (Array<#to_s>)

Returns:

  • (String)


153
154
155
# File 'lib/portfolio_manager/client.rb', line 153

def path_for(*args)
  "/#{args.collect { |arg| ERB::Util.url_encode(arg.to_s) }.join("/")}"
end

#request(verb = Net::HTTP::Get, path = "/", params = {}, initheader = {}, obj = nil, elename = nil, klass = nil, **options) ⇒ Object (protected)

Send HTTP request.

Parameters:

  • verb (Class) (defaults to: Net::HTTP::Get)
  • path (String) (defaults to: "/")
  • params (Hash<String, Object>) (defaults to: {})
  • initheader (Hash<String, String>) (defaults to: {})
  • obj (nil, Object) (defaults to: nil)
  • elename (nil, String) (defaults to: nil)
  • klass (nil, Class) (defaults to: nil)
  • options (Hash<Symbol, Object>)

Options Hash (**options):

  • :basic_auth (Boolean)

Returns:

  • (Object)

Raises:

See Also:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/portfolio_manager/client.rb', line 89

def request(verb = Net::HTTP::Get, path = "/", params = {}, initheader = {}, obj = nil, elename = nil, klass = nil, **options)
  uri = url_for(path, params)

  proxy_uri = uri.find_proxy

  request = verb.new(uri, initheader)

  request["Accept"] = "application/xml, text/xml"

  unless obj.nil?
    request["Content-Type"] = "application/xml; charset=UTF-8"

    request.body = PortfolioManager::Mapper.instance.obj2xml(obj, elename)
  end

  if options[:basic_auth]
    if @user.nil? || @pass.nil?
      raise PortfolioManager::HTTPBasicCredentialsNotFoundError.new(request)
    else
      request.basic_auth(@user, @pass)
    end
  end

  Net::HTTP.start(uri.host, uri.port, proxy_uri.nil? ? nil : proxy_uri.host, proxy_uri.nil? ? nil : proxy_uri.port, use_ssl: uri.is_a?(URI::HTTPS)) do |http|
    log(INFO, "Request:\n" + ([
      "Protocol: HTTP/#{Net::HTTP.version_1_2? ? "1.2" : "1.1"}",
      "Method: #{verb.name.split("::")[-1].upcase}",
      "Scheme: #{uri.scheme}",
      "Host: #{uri.host}",
      "Path: #{uri.path}",
      "QueryString: #{uri.query}",
      "Body: #{request.body}",
    ] + (["Accept", "Authorization", "Content-Type"] + initheader.keys).sort.uniq.collect { |key|
      request.key?(key) ? "#{key}: #{request[key]}" : nil
    }.compact).collect { |s|
      "\t#{s}"
    }.join("\n")) if @debug

    response = http.request(request)

    response_body_utf8 = response.body.force_encoding("UTF-8")

    log(INFO, "Response:\n" + ([
      "Protocol: HTTP/#{response.http_version}",
      "URI: #{response.uri}",
      "Code: #{response.code}",
      "Message: #{response.message}",
      "Body: #{response_body_utf8}",
    ]).compact.collect { |s|
      "\t#{s}"
    }.join("\n")) if @debug

    if response_body_utf8.start_with?("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
      PortfolioManager::Mapper.instance.xml2obj(response_body_utf8, response.code.start_with?("2") ? klass : PortfolioManager::Xml::ResponseType)
    else
      raise PortfolioManager::HTTPResponseError.new(response)
    end
  end
end

#url_for(path, params = {}) ⇒ URI (protected)

Build URL for given path and query parameters.

Parameters:

  • path (String)
  • params (Hash<String, Object>) (defaults to: {})

Returns:

  • (URI)


162
163
164
165
166
167
168
169
170
# File 'lib/portfolio_manager/client.rb', line 162

def url_for(path, params = {})
  query_string = params.each_pair.collect { |pair|
    key, value = *pair

    value.nil? ? nil : "#{ERB::Util.url_encode(key)}=#{ERB::Util.url_encode(value.to_s)}"
  }.compact.join("&")

  URI("#{@base_uri}#{path}#{query_string.empty? ? nil : "?#{query_string}"}")
end