grape 1.1.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (306) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +370 -44
  3. data/CONTRIBUTING.md +32 -1
  4. data/LICENSE +1 -1
  5. data/README.md +683 -87
  6. data/UPGRADING.md +481 -17
  7. data/grape.gemspec +15 -4
  8. data/lib/grape/api/helpers.rb +2 -0
  9. data/lib/grape/api/instance.rb +279 -0
  10. data/lib/grape/api.rb +144 -176
  11. data/lib/grape/config.rb +34 -0
  12. data/lib/grape/content_types.rb +34 -0
  13. data/lib/grape/cookies.rb +4 -0
  14. data/lib/grape/dry_types.rb +12 -0
  15. data/lib/grape/dsl/api.rb +1 -1
  16. data/lib/grape/dsl/callbacks.rb +21 -1
  17. data/lib/grape/dsl/configuration.rb +1 -1
  18. data/lib/grape/dsl/desc.rb +41 -23
  19. data/lib/grape/dsl/headers.rb +7 -2
  20. data/lib/grape/dsl/helpers.rb +10 -7
  21. data/lib/grape/dsl/inside_route.rb +118 -62
  22. data/lib/grape/dsl/logger.rb +2 -0
  23. data/lib/grape/dsl/middleware.rb +11 -4
  24. data/lib/grape/dsl/parameters.rb +33 -19
  25. data/lib/grape/dsl/request_response.rb +12 -9
  26. data/lib/grape/dsl/routing.rb +22 -13
  27. data/lib/grape/dsl/settings.rb +10 -6
  28. data/lib/grape/dsl/validations.rb +19 -14
  29. data/lib/grape/eager_load.rb +20 -0
  30. data/lib/grape/endpoint.rb +67 -58
  31. data/lib/grape/error_formatter/base.rb +2 -0
  32. data/lib/grape/error_formatter/json.rb +11 -7
  33. data/lib/grape/error_formatter/txt.rb +2 -0
  34. data/lib/grape/error_formatter/xml.rb +4 -6
  35. data/lib/grape/error_formatter.rb +4 -2
  36. data/lib/grape/exceptions/base.rb +23 -16
  37. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  38. data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
  39. data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
  40. data/lib/grape/exceptions/invalid_formatter.rb +2 -0
  41. data/lib/grape/exceptions/invalid_message_body.rb +2 -0
  42. data/lib/grape/exceptions/invalid_response.rb +11 -0
  43. data/lib/grape/exceptions/invalid_version_header.rb +2 -0
  44. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
  45. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
  46. data/lib/grape/exceptions/method_not_allowed.rb +2 -0
  47. data/lib/grape/exceptions/missing_group_type.rb +10 -1
  48. data/lib/grape/exceptions/missing_mime_type.rb +2 -0
  49. data/lib/grape/exceptions/missing_option.rb +2 -0
  50. data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
  51. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  52. data/lib/grape/exceptions/unknown_options.rb +2 -0
  53. data/lib/grape/exceptions/unknown_parameter.rb +2 -0
  54. data/lib/grape/exceptions/unknown_validator.rb +2 -0
  55. data/lib/grape/exceptions/unsupported_group_type.rb +10 -1
  56. data/lib/grape/exceptions/validation.rb +5 -8
  57. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  58. data/lib/grape/exceptions/validation_errors.rb +16 -13
  59. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  60. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  61. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  62. data/lib/grape/extensions/hash.rb +2 -0
  63. data/lib/grape/extensions/hashie/mash.rb +2 -0
  64. data/lib/grape/formatter/json.rb +3 -0
  65. data/lib/grape/formatter/serializable_hash.rb +4 -1
  66. data/lib/grape/formatter/txt.rb +2 -0
  67. data/lib/grape/formatter/xml.rb +3 -0
  68. data/lib/grape/formatter.rb +5 -3
  69. data/lib/grape/http/headers.rb +50 -18
  70. data/lib/grape/locale/en.yml +11 -8
  71. data/lib/grape/middleware/auth/base.rb +7 -7
  72. data/lib/grape/middleware/auth/dsl.rb +9 -2
  73. data/lib/grape/middleware/auth/strategies.rb +2 -0
  74. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  75. data/lib/grape/middleware/base.rb +13 -8
  76. data/lib/grape/middleware/error.rb +22 -17
  77. data/lib/grape/middleware/filter.rb +2 -0
  78. data/lib/grape/middleware/formatter.rb +12 -10
  79. data/lib/grape/middleware/globals.rb +2 -0
  80. data/lib/grape/middleware/helpers.rb +12 -0
  81. data/lib/grape/middleware/stack.rb +16 -6
  82. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -5
  83. data/lib/grape/middleware/versioner/header.rb +13 -9
  84. data/lib/grape/middleware/versioner/param.rb +4 -1
  85. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +5 -1
  86. data/lib/grape/middleware/versioner/path.rb +5 -1
  87. data/lib/grape/middleware/versioner.rb +2 -0
  88. data/lib/grape/namespace.rb +14 -2
  89. data/lib/grape/parser/json.rb +3 -1
  90. data/lib/grape/parser/xml.rb +3 -1
  91. data/lib/grape/parser.rb +4 -2
  92. data/lib/grape/path.rb +16 -3
  93. data/lib/grape/presenters/presenter.rb +2 -0
  94. data/lib/grape/request.rb +21 -9
  95. data/lib/grape/router/attribute_translator.rb +41 -8
  96. data/lib/grape/router/pattern.rb +21 -17
  97. data/lib/grape/router/route.rb +15 -29
  98. data/lib/grape/router.rb +36 -29
  99. data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
  100. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
  101. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
  102. data/lib/grape/types/invalid_value.rb +8 -0
  103. data/lib/grape/util/base_inheritable.rb +43 -0
  104. data/lib/grape/util/cache.rb +20 -0
  105. data/lib/grape/util/endpoint_configuration.rb +8 -0
  106. data/lib/grape/util/env.rb +19 -17
  107. data/lib/grape/util/inheritable_setting.rb +3 -3
  108. data/lib/grape/util/inheritable_values.rb +7 -25
  109. data/lib/grape/util/json.rb +4 -0
  110. data/lib/grape/util/lazy_block.rb +27 -0
  111. data/lib/grape/util/lazy_object.rb +43 -0
  112. data/lib/grape/util/lazy_value.rb +99 -0
  113. data/lib/grape/util/registrable.rb +2 -0
  114. data/lib/grape/util/reverse_stackable_values.rb +10 -35
  115. data/lib/grape/util/stackable_values.rb +21 -34
  116. data/lib/grape/util/strict_hash_configuration.rb +3 -1
  117. data/lib/grape/util/xml.rb +2 -0
  118. data/lib/grape/validations/attributes_doc.rb +58 -0
  119. data/lib/grape/validations/attributes_iterator.rb +16 -6
  120. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  121. data/lib/grape/validations/params_scope.rb +174 -94
  122. data/lib/grape/validations/single_attribute_iterator.rb +24 -0
  123. data/lib/grape/validations/types/array_coercer.rb +63 -0
  124. data/lib/grape/validations/types/build_coercer.rb +47 -49
  125. data/lib/grape/validations/types/custom_type_coercer.rb +30 -51
  126. data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
  127. data/lib/grape/validations/types/dry_type_coercer.rb +72 -0
  128. data/lib/grape/validations/types/file.rb +22 -18
  129. data/lib/grape/validations/types/invalid_value.rb +17 -0
  130. data/lib/grape/validations/types/json.rb +47 -39
  131. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  132. data/lib/grape/validations/types/primitive_coercer.rb +75 -0
  133. data/lib/grape/validations/types/set_coercer.rb +38 -0
  134. data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
  135. data/lib/grape/validations/types.rb +106 -63
  136. data/lib/grape/validations/validator_factory.rb +8 -11
  137. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  138. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  139. data/lib/grape/validations/validators/as_validator.rb +14 -0
  140. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  141. data/lib/grape/validations/validators/base.rb +84 -68
  142. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  143. data/lib/grape/validations/validators/default_validator.rb +51 -0
  144. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  145. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  146. data/lib/grape/validations/validators/multiple_params_base.rb +27 -16
  147. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  148. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  149. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  150. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  151. data/lib/grape/validations/validators/values_validator.rb +88 -0
  152. data/lib/grape/validations.rb +18 -6
  153. data/lib/grape/version.rb +3 -1
  154. data/lib/grape.rb +175 -94
  155. data/spec/grape/api/custom_validations_spec.rb +117 -44
  156. data/spec/grape/api/deeply_included_options_spec.rb +4 -4
  157. data/spec/grape/api/defines_boolean_in_params_spec.rb +38 -0
  158. data/spec/grape/api/documentation_spec.rb +59 -0
  159. data/spec/grape/api/inherited_helpers_spec.rb +1 -1
  160. data/spec/grape/api/instance_spec.rb +103 -0
  161. data/spec/grape/api/invalid_format_spec.rb +3 -1
  162. data/spec/grape/api/namespace_parameters_in_route_spec.rb +1 -1
  163. data/spec/grape/api/nested_helpers_spec.rb +1 -1
  164. data/spec/grape/api/optional_parameters_in_route_spec.rb +1 -1
  165. data/spec/grape/api/parameters_modification_spec.rb +2 -2
  166. data/spec/grape/api/patch_method_helpers_spec.rb +1 -1
  167. data/spec/grape/api/recognize_path_spec.rb +2 -2
  168. data/spec/grape/api/required_parameters_in_route_spec.rb +1 -1
  169. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +1 -1
  170. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  171. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +10 -16
  172. data/spec/grape/api/shared_helpers_spec.rb +1 -1
  173. data/spec/grape/api_remount_spec.rb +473 -0
  174. data/spec/grape/api_spec.rb +995 -231
  175. data/spec/grape/config_spec.rb +17 -0
  176. data/spec/grape/dsl/callbacks_spec.rb +3 -2
  177. data/spec/grape/dsl/desc_spec.rb +43 -17
  178. data/spec/grape/dsl/headers_spec.rb +40 -10
  179. data/spec/grape/dsl/helpers_spec.rb +6 -5
  180. data/spec/grape/dsl/inside_route_spec.rb +189 -38
  181. data/spec/grape/dsl/logger_spec.rb +17 -19
  182. data/spec/grape/dsl/middleware_spec.rb +11 -2
  183. data/spec/grape/dsl/parameters_spec.rb +3 -1
  184. data/spec/grape/dsl/request_response_spec.rb +8 -7
  185. data/spec/grape/dsl/routing_spec.rb +22 -9
  186. data/spec/grape/dsl/settings_spec.rb +1 -1
  187. data/spec/grape/dsl/validations_spec.rb +1 -16
  188. data/spec/grape/endpoint/declared_spec.rb +846 -0
  189. data/spec/grape/endpoint_spec.rb +136 -577
  190. data/spec/grape/entity_spec.rb +31 -24
  191. data/spec/grape/exceptions/base_spec.rb +81 -0
  192. data/spec/grape/exceptions/body_parse_errors_spec.rb +4 -1
  193. data/spec/grape/exceptions/invalid_accept_header_spec.rb +65 -23
  194. data/spec/grape/exceptions/invalid_formatter_spec.rb +1 -1
  195. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  196. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -2
  197. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  198. data/spec/grape/exceptions/missing_mime_type_spec.rb +1 -1
  199. data/spec/grape/exceptions/missing_option_spec.rb +2 -2
  200. data/spec/grape/exceptions/unknown_options_spec.rb +1 -1
  201. data/spec/grape/exceptions/unknown_validator_spec.rb +1 -1
  202. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  203. data/spec/grape/exceptions/validation_errors_spec.rb +21 -15
  204. data/spec/grape/exceptions/validation_spec.rb +6 -4
  205. data/spec/grape/extensions/param_builders/hash_spec.rb +8 -8
  206. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +9 -9
  207. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +9 -9
  208. data/spec/grape/integration/global_namespace_function_spec.rb +2 -2
  209. data/spec/grape/integration/rack_sendfile_spec.rb +14 -10
  210. data/spec/grape/integration/rack_spec.rb +25 -8
  211. data/spec/grape/loading_spec.rb +9 -9
  212. data/spec/grape/middleware/auth/base_spec.rb +2 -1
  213. data/spec/grape/middleware/auth/dsl_spec.rb +19 -10
  214. data/spec/grape/middleware/auth/strategies_spec.rb +62 -22
  215. data/spec/grape/middleware/base_spec.rb +36 -17
  216. data/spec/grape/middleware/error_spec.rb +11 -4
  217. data/spec/grape/middleware/exception_spec.rb +112 -162
  218. data/spec/grape/middleware/formatter_spec.rb +65 -29
  219. data/spec/grape/middleware/globals_spec.rb +8 -5
  220. data/spec/grape/middleware/stack_spec.rb +25 -13
  221. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -2
  222. data/spec/grape/middleware/versioner/header_spec.rb +37 -14
  223. data/spec/grape/middleware/versioner/param_spec.rb +8 -2
  224. data/spec/grape/middleware/versioner/path_spec.rb +6 -2
  225. data/spec/grape/middleware/versioner_spec.rb +2 -2
  226. data/spec/grape/named_api_spec.rb +19 -0
  227. data/spec/grape/parser_spec.rb +10 -6
  228. data/spec/grape/path_spec.rb +53 -53
  229. data/spec/grape/presenters/presenter_spec.rb +8 -7
  230. data/spec/grape/request_spec.rb +29 -3
  231. data/spec/grape/util/inheritable_setting_spec.rb +9 -8
  232. data/spec/grape/util/inheritable_values_spec.rb +5 -3
  233. data/spec/grape/util/reverse_stackable_values_spec.rb +5 -2
  234. data/spec/grape/util/stackable_values_spec.rb +10 -7
  235. data/spec/grape/util/strict_hash_configuration_spec.rb +2 -1
  236. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  237. data/spec/grape/validations/instance_behaivour_spec.rb +13 -14
  238. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +40 -0
  239. data/spec/grape/validations/params_scope_spec.rb +568 -99
  240. data/spec/grape/validations/single_attribute_iterator_spec.rb +57 -0
  241. data/spec/grape/validations/types/array_coercer_spec.rb +33 -0
  242. data/spec/grape/validations/types/primitive_coercer_spec.rb +150 -0
  243. data/spec/grape/validations/types/set_coercer_spec.rb +32 -0
  244. data/spec/grape/validations/types_spec.rb +44 -45
  245. data/spec/grape/validations/validators/all_or_none_spec.rb +134 -32
  246. data/spec/grape/validations/validators/allow_blank_spec.rb +137 -141
  247. data/spec/grape/validations/validators/at_least_one_of_spec.rb +169 -31
  248. data/spec/grape/validations/validators/coerce_spec.rb +491 -151
  249. data/spec/grape/validations/validators/default_spec.rb +242 -78
  250. data/spec/grape/validations/validators/exactly_one_of_spec.rb +198 -40
  251. data/spec/grape/validations/validators/except_values_spec.rb +6 -5
  252. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +181 -30
  253. data/spec/grape/validations/validators/presence_spec.rb +45 -2
  254. data/spec/grape/validations/validators/regexp_spec.rb +27 -33
  255. data/spec/grape/validations/validators/same_as_spec.rb +57 -0
  256. data/spec/grape/validations/validators/values_spec.rb +227 -180
  257. data/spec/grape/validations_spec.rb +502 -72
  258. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  259. data/spec/integration/multi_json/json_spec.rb +2 -2
  260. data/spec/integration/multi_xml/xml_spec.rb +2 -2
  261. data/spec/shared/versioning_examples.rb +34 -29
  262. data/spec/spec_helper.rb +31 -5
  263. data/spec/support/basic_auth_encode_helpers.rb +3 -1
  264. data/spec/support/chunks.rb +14 -0
  265. data/spec/support/content_type_helpers.rb +2 -0
  266. data/spec/support/endpoint_faker.rb +2 -0
  267. data/spec/support/file_streamer.rb +2 -0
  268. data/spec/support/integer_helpers.rb +2 -0
  269. data/spec/support/versioned_helpers.rb +8 -8
  270. metadata +111 -61
  271. data/Appraisals +0 -32
  272. data/Dangerfile +0 -2
  273. data/Gemfile +0 -33
  274. data/Gemfile.lock +0 -231
  275. data/Guardfile +0 -10
  276. data/RELEASING.md +0 -111
  277. data/Rakefile +0 -25
  278. data/benchmark/simple.rb +0 -27
  279. data/benchmark/simple_with_type_coercer.rb +0 -22
  280. data/gemfiles/multi_json.gemfile +0 -35
  281. data/gemfiles/multi_xml.gemfile +0 -35
  282. data/gemfiles/rack_1.5.2.gemfile +0 -35
  283. data/gemfiles/rack_edge.gemfile +0 -35
  284. data/gemfiles/rails_3.gemfile +0 -36
  285. data/gemfiles/rails_4.gemfile +0 -35
  286. data/gemfiles/rails_5.gemfile +0 -35
  287. data/gemfiles/rails_edge.gemfile +0 -35
  288. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  289. data/lib/grape/util/content_types.rb +0 -26
  290. data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
  291. data/lib/grape/validations/validators/all_or_none.rb +0 -20
  292. data/lib/grape/validations/validators/allow_blank.rb +0 -16
  293. data/lib/grape/validations/validators/as.rb +0 -15
  294. data/lib/grape/validations/validators/at_least_one_of.rb +0 -20
  295. data/lib/grape/validations/validators/coerce.rb +0 -74
  296. data/lib/grape/validations/validators/default.rb +0 -48
  297. data/lib/grape/validations/validators/exactly_one_of.rb +0 -29
  298. data/lib/grape/validations/validators/except_values.rb +0 -20
  299. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -25
  300. data/lib/grape/validations/validators/presence.rb +0 -10
  301. data/lib/grape/validations/validators/regexp.rb +0 -11
  302. data/lib/grape/validations/validators/values.rb +0 -71
  303. data/pkg/grape-0.17.0.gem +0 -0
  304. data/pkg/grape-0.19.0.gem +0 -0
  305. data/spec/grape/dsl/configuration_spec.rb +0 -14
  306. data/spec/grape/validations/attributes_iterator_spec.rb +0 -4
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grape/router'
4
+
5
+ module Grape
6
+ class API
7
+ # The API Instance class, is the engine behind Grape::API. Each class that inherits
8
+ # from this will represent a different API instance
9
+ class Instance
10
+ include Grape::DSL::API
11
+
12
+ class << self
13
+ attr_reader :instance, :base
14
+ attr_accessor :configuration
15
+
16
+ def given(conditional_option, &block)
17
+ evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block
18
+ end
19
+
20
+ def mounted(&block)
21
+ evaluate_as_instance_with_configuration(block, lazy: true)
22
+ end
23
+
24
+ def base=(grape_api)
25
+ @base = grape_api
26
+ grape_api.instances << self
27
+ end
28
+
29
+ def to_s
30
+ base&.to_s || super
31
+ end
32
+
33
+ def base_instance?
34
+ self == base.base_instance
35
+ end
36
+
37
+ # A class-level lock to ensure the API is not compiled by multiple
38
+ # threads simultaneously within the same process.
39
+ LOCK = Mutex.new
40
+
41
+ # Clears all defined routes, endpoints, etc., on this API.
42
+ def reset!
43
+ reset_endpoints!
44
+ reset_routes!
45
+ reset_validations!
46
+ end
47
+
48
+ # Parses the API's definition and compiles it into an instance of
49
+ # Grape::API.
50
+ def compile
51
+ @instance ||= new
52
+ end
53
+
54
+ # Wipe the compiled API so we can recompile after changes were made.
55
+ def change!
56
+ @instance = nil
57
+ end
58
+
59
+ # This is the interface point between Rack and Grape; it accepts a request
60
+ # from Rack and ultimately returns an array of three values: the status,
61
+ # the headers, and the body. See [the rack specification]
62
+ # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
63
+ def call(env)
64
+ compile!
65
+ call!(env)
66
+ end
67
+
68
+ # A non-synchronized version of ::call.
69
+ def call!(env)
70
+ instance.call(env)
71
+ end
72
+
73
+ # (see #cascade?)
74
+ def cascade(value = nil)
75
+ if value.nil?
76
+ inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
77
+ else
78
+ namespace_inheritable(:cascade, value)
79
+ end
80
+ end
81
+
82
+ def compile!
83
+ return if instance
84
+
85
+ LOCK.synchronize { compile unless instance }
86
+ end
87
+
88
+ # see Grape::Router#recognize_path
89
+ def recognize_path(path)
90
+ compile!
91
+ instance.router.recognize_path(path)
92
+ end
93
+
94
+ protected
95
+
96
+ def prepare_routes
97
+ endpoints.map(&:routes).flatten
98
+ end
99
+
100
+ # Execute first the provided block, then each of the
101
+ # block passed in. Allows for simple 'before' setups
102
+ # of settings stack pushes.
103
+ def nest(*blocks, &block)
104
+ blocks.compact!
105
+ if blocks.any?
106
+ evaluate_as_instance_with_configuration(block) if block
107
+ blocks.each { |b| evaluate_as_instance_with_configuration(b) }
108
+ reset_validations!
109
+ else
110
+ instance_eval(&block)
111
+ end
112
+ end
113
+
114
+ def evaluate_as_instance_with_configuration(block, lazy: false)
115
+ lazy_block = Grape::Util::LazyBlock.new do |configuration|
116
+ value_for_configuration = configuration
117
+ self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
118
+ response = instance_eval(&block)
119
+ self.configuration = value_for_configuration
120
+ response
121
+ end
122
+ if base && base_instance? && lazy
123
+ lazy_block
124
+ else
125
+ lazy_block.evaluate_from(configuration)
126
+ end
127
+ end
128
+
129
+ def inherited(subclass)
130
+ subclass.reset!
131
+ subclass.logger = logger.clone
132
+ end
133
+
134
+ def inherit_settings(other_settings)
135
+ top_level_setting.inherit_from other_settings.point_in_time_copy
136
+
137
+ # Propagate any inherited params down to our endpoints, and reset any
138
+ # compiled routes.
139
+ endpoints.each do |e|
140
+ e.inherit_settings(top_level_setting.namespace_stackable)
141
+ e.reset_routes!
142
+ end
143
+
144
+ reset_routes!
145
+ end
146
+ end
147
+
148
+ attr_reader :router
149
+
150
+ # Builds the routes from the defined endpoints, effectively compiling
151
+ # this API into a usable form.
152
+ def initialize
153
+ @router = Router.new
154
+ add_head_not_allowed_methods_and_options_methods
155
+ self.class.endpoints.each do |endpoint|
156
+ endpoint.mount_in(@router)
157
+ end
158
+
159
+ @router.compile!
160
+ @router.freeze
161
+ end
162
+
163
+ # Handle a request. See Rack documentation for what `env` is.
164
+ def call(env)
165
+ result = @router.call(env)
166
+ result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
167
+ result
168
+ end
169
+
170
+ # Some requests may return a HTTP 404 error if grape cannot find a matching
171
+ # route. In this case, Grape::Router adds a X-Cascade header to the response
172
+ # and sets it to 'pass', indicating to grape's parents they should keep
173
+ # looking for a matching route on other resources.
174
+ #
175
+ # In some applications (e.g. mounting grape on rails), one might need to trap
176
+ # errors from reaching upstream. This is effectivelly done by unsetting
177
+ # X-Cascade. Default :cascade is true.
178
+ def cascade?
179
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
180
+ return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)&.key?(:cascade)
181
+
182
+ true
183
+ end
184
+
185
+ reset!
186
+
187
+ private
188
+
189
+ # For every resource add a 'OPTIONS' route that returns an HTTP 204 response
190
+ # with a list of HTTP methods that can be called. Also add a route that
191
+ # will return an HTTP 405 response for any HTTP method that the resource
192
+ # cannot handle.
193
+ def add_head_not_allowed_methods_and_options_methods
194
+ versioned_route_configs = collect_route_config_per_pattern
195
+ # The paths we collected are prepared (cf. Path#prepare), so they
196
+ # contain already versioning information when using path versioning.
197
+ # Disable versioning so adding a route won't prepend versioning
198
+ # informations again.
199
+ without_root_prefix do
200
+ without_versioning do
201
+ versioned_route_configs.each do |config|
202
+ next if config[:options][:matching_wildchar]
203
+
204
+ allowed_methods = config[:methods].dup
205
+
206
+ allowed_methods |= [Grape::Http::Headers::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Grape::Http::Headers::GET)
207
+
208
+ allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
209
+
210
+ config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
211
+
212
+ attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
213
+ generate_not_allowed_method(config[:pattern], **attributes)
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ def collect_route_config_per_pattern
220
+ all_routes = self.class.endpoints.map(&:routes).flatten
221
+ routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp }
222
+
223
+ # Build the configuration based on the first endpoint and the collection of methods supported.
224
+ routes_by_regexp.values.map do |routes|
225
+ last_route = routes.last # Most of the configuration is taken from the last endpoint
226
+ matching_wildchar = routes.any? { |route| route.request_method == '*' }
227
+ {
228
+ options: { matching_wildchar: matching_wildchar },
229
+ pattern: last_route.pattern,
230
+ requirements: last_route.requirements,
231
+ path: last_route.origin,
232
+ endpoint: last_route.app,
233
+ methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method)
234
+ }
235
+ end
236
+ end
237
+
238
+ # Generate a route that returns an HTTP 405 response for a user defined
239
+ # path on methods not specified
240
+ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
241
+ supported_methods =
242
+ if self.class.namespace_inheritable(:do_not_route_options)
243
+ Grape::Http::Headers::SUPPORTED_METHODS
244
+ else
245
+ Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
246
+ end
247
+ not_allowed_methods = supported_methods - allowed_methods
248
+ @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
249
+ end
250
+
251
+ # Allows definition of endpoints that ignore the versioning configuration
252
+ # used by the rest of your API.
253
+ def without_versioning(&_block)
254
+ old_version = self.class.namespace_inheritable(:version)
255
+ old_version_options = self.class.namespace_inheritable(:version_options)
256
+
257
+ self.class.namespace_inheritable_to_nil(:version)
258
+ self.class.namespace_inheritable_to_nil(:version_options)
259
+
260
+ yield
261
+
262
+ self.class.namespace_inheritable(:version, old_version)
263
+ self.class.namespace_inheritable(:version_options, old_version_options)
264
+ end
265
+
266
+ # Allows definition of endpoints that ignore the root prefix used by the
267
+ # rest of your API.
268
+ def without_root_prefix(&_block)
269
+ old_prefix = self.class.namespace_inheritable(:root_prefix)
270
+
271
+ self.class.namespace_inheritable_to_nil(:root_prefix)
272
+
273
+ yield
274
+
275
+ self.class.namespace_inheritable(:root_prefix, old_prefix)
276
+ end
277
+ end
278
+ end
279
+ end
data/lib/grape/api.rb CHANGED
@@ -1,233 +1,201 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'grape/router'
4
+ require 'grape/api/instance'
2
5
 
