grape 1.1.0 → 1.5.3

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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +278 -44
  3. data/LICENSE +1 -1
  4. data/README.md +514 -69
  5. data/UPGRADING.md +424 -17
  6. data/grape.gemspec +13 -2
  7. data/lib/grape.rb +104 -71
  8. data/lib/grape/api.rb +138 -175
  9. data/lib/grape/api/helpers.rb +2 -0
  10. data/lib/grape/api/instance.rb +283 -0
  11. data/lib/grape/config.rb +34 -0
  12. data/lib/grape/content_types.rb +34 -0
  13. data/lib/grape/cookies.rb +2 -0
  14. data/lib/grape/dsl/api.rb +2 -0
  15. data/lib/grape/dsl/callbacks.rb +22 -0
  16. data/lib/grape/dsl/configuration.rb +2 -0
  17. data/lib/grape/dsl/desc.rb +41 -7
  18. data/lib/grape/dsl/headers.rb +2 -0
  19. data/lib/grape/dsl/helpers.rb +5 -2
  20. data/lib/grape/dsl/inside_route.rb +92 -49
  21. data/lib/grape/dsl/logger.rb +2 -0
  22. data/lib/grape/dsl/middleware.rb +9 -0
  23. data/lib/grape/dsl/parameters.rb +25 -14
  24. data/lib/grape/dsl/request_response.rb +4 -2
  25. data/lib/grape/dsl/routing.rb +17 -10
  26. data/lib/grape/dsl/settings.rb +7 -1
  27. data/lib/grape/dsl/validations.rb +24 -4
  28. data/lib/grape/eager_load.rb +20 -0
  29. data/lib/grape/endpoint.rb +59 -35
  30. data/lib/grape/error_formatter.rb +4 -2
  31. data/lib/grape/error_formatter/base.rb +2 -0
  32. data/lib/grape/error_formatter/json.rb +2 -0
  33. data/lib/grape/error_formatter/txt.rb +2 -0
  34. data/lib/grape/error_formatter/xml.rb +2 -0
  35. data/lib/grape/exceptions/base.rb +20 -14
  36. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  37. data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
  38. data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
  39. data/lib/grape/exceptions/invalid_formatter.rb +2 -0
  40. data/lib/grape/exceptions/invalid_message_body.rb +2 -0
  41. data/lib/grape/exceptions/invalid_response.rb +11 -0
  42. data/lib/grape/exceptions/invalid_version_header.rb +2 -0
  43. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
  44. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
  45. data/lib/grape/exceptions/method_not_allowed.rb +2 -0
  46. data/lib/grape/exceptions/missing_group_type.rb +2 -0
  47. data/lib/grape/exceptions/missing_mime_type.rb +2 -0
  48. data/lib/grape/exceptions/missing_option.rb +2 -0
  49. data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
  50. data/lib/grape/exceptions/unknown_options.rb +2 -0
  51. data/lib/grape/exceptions/unknown_parameter.rb +2 -0
  52. data/lib/grape/exceptions/unknown_validator.rb +2 -0
  53. data/lib/grape/exceptions/unsupported_group_type.rb +2 -0
  54. data/lib/grape/exceptions/validation.rb +4 -2
  55. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  56. data/lib/grape/exceptions/validation_errors.rb +16 -13
  57. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  58. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  59. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  60. data/lib/grape/extensions/hash.rb +2 -0
  61. data/lib/grape/extensions/hashie/mash.rb +2 -0
  62. data/lib/grape/formatter.rb +5 -3
  63. data/lib/grape/formatter/json.rb +2 -0
  64. data/lib/grape/formatter/serializable_hash.rb +2 -0
  65. data/lib/grape/formatter/txt.rb +2 -0
  66. data/lib/grape/formatter/xml.rb +2 -0
  67. data/lib/grape/http/headers.rb +50 -18
  68. data/lib/grape/locale/en.yml +3 -1
  69. data/lib/grape/middleware/auth/base.rb +7 -7
  70. data/lib/grape/middleware/auth/dsl.rb +2 -0
  71. data/lib/grape/middleware/auth/strategies.rb +2 -0
  72. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  73. data/lib/grape/middleware/base.rb +10 -7
  74. data/lib/grape/middleware/error.rb +21 -16
  75. data/lib/grape/middleware/filter.rb +2 -0
  76. data/lib/grape/middleware/formatter.rb +8 -6
  77. data/lib/grape/middleware/globals.rb +2 -0
  78. data/lib/grape/middleware/helpers.rb +12 -0
  79. data/lib/grape/middleware/stack.rb +13 -3
  80. data/lib/grape/middleware/versioner.rb +2 -0
  81. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
  82. data/lib/grape/middleware/versioner/header.rb +10 -8
  83. data/lib/grape/middleware/versioner/param.rb +3 -1
  84. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +4 -1
  85. data/lib/grape/middleware/versioner/path.rb +3 -1
  86. data/lib/grape/namespace.rb +14 -2
  87. data/lib/grape/parser.rb +4 -2
  88. data/lib/grape/parser/json.rb +3 -1
  89. data/lib/grape/parser/xml.rb +3 -1
  90. data/lib/grape/path.rb +15 -3
  91. data/lib/grape/presenters/presenter.rb +2 -0
  92. data/lib/grape/request.rb +19 -10
  93. data/lib/grape/router.rb +30 -29
  94. data/lib/grape/router/attribute_translator.rb +41 -8
  95. data/lib/grape/router/pattern.rb +20 -16
  96. data/lib/grape/router/route.rb +14 -28
  97. data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
  98. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
  99. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
  100. data/lib/grape/util/base_inheritable.rb +43 -0
  101. data/lib/grape/util/cache.rb +20 -0
  102. data/lib/grape/util/endpoint_configuration.rb +8 -0
  103. data/lib/grape/util/env.rb +19 -17
  104. data/lib/grape/util/inheritable_setting.rb +2 -0
  105. data/lib/grape/util/inheritable_values.rb +7 -25
  106. data/lib/grape/util/json.rb +2 -0
  107. data/lib/grape/util/lazy_block.rb +27 -0
  108. data/lib/grape/util/lazy_object.rb +43 -0
  109. data/lib/grape/util/lazy_value.rb +98 -0
  110. data/lib/grape/util/registrable.rb +2 -0
  111. data/lib/grape/util/reverse_stackable_values.rb +10 -35
  112. data/lib/grape/util/stackable_values.rb +21 -34
  113. data/lib/grape/util/strict_hash_configuration.rb +2 -0
  114. data/lib/grape/util/xml.rb +2 -0
  115. data/lib/grape/validations.rb +2 -0
  116. data/lib/grape/validations/attributes_iterator.rb +16 -6
  117. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  118. data/lib/grape/validations/params_scope.rb +51 -30
  119. data/lib/grape/validations/single_attribute_iterator.rb +24 -0
  120. data/lib/grape/validations/types.rb +13 -38
  121. data/lib/grape/validations/types/array_coercer.rb +65 -0
  122. data/lib/grape/validations/types/build_coercer.rb +47 -49
  123. data/lib/grape/validations/types/custom_type_coercer.rb +29 -51
  124. data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
  125. data/lib/grape/validations/types/dry_type_coercer.rb +76 -0
  126. data/lib/grape/validations/types/file.rb +22 -18
  127. data/lib/grape/validations/types/invalid_value.rb +24 -0
  128. data/lib/grape/validations/types/json.rb +46 -39
  129. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  130. data/lib/grape/validations/types/primitive_coercer.rb +67 -0
  131. data/lib/grape/validations/types/set_coercer.rb +40 -0
  132. data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
  133. data/lib/grape/validations/validator_factory.rb +8 -11
  134. data/lib/grape/validations/validators/all_or_none.rb +8 -13
  135. data/lib/grape/validations/validators/allow_blank.rb +3 -1
  136. data/lib/grape/validations/validators/as.rb +5 -4
  137. data/lib/grape/validations/validators/at_least_one_of.rb +7 -13
  138. data/lib/grape/validations/validators/base.rb +20 -16
  139. data/lib/grape/validations/validators/coerce.rb +46 -29
  140. data/lib/grape/validations/validators/default.rb +6 -6
  141. data/lib/grape/validations/validators/exactly_one_of.rb +10 -23
  142. data/lib/grape/validations/validators/except_values.rb +4 -2
  143. data/lib/grape/validations/validators/multiple_params_base.rb +17 -10
  144. data/lib/grape/validations/validators/mutual_exclusion.rb +8 -18
  145. data/lib/grape/validations/validators/presence.rb +3 -1
  146. data/lib/grape/validations/validators/regexp.rb +4 -2
  147. data/lib/grape/validations/validators/same_as.rb +26 -0
  148. data/lib/grape/validations/validators/values.rb +18 -6
  149. data/lib/grape/version.rb +3 -1
  150. data/spec/grape/api/custom_validations_spec.rb +5 -3
  151. data/spec/grape/api/deeply_included_options_spec.rb +2 -0
  152. data/spec/grape/api/defines_boolean_in_params_spec.rb +39 -0
  153. data/spec/grape/api/inherited_helpers_spec.rb +2 -0
  154. data/spec/grape/api/instance_spec.rb +104 -0
  155. data/spec/grape/api/invalid_format_spec.rb +2 -0
  156. data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
  157. data/spec/grape/api/nested_helpers_spec.rb +2 -0
  158. data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
  159. data/spec/grape/api/parameters_modification_spec.rb +3 -1
  160. data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
  161. data/spec/grape/api/recognize_path_spec.rb +2 -0
  162. data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
  163. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
  164. data/spec/grape/api/routes_with_requirements_spec.rb +61 -0
  165. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +2 -0
  166. data/spec/grape/api/shared_helpers_spec.rb +2 -0
  167. data/spec/grape/api_remount_spec.rb +473 -0
  168. data/spec/grape/api_spec.rb +565 -12
  169. data/spec/grape/config_spec.rb +19 -0
  170. data/spec/grape/dsl/callbacks_spec.rb +2 -0
  171. data/spec/grape/dsl/configuration_spec.rb +2 -0
  172. data/spec/grape/dsl/desc_spec.rb +42 -16
  173. data/spec/grape/dsl/headers_spec.rb +2 -0
  174. data/spec/grape/dsl/helpers_spec.rb +4 -2
  175. data/spec/grape/dsl/inside_route_spec.rb +184 -33
  176. data/spec/grape/dsl/logger_spec.rb +2 -0
  177. data/spec/grape/dsl/middleware_spec.rb +10 -0
  178. data/spec/grape/dsl/parameters_spec.rb +2 -0
  179. data/spec/grape/dsl/request_response_spec.rb +2 -0
  180. data/spec/grape/dsl/routing_spec.rb +12 -0
  181. data/spec/grape/dsl/settings_spec.rb +2 -0
  182. data/spec/grape/dsl/validations_spec.rb +2 -0
  183. data/spec/grape/endpoint/declared_spec.rb +601 -0
  184. data/spec/grape/endpoint_spec.rb +53 -523
  185. data/spec/grape/entity_spec.rb +9 -1
  186. data/spec/grape/exceptions/base_spec.rb +67 -0
  187. data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
  188. data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
  189. data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -0
  190. data/spec/grape/exceptions/invalid_response_spec.rb +13 -0
  191. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -0
  192. data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
  193. data/spec/grape/exceptions/missing_option_spec.rb +2 -0
  194. data/spec/grape/exceptions/unknown_options_spec.rb +2 -0
  195. data/spec/grape/exceptions/unknown_validator_spec.rb +2 -0
  196. data/spec/grape/exceptions/validation_errors_spec.rb +8 -4
  197. data/spec/grape/exceptions/validation_spec.rb +3 -1
  198. data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
  199. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
  200. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
  201. data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
  202. data/spec/grape/integration/rack_sendfile_spec.rb +14 -8
  203. data/spec/grape/integration/rack_spec.rb +25 -7
  204. data/spec/grape/loading_spec.rb +2 -0
  205. data/spec/grape/middleware/auth/base_spec.rb +2 -0
  206. data/spec/grape/middleware/auth/dsl_spec.rb +5 -3
  207. data/spec/grape/middleware/auth/strategies_spec.rb +3 -1
  208. data/spec/grape/middleware/base_spec.rb +10 -0
  209. data/spec/grape/middleware/error_spec.rb +3 -1
  210. data/spec/grape/middleware/exception_spec.rb +4 -2
  211. data/spec/grape/middleware/formatter_spec.rb +33 -16
  212. data/spec/grape/middleware/globals_spec.rb +2 -0
  213. data/spec/grape/middleware/stack_spec.rb +12 -0
  214. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
  215. data/spec/grape/middleware/versioner/header_spec.rb +9 -1
  216. data/spec/grape/middleware/versioner/param_spec.rb +3 -1
  217. data/spec/grape/middleware/versioner/path_spec.rb +3 -1
  218. data/spec/grape/middleware/versioner_spec.rb +2 -0
  219. data/spec/grape/named_api_spec.rb +21 -0
  220. data/spec/grape/parser_spec.rb +7 -5
  221. data/spec/grape/path_spec.rb +6 -4
  222. data/spec/grape/presenters/presenter_spec.rb +2 -0
  223. data/spec/grape/request_spec.rb +26 -0
  224. data/spec/grape/util/inheritable_setting_spec.rb +2 -0
  225. data/spec/grape/util/inheritable_values_spec.rb +2 -0
  226. data/spec/grape/util/reverse_stackable_values_spec.rb +2 -0
  227. data/spec/grape/util/stackable_values_spec.rb +3 -1
  228. data/spec/grape/util/strict_hash_configuration_spec.rb +2 -0
  229. data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
  230. data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
  231. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +41 -0
  232. data/spec/grape/validations/params_scope_spec.rb +213 -9
  233. data/spec/grape/validations/single_attribute_iterator_spec.rb +58 -0
  234. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  235. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  236. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  237. data/spec/grape/validations/types_spec.rb +9 -36
  238. data/spec/grape/validations/validators/all_or_none_spec.rb +140 -30
  239. data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
  240. data/spec/grape/validations/validators/at_least_one_of_spec.rb +175 -29
  241. data/spec/grape/validations/validators/coerce_spec.rb +476 -135
  242. data/spec/grape/validations/validators/default_spec.rb +172 -0
  243. data/spec/grape/validations/validators/exactly_one_of_spec.rb +204 -38
  244. data/spec/grape/validations/validators/except_values_spec.rb +4 -1
  245. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +186 -27
  246. data/spec/grape/validations/validators/presence_spec.rb +30 -0
  247. data/spec/grape/validations/validators/regexp_spec.rb +2 -0
  248. data/spec/grape/validations/validators/same_as_spec.rb +65 -0
  249. data/spec/grape/validations/validators/values_spec.rb +30 -5
  250. data/spec/grape/validations_spec.rb +388 -50
  251. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  252. data/spec/integration/multi_json/json_spec.rb +2 -0
  253. data/spec/integration/multi_xml/xml_spec.rb +2 -0
  254. data/spec/shared/versioning_examples.rb +22 -20
  255. data/spec/spec_helper.rb +12 -1
  256. data/spec/support/basic_auth_encode_helpers.rb +2 -0
  257. data/spec/support/chunks.rb +14 -0
  258. data/spec/support/content_type_helpers.rb +2 -0
  259. data/spec/support/eager_load.rb +19 -0
  260. data/spec/support/endpoint_faker.rb +2 -0
  261. data/spec/support/file_streamer.rb +2 -0
  262. data/spec/support/integer_helpers.rb +2 -0
  263. data/spec/support/versioned_helpers.rb +8 -8
  264. metadata +86 -48
  265. data/Appraisals +0 -32
  266. data/Dangerfile +0 -2
  267. data/Gemfile +0 -33
  268. data/Gemfile.lock +0 -231
  269. data/Guardfile +0 -10
  270. data/RELEASING.md +0 -111
  271. data/Rakefile +0 -25
  272. data/benchmark/simple.rb +0 -27
  273. data/benchmark/simple_with_type_coercer.rb +0 -22
  274. data/gemfiles/multi_json.gemfile +0 -35
  275. data/gemfiles/multi_xml.gemfile +0 -35
  276. data/gemfiles/rack_1.5.2.gemfile +0 -35
  277. data/gemfiles/rack_edge.gemfile +0 -35
  278. data/gemfiles/rails_3.gemfile +0 -36
  279. data/gemfiles/rails_4.gemfile +0 -35
  280. data/gemfiles/rails_5.gemfile +0 -35
  281. data/gemfiles/rails_edge.gemfile +0 -35
  282. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  283. data/lib/grape/util/content_types.rb +0 -26
  284. data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
  285. data/pkg/grape-0.17.0.gem +0 -0
  286. data/pkg/grape-0.19.0.gem +0 -0
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'shared/versioning_examples'
3
5
 
