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.
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