google_maps_service 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,8 @@
1
1
  require 'multi_json'
2
2
 
3
- module GoogleMapsService
3
+ module GoogleMapsService::Apis
4
4
 
5
- # Performs requests to the Google Maps Roads API."""
5
+ # Performs requests to the Google Maps Roads API.
6
6
  module Roads
7
7
 
8
8
  # Base URL of Google Maps Roads API
@@ -14,28 +14,37 @@ module GoogleMapsService
14
14
  # set of data with the points snapped to the most likely roads the vehicle
15
15
  # was traveling along.
16
16
  #
17
- # @param [] path The path to be snapped. A list of latitude/longitude tuples.
18
- # :type path: list
17
+ # @example Single point snap
18
+ # results = client.snap_to_roads([40.714728, -73.998672])
19
19
  #
20
- # @param [] interpolate Whether to interpolate a path to include all points
20
+ # @example Multi points snap
21
+ # path = [
22
+ # [-33.8671, 151.20714],
23
+ # [-33.86708, 151.20683000000002],
24
+ # [-33.867070000000005, 151.20674000000002],
25
+ # [-33.86703, 151.20625]
26
+ # ]
27
+ # results = client.snap_to_roads(path, interpolate: true)
28
+ #
29
+ # @param [Array] path The path to be snapped. Array of latitude/longitude pairs.
30
+ # @param [Boolean] interpolate Whether to interpolate a path to include all points
21
31
  # forming the full road-geometry. When true, additional interpolated
22
32
  # points will also be returned, resulting in a path that smoothly
23
33
  # follows the geometry of the road, even around corners and through
24
34
  # tunnels. Interpolated paths may contain more points than the
25
35
  # original path.
26
- # :type interpolate: bool
27
36
  #
28
- # :rtype: A list of snapped points.
29
- def snap_to_roads(path: nil, interpolate: false)
37
+ # @return [Array] Array of snapped points.
38
+ def snap_to_roads(path, interpolate: false)
30
39
  path = GoogleMapsService::Convert.waypoints(path)
31
40
 
32
41
  params = {
33
42
  path: path
34
43
  }
35
44
 
36
- params[:interpolate] = "true" if interpolate
45
+ params[:interpolate] = 'true' if interpolate
37
46
 