@@ -220,6 +222,15 @@ describe Grape::API do
220
222
  end
221
223
  end
222
224
 
225
+ describe '.call' do
226
+ context 'it does not add to the app setup' do
227
+ it 'calls the app' do
228
+ expect(subject).not_to receive(:add_setup)
229
+ subject.call({})
230
+ end
231
+ end
232
+ end
233
+
223
234
  describe '.route_param' do
224
235
  it 'adds a parameterized route segment namespace' do
225
236
  subject.namespace :users do
@@ -805,6 +816,71 @@ XML
805
816
  end
806
817
  end
807
818
 
819
+ describe 'when hook behaviour is controlled by attributes on the route ' do
820
+ before do
821
+ subject.before do
822
+ error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
823
+ end
824
+
825
+ subject.namespace 'example' do
826
+ before do
827
+ error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret]
828
+ end
829
+
830
+ desc 'it gets with secret', secret: 'password'
831
+ get { status(params[:id] == '504' ? 200 : 404) }
832
+
833
+ desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password'
834
+ post {}
835
+ end
836
+ end
837
+
838
+ context 'when HTTP method is not defined' do
839
+ let(:response) { delete('/example') }
840
+
841
+ it 'responds with a 405 status' do
842
+ expect(response.status).to eql 405
843
+ end
844
+ end
845
+
846
+ context 'when HTTP method is defined with attribute' do
847
+ let(:response) { post('/example?secret=incorrect_password') }
848
+ it 'responds with the defined error in the before hook' do
849
+ expect(response.status).to eql 401
850
+ end
851
+ end
852
+
853
+ context 'when HTTP method is defined and the underlying before hook expectation is not met' do
854
+ let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
855
+ it 'ends up in the endpoint' do
856
+ expect(response.status).to eql 401
857
+ end
858
+ end
859
+
860
+ context 'when HTTP method is defined and everything is like the before hooks expect' do
861
+ let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
862
+ it 'ends up in the endpoint' do
863
+ expect(response.status).to eql 201
864
+ end
865
+ end
866
+
867
+ context 'when HEAD is called for the defined GET' do
868
+ let(:response) { head('/example?id=504') }
869
+
870
+ it 'responds with 401 because before expectations in before hooks are not met' do
871
+ expect(response.status).to eql 401
872
+ end
873
+ end
874
+
875
+ context 'when HEAD is called for the defined GET' do
876
+ let(:response) { head('/example?id=504&secret=password') }
877
+
878
+ it 'responds with 200 because before hooks are not called' do
879
+ expect(response.status).to eql 200
880
+ end
881
+ end
882
+ end
883
+
808
884
  context 'allows HEAD on a GET request that' do
