restspec 0.0.4 → 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +5 -0
- data/CHANGELOG.md +0 -0
- data/README.md +11 -7
- data/Rakefile +7 -0
- data/bin/restspec +1 -1
- data/examples/store-api-tests/Gemfile.lock +3 -1
- data/examples/store-api-tests/api.md +47 -47
- data/examples/store-api-tests/spec/api/product_spec.rb +0 -2
- data/examples/store-api-tests/spec/api/restspec/endpoints.rb +6 -5
- data/examples/store-api-tests/spec/api/restspec/schemas.rb +2 -1
- data/{docs → guides}/endpoints.md +0 -0
- data/{docs → guides}/helpers.md +0 -0
- data/{docs → guides}/macros.md +0 -0
- data/{docs → guides}/matchers.md +0 -0
- data/{docs → guides}/schemas.md +0 -0
- data/{docs → guides}/tutorial.md +1 -1
- data/{docs → guides}/types.md +0 -0
- data/lib/restspec/configuration.rb +28 -4
- data/lib/restspec/endpoints/dsl.rb +281 -48
- data/lib/restspec/endpoints/endpoint.rb +18 -58
- data/lib/restspec/endpoints/has_schemas.rb +39 -0
- data/lib/restspec/endpoints/namespace.rb +4 -7
- data/lib/restspec/endpoints/network.rb +27 -0
- data/lib/restspec/endpoints/request.rb +3 -0
- data/lib/restspec/endpoints/response.rb +3 -0
- data/lib/restspec/endpoints/url_builder.rb +51 -0
- data/lib/restspec/rspec/api_macros.rb +2 -2
- data/lib/restspec/rspec/matchers/be_like_schema.rb +1 -1
- data/lib/restspec/rspec/matchers/be_like_schema_array.rb +1 -1
- data/lib/restspec/runners/docs/templates/docs.md.erb +2 -2
- data/lib/restspec/schema/attribute.rb +43 -0
- data/lib/restspec/schema/attribute_example.rb +13 -1
- data/lib/restspec/schema/checker.rb +80 -8
- data/lib/restspec/schema/dsl.rb +67 -11
- data/lib/restspec/schema/schema.rb +13 -1
- data/lib/restspec/schema/schema_example.rb +7 -1
- data/lib/restspec/schema/types/array_type.rb +42 -1
- data/lib/restspec/schema/types/basic_type.rb +62 -0
- data/lib/restspec/schema/types/boolean_type.rb +10 -0
- data/lib/restspec/schema/types/date_type.rb +12 -0
- data/lib/restspec/schema/types/datetime_type.rb +16 -0
- data/lib/restspec/schema/types/decimal_string_type.rb +16 -5
- data/lib/restspec/schema/types/decimal_type.rb +17 -1
- data/lib/restspec/schema/types/embedded_schema_type.rb +39 -8
- data/lib/restspec/schema/types/hash_type.rb +51 -12
- data/lib/restspec/schema/types/integer_type.rb +12 -1
- data/lib/restspec/schema/types/null_type.rb +7 -0
- data/lib/restspec/schema/types/one_of_type.rb +18 -0
- data/lib/restspec/schema/types/schema_id_type.rb +14 -17
- data/lib/restspec/schema/types/string_type.rb +9 -0
- data/lib/restspec/schema/types/type_methods.rb +32 -0
- data/lib/restspec/schema/types.rb +1 -18
- data/lib/restspec/shortcuts.rb +10 -0
- data/lib/restspec/stores/endpoint_store.rb +27 -2
- data/lib/restspec/stores/namespace_store.rb +23 -4
- data/lib/restspec/stores/schema_store.rb +15 -0
- data/lib/restspec/values/status_code.rb +16 -1
- data/lib/restspec/version.rb +1 -1
- data/lib/restspec.rb +2 -0
- data/restspec.gemspec +2 -0
- data/spec/restspec/endpoints/dsl_spec.rb +32 -19
- data/spec/restspec/endpoints/endpoint_spec.rb +20 -43
- data/spec/restspec/endpoints/namespace_spec.rb +0 -7
- data/spec/restspec/endpoints/request_spec.rb +33 -0
- data/spec/restspec/schema/attribute_spec.rb +44 -0
- data/spec/restspec/schema/checker_spec.rb +57 -0
- data/spec/restspec/schema/dsl_spec.rb +1 -1
- data/spec/restspec/schema/schema_spec.rb +15 -0
- data/spec/restspec/schema/types/basic_type_spec.rb +2 -2
- data/spec/restspec/schema/types/decimal_string_type_spec.rb +56 -0
- data/spec/restspec/schema/types/decimal_type_spec.rb +25 -0
- data/spec/restspec/schema/types/embedded_schema_type_spec.rb +32 -0
- data/spec/restspec/schema/types/hash_type_spec.rb +39 -0
- data/spec/restspec/schema/types/integer_type_spec.rb +28 -0
- data/spec/restspec/schema/types/one_of_type_spec.rb +21 -0
- data/spec/restspec/stores/endpoint_store_spec.rb +62 -0
- metadata +63 -10
- data/ROADMAP.md +0 -13
@@ -4,7 +4,23 @@ module Restspec
|
|
4
4
|
module Endpoints
|
5
5
|
HTTP_METHODS = [:get, :post, :put, :patch, :delete, :head]
|
6
6
|
|
7
|
+
# The Endpoints DSL is what should be used inside the `endpoints.rb` file.
|
8
|
+
# This class is related to the top-level namespace of the DSL.
|
7
9
|
class DSL
|
10
|
+
# Generates a new namespace that is just an entity that
|
11
|
+
# groups a couple of endpoints for whatever reason.
|
12
|
+
#
|
13
|
+
# @example with only a name
|
14
|
+
# namespace :books do
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example with a `base_path`
|
18
|
+
# namespace :books, base_path: '/publications' do
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @param name [String] the name of the namespace.
|
22
|
+
# @param base_path [String, nil] the base_path property of the namespace.
|
23
|
+
# @param block [:call] a block to yield to a newly created {NamespaceDSL}.
|
8
24
|
def namespace(name, base_path: nil, &block)
|
9
25
|
namespace = Namespace.create(name.to_s)
|
10
26
|
namespace.base_path = base_path
|
@@ -12,67 +28,199 @@ module Restspec
|
|
12
28
|
namespace_dsl.instance_eval(&block)
|
13
29
|
end
|
14
30
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
31
|
+
# This is actually a kind of namespace factory, that creates
|
32
|
+
# a namespace that have the next conventions:
|
33
|
+
# - The name is a pluralization of another name. (products, books, etc)
|
34
|
+
# - The base_path is, when not defined, the name of the namespace prepended with a "/"
|
35
|
+
# - Attaches a schema with the name of the namespace singularized to the namespace.
|
36
|
+
# (product, book, etc)
|
37
|
+
#
|
38
|
+
# In this way, it can express REST resources that groups a couple of endpoints related
|
39
|
+
# to them.
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# resource :books do
|
43
|
+
# namespace.schema_for(:response).name # book
|
44
|
+
# namespace.base_path # /books
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# @param (see #namespace)
|
48
|
+
#
|
49
|
+
def resource(name, base_path: nil, &block)
|
50
|
+
resource_name = name.to_s.singularize.to_sym
|
21
51
|
|
52
|
+
namespace name, base_path: (base_path || "/#{name}") do
|
53
|
+
schema resource_name
|
22
54
|
instance_eval(&block)
|
23
55
|
end
|
24
56
|
end
|
25
57
|
end
|
26
58
|
|
59
|
+
# The Namespace DSL is what should be used inside a namespace or resource block.
|
60
|
+
# Its major responsability is to add endpoints to the dsl.
|
27
61
|
class NamespaceDSL
|
28
|
-
attr_accessor :namespace, :
|
62
|
+
attr_accessor :namespace, :endpoint_config_blocks
|
29
63
|
|
30
64
|
def initialize(namespace)
|
31
65
|
self.namespace = namespace
|
32
|
-
self.
|
66
|
+
self.endpoint_config_blocks = []
|
33
67
|
end
|
34
68
|
|
69
|
+
# Defines a new endpoint with a name and a block
|
70
|
+
# that has a context equals to a {EndpointDSL} instance
|
71
|
+
# and saves the endpoint into the namespace.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# namespace :books do
|
75
|
+
# endpoint :get do
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# @param name [Symbol] the endpoint name.
|
80
|
+
# @param block [block] the block to call within an {EndpointDSL} instance.
|
35
81
|
def endpoint(name, &block)
|
36
82
|
endpoint = Endpoint.new(name)
|
37
83
|
endpoint_dsl = EndpointDSL.new(endpoint)
|
38
84
|
namespace.add_endpoint(endpoint)
|
39
85
|
|
40
86
|
endpoint_dsl.instance_eval(&block)
|
41
|
-
|
87
|
+
|
88
|
+
endpoint_config_blocks.each do |config_block|
|
89
|
+
endpoint_dsl.instance_eval(&config_block)
|
90
|
+
end
|
42
91
|
|
43
92
|
Restspec::EndpointStore.store(endpoint)
|
44
93
|
end
|
45
94
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
95
|
+
# @!macro http_method_endpoint
|
96
|
+
# Defines an endpoint with a **$0** http method.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# namespace :books do
|
100
|
+
# $0 :endpoint_name, '/path' # this endpoint will have the $0 http method attached
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# @param name [String] the name of the endpoint.
|
104
|
+
# @param path [String] the optional path of the endpoint.
|
105
|
+
# @param block [block] the block to call with the {EndpointDSL} instance.
|
106
|
+
|
107
|
+
# @macro http_method_endpoint
|
108
|
+
def get(name, path = '', &block)
|
109
|
+
setup_endpoint_from_http_method(:get, name, path, &block)
|
110
|
+
end
|
111
|
+
|
112
|
+
# @macro http_method_endpoint
|
113
|
+
def post(name, path = '', &block)
|
114
|
+
setup_endpoint_from_http_method(:post, name, path, &block)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @macro http_method_endpoint
|
118
|
+
def put(name, path = '', &block)
|
119
|
+
setup_endpoint_from_http_method(:put, name, path, &block)
|
53
120
|
end
|
54
121
|
|
122
|
+
# @macro http_method_endpoint
|
123
|
+
def patch(name, path = '', &block)
|
124
|
+
setup_endpoint_from_http_method(:patch, name, path, &block)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @macro http_method_endpoint
|
128
|
+
def delete(name, path = '', &block)
|
129
|
+
setup_endpoint_from_http_method(:delete, name, path, &block)
|
130
|
+
end
|
131
|
+
|
132
|
+
# @macro http_method_endpoint
|
133
|
+
def head(name, path = '', &block)
|
134
|
+
setup_endpoint_from_http_method(:head, name, path, &block)
|
135
|
+
end
|
136
|
+
|
137
|
+
# This defines an anonymous namespace with a base_path equals
|
138
|
+
# to '/:id' that will affect all his internals endpoints
|
139
|
+
# with the base_path of the parent namespace. Esentially:
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
# resource :books do
|
143
|
+
# member do
|
144
|
+
# get :show # /books/:id
|
145
|
+
# patch :update # /books/:id
|
146
|
+
# get :chapters, '/chapters' # /books/:id/chapters
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# @param base_path [String, nil] override of the base_pat
|
151
|
+
# @param identifier_name [String, :id] override of the key :id
|
152
|
+
# @param block [block] block that will be called with a {NamespaceDSL instance},
|
153
|
+
# typically for create endpoints inside.
|
55
154
|
def member(base_path: nil, identifier_name: 'id', &block)
|
56
155
|
member_namespace = namespace.add_anonymous_children_namespace
|
57
156
|
member_namespace.base_path = base_path || "/:#{identifier_name}"
|
58
|
-
NamespaceDSL.new(member_namespace)
|
157
|
+
member_dsl = NamespaceDSL.new(member_namespace)
|
158
|
+
member_dsl.endpoint_config_blocks += endpoint_config_blocks
|
159
|
+
member_dsl.instance_eval(&block)
|
59
160
|
end
|
60
161
|
|
162
|
+
# This defines an anonymous namespace with a base_path equals
|
163
|
+
# to the empty string that will affect all his internals endpoints
|
164
|
+
# with the base_path of the parent namespace. This should be used
|
165
|
+
# to group collection related endpoints.
|
166
|
+
#
|
167
|
+
# @example
|
168
|
+
# resource :books do
|
169
|
+
# collection do
|
170
|
+
# get :index # /books
|
171
|
+
# post :create # /books
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# @param base_path [String, nil] override of the base_pat
|
176
|
+
# @param block [block] block that will be called with a {NamespaceDSL instance},
|
177
|
+
# typically for create endpoints inside.
|
61
178
|
def collection(base_path: nil, &block)
|
62
179
|
collection_namespace = namespace.add_anonymous_children_namespace
|
63
180
|
collection_namespace.base_path = base_path
|
64
|
-
NamespaceDSL.new(collection_namespace)
|
181
|
+
collection_dsl = NamespaceDSL.new(collection_namespace)
|
182
|
+
collection_dsl.endpoint_config_blocks += endpoint_config_blocks
|
183
|
+
collection_dsl.instance_eval(&block)
|
65
184
|
end
|
66
185
|
|
67
|
-
|
68
|
-
|
69
|
-
|
186
|
+
# It attaches a schema to the namespace. This schema name will be
|
187
|
+
# used by the endpoints inside the namespace too.
|
188
|
+
#
|
189
|
+
# @example
|
190
|
+
# namespace :products do
|
191
|
+
# schema :product
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# @param name [Symbol] the schema name.
|
195
|
+
# @param options [Hash] A set of options to pass to {Endpoint#add_schema}.
|
196
|
+
def schema(name, options = {})
|
197
|
+
namespace.add_schema(name, options)
|
198
|
+
all { schema(name, options) }
|
70
199
|
end
|
71
200
|
|
201
|
+
# Defines a block that will be executed in every endpoint inside this namespace.
|
202
|
+
# It should only be one for namespace.
|
203
|
+
#
|
204
|
+
# @example
|
205
|
+
# resource :products, base_path: '/:country_id/products' do
|
206
|
+
# member do
|
207
|
+
# all do
|
208
|
+
# url_params(:country_id) { 'us' }
|
209
|
+
# end
|
210
|
+
#
|
211
|
+
# get :show # /us/products/:id
|
212
|
+
# put :update # /us/products/us/:id
|
213
|
+
# end
|
214
|
+
# end
|
215
|
+
#
|
216
|
+
# @param endpoints_config [block] block that will be called in the context of
|
217
|
+
# an {EndpointDSL} instance.
|
72
218
|
def all(&endpoints_config)
|
73
|
-
self.
|
219
|
+
self.endpoint_config_blocks << endpoints_config
|
74
220
|
end
|
75
221
|
|
222
|
+
# It calls {#all} with the {EndpointDSL#url_param} method.
|
223
|
+
# Please refer to the {EndpointDSL#url_param} method.
|
76
224
|
def url_param(param, &value_or_example_block)
|
77
225
|
all do
|
78
226
|
url_param(param, &value_or_example_block)
|
@@ -81,60 +229,145 @@ module Restspec
|
|
81
229
|
|
82
230
|
private
|
83
231
|
|
84
|
-
def
|
85
|
-
|
232
|
+
def setup_endpoint_from_http_method(http_method, endpoint_name, path, &block)
|
233
|
+
endpoint(endpoint_name) do
|
234
|
+
self.method http_method
|
235
|
+
self.path path
|
236
|
+
instance_eval(&block) if block.present?
|
237
|
+
end
|
86
238
|
end
|
87
239
|
end
|
88
240
|
|
241
|
+
# The Endpoint DSL is what should be used inside an endpoint block.
|
242
|
+
# Its major responsability is to define endpoint behavior.
|
89
243
|
class EndpointDSL < Struct.new(:endpoint)
|
244
|
+
# Defines what the HTTP method will be.
|
245
|
+
#
|
246
|
+
# @example
|
247
|
+
# endpoint :show do
|
248
|
+
# method :get
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# @param method [Symbol] the http method name
|
90
252
|
def method(method)
|
91
253
|
endpoint.method = method
|
92
254
|
end
|
93
255
|
|
256
|
+
# Defines what the path will be. It will be append
|
257
|
+
# to the namespace's `base_path` and to the Restspec
|
258
|
+
# config's `base_url`.
|
259
|
+
#
|
260
|
+
# @example
|
261
|
+
# endpoint :monkeys do
|
262
|
+
# path '/monkeys'
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# @param path [String] the path of the endpoint
|
94
266
|
def path(path)
|
95
267
|
endpoint.path = path
|
96
268
|
end
|
97
269
|
|
98
|
-
|
99
|
-
|
100
|
-
|
270
|
+
# Defines what the schema will be.
|
271
|
+
#
|
272
|
+
# @example
|
273
|
+
# endpoint :monkeys do
|
274
|
+
# schema :monkey
|
275
|
+
# end
|
276
|
+
#
|
277
|
+
# @param name [Symbol] The schema's name.
|
278
|
+
# @param options [Hash] A set of options to pass to {Endpoint#add_schema}.
|
279
|
+
def schema(name, options = {})
|
280
|
+
endpoint.add_schema(name, options)
|
101
281
|
end
|
102
282
|
|
283
|
+
# Remove the schema of the current endpoint. Some endpoints
|
284
|
+
# doesn't require schemas for payload or for responses. This
|
285
|
+
# is the typical case for DELETE requests.
|
286
|
+
#
|
287
|
+
# @example
|
288
|
+
# resource :game do
|
289
|
+
# delete(:destroy) { no_schema }
|
290
|
+
# end
|
291
|
+
#
|
292
|
+
def no_schema
|
293
|
+
endpoint.remove_schemas
|
294
|
+
end
|
295
|
+
|
296
|
+
# A mutable hash containing the headers for the endpoint
|
297
|
+
#
|
298
|
+
# @example
|
299
|
+
# endpoint :monkeys do
|
300
|
+
# headers['Content-Type'] = 'application/json'
|
301
|
+
# end
|
103
302
|
def headers
|
104
303
|
endpoint.headers
|
105
304
|
end
|
106
305
|
|
306
|
+
# This set url parameters for the endpoint. It receives a key and a block
|
307
|
+
# returning the value. If the value is a type, an example will be generated.
|
308
|
+
#
|
309
|
+
# @example with just a value:
|
310
|
+
# endpoint :monkeys, path: '/:id' do
|
311
|
+
# url_param(:id) { '1' }
|
312
|
+
# end # /1
|
313
|
+
#
|
314
|
+
# @example with a type:
|
315
|
+
# endpoint :monkeys, path: '/:id' do
|
316
|
+
# url_param(:id) { string } # a random string
|
317
|
+
# end # /{the random string}
|
318
|
+
#
|
319
|
+
# @param param [Symbol] the parameter name.
|
320
|
+
# @param value_or_type_block [block] the block returning the value.
|
107
321
|
def url_param(param, &value_or_type_block)
|
108
322
|
endpoint.add_url_param_block(param) do
|
109
|
-
|
110
|
-
|
323
|
+
ExampleOrValue.new(endpoint, param, value_or_type_block).value
|
324
|
+
end
|
325
|
+
end
|
111
326
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
327
|
+
# A special class to get a type example or a simple
|
328
|
+
# value from a block.
|
329
|
+
#
|
330
|
+
# If the block returns a type, the result should
|
331
|
+
# be one example of that type.
|
332
|
+
class ExampleOrValue
|
333
|
+
def initialize(endpoint, attribute_name, callable)
|
334
|
+
self.attribute_name = attribute_name
|
335
|
+
self.endpoint = endpoint
|
336
|
+
self.callable = callable
|
337
|
+
end
|
118
338
|
|
119
|
-
|
339
|
+
def value
|
340
|
+
if example?
|
341
|
+
raw_value.example_for(get_attribute)
|
120
342
|
else
|
121
|
-
|
343
|
+
raw_value
|
122
344
|
end
|
123
345
|
end
|
124
|
-
end
|
125
346
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
347
|
+
private
|
348
|
+
|
349
|
+
attr_accessor :attribute_name, :callable, :endpoint
|
350
|
+
|
351
|
+
def example?
|
352
|
+
raw_value.is_a?(Restspec::Schema::Types::BasicType)
|
130
353
|
end
|
131
|
-
end
|
132
|
-
end
|
133
354
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
355
|
+
def get_attribute
|
356
|
+
if (schema = endpoint.schema_for(:payload)).present? && schema.attributes[attribute_name]
|
357
|
+
schema.attributes[attribute_name]
|
358
|
+
else
|
359
|
+
Restspec::Schema::Attribute.new(attribute_name, context.integer)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def raw_value
|
364
|
+
@raw_value ||= context.instance_eval(&callable)
|
365
|
+
end
|
366
|
+
|
367
|
+
def context
|
368
|
+
@context ||= Object.new.tap do |context|
|
369
|
+
context.extend(Restspec::Schema::Types::TypeMethods)
|
370
|
+
end
|
138
371
|
end
|
139
372
|
end
|
140
373
|
end
|
@@ -3,15 +3,14 @@ require 'httparty'
|
|
3
3
|
module Restspec
|
4
4
|
module Endpoints
|
5
5
|
class Endpoint < Struct.new(:name)
|
6
|
-
|
7
|
-
attr_writer :schema_name
|
8
|
-
attr_reader :last_response, :last_request
|
6
|
+
include HasSchemas
|
9
7
|
|
10
|
-
|
8
|
+
attr_accessor :method, :path, :namespace, :raw_url_params
|
9
|
+
attr_reader :last_response, :last_request
|
11
10
|
|
12
11
|
def execute(body: {}, url_params: {}, query_params: {})
|
13
|
-
|
14
|
-
request = Request.new(method,
|
12
|
+
url = URLBuilder.new(full_path, self.url_params.merge(url_params), query_params).full_url
|
13
|
+
request = Request.new(method, url, full_headers, body || payload)
|
15
14
|
|
16
15
|
Network.request(request).tap do |response|
|
17
16
|
self.last_request = inject_self_into(response, :endpoint)
|
@@ -30,21 +29,6 @@ module Restspec
|
|
30
29
|
[namespace.try(:name), name].compact.join("/")
|
31
30
|
end
|
32
31
|
|
33
|
-
def schema_name
|
34
|
-
@schema_name || namespace.try(:schema_name)
|
35
|
-
end
|
36
|
-
|
37
|
-
def schema
|
38
|
-
@schema ||= begin
|
39
|
-
found_schema = schema_from_store
|
40
|
-
if found_schema.present?
|
41
|
-
found_schema.clone.extend_with(schema_extensions || {})
|
42
|
-
else
|
43
|
-
nil
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
32
|
def full_path
|
49
33
|
if namespace && in_member_or_collection?
|
50
34
|
"#{namespace.full_base_path}#{path}"
|
@@ -57,8 +41,12 @@ module Restspec
|
|
57
41
|
@headers ||= {}
|
58
42
|
end
|
59
43
|
|
44
|
+
def payload
|
45
|
+
@payload ||= internal_payload
|
46
|
+
end
|
47
|
+
|
60
48
|
def url_params
|
61
|
-
@url_params ||=
|
49
|
+
@url_params ||= URLBuilder.new(full_path, raw_url_params).url_params
|
62
50
|
end
|
63
51
|
|
64
52
|
def add_url_param_block(param, &block)
|
@@ -72,20 +60,21 @@ module Restspec
|
|
72
60
|
private
|
73
61
|
|
74
62
|
attr_writer :last_response, :last_request
|
63
|
+
attr_accessor :internal_payload
|
75
64
|
|
76
|
-
def
|
77
|
-
|
65
|
+
def internal_payload
|
66
|
+
schema = schema_for(:payload)
|
67
|
+
if schema.present?
|
68
|
+
Restspec::Schema::SchemaExample.new(schema).value
|
69
|
+
else
|
70
|
+
{}
|
71
|
+
end
|
78
72
|
end
|
79
73
|
|
80
74
|
def inject_self_into(object, property)
|
81
75
|
object.tap { object.send(:"#{property}=", self) }
|
82
76
|
end
|
83
77
|
|
84
|
-
def build_full_url(url_params, query_params)
|
85
|
-
full_url_params = self.url_params.merge(Values::SuperHash.new(url_params))
|
86
|
-
build_url(full_url_params, query_params)
|
87
|
-
end
|
88
|
-
|
89
78
|
def raw_url_params
|
90
79
|
@raw_url_params ||= Restspec::Values::SuperHash.new
|
91
80
|
end
|
@@ -94,31 +83,6 @@ module Restspec
|
|
94
83
|
namespace.anonymous?
|
95
84
|
end
|
96
85
|
|
97
|
-
def calculate_url_params
|
98
|
-
raw_url_params.inject({}) do |hash, (key, value)|
|
99
|
-
real_value = if value.respond_to?(:call)
|
100
|
-
value.call
|
101
|
-
else
|
102
|
-
value
|
103
|
-
end
|
104
|
-
|
105
|
-
hash.merge(key.to_sym => real_value)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def build_url(full_url_params, query_params)
|
110
|
-
query_string = query_params.to_param
|
111
|
-
full_query_string = query_string.present? ? "?#{query_string}" : ""
|
112
|
-
|
113
|
-
base_url + path_from_params(full_url_params) + full_query_string
|
114
|
-
end
|
115
|
-
|
116
|
-
def path_from_params(url_params)
|
117
|
-
full_path.gsub(PARAM_INTERPOLATION_REGEX) do
|
118
|
-
url_params[$1] || url_params[$1.to_sym]
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
86
|
def full_headers
|
123
87
|
config_headers.merge(headers)
|
124
88
|
end
|
@@ -126,10 +90,6 @@ module Restspec
|
|
126
90
|
def config_headers
|
127
91
|
Restspec.config.try(:request).try(:headers) || {}
|
128
92
|
end
|
129
|
-
|
130
|
-
def base_url
|
131
|
-
@base_url ||= (Restspec.config.base_url || '')
|
132
|
-
end
|
133
93
|
end
|
134
94
|
end
|
135
95
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Restspec
|
4
|
+
module Endpoints
|
5
|
+
module HasSchemas
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
DEFAULT_ROLES = [:response]
|
9
|
+
ROLES = [:response, :payload]
|
10
|
+
|
11
|
+
def schema_roles
|
12
|
+
@schema_roles ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_schema(schema_name, options)
|
16
|
+
roles = options.delete(:for) || DEFAULT_ROLES
|
17
|
+
|
18
|
+
roles.each do |role|
|
19
|
+
schema_roles[role] = Restspec::SchemaStore.fetch(schema_name).clone
|
20
|
+
if options.any?
|
21
|
+
schema_roles[role].extend_with(options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_schemas
|
27
|
+
schema_roles.values
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_schemas
|
31
|
+
schema_roles.clear
|
32
|
+
end
|
33
|
+
|
34
|
+
def schema_for(role_name)
|
35
|
+
schema_roles[role_name]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Restspec
|
2
2
|
module Endpoints
|
3
3
|
class Namespace
|
4
|
-
|
5
|
-
|
4
|
+
include HasSchemas
|
5
|
+
|
6
|
+
attr_accessor :base_path, :parent_namespace, :children_namespaces
|
6
7
|
attr_reader :endpoints
|
7
8
|
|
8
|
-
def self.create(name
|
9
|
+
def self.create(name)
|
9
10
|
namespace = new(name)
|
10
11
|
Stores::NamespaceStore.store(namespace)
|
11
12
|
namespace
|
@@ -50,10 +51,6 @@ module Restspec
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
def schema_name
|
54
|
-
@schema_name || parent_namespace.try(:schema_name)
|
55
|
-
end
|
56
|
-
|
57
54
|
def name
|
58
55
|
if top_level_namespace?
|
59
56
|
@name
|
@@ -3,6 +3,30 @@ module Restspec
|
|
3
3
|
module Network
|
4
4
|
extend self
|
5
5
|
|
6
|
+
# Make a request using a {Restspec::Endpoints::Request request} object
|
7
|
+
# to some place. To actually send the information through the wire, this
|
8
|
+
# method uses a network adapter, that defaults to an instance of {HTTPartyNetworkAdapter},
|
9
|
+
# that uses the [httparty](https://github.com/jnunemaker/httparty) gem to make the request.
|
10
|
+
#
|
11
|
+
# The network adapter can be replaced setting the following option in the Restspec configuration:
|
12
|
+
#
|
13
|
+
# ```ruby
|
14
|
+
# config.request.network_adapter = ->{ MyAwesomeNetworkAdapter.new }
|
15
|
+
# ```
|
16
|
+
# This new `MyAwesomeNetworkAdapter` class should respond to just one method called
|
17
|
+
# `request` that returns a triad of values: A status code, the response headers and the
|
18
|
+
# body of the response. For example:
|
19
|
+
#
|
20
|
+
# ```ruby
|
21
|
+
# class MyAwesomeNetworkAdapter
|
22
|
+
# def request(request_object) # it just echoes the payload
|
23
|
+
# [200, {'Content-Type' => 'application/json'}, request_object.payload || {}]
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# ```
|
27
|
+
#
|
28
|
+
# @param request_object [Restspec::Endpoints::Request] the request to make.
|
29
|
+
# @return [Restspec::Endpoints::Response] the response from the wire.
|
6
30
|
def request(request_object)
|
7
31
|
code, headers, body = network_adapter.request(request_object)
|
8
32
|
Response.new(code, headers, body)
|
@@ -22,7 +46,10 @@ module Restspec
|
|
22
46
|
HTTPartyNetworkAdapter.new
|
23
47
|
end
|
24
48
|
|
49
|
+
# It uses [httparty](https://github.com/jnunemaker/httparty) to make a request.
|
25
50
|
class HTTPartyNetworkAdapter
|
51
|
+
# @param request_object [Restspec::Endpoints::Request] the request to make.
|
52
|
+
# @return Array of three values representing the status code, the response's headers and the response's body. The first one is a number and the last two are hashes.
|
26
53
|
def request(request_object)
|
27
54
|
response = HTTParty.send(
|
28
55
|
request_object.method,
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module Restspec
|
2
2
|
module Endpoints
|
3
|
+
# A bag for request data.
|
3
4
|
class Request < Struct.new(:method, :url, :headers, :payload)
|
5
|
+
# Allows to set the endpoint used to generate this request.
|
4
6
|
attr_accessor :endpoint
|
5
7
|
|
8
|
+
# @return [String] a json encoded payload
|
6
9
|
def raw_payload
|
7
10
|
@raw_payload ||= (payload || '').to_json
|
8
11
|
end
|
@@ -2,6 +2,9 @@ require 'delegate'
|
|
2
2
|
|
3
3
|
module Restspec
|
4
4
|
module Endpoints
|
5
|
+
# A response is a representation of the triad returned
|
6
|
+
# by the API calls. They represent the status code, the headers
|
7
|
+
# and the response's body.
|
5
8
|
class Response
|
6
9
|
attr_accessor :endpoint, :headers, :code, :raw_body
|
7
10
|
|