3
6
  module Grape
4
7
  # The API class is the primary entry point for creating Grape APIs. Users
5
8
  # should subclass this class in order to build an API.
6
9
  class API
7
- include Grape::DSL::API
10
+ # Class methods that we want to call on the API rather than on the API object
11
+ NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
12
+
13
+ class Boolean
14
+ def self.build(val)
15
+ return nil if val != true && val != false
16
+
17
+ new
18
+ end
19
+ end
20
+
21
+ class Instance
22
+ Boolean = Grape::API::Boolean
23
+ end
8
24
 
9
25
  class << self
10
- attr_reader :instance
26
+ attr_accessor :base_instance, :instances
11
27
 
12
- # A class-level lock to ensure the API is not compiled by multiple
13
- # threads simultaneously within the same process.
14
- LOCK = Mutex.new
28
+ # Rather than initializing an object of type Grape::API, create an object of type Instance
29
+ def new(*args, &block)
30
+ base_instance.new(*args, &block)
31
+ end
32
+
33
+ # When inherited, will create a list of all instances (times the API was mounted)
34
+ # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
35
+ def inherited(api)
36
+ super
15
37
 
16
- # Clears all defined routes, endpoints, etc., on this API.
17
- def reset!
18
- reset_endpoints!
19
- reset_routes!
20
- reset_validations!
38
+ api.initial_setup(Grape::API == self ? Grape::API::Instance : @base_instance)
39
+ api.override_all_methods!
21
40
  end