809
885
  before do
810
886
  subject.get 'example' do
@@ -876,6 +952,40 @@ XML
876
952
  end
877
953
  end
878
954
 
955
+ describe '.compile!' do
956
+ it 'requires the grape/eager_load file' do
957
+ expect(app).to receive(:require).with('grape/eager_load') { nil }
958
+ app.compile!
959
+ end
960
+
961
+ it 'compiles the instance for rack!' do
962
+ stubbed_object = double(:instance_for_rack)
963
+ allow(app).to receive(:instance_for_rack) { stubbed_object }
964
+ end
965
+ end
966
+
967
+ # NOTE: this method is required to preserve the ability of pre-mounting
968
+ # the root API into a namespace, it may be deprecated in the future.
969
+ describe 'instance_for_rack' do
970
+ context 'when the app was not mounted' do
971
+ it 'returns the base_instance' do
972
+ expect(app.send(:instance_for_rack)).to eq app.base_instance
973
+ end
974
+ end
975
+
976
+ context 'when the app was mounted' do
977
+ it 'returns the first mounted instance' do
978
+ mounted_app = app
979
+ Class.new(Grape::API) do
980
+ namespace 'new_namespace' do
981
+ mount mounted_app
982
+ end
983
+ end
984
+ expect(app.send(:instance_for_rack)).to eq app.send(:mounted_instances).first
985
+ end
986
+ end
987
+ end
988
+
879
989
  describe 'filters' do