38
- return get("/v1/snapToRoads", params,
47
+ return get('/v1/snapToRoads', params,
39
48
  base_url: ROADS_BASE_URL,
40
49
  accepts_client_id: false,
41
50
  custom_response_decoder: method(:extract_roads_body))[:snappedPoints]
@@ -43,37 +52,53 @@ module GoogleMapsService
43
52
 
44
53
  # Returns the posted speed limit (in km/h) for given road segments.
45
54
  #
55
+ # @example Multi places snap
56
+ # place_ids = [
57
+ # 'ChIJ0wawjUCuEmsRgfqC5Wd9ARM',
58
+ # 'ChIJ6cs2kkCuEmsRUfqC5Wd9ARM'
59
+ # ]
60
+ # results = client.speed_limits(place_ids)
61
+ #
46
62
  # @param [String, Array<String>] place_ids The Place ID of the road segment. Place IDs are returned
47
63
  # by the snap_to_roads function. You can pass up to 100 Place IDs.
48
- # @return Array of speed limits.
49
- def speed_limits(place_ids: nil)
50
- params = GoogleMapsService::Convert.as_list(place_ids).map { |place_id| ["placeId", place_id] }
64
+ #
65
+ # @return [Array] Array of speed limits.
66
+ def speed_limits(place_ids)
67
+ params = GoogleMapsService::Convert.as_list(place_ids).map { |place_id| ['placeId', place_id] }
51
68
 
52
- return get("/v1/speedLimits", params,
69
+ return get('/v1/speedLimits', params,
53
70
  base_url: ROADS_BASE_URL,
54
71
  accepts_client_id: false,
55
72
  custom_response_decoder: method(:extract_roads_body))[:speedLimits]
56
73
  end
57
74
 
58
-
59
75
  # Returns the posted speed limit (in km/h) for given road segments.
60
76
  #
61
77
  # The provided points will first be snapped to the most likely roads the
62
78
  # vehicle was traveling along.
63
79
  #
80
+ # @example Multi points snap
81
+ # path = [
82
+ # [-33.8671, 151.20714],
83
+ # [-33.86708, 151.20683000000002],
84
+ # [-33.867070000000005, 151.20674000000002],
85
+ # [-33.86703, 151.20625]
86
+ # ]
87
+ # results = client.snapped_speed_limits(path)
88
+ #
64
89
  # @param [Hash, Array] path The path of points to be snapped. A list of (or single)
65
90
  # latitude/longitude tuples.
66
91
  #
67
- # @return [Hash] a dict with both a list of speed limits and a list of the snapped
92
+ # @return [Hash] A hash with both a list of speed limits and a list of the snapped
68
93
  # points.
69
- def snapped_speed_limits(path: nil)
94
+ def snapped_speed_limits(path)
70
95
  path = GoogleMapsService::Convert.waypoints(path)
71
96
 
72
97
  params = {
73
98
  path: path
74
99
  }
75
100
 
76
- return get("/v1/speedLimits", params,
101
+ return get('/v1/speedLimits', params,
77
102
  base_url: ROADS_BASE_URL,
78
103
  accepts_client_id: false,
79
104
  custom_response_decoder: method(:extract_roads_body))
@@ -88,33 +113,38 @@ module GoogleMapsService
88
113
  unless response.status_code == 200
89
114
  check_response_status_code(response)
90
115
  end
91
- raise GoogleMapsService::Error::ApiError.new(response), "Received a malformed response."
116
+ raise GoogleMapsService::Error::ApiError.new(response), 'Received a malformed response.'
92
117
  end
93
118
 
94
- if body.has_key?(:error)
95
- error = body[:error]
96
- status = error[:status]
97
-
98
- case status
99
- when 'INVALID_ARGUMENT'
100
- if error[:message] == 'The provided API key is invalid.'
101
- raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
102
- end
103
- raise GoogleMapsService::Error::InvalidRequestError.new(response), error[:message]
104
- when 'PERMISSION_DENIED'
105
- raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
106
- when 'RESOURCE_EXHAUSTED'
107
- raise GoogleMapsService::Error::RateLimitError.new(response), error[:message]
108
- else
109
- raise GoogleMapsService::Error::ApiError.new(response), error[:message]
110
- end
111
- end
119
+ check_roads_body_error(response, body)
112
120
 
113
121
  unless response.status_code == 200
114
122
  raise GoogleMapsService::Error::ApiError.new(response)
115
123
  end
116
-
117
124
  return body
118
125
  end
126
+
127
+ # Check response body for error status.
128
+ #
129
+ # @param [Hurley::Response] body Response object.
130
+ # @param [Hash] body Response body.
131
+ def check_roads_body_error(response, body)
132
+ error = body[:error]
133
+ return unless error
134
+
135
+ case error[:status]
136
+ when 'INVALID_ARGUMENT'
137
+ if error[:message] == 'The provided API key is invalid.'
138
+ raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
139
+ end
140
+ raise GoogleMapsService::Error::InvalidRequestError.new(response), error[:message]
141
+ when 'PERMISSION_DENIED'
142
+ raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
143
+ when 'RESOURCE_EXHAUSTED'
144
+ raise GoogleMapsService::Error::RateLimitError.new(response), error[:message]
145
+ else
146
+ raise GoogleMapsService::Error::ApiError.new(response), error[:message]
147
+ end
148
+ end
119
149
  end
120
150
  end
@@ -1,6 +1,6 @@
1
1
  require 'date'
2
2
 
3
- module GoogleMapsService
3
+ module GoogleMapsService::Apis
4
4
 
5
5
  # Performs requests to the Google Maps TimeZone API."""
6
6
  module TimeZone
@@ -8,20 +8,25 @@ module GoogleMapsService
8
8
  # Get time zone for a location on the earth, as well as that location's
9
9
  # time offset from UTC.
10
10
  #
11
+ # @example Current time zone
12
+ # timezone = client.timezone([39.603481, -119.682251])
13
+ #
14
+ # @example Time zone at certain time
15
+ # timezone = client.timezone([39.603481, -119.682251], timestamp: Time.at(1608))
16
+ #
11
17
  # @param [Hash, Array] location The latitude/longitude value representing the location to
12
18
  # look up.
13
19
  # @param [Integer, DateTime] timestamp Timestamp specifies the desired time as seconds since
14
20
  # midnight, January 1, 1970 UTC. The Time Zone API uses the timestamp to
15
21
  # determine whether or not Daylight Savings should be applied. Times
16
22
  # before 1970 can be expressed as negative values. Optional. Defaults to
17
- # ``Time.now``.
23
+ # `Time.now`.
18
24
  # @param [String] language The language in which to return results.
19
25
  #
20
- # @return [Hash]
21
- def timezone(location: nil,
22
- timestamp: nil, language: nil)
26
+ # @return [Hash] Time zone object.
27
+ def timezone(location, timestamp: Time.now, language: nil)
23
28
  location = GoogleMapsService::Convert.latlng(location)
24
- timestamp = GoogleMapsService::Convert.time(timestamp || Time.now)
29
+ timestamp = GoogleMapsService::Convert.time(timestamp)
25
30
 
26
31
  params = {
27
32
  location: location,
@@ -30,7 +35,7 @@ module GoogleMapsService
30
35
 
31
36
  params[:language] = language if language
32
37
 
33
- return get( "/maps/api/timezone/json", params)
38
+ return get('/maps/api/timezone/json', params)
34
39
  end
35
40
  end
36
41
  end
@@ -1,48 +1,52 @@
1
- require 'uri'
2
1
  require 'hurley'
3
2
  require 'multi_json'
4
3
  require 'retriable'
5
4
  require 'thread'
6
5
 
6
+ require 'google_maps_service/errors'
7
+ require 'google_maps_service/convert'
8
+ require 'google_maps_service/url'
9
+ require 'google_maps_service/apis/directions'
10
+ require 'google_maps_service/apis/distance_matrix'
11
+ require 'google_maps_service/apis/elevation'
12
+ require 'google_maps_service/apis/geocoding'
13
+ require 'google_maps_service/apis/roads'
14
+ require 'google_maps_service/apis/time_zone'
15
+
7
16
  module GoogleMapsService
17
+
18
+ # Core client functionality, common across all API requests (including performing
19
+ # HTTP requests).
8
20
  class Client
9
- USER_AGENT = "GoogleGeoApiClientRuby/#{GoogleMapsService::VERSION}"
10
- DEFAULT_BASE_URL = "https://maps.googleapis.com"
21
+ # Default Google Maps Web Service base endpoints
22
+ DEFAULT_BASE_URL = 'https://maps.googleapis.com'
23
+
24
+ # Errors those could be retriable.
11
25
  RETRIABLE_ERRORS = [GoogleMapsService::Error::ServerError, GoogleMapsService::Error::RateLimitError]
12
26
 
13
- include GoogleMapsService::Directions
14
- include GoogleMapsService::DistanceMatrix
15
- include GoogleMapsService::Elevation
16
- include GoogleMapsService::Geocoding
17
- include GoogleMapsService::Roads
18
- include GoogleMapsService::TimeZone
27
+ include GoogleMapsService::Apis::Directions
28
+ include GoogleMapsService::Apis::DistanceMatrix
29
+ include GoogleMapsService::Apis::Elevation
30
+ include GoogleMapsService::Apis::Geocoding
31
+ include GoogleMapsService::Apis::Roads
32
+ include GoogleMapsService::Apis::TimeZone
19
33
 
20
34
  # Secret key for accessing Google Maps Web Service.
21
- # Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key
35
+ # Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key.
22
36
  # @return [String]
23
- attr_reader :key
37
+ attr_accessor :key
24
38
 
25
39
  # Client id for using Maps API for Work services.
26
40
  # @return [String]
27
- attr_reader :client_id
41
+ attr_accessor :client_id
28
42
 
29
43
  # Client secret for using Maps API for Work services.
30
44
  # @return [String]
31
- attr_reader :client_secret
32
-
33
- # Connection timeout for HTTP requests, in seconds.
34
- # You should specify read_timeout in addition to this option.
35
- # @return [Integer]
36
- attr_reader :connect_timeout
37
-
38
- # Read timeout for HTTP requests, in seconds.
39
- # You should specify connect_timeout in addition to this
40
- # @return [Integer]
41
- attr_reader :read_timeout
45
+ attr_accessor :client_secret
42
46
 
43
47
  # Timeout across multiple retriable requests, in seconds.
44
48
  # @return [Integer]
45
- attr_reader :retry_timeout
49
+ attr_accessor :retry_timeout
46
50
 
47
51
  # Number of queries per second permitted.
48
52
  # If the rate limit is reached, the client will sleep for
@@ -50,25 +54,77 @@ module GoogleMapsService
50
54
  # @return [Integer]
51
55
  attr_reader :queries_per_second
52
56
 
53
- def initialize(options={})
57
+ # Construct Google Maps Web Service API client.
58
+ #
59
+ # This gem uses [Hurley](https://github.com/lostisland/hurley) as internal HTTP client.
60
+ # You can configure `Hurley::Client` through `request_options` and `ssl_options` parameters.
61
+ # You can also directly get the `Hurley::Client` object via {#client} method.
62
+ #
63
+ # @example Setup API keys
64
+ # gmaps = GoogleMapsService::Client.new(key: 'Add your key here')
65
+ #
66
+ # @example Setup client IDs
67
+ # gmaps = GoogleMapsService::Client.new(
68
+ # client_id: 'Add your client id here',
69
+ # client_secret: 'Add your client secret here'
70
+ # )
71
+ #
72
+ # @example Setup time out and QPS limit
73
+ # gmaps = GoogleMapsService::Client.new(
74
+ # key: 'Add your key here',
75
+ # retry_timeout: 20,
76
+ # queries_per_second: 10
77
+ # )
78
+ #
79
+ # @example Request behind proxy
80
+ # request_options = Hurley::RequestOptions.new
81
+ # request_options.proxy = Hurley::Url.parse 'http://user:password@proxy.example.com:3128'
82
+ #
83
+ # gmaps = GoogleMapsService::Client.new(
84
+ # key: 'Add your key here',
85
+ # request_options: request_options
86
+ # )
87
+ #
88
+ # @example Using Excon and Http Cache
89
+ # require 'hurley-excon' # https://github.com/lostisland/hurley-excon
90
+ # require 'hurley/http_cache' # https://github.com/plataformatec/hurley-http-cache
91
+ #
92
+ # gmaps = GoogleMapsService::Client.new(
93
+ # key: 'Add your key here',
94
+ # connection: Hurley::HttpCache.new(HurleyExcon::Connection.new)
95
+ # )
96
+ #
97
+ #
98
+ #
99
+ # @option options [String] :key Secret key for accessing Google Maps Web Service.
100
+ # Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key.
101
+ # @option options [String] :client_id Client id for using Maps API for Work services.
102
+ # @option options [String] :client_secret Client secret for using Maps API for Work services.
103
+ # @option options [Integer] :retry_timeout Timeout across multiple retriable requests, in seconds.
104
+ # @option options [Integer] :queries_per_second Number of queries per second permitted.
105
+ #
106
+ # @option options [Hurley::RequestOptions] :request_options HTTP client request options.
107
+ # See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.
108
+ # @option options [Hurley::SslOptions] :ssl_options HTTP client SSL options.
109
+ # See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.
110
+ # @option options [Object] :connection HTTP client connection.
111
+ # By default, the default Hurley's HTTP client connection (Net::Http) will be used.
112
+ # See https://github.com/lostisland/hurley/blob/master/README.md#connections.
113
+ def initialize(**options)
54
114
  @key = options[:key] || GoogleMapsService.key
55
115
  @client_id = options[:client_id] || GoogleMapsService.client_id
56
116
  @client_secret = options[:client_secret] || GoogleMapsService.client_secret
57
- @connect_timeout = options[:connect_timeout] || GoogleMapsService.connect_timeout
58
- @read_timeout = options[:read_timeout] || GoogleMapsService.read_timeout
59
117
  @retry_timeout = options[:retry_timeout] || GoogleMapsService.retry_timeout || 60
60
118
  @queries_per_second = options[:queries_per_second] || GoogleMapsService.queries_per_second
119
+ @request_options = options[:request_options] || GoogleMapsService.request_options
120
+ @ssl_options = options[:ssl_options] || GoogleMapsService.ssl_options
121
+ @connection = options[:connection] || GoogleMapsService.connection
61
122
 
62
- # Prepare "tickets" for calling API
63
- if @queries_per_second
64
- @sent_times = SizedQueue.new @queries_per_second
65
- @queries_per_second.times do
66
- @sent_times << 0
67
- end
68
- end
123
+ #
124
+ initialize_qps if @queries_per_second
69
125
  end
70
126
 
71
- # Get the current HTTP client
127
+ # Get the current HTTP client.
72
128
  # @return [Hurley::Client]
73
129
  def client
74
130
  @client ||= new_client
@@ -76,92 +132,68 @@ module GoogleMapsService
76
132
 
77
133
  protected
78
134
 
79
- # Create a new HTTP client
135
+ # Initialize QPS queue. QPS queue is a "tickets" for calling API
136
+ def initialize_qps
137
+ @qps_queue = SizedQueue.new @queries_per_second
138
+ @queries_per_second.times do
139
+ @qps_queue << 0
140
+ end
141
+ end
142
+
143
+ # Create a new HTTP client.
80
144
  # @return [Hurley::Client]
81
145
  def new_client
82
146
  client = Hurley::Client.new
147
+
148
+ client.connection = @connection if @connection
149
+ @request_options.each_pair {|key, value| client.request_options[key] = value } if @request_options
150
+ @ssl_options.each_pair {|key, value| client.ssl_options[key] = value } if @ssl_options
151
+
83
152
  client.request_options.query_class = Hurley::Query::Flat
84
- client.request_options.timeout = @read_timeout if @read_timeout
85
- client.request_options.open_timeout = @connect_timeout if @connect_timeout
86
- client.header[:user_agent] = USER_AGENT
153
+ client.header[:user_agent] = user_agent
87
154
  client
88
155
  end
89
156
 
157
+ # Build the user agent header
158
+ # @return [String]
159
+ def user_agent
160
+ sprintf('google-maps-services-ruby/%s %s',
161
+ GoogleMapsService::VERSION,
162
+ GoogleMapsService::OS_VERSION)
163
+ end
164
+
165
+ # Make API call.
166
+ #
167
+ # @param [String] path Url path.
168
+ # @param [String] params Request parameters.
169
+ # @param [String] base_url Base Google Maps Web Service API endpoint url.
170
+ # @param [Boolean] accepts_client_id Sign the request using API {#keys} instead of {#client_id}.
171
+ # @param [Method] custom_response_decoder Custom method to decode raw API response.
172
+ #
173
+ # @return [Object] Decoded response body.
90
174
  def get(path, params, base_url: DEFAULT_BASE_URL, accepts_client_id: true, custom_response_decoder: nil)
91
175
  url = base_url + generate_auth_url(path, params, accepts_client_id)
92
176
 
93
177
  Retriable.retriable timeout: @retry_timeout, on: RETRIABLE_ERRORS do |try|
94
178
  # Get/wait the request "ticket" if QPS is configured
95
179
  # Check for previous request time, it must be more than a second ago before calling new request
96
- if @sent_times
97
- elapsed_since_earliest = Time.now - @sent_times.pop
180
+ if @qps_queue
181
+ elapsed_since_earliest = Time.now - @qps_queue.pop
98
182
  sleep(1 - elapsed_since_earliest) if elapsed_since_earliest.to_f < 1
99
183
  end
100
184
 
101
- response = client.get url
102
-
103
- # Release request "ticket"
104
- @sent_times << Time.now if @sent_times
185
+ begin
186
+ response = client.get url
187
+ ensure
188
+ # Release request "ticket"
189
+ @qps_queue << Time.now if @qps_queue
190
+ end
105
191
 
106
192
  return custom_response_decoder.call(response) if custom_response_decoder
107
193
  decode_response_body(response)
108
194
  end
109
195
  end
110
196
 
111
- # Extract and parse body response as hash. Throw an error if there is something wrong with the response.
112
- #
113
- # @param [Hurley::Response] response Web API response.
114
- #
115
- # @return [Hash] Response body as hash. The hash key will be symbolized.
116
- #
117
- # @raise [GoogleMapsService::Error::RedirectError] The response redirects to another URL.
118
- # @raise [GoogleMapsService::Error::RequestDeniedError] The credential (key or client id pair) is not valid.
119
- # @raise [GoogleMapsService::Error::ClientError] The request is invalid and should not be retried without modification.
120
- # @raise [GoogleMapsService::Error::ServerError] An error occurred on the server and the request can be retried.
121
- # @raise [GoogleMapsService::Error::TransmissionError] Unknown response status code.
122
- # @raise [GoogleMapsService::Error::RateLimitError] The quota for the credential is already pass the limit.
123
- # @raise [GoogleMapsService::Error::ApiError] The Web API error.
124
- def decode_response_body(response)
125
- check_response_status_code(response)
126
-
127
- body = MultiJson.load(response.body, :symbolize_keys => true)
128
-
129
- case body[:status]
130
- when 'OK', 'ZERO_RESULTS'
131
- return body
132
- when 'OVER_QUERY_LIMIT'
133
- raise GoogleMapsService::Error::RateLimitError.new(response), body[:error_message]
134
- when 'REQUEST_DENIED'
135
- raise GoogleMapsService::Error::RequestDeniedError.new(response), body[:error_message]
136
- when 'INVALID_REQUEST'
137
- raise GoogleMapsService::Error::InvalidRequestError.new(response), body[:error_message]
138
- else
139
- raise GoogleMapsService::Error::ApiError.new(response), body[:error_message]
140
- end
141
- end
142
-
143
- def check_response_status_code(response)
144
- case response.status_code
145
- when 200..300
146
- # Do-nothing
147
- when 301, 302, 303, 307
148
- message = sprintf('Redirect to %s', response.header[:location])
149
- raise GoogleMapsService::Error::RedirectError.new(response), message
150
- when 401
151
- message = 'Unauthorized'
152
- raise GoogleMapsService::Error::ClientError.new(response), message
153
- when 304, 400, 402...500
154
- message = 'Invalid request'
155
- raise GoogleMapsService::Error::ClientError.new(response), message
156
- when 500..600
157
- message = 'Server error'
158
- raise GoogleMapsService::Error::ServerError.new(response), message
159
- else
160
- message = 'Unknown error'
161
- raise GoogleMapsService::Error::Error.new(response), message
162
- end
163
- end
164
-
165
197
  # Returns the path and query string portion of the request URL,
166
198
  # first adding any necessary parameters.
167
199
  #
@@ -181,83 +213,68 @@ module GoogleMapsService
181
213
  if accepts_client_id and @client_id and @client_secret
182
214
  params << ["client", @client_id]
183
215
 
184
- path = [path, self.class.urlencode_params(params)].join("?")
185
- sig = self.class.sign_hmac(@client_secret, path)
216
+ path = [path, GoogleMapsService::Url.urlencode_params(params)].join("?")
217
+ sig = GoogleMapsService::Url.sign_hmac(@client_secret, path)
186
218
  return path + "&signature=" + sig
187
219
  end
188
220
 
189
221
  if @key
190
222
  params << ["key", @key]
191
- return path + "?" + self.class.urlencode_params(params)
223
+ return path + "?" + GoogleMapsService::Url.urlencode_params(params)
192
224
  end
193
225
 
194
226
  raise ArgumentError, "Must provide API key for this API. It does not accept enterprise credentials."
195
227
  end
196
228
 
197
- # Returns a base64-encoded HMAC-SHA1 signature of a given string.
229
+ # Extract and parse body response as hash. Throw an error if there is something wrong with the response.
198
230
  #
199
- # @param [String] secret The key used for the signature, base64 encoded.
200
- # @param [String] payload The payload to sign.
231
+ # @param [Hurley::Response] response Web API response.
201
232
  #
202
- # @return [String]
203
- def self.sign_hmac(secret, payload)
204
- require 'base64'
205
- require 'hmac'
206
- require 'hmac-sha1'
207
-
208
- secret = secret.encode('ASCII')
209
- payload = payload.encode('ASCII')
210
-
211
- # Decode the private key
212
- raw_key = Base64.urlsafe_decode64(secret)
213
-
214
- # Create a signature using the private key and the URL
215
- sha1 = HMAC::SHA1.new(raw_key)
216
- sha1 << payload
217
- raw_signature = sha1.digest()
218
-
219
- # Encode the signature into base64 for url use form.
220
- signature = Base64.urlsafe_encode64(raw_signature)
221
- return signature
233
+ # @return [Hash] Response body as hash. The hash key will be symbolized.
234
+ def decode_response_body(response)
235
+ check_response_status_code(response)
236
+ body = MultiJson.load(response.body, :symbolize_keys => true)
237
+ check_body_error(response, body)
238
+ body
222
239
  end
223
240
 
224
- # URL encodes the parameters.
225
- # @param [Hash, Array<Array>] params The parameters
226
- # @return [String]
227
- def self.urlencode_params(params)
228
- unquote_unreserved(URI.encode_www_form(params))
241
+ # Check HTTP response status code. Raise error if the status is not 2xx.
242
+ #
243
+ # @param [Hurley::Response] response Web API response.
244
+ def check_response_status_code(response)
245
+ case response.status_code
246
+ when 200..300
247
+ # Do-nothing
248
+ when 301, 302, 303, 307
249
+ raise GoogleMapsService::Error::RedirectError.new(response), sprintf('Redirect to %s', response.header[:location])
250
+ when 401
251
+ raise GoogleMapsService::Error::ClientError.new(response), 'Unauthorized'
252
+ when 304, 400, 402...500
253
+ raise GoogleMapsService::Error::ClientError.new(response), 'Invalid request'
254
+ when 500..600
255
+ raise GoogleMapsService::Error::ServerError.new(response), 'Server error'
256
+ else
257
+ raise ArgumentError, 'Invalid response status code'
258
+ end
229
259
  end
230
260
 
231
- # The unreserved URI characters (RFC 3986)
232
- UNRESERVED_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
233
-
234
- # Un-escape any percent-escape sequences in a URI that are unreserved
235
- # characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
261
+ # Check response body for error status.
236
262
  #
237
- # @param [String] uri
238
- #
239
- # @return [String]
240
- def self.unquote_unreserved(uri)
241
- parts = uri.split('%')
242
-
243
- (1..parts.length-1).each do |i|
244
- h = parts[i][0..1]
245
-
246
- if h.length == 2 and !h.match(/[^A-Za-z0-9]/)
247
- c = h.to_i(16).chr
248
-
249
- if UNRESERVED_SET.include?(c)
250
- parts[i] = c + parts[i][2..-1]
251
- else
252
- parts[i] = "%#{parts[i]}"
253
- end
254
- else
255
- parts[i] = "%#{parts[i]}"
256
- end
263
+ # @param [Hurley::Response] body Response object.
264
+ # @param [Hash] body Response body.
265
+ def check_body_error(response, body)
266
+ case body[:status]
267
+ when 'OK', 'ZERO_RESULTS'
268
+ # Do-nothing
269
+ when 'OVER_QUERY_LIMIT'
270
+ raise GoogleMapsService::Error::RateLimitError.new(response), body[:error_message]
271
+ when 'REQUEST_DENIED'
272
+ raise GoogleMapsService::Error::RequestDeniedError.new(response), body[:error_message]
273
+ when 'INVALID_REQUEST'
274
+ raise GoogleMapsService::Error::InvalidRequestError.new(response), body[:error_message]
275
+ else
276
+ raise GoogleMapsService::Error::ApiError.new(response), body[:error_message]
257
277
  end
258
-
259
- return parts.join
260
278
  end
261
-
262
279
  end
263
280
  end