22
41
 
23
- # Parses the API's definition and compiles it into an instance of
24
- # Grape::API.
25
- def compile
26
- @instance ||= new
42
+ # Initialize the instance variables on the remountable class, and the base_instance
43
+ # an instance that will be used to create the set up but will not be mounted
44
+ def initial_setup(base_instance_parent)
45
+ @instances = []
46
+ @setup = Set.new
47
+ @base_parent = base_instance_parent
48
+ @base_instance = mount_instance
27
49
  end
28
50
 
29
- # Wipe the compiled API so we can recompile after changes were made.
30
- def change!
31
- @instance = nil
51
+ # Redefines all methods so that are forwarded to add_setup and be recorded
52
+ def override_all_methods!
53
+ (base_instance.methods - NON_OVERRIDABLE).each do |method_override|
54
+ define_singleton_method(method_override) do |*args, &block|
55
+ add_setup(method_override, *args, &block)
56
+ end
57
+ end
58
+ end
59
+
60
+ # Configure an API from the outside. If a block is given, it'll pass a
61
+ # configuration hash to the block which you can use to configure your
62
+ # API. If no block is given, returns the configuration hash.
63
+ # The configuration set here is accessible from inside an API with
64
+ # `configuration` as normal.
65
+ def configure
66
+ config = @base_instance.configuration
67
+ if block_given?
68
+ yield config
69
+ self
70
+ else
71
+ config
72
+ end
32
73
  end