880
990
  it 'adds a before filter' do
881
991
  subject.before { @foo = 'first' }
@@ -890,7 +1000,7 @@ XML
890
1000
 
891
1001
  it 'adds a before filter to current and child namespaces only' do
892
1002
  subject.get '/' do
893
- "root - #{@foo}"
1003
+ "root - #{instance_variable_defined?(:@foo) ? @foo : nil}"
894
1004
  end
895
1005
  subject.namespace :blah do
896
1006
  before { @foo = 'foo' }
@@ -1039,6 +1149,11 @@ XML
1039
1149
  expect(last_response.headers['Content-Type']).to eq('text/plain')
1040
1150
  end
1041
1151
 
1152
+ it 'does not set Cache-Control' do
1153
+ get '/foo'
1154
+ expect(last_response.headers['Cache-Control']).to eq(nil)
1155
+ end
1156
+
1042
1157
  it 'sets content type for xml' do
1043
1158
  get '/foo.xml'
1044
1159
  expect(last_response.headers['Content-Type']).to eq('application/xml')
@@ -1090,7 +1205,7 @@ XML
1090
1205
 
1091
1206
  subject.use Rack::Chunked
1092
1207
  subject.get('/stream') { stream test_stream }
1093
- get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1'
1208
+ get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'
1094
1209
 
