citygrid_api 0.0.7 → 0.0.9

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.7
1
+ 0.0.9
data/citygrid_api.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "citygrid_api"
8
- s.version = "0.0.7"
8
+ s.version = "0.0.9"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Elpizo Choi", "Joseph Chen"]
12
- s.date = "2012-02-27"
12
+ s.date = "2012-03-01"
13
13
  s.description = "Ruby wrapper for CityGrid APIs"
14
14
  s.email = "Joseph.Chen@citygridmedia.com"
15
15
  s.extra_rdoc_files = [
@@ -105,6 +105,7 @@ Gem::Specification.new do |s|
105
105
  "lib/citygrid/api/mutable.rb",
106
106
  "lib/citygrid/api/response.rb",
107
107
  "lib/citygrid/api/searchable.rb",
108
+ "lib/citygrid/citygrid_exceptions.rb",
108
109
  "lib/citygrid/details.rb",
109
110
  "lib/citygrid/listing.rb",
110
111
  "lib/citygrid/offers.rb",
@@ -13,11 +13,11 @@ class CityGrid
13
13
  :body => {"mutateOperationListResource" => [
14
14
  {
15
15
  "operand" => {
16
- "imageType" => type,
17
- "imageName" => name,
18
- "imageFormat" => format,
19
- "image" => image_data
20
- },
16
+ "imageType" => type,
17
+ "imageName" => name,
18
+ "imageFormat" => format,
19
+ "image" => image_data
20
+ },
21
21
  "operator" => "ADD",
22
22
  "userId" => user_id
23
23
  }
data/lib/citygrid/api.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require "httparty"
2
2
  require "json"
3
+ require "citygrid/citygrid_exceptions"
3
4
 
4
5
  class CityGrid
5
6
  class API
6
7
  include HTTParty
8
+ include CityGridExceptions
7
9
  #debug_output $stderr
8
10
 
9
11
  DEFAULT_HEADERS = {
@@ -83,128 +85,146 @@ class CityGrid
83
85
  "options" => Net::HTTP::Options
84
86
  }
85
87
 
86
- # Transform response into API::Response object
87
- # or throw exception if an error exists
88
- def request_and_handle http_method, path, options
89
- if http_method.is_a?(String) || http_method.is_a?(Symbol)
90
- http_method = HTTP_METHODS[http_method.to_s]
91
- raise "Unknown http method: #{http_method}" unless http_method
92
- end
93
-
94
- req_options = default_options.dup
95
- req_options = req_options.merge(options)
96
-
97
- raise ConfigurationError.new "No endpoint defined" if !path || path.empty?
98
- raise ConfigurationError.new "No hostname defined" if !req_options[:base_uri] || req_options[:base_uri].empty?
99
-
100
- req = HTTParty::Request.new http_method, path, req_options
101
-
102
-
103
- begin
104
- response = req.perform
105
- rescue => ex
106
- raise RequestError.new req, ex
107
- ensure
108
- if defined?(Rails.logger)
109
- Rails.logger.info req.to_curl
110
- else
111
- puts req.to_curl
112
- end
113
- end
114
-
115
- if !response.parsed_response.is_a?(Hash)
116
- raise ResponseParseError.new req, response
117
- elsif response["errors"]
118
- raise ResponseError.new req, response["errors"], response
119
- elsif response["message"] && response["message"] == "Invalid Token or Expired"
120
- raise InvalidAuthToken.new
88
+ def strip_unsafe_params options
89
+ puts "OPTIONS: #{options}"
90
+ unsafe_params = {
91
+ :password => "[FILTERED]", :securityCode => "[FILTERED]",
92
+ :cardNumber => "[FILTERED]", :expirationMonth => "[FILTERED]",
93
+ :expirationYear => "[FILTERED]"
94
+ }
95
+ if !options[:query].nil?
96
+ to_merge[:query] = options[:query].merge(unsafe_params.select { |k| options.keys.include? k })
97
+ return options.merge(to_merge)
98
+ end
99
+ end
100
+
101
+ #
102
+ def parse_multiple_responses response
103
+ parsing = response.values.select{ |x| x.is_a? Array }.first
104
+ if parsing.nil? || parsing == []
105
+ ap "Response was too hard to parse... letting it through..."
106
+ return parsing
107
+ elsif parsing != nil && parsing != []
108
+ if parsing[0]["response"]
109
+ parsing = [parsing[0]["response"]["code"], parsing[0]["response"]["message"]]
110
+ return parsing
121
111
  else
122
- return CityGrid::API::Response.new response
112
+ # this accomodates geocode response which does not contain a response node
113
+ ap "Response was too hard to parse... letting it through..."
114
+ return nil
123
115
  end
124
-
125
- rescue => ex
126
- raise ex if CityGrid.raise_errors?
116
+ else
117
+ # We should figure out a better way to do this
118
+ raise CityGridExceptions::APIError.new "Received a JSON error code but it could not be parsed: #{response}"
127
119
  end
128
120
  end
129
121
 
130
- # ERRORS
131
- class APIError < StandardError
132
- attr_accessor :request
133
-
134
- def initialize msg, request
135
- super msg
122
+ # Transform response into API::Response object
123
+ # or throw exception if an error exists
124
+ def request_and_handle http_method, path, options
125
+ if http_method.is_a?(String) || http_method.is_a?(Symbol)
126
+ http_method = HTTP_METHODS[http_method.to_s]
127
+ raise "Unknown http method: #{http_method}" unless http_method
136
128
  end
137
- end
138
129
 
139
- class ResponseError < APIError
140
- attr_accessor :errors, :response
130
+ req_options = default_options.dup
131
+ req_options = req_options.merge(options)
141
132
 
142
- def initialize request, errors, response
143
- self.errors = errors
144
- self.response = response
145
-
146
- super "API returned error message", request
147
- end
148
- end
149
-
150
- class RequestError < APIError
151
- attr_accessor :inner_exception
133
+ raise ConfigurationError.new "No endpoint defined" if !path || path.empty?
134
+ raise ConfigurationError.new "No hostname defined" if !req_options[:base_uri] || req_options[:base_uri].empty?
152
135
 
153
- def initialize request, inner_exception, msg = nil
154
- self.inner_exception = inner_exception
155
- self.request = request
156
- super msg || "Error while performing request: #{inner_exception.message}", request
136
+ # prepare request and sanitized request for logs
137
+ #puts "Options after strip unsafe: #{strip_unsafe_params(req_options)}"
138
+ #puts "options before that: #{req_options}"
139
+ #safe_req_options = strip_unsafe_params(req_options)
140
+ req = HTTParty::Request.new http_method, path, req_options
141
+ #req_to_output = HTTParty::Request.new http_method, path, safe_req_options
142
+ # ap "HERE ARE THE INGREDIENTS OF THE REQUEST:"
143
+ # ap "http_method is: #{http_method}"
144
+ # ap "path is: #{path}"
145
+ # ap "req_options is: #{req_options}"
146
+
147
+ begin
148
+ response = req.perform
149
+ rescue => ex
150
+ raise CityGridExceptions::RequestError.new req, ex
151
+ ensure
152
+ if defined?(Rails.logger)
153
+ Rails.logger.info req.to_curl
154
+ else
155
+ puts req.to_curl
156
+ ap response
157
+ end
157
158
  end
158
- end
159
-
160
- class ResponseParseError < APIError
161
- attr_accessor :server_msg, :description, :raw_response
162
- def initialize request, response
163
- self.raw_response = response
164
- # parse Tomcat error report
165
- if response.match /<title>Apache Tomcat.* - Error report<\/title>/
166
- response.scan(/<p><b>(message|description)<\/b> *<u>(.*?)<\/u><\/p>/).each do |match|
167
- case match[0]
168
- when "message"
169
- self.server_msg = match[1]
170
- when "description"
171
- self.description = match[1]
172
- end
173
- end
174
-
175
- error_body = response.match(/<body>(.*?)<\/body>/m)[1]
176
-
177
- msg = <<-EOS
178
- Unexpected response format. Expected response to be a hash, but was instead:\n#{error_body}\n
179
- EOS
180
159
 
181
- super msg, request
160
+ begin
161
+ # catch unparsable responses (html etc)
162
+ if !response.parsed_response.is_a?(Hash)
163
+ ap "[gem] the response was unparsable (response was not a hash)"
164
+ raise CityGridExceptions::ResponseParseError.new req, response
165
+ # catch responses not in new response format
166
+ elsif response["errors"]
167
+ ap "[gem] An error in the old response format was caught. Raising a general response error..."
168
+ raise CityGridExceptions::ResponseError.new req, response["errors"], response
169
+
170
+ # Parse and handle new response codes
171
+ elsif (response["response"] && response["response"]["code"] != "SUCCESS") &&
172
+ (response["response"] && response["response"]["code"] != 200) &&
173
+ (response["response"] && response["response"]["code"] != 400)
174
+ error_code = response["response"]["code"]
175
+ ap "[gem] The response was contained in the first level of the response hash. Below:"
176
+ ap response
177
+ ap "found error code: #{error_code}"
178
+ ap "****************************************************************************"
179
+ raise CityGridExceptions.appropriate_error(error_code).new req, response, response["response"]["message"].to_s #+ " " + CityGridExceptions.print_superclasses(error_code)
180
+ # if the response is a nested hash/nested hash containing arrays
181
+ elsif response["totalNumEntries"] && response["response"].nil?
182
+ ap "[gem] now parsing a response with multiple entries: #{response}"
183
+ error_code = parse_multiple_responses(response)
184
+ ap "the error code that came back is #{error_code}"
185
+ if error_code.nil? || error_code == []
186
+ ap "[gem] passing over this for now"
187
+ return CityGrid::API::Response.new response # pass over for now
188
+ elsif error_code[0] == "SUCCESS" || error_code[0] == 200 || error_code[0] == 400
189
+ return CityGrid::API::Response.new response
190
+ else
191
+ ap "[gem] we found an error and it was #{error_code[1]}"
192
+ raise CityGridExceptions.appropriate_error(error_code[0]).new req, response, error_code[1].to_s + " "# + CityGridExceptions.print_superclasses(error_code[0])
193
+ end
182
194
  else
183
- msg = <<-EOS
184
- Unexpected response format. Expected response to be a hash, but was instead:\n#{response.parsed_response}\n
185
- EOS
186
-
187
- super msg, request
195
+ return CityGrid::API::Response.new response
188
196
  end
197
+ rescue => ex
198
+ ap "The gem threw an error: #{ex}"
199
+ raise ex if CityGrid.raise_errors?
189
200
  end
190
201
  end
191
202
 
192
- class InvalidAuthToken < StandardError
193
- def initialize message = "Invalid Token or Expired"
194
- super message
195
- end
196
- end
203
+ # Errors
197
204
 
198
- class MissingAuthToken < StandardError
199
- def initialize
200
- super "Missing authToken - token is required"
205
+ class MUSHPendingChanges <StandardError
206
+ def initialize message = "The are currently pending changes in the mush. Cannot update."
207
+ super message
201
208
  end
202
209
  end
203
210
 
211
+ # class InvalidAuthToken < StandardError
212
+ # def initialize message = "Invalid Token or Expired"
213
+ # super message
214
+ # end
215
+ # end
216
+
217
+ # class MissingAuthToken < StandardError
218
+ # def initialize
219
+ # super "Missing authToken - token is required"
220
+ # end
221
+ # end
222
+
204
223
  class ConfigurationError < StandardError
205
224
  def initialize message = "Invalid Configuration"
206
225
  super message
207
226
  end
208
227
  end
209
228
  end
210
- end
229
+ end
230
+ end
@@ -0,0 +1,199 @@
1
+ module CityGridExceptions
2
+
3
+ # Define parent error classes
4
+ # All errors thrown in the API should extend APIError - Level 1
5
+ class APIError < StandardError
6
+ attr_accessor :request
7
+
8
+ def initialize request, response, msg = "An API error occurred"
9
+ super msg
10
+ end
11
+ end
12
+
13
+ # Level 2 - These represent three different error scenarios:
14
+ # 1. Response is totally not parsable to JSON
15
+ # 2. The API call/parameters were malformed
16
+ # 3. The request was fine but their was an error API side
17
+
18
+ class ResponseParseError < APIError
19
+ attr_accessor :server_msg, :description, :raw_response
20
+ def initialize request, response, msg = nil
21
+ self.raw_response = response
22
+ # parse Tomcat error report
23
+ if response.match /<title>Apache Tomcat.* - Error report<\/title>/
24
+ response.scan(/<p><b>(message|description)<\/b> *<u>(.*?)<\/u><\/p>/).each do |match|
25
+ case match[0]
26
+ when "message"
27
+ self.server_msg = match[1]
28
+ when "description"
29
+ self.description = match[1]
30
+ end
31
+ end
32
+
33
+ error_body = response.match(/<body>(.*?)<\/body>/m)[1]
34
+
35
+ msg = <<-EOS
36
+ Unexpected response format. Expected response to be a hash, but was instead:\n#{error_body}\n
37
+ EOS
38
+
39
+ super msg, request
40
+ else
41
+ msg = <<-EOS
42
+ Unexpected response format. Expected response to be a hash, but was instead:\n#{response.parsed_response}\n
43
+ EOS
44
+
45
+ super msg, request
46
+ end
47
+ end
48
+ end
49
+
50
+ class RequestError < APIError
51
+ attr_accessor :inner_exception
52
+
53
+ def initialize request, response, msg = nil
54
+ self.inner_exception = inner_exception
55
+ self.request = request
56
+ super msg, request
57
+ #super msg || "Error while performing request: #{inner_exception.message}", request
58
+ end
59
+ end
60
+
61
+ class ResponseError < APIError
62
+ attr_accessor :errors, :response
63
+
64
+ def initialize request, errors, response
65
+ self.errors = errors
66
+ self.response = response
67
+
68
+ super "API returned error message", request
69
+ end
70
+ end
71
+
72
+ # Level 3
73
+ class GeneralError < APIError; end
74
+ class HeaderError < APIError; end
75
+ class AuthenticationError < APIError; end
76
+ class AuthorizationError < APIError; end
77
+ class OperatorError < RequestError; end
78
+ class GeneralDataError < APIError; end
79
+ class SpecificDataError < APIError; end
80
+
81
+ # Level 4
82
+
83
+ # General Errors
84
+ class SystemErrorTryAgainError < GeneralError; end
85
+ class SystemErrorUnknownError < GeneralError; end
86
+ class BadRequestTypeError < GeneralError; end
87
+
88
+
89
+
90
+ # HeaderErrors < RequestError
91
+ class ContentTypeRequiredError < HeaderError; end
92
+ class ContentTypeInvalidError < HeaderError; end
93
+ class AcceptRequiredError < HeaderError; end
94
+ class AcceptInvalidError < HeaderError; end
95
+
96
+ # Authentication Error
97
+ class AuthTokenInvalidError < AuthenticationError; end
98
+ class AuthTokenExpiredError < AuthenticationError; end
99
+ class AuthTokenNoneError < AuthenticationError; end
100
+ class UsernameRequiredError < AuthenticationError; end
101
+ class PasswordRequiredError < AuthenticationError; end
102
+ class AccountNotFoundError < AuthenticationError; end
103
+
104
+ #Authorization Error
105
+ class PermissionDeniedError < AuthorizationError; end
106
+ class NoPermissionsError < AuthorizationError; end
107
+
108
+ # Request Error
109
+ class ParameterRequiredError < RequestError; end
110
+ class ParameterRequiredConditionalError < RequestError; end
111
+ class ParameterInvalidError < RequestError; end
112
+ class ParameterFormatError < RequestError; end
113
+ class ParameterNotSupportedError < RequestError; end
114
+ class ParameterRangeTooLowError < RequestError; end
115
+ class ParameterRangeTooHighError < RequestError; end
116
+ class ParameterSizeLimitExceededError < RequestError; end
117
+ class ParameterCannotBeZeroError < RequestError; end
118
+ class ParameterOnlyOne < RequestError; end
119
+
120
+ # Operator Error
121
+ class OperatorInvalidError < OperatorError; end
122
+
123
+ # General Data Errors
124
+ class EntityNotFoundError < GeneralDataError; end
125
+ class EntityExistsError < GeneralDataError; end
126
+ class EntityLimitError < GeneralDataError; end
127
+ class EntityAlreadyInUseError < GeneralDataError; end
128
+ class EntityExpiredError < GeneralDataError; end
129
+ class EntityInactiveError < GeneralDataError; end
130
+ class EntityNotEligibleError < GeneralDataError; end
131
+ class EntityNotModifiedError < GeneralDataError; end
132
+ class EntityStateInvalidError < GeneralDataError; end
133
+ class EntityMissingDataError < GeneralDataError; end
134
+ class DataNotFoundError < GeneralDataError; end
135
+ class AssociationExistsError < GeneralDataError; end
136
+ class NoAssociationExistsError < GeneralDataError; end
137
+ class DuplicateError < GeneralDataError; end
138
+ class DateBeforeDateError < GeneralDataError; end
139
+ class RemoveNotAllowedError < GeneralDataError; end
140
+
141
+ #data errors - specific
142
+ class MopExpiredError < SpecificDataError; end
143
+ class AccountInactiveError < SpecificDataError; end
144
+ class AccountDelinquentError < SpecificDataError; end
145
+ class MonthlyBudgetReachedError < SpecificDataError; end
146
+ class QuotaExceededError < SpecificDataError; end
147
+ class RateExceededError < SpecificDataError; end
148
+
149
+ # unused errors
150
+ #400 => RequestError,
151
+ @possible_errors =
152
+ {
153
+ 0 => ResponseError, nil => ResponseParseError, "" => ResponseParseError,
154
+ 401 => AuthenticationError, 403 => RequestError, 405 => RequestError, 406 => HeaderError,
155
+ 500 => ResponseError, "SYSTEM_ERROR_TRY_AGAIN" => SystemErrorTryAgainError,
156
+ "SYSTEM_ERROR_UNKNOWN" => SystemErrorUnknownError, "BAD_REQUEST_TYPE" => BadRequestTypeError,
157
+ "HEADER_CONTENT_TYPE_IS_REQUIRED" => ContentTypeRequiredError, "HEADER_CONTENT_TYPE_INVALID" => ContentTypeInvalidError,
158
+ "HEADER_ACCEPT_IS_REQUIRED" => AcceptRequiredError, "HEADER_ACCEPT_INVALID" => AcceptInvalidError,
159
+ "AUTH_TOKEN_INVALID" => AuthTokenInvalidError, "AUTH_TOKEN_EXPIRED" => AuthTokenExpiredError,
160
+ "AUTH_TOKEN_NONE" => AuthTokenNoneError,
161
+ "USERNAME_IS_REQUIRED" => UsernameRequiredError, "PASSWORD_IS_REQUIRED" => PasswordRequiredError,
162
+ "ACCOUNT_NOT_FOUND" => AccountNotFoundError, "PERMISSION_DENIED" => PermissionDeniedError,
163
+ "NO_PERMISSIONS" => NoPermissionsError, "PARAMETER_REQUIRED" => ParameterRequiredError,
164
+ "PARAMETER_REQUIRED_CONDITIONAL" => ParameterRequiredConditionalError, "PARAMETER_INVALID" => ParameterInvalidError,
165
+ "PARAMETER_FORMAT" => ParameterFormatError, "PARAMETER_NOT_SUPPORTED" => ParameterNotSupportedError,
166
+ "PARAMETER_RANGE_TOO_LOW" => ParameterRangeTooLowError, "PARAMETER_RANGE_TOO_HIGH" => ParameterRangeTooHighError,
167
+ "PARAMETER_SIZE_LIMIT_EXCEEDED" => ParameterSizeLimitExceededError, "PARAMETER_CANNOT_BE_ZERO" => ParameterCannotBeZeroError,
168
+ "PARAMETER_ONLY_ONE" => ParameterOnlyOne, "OPERATOR_INVALID" => OperatorInvalidError,
169
+ "ENTITY_NOT_FOUND" => EntityNotFoundError, "ENTITY_EXISTS" => EntityExistsError,
170
+ "ENTITY_LIMIT" => EntityLimitError, "ENTITY_ALREADY_IN_USE" => EntityAlreadyInUseError,
171
+ "ENTITY_EXPIRED" => EntityExpiredError, "ENTITY_INACTIVE" => EntityInactiveError,
172
+ "ENTITY_NOT_ELIGIBLE" => EntityNotEligibleError, "ENTITY_NOT_MODIFIED" => EntityNotModifiedError,
173
+ "ENTITY_STATE_INVALID" => EntityStateInvalidError, "ENTITY_MISSING_DATA" => EntityMissingDataError,
174
+ "DATA_NOT_FOUND" => DataNotFoundError, "ASSOCIATION_EXISTS" => AssociationExistsError,
175
+ "NO_ASSOCIATION_EXISTS" => NoAssociationExistsError, "DUPLICATE" => DuplicateError,
176
+ "DATE_BEFORE_DATE" => DateBeforeDateError, "REMOVE_NOT_ALLOWED" => RemoveNotAllowedError,
177
+ "MOP_EXPIRED" => MopExpiredError, "ACCOUNT_INACTIVE" => AccountInactiveError,
178
+ "ACCOUNT_DELINQUENT" => AccountDelinquentError, "MONTHLY_BUDGET_REACHED" => MonthlyBudgetReachedError,
179
+ "QUOTA_EXCEEDED" => QuotaExceededError,"RATE_EXCEEDED" => RateExceededError
180
+ }
181
+
182
+ def CityGridExceptions.appropriate_error error_code
183
+ if @possible_errors.include?(error_code)
184
+ return @possible_errors[error_code]
185
+ else
186
+ return APIError
187
+ end
188
+ end
189
+
190
+ def CityGridExceptions.print_superclasses error_code
191
+ begin
192
+ raise appropriate_error[error_code]
193
+ rescue => ex
194
+ class_hierarchy = ex.class.ancestors
195
+ class_hierarchy.slice!(class_hierarchy.index(StandardError)..-1)
196
+ return class_hierarchy.reverse.join("::")
197
+ end
198
+ end
199
+ end
data/test/helper.rb CHANGED
@@ -22,9 +22,9 @@ unless defined? IN_DASHBOARD
22
22
 
23
23
  # load default config
24
24
  CityGrid.load_config File.expand_path(File.join(File.dirname(__FILE__), '..', 'citygrid_api.yml.sample'))
25
-
25
+
26
26
  # CityGrid.load_config File.expand_path(File.join(File.dirname(__FILE__), '..', 'citygrid_api.yml.sandbox'))
27
-
27
+
28
28
  # Run code with rescue so that exceptions
29
29
  # will be printed, but won't stop test suite
30
30
  def run_with_rescue
@@ -45,10 +45,10 @@ unless defined? IN_DASHBOARD
45
45
  false # return false
46
46
  end
47
47
  end
48
-
48
+
49
49
  # patch in VCR
50
50
  require 'vcr'
51
-
51
+
52
52
  VCR.config do |c|
53
53
  c.cassette_library_dir = 'fixtures/vcr_cassettes'
54
54
  c.stub_with :webmock
@@ -69,8 +69,8 @@ unless defined? IN_DASHBOARD
69
69
  # end
70
70
  # end
71
71
  end
72
-
72
+
73
73
  end
74
74
  end
75
-
75
+
76
76
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: citygrid_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-02-27 00:00:00.000000000Z
13
+ date: 2012-03-01 00:00:00.000000000Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: httparty
17
- requirement: &70338570510360 !ruby/object:Gem::Requirement
17
+ requirement: &70321245070140 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 0.8.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70338570510360
25
+ version_requirements: *70321245070140
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: json
28
- requirement: &70338570509420 !ruby/object:Gem::Requirement
28
+ requirement: &70321245069540 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - =
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 1.5.3
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70338570509420
36
+ version_requirements: *70321245069540
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: riot
39
- requirement: &70338570508480 !ruby/object:Gem::Requirement
39
+ requirement: &70321245068820 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ~>
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 0.12.4
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *70338570508480
47
+ version_requirements: *70321245068820
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: awesome_print
50
- requirement: &70338570507560 !ruby/object:Gem::Requirement
50
+ requirement: &70321245068080 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ~>
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: 0.4.0
56
56
  type: :runtime
57
57
  prerelease: false
58
- version_requirements: *70338570507560
58
+ version_requirements: *70321245068080
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: bundler
61
- requirement: &70338570506520 !ruby/object:Gem::Requirement
61
+ requirement: &70321245067160 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ~>
@@ -66,10 +66,10 @@ dependencies:
66
66
  version: 1.0.0
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *70338570506520
69
+ version_requirements: *70321245067160
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: jeweler
72
- requirement: &70338570505260 !ruby/object:Gem::Requirement
72
+ requirement: &70321245063220 !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
75
  - - ~>
@@ -77,10 +77,10 @@ dependencies:
77
77
  version: 1.6.2
78
78
  type: :development
79
79
  prerelease: false
80
- version_requirements: *70338570505260
80
+ version_requirements: *70321245063220
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: rcov
83
- requirement: &70338570503500 !ruby/object:Gem::Requirement
83
+ requirement: &70321245062700 !ruby/object:Gem::Requirement
84
84
  none: false
85
85
  requirements:
86
86
  - - ! '>='
@@ -88,10 +88,10 @@ dependencies:
88
88
  version: '0'
89
89
  type: :development
90
90
  prerelease: false
91
- version_requirements: *70338570503500
91
+ version_requirements: *70321245062700
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: vcr
94
- requirement: &70338570501600 !ruby/object:Gem::Requirement
94
+ requirement: &70321245061660 !ruby/object:Gem::Requirement
95
95
  none: false
96
96
  requirements:
97
97
  - - ! '>='
@@ -99,10 +99,10 @@ dependencies:
99
99
  version: '0'
100
100
  type: :development
101
101
  prerelease: false
102
- version_requirements: *70338570501600
102
+ version_requirements: *70321245061660
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: webmock
105
- requirement: &70338570500360 !ruby/object:Gem::Requirement
105
+ requirement: &70321245059820 !ruby/object:Gem::Requirement
106
106
  none: false
107
107
  requirements:
108
108
  - - ! '>='
@@ -110,7 +110,7 @@ dependencies:
110
110
  version: '0'
111
111
  type: :development
112
112
  prerelease: false
113
- version_requirements: *70338570500360
113
+ version_requirements: *70321245059820
114
114
  description: Ruby wrapper for CityGrid APIs
115
115
  email: Joseph.Chen@citygridmedia.com
116
116
  executables: []
@@ -207,6 +207,7 @@ files:
207
207
  - lib/citygrid/api/mutable.rb
208
208
  - lib/citygrid/api/response.rb
209
209
  - lib/citygrid/api/searchable.rb
210
+ - lib/citygrid/citygrid_exceptions.rb
210
211
  - lib/citygrid/details.rb
211
212
  - lib/citygrid/listing.rb
212
213
  - lib/citygrid/offers.rb