api_smith 1.0.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.
@@ -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
+