33
74
 
34
75
  # This is the interface point between Rack and Grape; it accepts a request
35
76
  # from Rack and ultimately returns an array of three values: the status,
36
77
  # the headers, and the body. See [the rack specification]
37
78
  # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
38
- def call(env)
39
- LOCK.synchronize { compile } unless instance
40
- call!(env)
41
- end
42
-
43
- # A non-synchronized version of ::call.
44
- def call!(env)
45
- instance.call(env)
79
+ # NOTE: This will only be called on an API directly mounted on RACK
80
+ def call(*args, &block)
81
+ instance_for_rack.call(*args, &block)
46
82
  end
47
83
 
48
- # (see #cascade?)
49
- def cascade(value = nil)
50
- if value.nil?
51
- inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
84
+ # Alleviates problems with autoloading by tring to search for the constant
85
+ def const_missing(*args)
86
+ if base_instance.const_defined?(*args)
87
+ base_instance.const_get(*args)
52
88
  else
53
- namespace_inheritable(:cascade, value)
89
+ super
54
90
  end
55
91
  end
56
92
 
57
- # see Grape::Router#recognize_path
58
- def recognize_path(path)
59
- LOCK.synchronize { compile } unless instance
60
- instance.router.recognize_path(path)
93
+ # The remountable class can have a configuration hash to provide some dynamic class-level variables.
94
+ # For instance, a description could be done using: `desc configuration[:description]` if it may vary
95
+ # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
96
+ # too much, you may actually want to provide a new API rather than remount it.
97
+ def mount_instance(**opts)
98
+ instance = Class.new(@base_parent)
99
+ instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
100
+ instance.base = self
101
+ replay_setup_on(instance)
102
+ instance
103
+ end
104
+
105
+ # Replays the set up to produce an API as defined in this class, can be called
106
+ # on classes that inherit from Grape::API
107
+ def replay_setup_on(instance)
108
+ @setup.each do |setup_step|
109
+ replay_step_on(instance, setup_step)
110
+ end
61
111
  end
