restspec 0.0.4 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -0
  3. data/CHANGELOG.md +0 -0
  4. data/README.md +11 -7
  5. data/Rakefile +7 -0
  6. data/bin/restspec +1 -1
  7. data/examples/store-api-tests/Gemfile.lock +3 -1
  8. data/examples/store-api-tests/api.md +47 -47
  9. data/examples/store-api-tests/spec/api/product_spec.rb +0 -2
  10. data/examples/store-api-tests/spec/api/restspec/endpoints.rb +6 -5
  11. data/examples/store-api-tests/spec/api/restspec/schemas.rb +2 -1
  12. data/{docs → guides}/endpoints.md +0 -0
  13. data/{docs → guides}/helpers.md +0 -0
  14. data/{docs → guides}/macros.md +0 -0
  15. data/{docs → guides}/matchers.md +0 -0
  16. data/{docs → guides}/schemas.md +0 -0
  17. data/{docs → guides}/tutorial.md +1 -1
  18. data/{docs → guides}/types.md +0 -0
  19. data/lib/restspec/configuration.rb +28 -4
  20. data/lib/restspec/endpoints/dsl.rb +281 -48
  21. data/lib/restspec/endpoints/endpoint.rb +18 -58
  22. data/lib/restspec/endpoints/has_schemas.rb +39 -0
  23. data/lib/restspec/endpoints/namespace.rb +4 -7
  24. data/lib/restspec/endpoints/network.rb +27 -0
  25. data/lib/restspec/endpoints/request.rb +3 -0
  26. data/lib/restspec/endpoints/response.rb +3 -0
  27. data/lib/restspec/endpoints/url_builder.rb +51 -0
  28. data/lib/restspec/rspec/api_macros.rb +2 -2
  29. data/lib/restspec/rspec/matchers/be_like_schema.rb +1 -1
  30. data/lib/restspec/rspec/matchers/be_like_schema_array.rb +1 -1
  31. data/lib/restspec/runners/docs/templates/docs.md.erb +2 -2
  32. data/lib/restspec/schema/attribute.rb +43 -0
  33. data/lib/restspec/schema/attribute_example.rb +13 -1
  34. data/lib/restspec/schema/checker.rb +80 -8
  35. data/lib/restspec/schema/dsl.rb +67 -11
  36. data/lib/restspec/schema/schema.rb +13 -1
  37. data/lib/restspec/schema/schema_example.rb +7 -1
  38. data/lib/restspec/schema/types/array_type.rb +42 -1
  39. data/lib/restspec/schema/types/basic_type.rb +62 -0
  40. data/lib/restspec/schema/types/boolean_type.rb +10 -0
  41. data/lib/restspec/schema/types/date_type.rb +12 -0
  42. data/lib/restspec/schema/types/datetime_type.rb +16 -0
  43. data/lib/restspec/schema/types/decimal_string_type.rb +16 -5
  44. data/lib/restspec/schema/types/decimal_type.rb +17 -1
  45. data/lib/restspec/schema/types/embedded_schema_type.rb +39 -8
  46. data/lib/restspec/schema/types/hash_type.rb +51 -12
  47. data/lib/restspec/schema/types/integer_type.rb +12 -1
  48. data/lib/restspec/schema/types/null_type.rb +7 -0
  49. data/lib/restspec/schema/types/one_of_type.rb +18 -0
  50. data/lib/restspec/schema/types/schema_id_type.rb +14 -17
  51. data/lib/restspec/schema/types/string_type.rb +9 -0
  52. data/lib/restspec/schema/types/type_methods.rb +32 -0
  53. data/lib/restspec/schema/types.rb +1 -18
  54. data/lib/restspec/shortcuts.rb +10 -0
  55. data/lib/restspec/stores/endpoint_store.rb +27 -2
  56. data/lib/restspec/stores/namespace_store.rb +23 -4
  57. data/lib/restspec/stores/schema_store.rb +15 -0
  58. data/lib/restspec/values/status_code.rb +16 -1
  59. data/lib/restspec/version.rb +1 -1
  60. data/lib/restspec.rb +2 -0
  61. data/restspec.gemspec +2 -0
  62. data/spec/restspec/endpoints/dsl_spec.rb +32 -19
  63. data/spec/restspec/endpoints/endpoint_spec.rb +20 -43
  64. data/spec/restspec/endpoints/namespace_spec.rb +0 -7
  65. data/spec/restspec/endpoints/request_spec.rb +33 -0
  66. data/spec/restspec/schema/attribute_spec.rb +44 -0
  67. data/spec/restspec/schema/checker_spec.rb +57 -0
  68. data/spec/restspec/schema/dsl_spec.rb +1 -1
  69. data/spec/restspec/schema/schema_spec.rb +15 -0
  70. data/spec/restspec/schema/types/basic_type_spec.rb +2 -2
  71. data/spec/restspec/schema/types/decimal_string_type_spec.rb +56 -0
  72. data/spec/restspec/schema/types/decimal_type_spec.rb +25 -0
  73. data/spec/restspec/schema/types/embedded_schema_type_spec.rb +32 -0
  74. data/spec/restspec/schema/types/hash_type_spec.rb +39 -0
  75. data/spec/restspec/schema/types/integer_type_spec.rb +28 -0
  76. data/spec/restspec/schema/types/one_of_type_spec.rb +21 -0
  77. data/spec/restspec/stores/endpoint_store_spec.rb +62 -0
  78. metadata +63 -10
  79. 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