1095
1210
  expect(last_response.headers['Content-Type']).to eq('text/plain')
1096
1211
  expect(last_response.headers['Content-Length']).to eq(nil)
@@ -1348,6 +1463,28 @@ XML
1348
1463
  end
1349
1464
  end
1350
1465
 
1466
+ describe '.insert' do
1467
+ it 'inserts middleware in a specific location in the stack' do
1468
+ m = Class.new(Grape::Middleware::Base) do
1469
+ def call(env)
1470
+ env['phony.args'] ||= []
1471
+ env['phony.args'] << @options[:message]
1472
+ @app.call(env)
1473
+ end
1474
+ end
1475
+
1476
+ subject.use ApiSpec::PhonyMiddleware, 'bye'
1477
+ subject.insert 0, m, message: 'good'
1478
+ subject.insert 0, m, message: 'hello'
1479
+ subject.get '/' do
1480
+ env['phony.args'].join(' ')
1481
+ end
1482
+
1483
+ get '/'
1484
+ expect(last_response.body).to eql 'hello good bye'
1485
+ end
1486
+ end
1487
+
1351
1488
  describe '.http_basic' do
1352
1489
  it 'protects any resources on the same scope' do
1353
1490
  subject.http_basic do |u, _p|
@@ -1463,6 +1600,11 @@ XML
1463
1600
  expect(subject.io).to receive(:write).with(message)
1464
1601
  subject.logger.info 'this will be logged'
1465
1602
  end
1603
+
1604
+ it 'does not unnecessarily retain duplicate setup blocks' do
1605
+ subject.logger
1606
+ expect { subject.logger }.to_not change(subject.instance_variable_get(:@setup), :size)
1607
+ end
1466
1608
  end
1467
1609
 
1468
1610
  describe '.helpers' do
@@ -1597,6 +1739,199 @@ XML
1597
1739
  end
1598
1740
  end
1599
1741
 