62
112
 
63
- protected
113
+ def respond_to?(method, include_private = false)
114
+ super(method, include_private) || base_instance.respond_to?(method, include_private)
115
+ end
64
116
 
65
- def prepare_routes
66
- endpoints.map(&:routes).flatten
117
+ def respond_to_missing?(method, include_private = false)
118
+ base_instance.respond_to?(method, include_private)
67
119
  end
68
120
 
69
- # Execute first the provided block, then each of the
70
- # block passed in. Allows for simple 'before' setups
71
- # of settings stack pushes.
72
- def nest(*blocks, &block)
73
- blocks.reject!(&:nil?)
74
- if blocks.any?
75
- instance_eval(&block) if block_given?
76
- blocks.each { |b| instance_eval(&b) }
77
- reset_validations!
121
+ def method_missing(method, *args, &block)
122
+ # If there's a missing method, it may be defined on the base_instance instead.
123
+ if respond_to_missing?(method)
124
+ base_instance.send(method, *args, &block)
78
125
  else
79
- instance_eval(&block)
126
+ super
80
127
  end
81
128
  end
82
129
 
83
- def inherited(subclass)
84
- subclass.reset!
85
- subclass.logger = logger.clone
130
+ def compile!
131
+ require 'grape/eager_load'
132
+ instance_for_rack.compile! # See API::Instance.compile!
86
133
  end