- def resource(name, options = {}, &block)
16
- namespace name, base_path: (options[:base_path] || "/#{name}") do
17
- if self.namespace.schema_name.blank?
18
- schema_name = name.to_s.singularize
19
- schema(schema_name.to_sym)
20
- end
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, :endpoint_base_path, :resource_endpoint_base_path, :common_endpoints_config_block
62
+ attr_accessor :namespace, :endpoint_config_blocks
29
63
 
30
64
  def initialize(namespace)
31
65
  self.namespace = namespace
32
- self.resource_endpoint_base_path = ''
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
- endpoint_dsl.instance_eval(&common_endpoints_config_block)
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
- HTTP_METHODS.each do |http_method|
47
- define_method(http_method) do |name, path = '', &block|
48
- endpoint(name) do
49
- public_send(http_method, path)
50
- instance_eval(&block) if block.present?
51
- end
52
- end
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).instance_eval(&block)
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).instance_eval(&block)
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
- def schema(name, schema_extensions = {})
68
- namespace.schema_name = name
69
- namespace.schema_extensions = schema_extensions
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.common_endpoints_config_block = endpoints_config
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 common_endpoints_config_block
85
- @common_endpoints_config_block ||= (Proc.new {})
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
- def schema(name, schema_extensions = {})
99
- endpoint.schema_name = name
100
- endpoint.schema_extensions = schema_extensions
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
- value_or_type_context = ValueOrTypeContext.new
110
- value_or_type = value_or_type_context.instance_eval(&value_or_type_block)
323
+ ExampleOrValue.new(endpoint, param, value_or_type_block).value
324
+ end
325
+ end
111
326
 
112
- if value_or_type.is_a?(Restspec::Schema::Types::BasicType)
113
- attribute = if endpoint.schema && endpoint.schema.attributes[param]
114
- endpoint.schema.attributes[param]
115
- else
116
- Restspec::Schema::Attribute.new(param, value_or_type_context.integer)
117
- end
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
- value_or_type.example_for(attribute)
339
+ def value
340
+ if example?
341
+ raw_value.example_for(get_attribute)
120
342
  else
121
- value_or_type
343
+ raw_value
122
344
  end
123
345
  end
124
- end
125
346
 
126
- HTTP_METHODS.each do |http_method|
127
- define_method(http_method) do |path|
128
- self.method http_method
129
- self.path path
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
- class ValueOrTypeContext
135
- Restspec::Schema::Types::ALL.each do |type_name, type_class|
136
- define_method(type_name) do |options = {}|
137
- type_class.new(options)
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
- attr_accessor :method, :path, :namespace, :raw_url_params, :schema_extensions
7
- attr_writer :schema_name
8
- attr_reader :last_response, :last_request
6
+ include HasSchemas
9
7
 
10
- PARAM_INTERPOLATION_REGEX = /:([\w]+)/
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
- full_url = build_full_url(url_params, query_params)
14
- request = Request.new(method, full_url, full_headers, body)
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 ||= Restspec::Values::SuperHash.new(calculate_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 schema_from_store
77
- Restspec::SchemaStore.get(schema_name)
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
- attr_accessor :base_path, :parent_namespace, :children_namespaces, :schema_extensions
5
- attr_writer :schema_name
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