restspec 0.0.4 → 0.1
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.
- 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
|
|