google_maps_service 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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