87
134
 
88
- def inherit_settings(other_settings)
89
- top_level_setting.inherit_from other_settings.point_in_time_copy
135
+ private
90
136
 
91
- # Propagate any inherited params down to our endpoints, and reset any
92
- # compiled routes.
93
- endpoints.each do |e|
94
- e.inherit_settings(top_level_setting.namespace_stackable)
95
- e.reset_routes!
137
+ def instance_for_rack
138
+ if never_mounted?
139
+ base_instance
140
+ else
141
+ mounted_instances.first
96
142
  end
97
-
98
- reset_routes!
99
143
  end
100
- end
101
-
102
- attr_reader :router
103
144
 
104
- # Builds the routes from the defined endpoints, effectively compiling
105
- # this API into a usable form.
106
- def initialize
107
- @router = Router.new
108
- add_head_not_allowed_methods_and_options_methods
109
- self.class.endpoints.each do |endpoint|
110
- endpoint.mount_in(@router)
145
+ # Adds a new stage to the set up require to get a Grape::API up and running
146
+ def add_setup(method, *args, &block)
147
+ setup_step = { method: method, args: args, block: block }
148
+ @setup += [setup_step]
149
+ last_response = nil
150
+ @instances.each do |instance|
151
+ last_response = replay_step_on(instance, setup_step)
152
+ end
153
+ last_response
111
154
  end
