api_smith 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ # Provides a simple set of tools built on top of Hashie and HTTParty to make
2
+ # it easier to build clients for different apis.
3
+
4
+ # @see APISmith::Smash
5
+ # @see APISmith::Base
6
+ #
7
+ # @author Darcy Laycock
8
+ # @author Steve Webb
9
+ module APISmith
10
+
11
+ require 'api_smith/version'
12
+ require 'api_smith/smash'
13
+ require 'api_smith/client'
14
+ require 'api_smith/base'
15
+
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'api_smith/client'
2
+
3
+ module APISmith
4
+ # A base class for building api clients (with a specified endpoint and general
5
+ # shared options) on top of HTTParty, including response unpacking and transformation.
6
+ #
7
+ # Used to convert APISmith::Client to a class (versus a mixin), making it useable
8
+ # in certain other situations where it isn't necessarily useful otherwise.
9
+ #
10
+ # @author Darcy Laycock
11
+ # @author Steve Webb
12
+ class Base
13
+ include APISmith::Client
14
+
15
+ def initialize(*)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,319 @@
1
+ require 'httparty'
2
+
3
+ module APISmith
4
+ # A mixin providing the base set of functionality for building API clients.
5
+ #
6
+ # @see InstanceMethods
7
+ # @see ClassMethods
8
+ # @see HTTParty
9
+ #
10
+ # @author Darcy Laycock
11
+ # @author Steve Webb
12
+ module Client
13
+
14
+ # Hooks into the mixin process to add HTTParty and the two APISmith::Client
15
+ # components to the given parent automatically.
16
+ # @param [Class] parent the object this is being mixed into
17
+ def self.included(parent)
18
+ parent.class_eval do
19
+ include HTTParty
20
+ include InstanceMethods
21
+ extend ClassMethods
22
+ end
23
+ end
24
+
25
+ # The most important part of the client functionality - namely, provides a set of tools
26
+ # and methods that make it possible to build API clients. This is where the bulk of the
27
+ # client work takes place.
28
+ module InstanceMethods
29
+
30
+ # Given a path relative to the endpoint (or `/`), will perform a GET request.
31
+ #
32
+ # If provided, will use a `:transformer` to convert the resultant data into
33
+ # a useable object. Also, in the case an object is nested, allows you to
34
+ # traverse an object.
35
+ #
36
+ # @param [String] path the relative path to the object, pre-normalisation
37
+ # @param [Hash] options the raw options to be passed to ``#request!``
38
+ # @see #request!
39
+ def get(path, options = {})
40
+ request! :get, path, options, :query
41
+ end
42
+
43
+ # Given a path relative to the endpoint (or `/`), will perform a POST request.
44
+ #
45
+ # If provided, will use a `:transformer` to convert the resultant data into
46
+ # a useable object. Also, in the case an object is nested, allows you to
47
+ # traverse an object.
48
+ #
49
+ # @param [String] path the relative path to the object, pre-normalisation
50
+ # @param [Hash] options the raw options to be passed to ``#request!``
51
+ # @see #request!
52
+ def post(path, options = {})
53
+ request! :post, path, options, :query, :body
54
+ end
55
+
56
+ # Given a path relative to the endpoint (or `/`), will perform a PUT request.
57
+ #
58
+ # If provided, will use a `:transformer` to convert the resultant data into
59
+ # a useable object. Also, in the case an object is nested, allows you to
60
+ # traverse an object.
61
+ #
62
+ # @param [String] path the relative path to the object, pre-normalisation
63
+ # @param [Hash] options the raw options to be passed to ``#request!``
64
+ # @see #request!
65
+ def put(path, options = {})
66
+ request! :put, path, options, :query, :body
67
+ end
68
+
69
+ # Given a path relative to the endpoint (or `/`), will perform a DELETE request.
70
+ #
71
+ # If provided, will use a `:transformer` to convert the resultant data into
72
+ # a useable object. Also, in the case an object is nested, allows you to
73
+ # traverse an object.
74
+ #
75
+ # @param [String] path the relative path to the object, pre-normalisation
76
+ # @param [Hash] options the raw options to be passed to ``#request!``
77
+ # @see #request!
78
+ def delete(path, options = {})
79
+ request! :delete, path, options, :query
80
+ end
81
+
82
+ # Performs a HTTP request using HTTParty, using a set of expanded options built up by the current client.
83
+ #
84
+ # @param [:get, :post, :put, :delete] method the http request method to use
85
+ # @param [String] path the request path, relative to either the endpoint or /.
86
+ # @param [Hash{Symbol => Object}] options the options for the given request.
87
+ # @param [Array<Symbol>] param_types the given parameter types (e.g. :body, :query) to add to the request
88
+ #
89
+ # @option options [true,false] :skip_endpoint If true, don't expand the given path before processing.
90
+ # @option options [Array] :response_container If present, it will traverse an array of objects to unpack
91
+ # @option options [Hash] :extra_request Extra raw, request options to pass in to the request
92
+ # @option options [Hash] :extra_body Any parameters to add to the request body
93
+ # @option options [Hash] :extra_query Any parameters to add to the request query string
94
+ # @option options [#call] :transform An object, invoked via #call, that takes the response and
95
+ # transformers it into a useable form.
96
+ #
97
+ # @return the response, defaults to the raw HTTParty::Response
98
+ #
99
+ # @see #path_for
100
+ # @see #extract_response
101
+ # @see #transform_response
102
+ def request!(method, path, options, *param_types)
103
+ # Merge in the default request options, e.g. those to be passed to HTTParty raw
104
+ request_options = merged_options_for(:request, options)
105
+ # Exapdn the path out into a full version when the endpoint is present.
106
+ full_path = request_options[:skip_endpoint] ? path : path_for(path)
107
+ # For each of the given param_types (e.g. :query, :body) will automatically
108
+ # merge in the options for the current request.
109
+ param_types.each do |type|
110
+ request_options[type] = merged_options_for(type, options)
111
+ end
112
+ # Finally, use HTTParty to get the response
113
+ response = self.class.send method, full_path, request_options
114
+ # Pre-process the response to check for errors.
115
+ check_response_errors response
116
+ # Unpack the response using the :response_container option
117
+ inner_response = extract_response path, response, options
118
+ # Finally, apply any transformations
119
+ transform_response inner_response, options
120
+ end
121
+
122
+ private
123
+
124
+ # Provides a hook to handle checking errors on API responses. This is called
125
+ # post-fetch and pre-unpacking / transformation. It is passed the apis response
126
+ # post-decoding (meaning JSON etc have been parsed into normal ruby objects).
127
+ # @param [Object] response the raw decoded api response
128
+ def check_response_errors(response)
129
+ end
130
+
131
+ # Merges in options of a given type into the base options, taking into account
132
+ #
133
+ # * Shared options (e.g. #base_query_options)
134
+ # * Instance-level options (e.g. #query_options)
135
+ # * Call-level options (e.g. the :extra_query option)
136
+ #
137
+ # @param [Symbol] type the type of options, one of :body, :query or :request
138
+ # @param [Hash] options the hash to check for the `:extra_{type}` option.
139
+ # @return [Hash] a hash of the merged options.
140
+ def merged_options_for(type, options)
141
+ base = send :"base_#{type}_options"
142
+ base.merge!(send(:"#{type}_options") || {})
143
+ base.merge! options.fetch(:"extra_#{type}", {})
144
+ base
145
+ end
146
+
147
+ # The base set of body parameters, common to all instances of the client.
148
+ # Ideally, in your client you'd override this to return other required
149
+ # parameters that are the same across all client subclasses e.g. the format.
150
+ #
151
+ # These will automatically be included in POST and PUT requests but not
152
+ # GET or DELETE requests.
153
+ #
154
+ # @example
155
+ # def base_body_options
156
+ # {:format => 'json'}
157
+ # end
158
+ #
159
+ def base_body_options
160
+ {}
161
+ end
162
+
163
+ # The base set of query parameters, common to all instances of the client.
164
+ # Ideally, in your client you'd override this to return other required
165
+ # parameters that are the same across all client subclasses e.g. the format.
166
+ #
167
+ # These will automatically be included in all requests as part of the query
168
+ # string.
169
+ #
170
+ # @example
171
+ # def base_query_options
172
+ # {:format => 'json'}
173
+ # end
174
+ #
175
+ def base_query_options
176
+ {}
177
+ end
178
+
179
+ # The base set of request options as accepted by HTTParty. These can be used to
180
+ # setup things like the normaliser HTTParty will use for parameters.
181
+ def base_request_options
182
+ {}
183
+ end
184
+
185
+ # Per-instance configurable query parameters.
186
+ # @return [Hash] the instance-specific query parameters.
187
+ # @see #add_query_options!
188
+ def query_options
189
+ @query_options ||= {}
190
+ end
191
+
192
+ # Per-instance configurable body parameters.
193
+ # @return [Hash] the instance-specific body parameters.
194
+ # @see #add_body_options!
195
+ def body_options
196
+ @body_options ||= {}
197
+ end
198
+
199
+ # Per-instance configurable request options.
200
+ # @return [Hash] the instance-specific request options.
201
+ # @see #add_request_options!
202
+ def request_options
203
+ @request_options ||= {}
204
+ end
205
+
206
+ # Merges in a hash of extra query parameters to the given request, applying
207
+ # them for every request that has query string parameters. Typically
208
+ # called from inside #initialize.
209
+ # @param [Hash{Symbol => Object}] value a hash of options to add recently
210
+ def add_query_options!(value)
211
+ query_options.merge! value
212
+ end
213
+
214
+ # Merges in a hash of extra body parameters to the given request, applying
215
+ # them for every request that has body parameters. Typically called from
216
+ # inside #initialize.
217
+ # @param [Hash{Symbol => Object}] value a hash of options to add recently
218
+ def add_body_options!(value)
219
+ body_options.merge! value
220
+ end
221
+
222
+ # Merges in a hash of request options to the given request, applying
223
+ # them for every request that has query string parameters. Typically called
224
+ # from inside #initialize.
225
+ # @param [Hash{Symbol => Object}] value a hash of options to add recently
226
+ def add_request_options!(value)
227
+ request_options.merge! value
228
+ end
229
+
230
+ # Given a path, expands it relative to the / and the defined endpoint
231
+ # for this class.
232
+ # @param [String] path the current, unexpanded path for the api call.
233
+ # @example With an endpoint of v1
234
+ # path_for('test') # => "/v1/test"
235
+ def path_for(path)
236
+ File.join(*['', endpoint, path].compact)
237
+ end
238
+
239
+ # Given a path, response and options, will walk the response object
240
+ # (typically hashes and arrays) to unpack / extract the users response.
241
+ #
242
+ # Note that the response container will be found either via a :response_container
243
+ # option or, if not specified at all, the result of #default_response_container to
244
+ # 'get' the part of the response that the user cares about.
245
+ #
246
+ # @param [String] path the path used for the request#
247
+ # @param [Hash, Array] response the object returned from the api call
248
+ # @param [Hash] options the options passed to the api call
249
+ # @option options [Array<Symbol, String, Integer>] :response_container the container to unpack
250
+ # from, e.g. ["a", 1, "b"], %w(a 2 3) or something else.
251
+ def extract_response(path, response, options)
252
+ # First, get the response container options
253
+ response_container = options.fetch(:response_container) do
254
+ default_response_container(path, options)
255
+ end
256
+ # And then unpack then
257
+ if response_container
258
+ response_keys = Array(response_container)
259
+ response = response_keys.inject(response) do |r, key|
260
+ r.respond_to?(:[]) ? r[key] : r
261
+ end
262
+ end
263
+ response
264
+ end
265
+
266
+ # Takes a response and, if present, uses the :transform option to convert
267
+ # it into a useable object.
268
+ # @param [Hash, Array] response the object returned from the api call
269
+ # @param [Hash] options the options passed to the api call
270
+ # @option options [#call] :transform If present, passed the unpack response.
271
+ # @option option [#call] :transformer see the :transform option
272
+ # @return [Object] the transformed response, or the response itself if no :transform
273
+ # option is passed.
274
+ def transform_response(response, options)
275
+ transformer = options[:transform] || options[:transformer]
276
+ if transformer
277
+ transformer.call response
278
+ else
279
+ response
280
+ end
281
+ end
282
+
283
+ # Returns the current api endpoint, if present.
284
+ # @return [nil, String] the current endpoint
285
+ def endpoint
286
+ nil
287
+ end
288
+
289
+ # A hook method to define the default response container for a given
290
+ # path and set of options to an API call. Intended to be used inside
291
+ # subclasses to make it possible to define a standardised way to unpack
292
+ # responses without having to pass a `:response_container` option.
293
+ # @param [String] path the current path to the request
294
+ # @param [Hash] options the set of options passed to #request!
295
+ # @return [nil, Array] the array of indices (either hash / array indices)
296
+ # to unpack the response via.
297
+ def default_response_container(path, options)
298
+ nil
299
+ end
300
+
301
+ end
302
+
303
+ # Class level methods to let you configure your api client.
304
+ module ClassMethods
305
+
306
+ # When present, lets you specify the api for the given client.
307
+ # @param [String, nil] value the endpoint to use.
308
+ # @example Setting a string endpoint
309
+ # endpoint 'v1'
310
+ # @example Unsetting the string endpoint
311
+ # endpoint nil
312
+ def endpoint(value = nil)
313
+ define_method(:endpoint) { value }
314
+ end
315
+
316
+ end
317
+
318
+ end
319
+ end
@@ -0,0 +1,212 @@
1
+ require 'hashie/dash'
2
+
3
+ module APISmith
4
+ # Extends Hashie::Dash to suppress unknown keys when passing data, but
5
+ # is configurable to raises an UnknownKey exception when accessing keys in the
6
+ # Smash.
7
+ #
8
+ # APISmith::Smash is a subclass of Hashie::Dash that adds several features
9
+ # making it suitable for use in writing api clients. Namely,
10
+ #
11
+ # * The ability to silence exceptions on unknown keys (vs. Raising NoMethodError)
12
+ # * The ability to define conversion of incoming data via transformers
13
+ # * The ability to define aliases for keys via the from parameter.
14
+ #
15
+ # @author Darcy Laycock
16
+ # @author Steve Webb
17
+ #
18
+ # @example a simple, structured object with the most common use cases.
19
+ # class MyResponse < APISmith::Smash
20
+ # property :full_name, :from => :fullName
21
+ # property :value_percentage, :transformer => :to_f
22
+ # property :short_name
23
+ # property :created, :transformer => lambda { |v| Date.parse(v) }
24
+ # end
25
+ #
26
+ # response = MyResponse.new({
27
+ # :fullName => "Bob Smith",
28
+ # :value_percentage => "10.5",
29
+ # :short_name => 'Bob',
30
+ # :created => '2010-12-28'
31
+ # })
32
+ #
33
+ # p response.short_name # => "Bob"
34
+ # p response.full_name # => "Bob Smith"
35
+ # p response.value_percentage # => 10.5
36
+ # p response.created.class # => Date
37
+ #
38
+ class Smash < Hashie::Dash
39
+ # When we access an unknown property, we raise the unknown key instead of
40
+ # a NoMethodError on undefined keys so that we can do a target rescue.
41
+ class UnknownKey < StandardError; end
42
+
43
+ # Returns a class-specific hash of transformers, containing the attribute
44
+ # name mapped to the transformer that responds to call.
45
+ # @return The hash of transformers.
46
+ def self.transformers
47
+ (@transformers ||= {})
48
+ end
49
+
50
+ # Returns a class-specific hash of incoming keys and their resultant
51
+ # property name, useful for mapping non-standard names (e.g. displayName)
52
+ # to their more ruby-like equivelant (e.g. display_name).
53
+ # @return The hash of key mappings.
54
+ def self.key_mapping
55
+ (@key_mapping ||= {})
56
+ end
57
+
58
+ # Test if the object should raise a NoMethodError exception on unknown
59
+ # property accessors or whether it should be silenced.
60
+ #
61
+ # @return true if an exception will be raised when accessing an unknown key
62
+ # else, false.
63
+ def self.exception_on_unknown_key?
64
+ defined?(@exception_on_unknown_key) && @exception_on_unknown_key
65
+ end
66
+
67
+ # Sets whether or not Smash should raise NoMethodError on an unknown key.
68
+ # Sets it for the current class.
69
+ #
70
+ # @param [Boolean] value true to throw exceptions.
71
+ def self.exception_on_unknown_key=(value)
72
+ @exception_on_unknown_key = value
73
+ end
74
+ self.exception_on_unknown_key = false
75
+
76
+ # Sets the transformer that is invoked when the given key is set.
77
+ #
78
+ # @param [Symbol] key The key should this transformer operate on
79
+ # @param [#call] value If a block isn't given, used to transform via #call.
80
+ # @param [Block] blk The block used to transform the key.
81
+ def self.transformer_for(key, value = nil, &blk)
82
+ if blk.nil? && value
83
+ blk = value.respond_to?(:call) ? value : value.to_sym.to_proc
84
+ end
85
+ raise ArgumentError, 'must provide a transformation' if blk.nil?
86
+ transformers[key.to_s] = blk
87
+ # For each subclass, set the transformer.
88
+ Array(@subclasses).each { |klass| klass.transformer_for(key, value) }
89
+ end
90
+
91
+ # Hook to make it inherit instance variables correctly. Called once
92
+ # the Smash is inherited from in another object to maintain state.
93
+ def self.inherited(klass)
94
+ super
95
+ klass.instance_variable_set '@transformers', transformers.dup
96
+ klass.instance_variable_set '@key_mapping', key_mapping.dup
97
+ klass.instance_variable_set '@exception_on_unknown_key', exception_on_unknown_key?
98
+ end
99
+
100
+ # Create a new property (i.e., hash key) for this Object type. This method
101
+ # allows for converting property names and defining custom transformers for
102
+ # more complex types.
103
+ #
104
+ # @param [Symbol] property_name The property name (duh).
105
+ # @param [Hash] options
106
+ # @option options [String, Array<String>] :from Also accept values for this property when
107
+ # using the key(s) specified in from.
108
+ # @option options [Block] :transformer Specify a class or block to use when transforming the data.
109
+ def self.property(property_name, options = {})
110
+ super
111
+ if options[:from]
112
+ property_name = property_name.to_s
113
+ Array(options[:from]).each do |k|
114
+ key_mapping[k.to_s] = property_name
115
+ end
116
+ end
117
+ if options[:transformer]
118
+ transformer_for property_name, options[:transformer]
119
+ end
120
+ end
121
+
122
+ # Does this Smash class contain a specific property (key),
123
+ # or does it have a key mapping (via :from)
124
+ #
125
+ # @param [Symbol] key the property to test for.
126
+ # @return [Boolean] true if this class contains the key; else, false.
127
+ def self.property?(key)
128
+ super || key_mapping.has_key?(key.to_s)
129
+ end
130
+
131
+ # Automates type conversion (including on Array and Hashes) to this type.
132
+ # Used so we can pass this class similarily to how we pass lambdas as an
133
+ # object, primarily for use as transformers.
134
+ #
135
+ # @param [Object] the object to attempt to convert.
136
+ # @return [Array<Smash>, Smash] The converted object / array of objects if
137
+ # possible, otherwise nil.
138
+ def self.call(value)
139
+ if value.is_a?(Array)
140
+ value.map { |v| call v }.compact
141
+ elsif value.is_a?(Hash)
142
+ new value
143
+ else
144
+ nil
145
+ end
146
+ end
147
+
148
+ # Access the value responding to a key, normalising the key into a form
149
+ # we know (e.g. processing the from value to convert it to the actual
150
+ # property name).
151
+ #
152
+ # @param [Symbol] property the key to check for.
153
+ # @return The value corresponding to property. nil if it does not exist.
154
+ def [](property)
155
+ super transform_key(property)
156
+ rescue UnknownKey
157
+ nil
158
+ end
159
+
160
+ # Sets the value for a given key. Transforms the key first (e.g. taking into
161
+ # account from values) and transforms the property using any transformers.
162
+ #
163
+ # @param [Symbol] property the key to set.
164
+ # @param [String] value the value to set.
165
+ # @return If the property exists value is returned; else, nil.
166
+ def []=(property, value)
167
+ key = transform_key(property)
168
+ super key, transform_property(key, value)
169
+ rescue UnknownKey
170
+ nil
171
+ end
172
+
173
+ private
174
+
175
+ # Overrides the Dashie check to raise a custom exception that we can
176
+ # rescue from when the key is unknown.
177
+ def assert_property_exists!(property)
178
+ has_property = self.class.property?(property)
179
+ unless has_property
180
+ exception = self.class.exception_on_unknown_key? ? NoMethodError : UnknownKey
181
+ raise exception, "The property '#{property}' is not defined on this #{self.class.name}"
182
+ end
183
+ end
184
+
185
+ # Transforms a given key into it's normalised alternative, making it
186
+ # suitable for automatically mapping external objects into a useable
187
+ # local version.
188
+ # @param [Symbol, String] key the starting key, pre-transformation
189
+ # @return [String] the transformed key, ready for use internally.
190
+ def transform_key(key)
191
+ self.class.key_mapping[key.to_s] || default_key_transformation(key)
192
+ end
193
+
194
+ # By default, we transform the key using #to_s, making it useable
195
+ # as a hash index. If you want to, for example, add leading underscores,
196
+ # you're do so here.
197
+ def default_key_transformation(key)
198
+ key.to_s
199
+ end
200
+
201
+ # Given a key and a value, applies any incoming data transformations as appropriate.
202
+ # @param [String, Symbol] key the property key
203
+ # @param [Object] value the incoming value of the given property
204
+ # @return [Object] the transformed value for the given key
205
+ # @see Smash.transformer_for
206
+ def transform_property(key, value)
207
+ transformation = self.class.transformers[key.to_s]
208
+ transformation ? transformation.call(value) : value
209
+ end
210
+
211
+ end
212
+ end
@@ -0,0 +1,4 @@
1
+ module APISmith
2
+ # The current version of API Smith
3
+ VERSION = "1.0.0".freeze
4
+ end
@@ -0,0 +1,42 @@
1
+ module APISmith
2
+ # A set of extensions to make using APISmith with WebMock (or most test utilities in general)
3
+ # simpler when it comes checking values. Please note this is primarily intended for use with
4
+ # rspec due to dependence on subject in some places.
5
+ #
6
+ # @author Darcy Laycock
7
+ # @author Steve Webb
8
+ module WebMockExtensions
9
+
10
+ # Returns the class of the current subject
11
+ # @return [Class] the subject class
12
+ def subject_api_class
13
+ subject.is_a?(Class) ? subject : subject.class
14
+ end
15
+
16
+ # Returns an instance of the subject class, created via allocate (vs. new)
17
+ # @return [Object] the instance
18
+ def subject_class_instance
19
+ @subject_class_instance ||= subject_api_class.allocate
20
+ end
21
+
22
+ # Expands the given path relative to the API for the current subject class.
23
+ # Namely, this makes it possible to convert a relative path to an endpoint-specified
24
+ # path.
25
+ # @param [String] path the path to expand, minus endpoint etc.
26
+ # @return [String] the expanded path
27
+ def api_url_for(path)
28
+ path = subject_class_instance.send(:path_for, path)
29
+ base_uri = subject_api_class.base_uri
30
+ File.join base_uri, path
31
+ end
32
+
33
+ # Short hand for #stub_request that lets you give it a relative path prior to expanding it.
34
+ # @param [:get, :post, :put, :delete] the verb for the request
35
+ # @param [String] the relative path for the api
36
+ # @return [Object] the result from stub_request
37
+ def stub_api(type, path)
38
+ stub_request(type, api_url_for(path))
39
+ end
40
+
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: api_smith
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Darcy Laycock
14
+ - Steve Webb
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-06-21 00:00:00 +08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: httparty
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: hashie
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 15
45
+ segments:
46
+ - 1
47
+ - 0
48
+ version: "1.0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rr
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: rspec
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 2
76
+ - 0
77
+ version: "2.0"
78
+ type: :development
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: fuubar
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ type: :development
93
+ version_requirements: *id005
94
+ description: APISmith provides tools to make working with structured HTTP-based apis even easier.
95
+ email:
96
+ - sutto@thefrontiergroup.com.au
97
+ executables: []
98
+
99
+ extensions: []
100
+
101
+ extra_rdoc_files: []
102
+
103
+ files:
104
+ - lib/api_smith/base.rb
105
+ - lib/api_smith/client.rb
106
+ - lib/api_smith/smash.rb
107
+ - lib/api_smith/version.rb
108
+ - lib/api_smith/web_mock_extensions.rb
109
+ - lib/api_smith.rb
110
+ has_rdoc: true
111
+ homepage: http://github.com/thefrontiergroup
112
+ licenses: []
113
+
114
+ post_install_message:
115
+ rdoc_options: []
116
+
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 23
134
+ segments:
135
+ - 1
136
+ - 3
137
+ - 6
138
+ version: 1.3.6
139
+ requirements: []
140
+
141
+ rubyforge_project:
142
+ rubygems_version: 1.6.2
143
+ signing_key:
144
+ specification_version: 3
145
+ summary: A simple layer on top of HTTParty for building API's
146
+ test_files: []
147
+