1742
+ describe 'lifecycle' do
1743
+ let!(:lifecycle) { [] }
1744
+ let!(:standard_cycle) do
1745
+ %i[before before_validation after_validation api_call after finally]
1746
+ end
1747
+
1748
+ let!(:validation_error) do
1749
+ %i[before before_validation finally]
1750
+ end
1751
+
1752
+ let!(:errored_cycle) do
1753
+ %i[before before_validation after_validation api_call finally]
1754
+ end
1755
+
1756
+ before do
1757
+ current_cycle = lifecycle
1758
+
1759
+ subject.before do
1760
+ current_cycle << :before
1761
+ end
1762
+
1763
+ subject.before_validation do
1764
+ current_cycle << :before_validation
1765
+ end
1766
+
1767
+ subject.after_validation do
1768
+ current_cycle << :after_validation
1769
+ end
1770
+
1771
+ subject.after do
1772
+ current_cycle << :after
1773
+ end
1774
+
1775
+ subject.finally do
1776
+ current_cycle << :finally
1777
+ end
1778
+ end
1779
+
1780
+ context 'when the api_call succeeds' do
1781
+ before do
1782
+ current_cycle = lifecycle
1783
+
1784
+ subject.get 'api_call' do
1785
+ current_cycle << :api_call
1786
+ end
1787
+ end
1788
+
1789
+ it 'follows the standard life_cycle' do
1790
+ get '/api_call'
1791
+ expect(lifecycle).to eq standard_cycle
1792
+ end
1793
+ end
1794
+
1795
+ context 'when the api_call has a controlled error' do
1796
+ before do
1797
+ current_cycle = lifecycle
1798
+
1799
+ subject.get 'api_call' do
1800
+ current_cycle << :api_call
1801
+ error!(:some_error)
1802
+ end
1803
+ end
1804
+
1805
+ it 'follows the errored life_cycle (skips after)' do
1806
+ get '/api_call'
1807
+ expect(lifecycle).to eq errored_cycle
1808
+ end
1809
+ end
1810
+
1811
+ context 'when the api_call has an exception' do
1812
+ before do
1813
+ current_cycle = lifecycle
1814
+
1815
+ subject.get 'api_call' do
1816
+ current_cycle << :api_call
1817
+ raise StandardError
1818
+ end
1819
+ end
1820
+
1821
+ it 'follows the errored life_cycle (skips after)' do
1822
+ expect { get '/api_call' }.to raise_error(StandardError)
1823
+ expect(lifecycle).to eq errored_cycle
1824
+ end
1825
+ end
1826
+
1827
+ context 'when the api_call fails validation' do
1828
+ before do
1829
+ current_cycle = lifecycle
1830
+
1831
+ subject.params do
1832
+ requires :some_param, type: String
1833
+ end
1834
+
1835
+ subject.get 'api_call' do
1836
+ current_cycle << :api_call
1837
+ end
1838
+ end
1839
+
1840
+ it 'follows the failed_validation cycle (skips after_validation, api_call & after)' do
1841
+ get '/api_call'
1842
+ expect(lifecycle).to eq validation_error
1843
+ end
1844
+ end
1845
+ end
1846
+
1847
+ describe '.finally' do
1848
+ let!(:code) { { has_executed: false } }
1849
+ let(:block_to_run) do
1850
+ code_to_execute = code
1851
+ proc do
1852
+ code_to_execute[:has_executed] = true
1853
+ end
1854
+ end
1855
+
1856
+ context 'when the ensure block has no exceptions' do
1857
+ before { subject.finally(&block_to_run) }
1858
+
1859
+ context 'when no API call is made' do
1860
+ it 'has not executed the ensure code' do
1861
+ expect(code[:has_executed]).to be false
1862
+ end
1863
+ end
1864
+
1865
+ context 'when no errors occurs' do
1866
+ before do
1867
+ subject.get '/no_exceptions' do
1868
+ 'success'
1869
+ end
1870
+ end
1871
+
1872
+ it 'executes the ensure code' do
1873
+ get '/no_exceptions'
1874
+ expect(last_response.body).to eq 'success'
1875
+ expect(code[:has_executed]).to be true
1876
+ end
1877
+
1878
+ context 'with a helper' do
1879
+ let(:block_to_run) do
1880
+ code_to_execute = code
1881
+ proc do
1882
+ code_to_execute[:value] = some_helper
1883
+ end
1884
+ end
1885
+
1886
+ before do
1887
+ subject.helpers do
1888
+ def some_helper
1889
+ 'some_value'
1890
+ end
1891
+ end
1892
+
1893
+ subject.get '/with_helpers' do
1894
+ 'success'
1895
+ end
1896
+ end
1897
+
1898
+ it 'has access to the helper' do
1899
+ get '/with_helpers'
1900
+ expect(code[:value]).to eq 'some_value'
1901
+ end
1902
+ end
1903
+ end
1904
+
1905
+ context 'when an unhandled occurs inside the API call' do
1906
+ before do
1907
+ subject.get '/unhandled_exception' do
1908
+ raise StandardError
1909
+ end
1910
+ end
1911
+
1912
+ it 'executes the ensure code' do
1913
+ expect { get '/unhandled_exception' }.to raise_error StandardError
1914
+ expect(code[:has_executed]).to be true
1915
+ end
1916
+ end
1917
+
1918
+ context 'when a handled error occurs inside the API call' do
1919
+ before do
1920
+ subject.rescue_from(StandardError) { error! 'handled' }
1921
+ subject.get '/handled_exception' do
1922
+ raise StandardError
1923
+ end
1924
+ end
1925
+
1926
+ it 'executes the ensure code' do
1927
+ get '/handled_exception'
1928
+ expect(code[:has_executed]).to be true
1929
+ expect(last_response.body).to eq 'handled'
1930
+ end
1931
+ end
1932
+ end
1933
+ end
1934
+
1600
1935
  describe '.rescue_from' do
1601
1936
  it 'does not rescue errors when rescue_from is not set' do
1602
1937
  subject.get '/exception' do
@@ -1643,9 +1978,9 @@ XML
1643
1978
  it 'avoids polluting global namespace' do
1644
1979
  env = Rack::MockRequest.env_for('/')