112
155
 
113
- @router.compile!
114
- @router.freeze
115
- end
116
-
117
- # Handle a request. See Rack documentation for what `env` is.
118
- def call(env)
119
- result = @router.call(env)
120
- result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
121
- result
122
- end
156
+ def replay_step_on(instance, setup_step)
157
+ return if skip_immediate_run?(instance, setup_step[:args])
123
158
 
124
- # Some requests may return a HTTP 404 error if grape cannot find a matching
125
- # route. In this case, Grape::Router adds a X-Cascade header to the response
126
- # and sets it to 'pass', indicating to grape's parents they should keep
127
- # looking for a matching route on other resources.
128
- #
129
- # In some applications (e.g. mounting grape on rails), one might need to trap
130
- # errors from reaching upstream. This is effectivelly done by unsetting
131
- # X-Cascade. Default :cascade is true.
132
- def cascade?
133
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
134
- return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
135
- true
136
- end
137
-
138
- reset!
139
-
140
- private
141
-
142
- # For every resource add a 'OPTIONS' route that returns an HTTP 204 response
143
- # with a list of HTTP methods that can be called. Also add a route that
144
- # will return an HTTP 405 response for any HTTP method that the resource
145
- # cannot handle.
146
- def add_head_not_allowed_methods_and_options_methods
147
- routes_map = {}
148
-
149
- self.class.endpoints.each do |endpoint|
150
- routes = endpoint.routes
151
- routes.each do |route|
152
- # using the :any shorthand produces [nil] for route methods, substitute all manually
153
- route_key = route.pattern.to_regexp
154
- routes_map[route_key] ||= {}
155
- route_settings = routes_map[route_key]
156
- route_settings[:pattern] = route.pattern
157
- route_settings[:requirements] = route.requirements
158
- route_settings[:path] = route.origin
159
- route_settings[:methods] ||= []
160
- route_settings[:methods] << route.request_method
161
- route_settings[:endpoint] = route.app
162
-
163
- # using the :any shorthand produces [nil] for route methods, substitute all manually
164
- route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
159
+ args = evaluate_arguments(instance.configuration, *setup_step[:args])
160
+ response = instance.send(setup_step[:method], *args, &setup_step[:block])
161
+ if skip_immediate_run?(instance, [response])
162
+ response
163
+ else
164
+ evaluate_arguments(instance.configuration, response).first
165
165
  end
166
166
  end
167
167
 
168
- # The paths we collected are prepared (cf. Path#prepare), so they
169
- # contain already versioning information when using path versioning.
170
- # Disable versioning so adding a route won't prepend versioning
171
- # informations again.
172
- without_root_prefix do
173
- without_versioning do
174
- routes_map.each do |_, config|
175
- methods = config[:methods]
176
- allowed_methods = methods.dup
177
-
178
- unless self.class.namespace_inheritable(:do_not_route_head)
179
- allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
180
- end
181
-
182
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
168
+ # Skips steps that contain arguments to be lazily executed (on re-mount time)
169
+ def skip_immediate_run?(instance, args)
170
+ instance.base_instance? &&
171
+ (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
172
+ end
183
173
 
184
- unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
185
- config[:endpoint].options[:options_route_enabled] = true
186
- end
174
+ def any_lazy?(args)
175
+ args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? }
176
+ end
187
177
 
188
- attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
189
- generate_not_allowed_method(config[:pattern], attributes)
178
+ def evaluate_arguments(configuration, *args)
179
+ args.map do |argument|
180
+ if argument.respond_to?(:lazy?) && argument.lazy?
181
+ argument.evaluate_from(configuration)
182
+ elsif argument.is_a?(Hash)
183
+ argument.transform_values { |value| evaluate_arguments(configuration, value).first }
184
+ elsif argument.is_a?(Array)
185
+ evaluate_arguments(configuration, *argument)
186
+ else
187
+ argument
190
188
  end
191
189
  end
192
190
  end
193
- end
194
-
195
- # Generate a route that returns an HTTP 405 response for a user defined
196
- # path on methods not specified
197
- def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
198
- not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
199
- not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
200
191
 
201
- return if not_allowed_methods.empty?
202
-
203
- @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
204
- end
205
-
206
- # Allows definition of endpoints that ignore the versioning configuration
207
- # used by the rest of your API.
208
- def without_versioning(&_block)
209
- old_version = self.class.namespace_inheritable(:version)
210
- old_version_options = self.class.namespace_inheritable(:version_options)
211
-
212
- self.class.namespace_inheritable_to_nil(:version)
213
- self.class.namespace_inheritable_to_nil(:version_options)
214
-
215
- yield
216
-
217
- self.class.namespace_inheritable(:version, old_version)
218
- self.class.namespace_inheritable(:version_options, old_version_options)
219
- end
220
-
221
- # Allows definition of endpoints that ignore the root prefix used by the
222
- # rest of your API.
223
- def without_root_prefix(&_block)
224
- old_prefix = self.class.namespace_inheritable(:root_prefix)
225
-
226
- self.class.namespace_inheritable_to_nil(:root_prefix)
227
-
228
- yield
192
+ def never_mounted?
193
+ mounted_instances.empty?
194
+ end
229
195
 
230
- self.class.namespace_inheritable(:root_prefix, old_prefix)
196
+ def mounted_instances
197
+ instances - [base_instance]
198
+ end
231
199
  end
232
200
  end
233
201
  end