1645
1980
 
1646
- expect(a.call(env)[2].body).to eq(['foo'])
1647
- expect(b.call(env)[2].body).to eq(['bar'])
1648
- expect(a.call(env)[2].body).to eq(['foo'])
1981
+ expect(read_chunks(a.call(env)[2])).to eq(['foo'])
1982
+ expect(read_chunks(b.call(env)[2])).to eq(['bar'])
1983
+ expect(read_chunks(a.call(env)[2])).to eq(['foo'])
1649
1984
  end
1650
1985
  end
1651
1986
 
@@ -1682,6 +2017,26 @@ XML
1682
2017
  expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')
1683
2018
  end
1684
2019
 
2020
+ it 'mimics default ruby "rescue" handler' do
2021
+ # The exception is matched to the rescue starting at the top, and matches only once
2022
+
2023
+ subject.rescue_from ArgumentError do |e|
2024
+ error!(e, 402)
2025
+ end
2026
+ subject.rescue_from StandardError do |e|
2027
+ error!(e, 401)
2028
+ end
2029
+
2030
+ subject.get('/child_of_standard_error') { raise ArgumentError }
2031
+ subject.get('/standard_error') { raise StandardError }
2032
+
2033
+ get '/child_of_standard_error'
2034
+ expect(last_response.status).to eql 402
2035
+
2036
+ get '/standard_error'
2037
+ expect(last_response.status).to eql 401
2038
+ end
2039
+
1685
2040
  context 'CustomError subclass of Grape::Exceptions::Base' do
1686
2041
  before do
1687
2042
  module ApiSpec
@@ -1700,7 +2055,7 @@ XML
1700
2055
  rack_response('New Error', e.status)
1701
2056
  end
1702
2057
  subject.get '/custom_error' do
1703
- raise ApiSpec::CustomError, status: 400, message: 'Custom Error'
2058
+ raise ApiSpec::CustomError.new(status: 400, message: 'Custom Error')
1704
2059
  end
1705
2060
 
1706
2061
  get '/custom_error'
@@ -1723,6 +2078,16 @@ XML
1723
2078
  expect(last_response.status).to eql 500
1724
2079
  expect(last_response.body).to eq('Formatter Error')
1725
2080
  end
2081
+
2082
+ it 'uses default_rescue_handler to handle invalid response from rescue_from' do
2083
+ subject.rescue_from(:all) { 'error' }
2084
+ subject.get('/') { raise }
2085
+
2086
+ expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
2087
+ get '/'
2088
+ expect(last_response.status).to eql 500
2089
+ expect(last_response.body).to eql 'Invalid response'
2090
+ end
1726
2091
  end
1727
2092
 
1728
2093
  describe '.rescue_from klass, block' do
@@ -3177,6 +3542,43 @@ XML
3177
3542
  expect { a.mount b }.to_not raise_error
3178
3543
  end
3179
3544
  end
3545
+
3546
+ context 'when including a module' do
3547
+ let(:included_module) do
3548
+ Module.new do
3549
+ def self.included(base)
3550
+ base.extend(ClassMethods)
3551
+ end
3552
+ module ClassMethods
3553
+ def my_method
3554
+ @test = true
3555
+ end
3556
+ end
3557
+ end
3558
+ end
3559
+
3560
+ it 'should correctly include module in nested mount' do
3561
+ module_to_include = included_module
3562
+ v1 = Class.new(Grape::API) do
3563
+ version :v1, using: :path
3564
+ include module_to_include
3565
+ my_method
3566
+ end
3567
+ v2 = Class.new(Grape::API) do
3568
+ version :v2, using: :path
3569
+ end
3570
+ segment_base = Class.new(Grape::API) do
3571
+ mount v1
3572
+ mount v2
3573
+ end
3574
+
3575
+ Class.new(Grape::API) do
3576
+ mount segment_base
3577
+ end
3578
+
3579
+ expect(v1.my_method).to be_truthy
3580
+ end
3581
+ end
3180
3582
  end
3181
3583
  end
3182
3584
 
@@ -3192,7 +3594,7 @@ XML
3192
3594
  it 'sets the instance' do
3193
3595
  expect(subject.instance).to be_nil
3194
3596
  subject.compile
3195
- expect(subject.instance).to be_kind_of(subject)
3597
+ expect(subject.instance).to be_kind_of(subject.base_instance)
3196
3598
  end
3197
3599
  end
3198
3600
 
@@ -3350,12 +3752,13 @@ XML
3350
3752
  end
3351
3753
  end
3352
3754
  context ':serializable_hash' do
3353
- before(:each) do
3354
- class SerializableHashExample
3355
- def serializable_hash
3356
- { abc: 'def' }
3357
- end
3755
+ class SerializableHashExample
3756
+ def serializable_hash
3757
+ { abc: 'def' }
3358
3758
  end
3759
+ end
3760
+
3761
+ before(:each) do
3359
3762
  subject.format :serializable_hash
3360
3763
  end
3361
3764
  it 'instance' do
@@ -3449,6 +3852,44 @@ XML
3449
3852
  end
3450
3853
  end
3451
3854
 
3855
+ describe '.configure' do
3856
+ context 'when given a block' do
3857
+ it 'returns self' do
3858
+ expect(subject.configure {}).to be subject
3859
+ end
3860
+
3861
+ it 'calls the block passing the config' do
3862
+ call = [false, nil]
3863
+ subject.configure do |config|
3864
+ call = [true, config]
3865
+ end
3866
+
3867
+ expect(call[0]).to be true
3868
+ expect(call[1]).not_to be_nil
3869
+ end
3870
+ end
3871
+
3872
+ context 'when not given a block' do
3873
+ it 'returns a configuration object' do
3874
+ expect(subject.configure).to respond_to(:[], :[]=)
3875
+ end
3876
+ end
3877
+
3878
+ it 'allows configuring the api' do
3879
+ subject.configure do |config|
3880
+ config[:hello] = 'hello'
3881
+ config[:bread] = 'bread'
3882
+ end
3883
+
3884
+ subject.get '/hello-bread' do
3885
+ "#{configuration[:hello]} #{configuration[:bread]}"
3886
+ end
3887
+
3888
+ get '/hello-bread'
3889
+ expect(last_response.body).to eq 'hello bread'
3890
+ end
3891
+ end
3892
+
3452
3893
  context 'catch-all' do
3453
3894
  before do
3454
3895
  api1 = Class.new(Grape::API)
@@ -3580,4 +4021,116 @@ XML
3580
4021
  end
3581
4022
  end
3582
4023
  end
4024
+
4025
+ describe 'normal class methods' do
4026
+ subject(:grape_api) { Class.new(Grape::API) }
4027
+
4028
+ before do
4029
+ stub_const('MyAPI', grape_api)
4030
+ end
4031
+
4032
+ it 'can find the appropiate name' do
4033
+ expect(grape_api.name).to eq 'MyAPI'
4034
+ end
4035
+
4036
+ it 'is equal to itself' do
4037
+ expect(grape_api.itself).to eq grape_api
4038
+ expect(grape_api).to eq MyAPI
4039
+ expect(grape_api.eql?(MyAPI))
4040
+ end
4041
+ end
4042
+
4043
+ describe 'const_missing' do
4044
+ subject(:grape_api) { Class.new(Grape::API) }
4045
+ let(:mounted) do
4046
+ Class.new(Grape::API) do
4047
+ get '/missing' do
4048
+ SomeRandomConstant
4049
+ end
4050
+ end
4051
+ end
4052
+
4053
+ before { subject.mount mounted => '/const' }
4054
+
4055
+ it 'raises an error' do
4056
+ expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
4057
+ end
4058
+ end
4059
+
4060
+ describe 'custom route helpers on nested APIs' do
4061
+ let(:shared_api_module) do
4062
+ Module.new do
4063
+ # rubocop:disable Style/ExplicitBlockArgument because this causes
4064
+ # the underlying issue in this form
4065
+ def uniqe_id_route
4066
+ params do
4067
+ use :unique_id
4068
+ end
4069
+ route_param(:id) do
4070
+ yield
4071
+ end
4072
+ end
4073
+ # rubocop:enable Style/ExplicitBlockArgument
4074
+ end
4075
+ end
4076
+ let(:shared_api_definitions) do
4077
+ Module.new do
4078
+ extend ActiveSupport::Concern
4079
+
4080
+ included do
4081
+ helpers do
4082
+ params :unique_id do
4083
+ requires :id, type: String,
4084
+ allow_blank: false,
4085
+ regexp: /\d+-\d+/
4086
+ end
4087
+ end
4088
+ end
4089
+ end
4090
+ end
4091
+ let(:orders_root) do
4092
+ shared = shared_api_definitions
4093
+ find = orders_find_endpoint
4094
+ Class.new(Grape::API) do
4095
+ include shared
4096
+
4097
+ namespace(:orders) do
4098
+ mount find
4099
+ end
4100
+ end
4101
+ end
4102
+ let(:orders_find_endpoint) do
4103
+ shared = shared_api_definitions
4104
+ Class.new(Grape::API) do
4105
+ include shared
4106
+
4107
+ uniqe_id_route do
4108
+ desc 'Fetch a single order' do
4109
+ detail 'While specifying the order id on the route'
4110
+ end
4111
+ get { params[:id] }
4112
+ end
4113
+ end
4114
+ end
4115
+ subject(:grape_api) do
4116
+ Class.new(Grape::API) do
4117
+ version 'v1', using: :path
4118
+ end
4119
+ end
4120
+
4121
+ before do
4122
+ Grape::API::Instance.extend(shared_api_module)
4123
+ subject.mount orders_root
4124
+ end
4125
+
4126
+ it 'returns an error when the id is bad' do
4127
+ get '/v1/orders/abc'
4128
+ expect(last_response.body).to be_eql('id is invalid')
4129
+ end
4130
+
4131
+ it 'returns the given id when it is valid' do
4132
+ get '/v1/orders/1-2'
4133
+ expect(last_response.body).to be_eql('1-2')
4134
+ end
4135
+ end
3583
4136
  end