grape 1.8.0 → 3.3.5

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 (312) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -1
  3. data/CONTRIBUTING.md +3 -11
  4. data/README.md +648 -552
  5. data/UPGRADING.md +803 -7
  6. data/grape.gemspec +11 -14
  7. data/lib/grape/api/instance.rb +68 -158
  8. data/lib/grape/api.rb +71 -107
  9. data/lib/grape/content_types.rb +23 -11
  10. data/lib/grape/cookies.rb +28 -25
  11. data/lib/grape/declared_params_handler.rb +116 -0
  12. data/lib/grape/dry_types.rb +48 -6
  13. data/lib/grape/dsl/callbacks.rb +14 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +15 -63
  16. data/lib/grape/dsl/entity.rb +88 -0
  17. data/lib/grape/dsl/headers.rb +2 -2
  18. data/lib/grape/dsl/helpers.rb +83 -64
  19. data/lib/grape/dsl/inside_route.rb +56 -295
  20. data/lib/grape/dsl/logger.rb +4 -9
  21. data/lib/grape/dsl/middleware.rb +22 -40
  22. data/lib/grape/dsl/parameters.rb +50 -82
  23. data/lib/grape/dsl/request_response.rb +144 -146
  24. data/lib/grape/dsl/rescue_options.rb +24 -0
  25. data/lib/grape/dsl/routing.rb +261 -186
  26. data/lib/grape/dsl/settings.rb +29 -135
  27. data/lib/grape/dsl/validations.rb +39 -32
  28. data/lib/grape/dsl/version_options.rb +23 -0
  29. data/lib/grape/endpoint/options.rb +19 -0
  30. data/lib/grape/endpoint.rb +277 -250
  31. data/lib/grape/{util/env.rb → env.rb} +1 -8
  32. data/lib/grape/error_formatter/base.rb +56 -21
  33. data/lib/grape/error_formatter/json.rb +13 -15
  34. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  35. data/lib/grape/error_formatter/txt.rb +12 -18
  36. data/lib/grape/error_formatter/xml.rb +3 -13
  37. data/lib/grape/error_formatter.rb +5 -25
  38. data/lib/grape/exceptions/base.rb +22 -58
  39. data/lib/grape/exceptions/error_response.rb +45 -0
  40. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  41. data/lib/grape/exceptions/internal_server_error.rb +16 -0
  42. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  43. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  44. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  45. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  46. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  47. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  48. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  49. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  50. data/lib/grape/exceptions/request_error.rb +11 -0
  51. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  52. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  53. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  54. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  55. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  56. data/lib/grape/exceptions/validation.rb +25 -9
  57. data/lib/grape/exceptions/validation_array_errors.rb +5 -0
  58. data/lib/grape/exceptions/validation_errors.rb +22 -26
  59. data/lib/grape/formatter/base.rb +16 -0
  60. data/lib/grape/formatter/json.rb +4 -6
  61. data/lib/grape/formatter/serializable_hash.rb +6 -10
  62. data/lib/grape/formatter/txt.rb +3 -5
  63. data/lib/grape/formatter/xml.rb +4 -6
  64. data/lib/grape/formatter.rb +7 -25
  65. data/lib/grape/json.rb +46 -0
  66. data/lib/grape/locale/en.yml +43 -42
  67. data/lib/grape/middleware/auth/base.rb +11 -33
  68. data/lib/grape/middleware/auth/dsl.rb +37 -31
  69. data/lib/grape/middleware/auth/strategies.rb +1 -2
  70. data/lib/grape/middleware/base.rb +56 -33
  71. data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
  72. data/lib/grape/middleware/error.rb +183 -91
  73. data/lib/grape/middleware/formatter.rb +94 -105
  74. data/lib/grape/middleware/precomputed_content_types.rb +46 -0
  75. data/lib/grape/middleware/stack.rb +27 -38
  76. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  77. data/lib/grape/middleware/versioner/base.rb +72 -0
  78. data/lib/grape/middleware/versioner/header.rb +44 -129
  79. data/lib/grape/middleware/versioner/param.rb +4 -25
  80. data/lib/grape/middleware/versioner/path.rb +13 -32
  81. data/lib/grape/middleware/versioner.rb +7 -14
  82. data/lib/grape/mountable.rb +22 -0
  83. data/lib/grape/namespace.rb +18 -12
  84. data/lib/grape/params_builder/base.rb +20 -0
  85. data/lib/grape/params_builder/hash.rb +11 -0
  86. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  87. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  88. data/lib/grape/params_builder.rb +15 -0
  89. data/lib/grape/parser/base.rb +16 -0
  90. data/lib/grape/parser/json.rb +6 -8
  91. data/lib/grape/parser/xml.rb +6 -8
  92. data/lib/grape/parser.rb +5 -23
  93. data/lib/grape/path.rb +38 -63
  94. data/lib/grape/railtie.rb +9 -0
  95. data/lib/grape/request.rb +168 -26
  96. data/lib/grape/router/base_route.rb +48 -0
  97. data/lib/grape/router/greedy_route.rb +26 -0
  98. data/lib/grape/router/mustermann_pattern.rb +44 -0
  99. data/lib/grape/router/pattern.rb +30 -34
  100. data/lib/grape/router/route.rb +28 -61
  101. data/lib/grape/router.rb +71 -86
  102. data/lib/grape/serve_stream/file_body.rb +1 -0
  103. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  104. data/lib/grape/serve_stream/stream_response.rb +1 -0
  105. data/lib/grape/testing.rb +33 -0
  106. data/lib/grape/util/api_description.rb +58 -0
  107. data/lib/grape/util/base_inheritable.rb +14 -14
  108. data/lib/grape/util/cache.rb +1 -3
  109. data/lib/grape/util/deep_freeze.rb +35 -0
  110. data/lib/grape/util/endpoint_configuration.rb +1 -1
  111. data/lib/grape/util/header.rb +13 -0
  112. data/lib/grape/util/inheritable_setting.rb +51 -27
  113. data/lib/grape/util/inheritable_values.rb +7 -5
  114. data/lib/grape/util/lazy/base.rb +16 -0
  115. data/lib/grape/util/lazy/block.rb +22 -0
  116. data/lib/grape/util/lazy/value.rb +31 -0
  117. data/lib/grape/util/lazy/value_array.rb +21 -0
  118. data/lib/grape/util/lazy/value_enumerable.rb +31 -0
  119. data/lib/grape/util/lazy/value_hash.rb +21 -0
  120. data/lib/grape/util/media_type.rb +67 -0
  121. data/lib/grape/util/path_normalizer.rb +34 -0
  122. data/lib/grape/util/registry.rb +27 -0
  123. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  124. data/lib/grape/util/stackable_values.rb +12 -14
  125. data/lib/grape/util/translation.rb +42 -0
  126. data/lib/grape/validations/attributes_iterator.rb +43 -27
  127. data/lib/grape/validations/coerce_options.rb +21 -0
  128. data/lib/grape/validations/contract_scope.rb +28 -0
  129. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  130. data/lib/grape/validations/oneof_collector.rb +39 -0
  131. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  132. data/lib/grape/validations/params_documentation.rb +52 -0
  133. data/lib/grape/validations/params_scope.rb +180 -303
  134. data/lib/grape/validations/shared_options.rb +19 -0
  135. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  136. data/lib/grape/validations/types/array_coercer.rb +4 -7
  137. data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
  138. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  139. data/lib/grape/validations/types/dry_type_coercer.rb +15 -28
  140. data/lib/grape/validations/types/json.rb +0 -2
  141. data/lib/grape/validations/types/primitive_coercer.rb +11 -35
  142. data/lib/grape/validations/types/set_coercer.rb +1 -4
  143. data/lib/grape/validations/types/variant_collection_coercer.rb +11 -0
  144. data/lib/grape/validations/types.rb +29 -54
  145. data/lib/grape/validations/validations_spec.rb +164 -0
  146. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  147. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  148. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  149. data/lib/grape/validations/validators/base.rb +120 -36
  150. data/lib/grape/validations/validators/coerce_validator.rb +18 -36
  151. data/lib/grape/validations/validators/contract_scope_validator.rb +45 -0
  152. data/lib/grape/validations/validators/default_validator.rb +13 -16
  153. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  154. data/lib/grape/validations/validators/except_values_validator.rb +15 -5
  155. data/lib/grape/validations/validators/length_validator.rb +48 -0
  156. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  157. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  158. data/lib/grape/validations/validators/oneof_validator.rb +49 -0
  159. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  160. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  161. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  162. data/lib/grape/validations/validators/values_validator.rb +36 -65
  163. data/lib/grape/validations.rb +8 -21
  164. data/lib/grape/version.rb +1 -2
  165. data/lib/grape/xml.rb +17 -0
  166. data/lib/grape.rb +75 -288
  167. metadata +79 -291
  168. data/lib/grape/api/helpers.rb +0 -9
  169. data/lib/grape/dsl/api.rb +0 -19
  170. data/lib/grape/dsl/configuration.rb +0 -15
  171. data/lib/grape/eager_load.rb +0 -20
  172. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  173. data/lib/grape/exceptions/missing_option.rb +0 -11
  174. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  175. data/lib/grape/exceptions/unknown_options.rb +0 -11
  176. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
  177. data/lib/grape/extensions/hash.rb +0 -22
  178. data/lib/grape/extensions/hashie/mash.rb +0 -26
  179. data/lib/grape/http/headers.rb +0 -61
  180. data/lib/grape/middleware/globals.rb +0 -16
  181. data/lib/grape/middleware/helpers.rb +0 -12
  182. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  183. data/lib/grape/router/attribute_translator.rb +0 -63
  184. data/lib/grape/types/invalid_value.rb +0 -8
  185. data/lib/grape/util/json.rb +0 -12
  186. data/lib/grape/util/lazy_block.rb +0 -27
  187. data/lib/grape/util/lazy_object.rb +0 -43
  188. data/lib/grape/util/lazy_value.rb +0 -91
  189. data/lib/grape/util/registrable.rb +0 -15
  190. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  191. data/lib/grape/util/xml.rb +0 -10
  192. data/lib/grape/validations/attributes_doc.rb +0 -58
  193. data/lib/grape/validations/types/build_coercer.rb +0 -94
  194. data/lib/grape/validations/validator_factory.rb +0 -15
  195. data/spec/grape/api/custom_validations_spec.rb +0 -213
  196. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  197. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  198. data/spec/grape/api/documentation_spec.rb +0 -59
  199. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  200. data/spec/grape/api/instance_spec.rb +0 -103
  201. data/spec/grape/api/invalid_format_spec.rb +0 -45
  202. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  203. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  204. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  205. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  206. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  207. data/spec/grape/api/recognize_path_spec.rb +0 -21
  208. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  209. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  210. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  211. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  212. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  213. data/spec/grape/api_remount_spec.rb +0 -509
  214. data/spec/grape/api_spec.rb +0 -4356
  215. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  216. data/spec/grape/dsl/desc_spec.rb +0 -98
  217. data/spec/grape/dsl/headers_spec.rb +0 -62
  218. data/spec/grape/dsl/helpers_spec.rb +0 -100
  219. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  220. data/spec/grape/dsl/logger_spec.rb +0 -24
  221. data/spec/grape/dsl/middleware_spec.rb +0 -60
  222. data/spec/grape/dsl/parameters_spec.rb +0 -180
  223. data/spec/grape/dsl/request_response_spec.rb +0 -225
  224. data/spec/grape/dsl/routing_spec.rb +0 -275
  225. data/spec/grape/dsl/settings_spec.rb +0 -261
  226. data/spec/grape/dsl/validations_spec.rb +0 -55
  227. data/spec/grape/endpoint/declared_spec.rb +0 -846
  228. data/spec/grape/endpoint_spec.rb +0 -1085
  229. data/spec/grape/entity_spec.rb +0 -336
  230. data/spec/grape/exceptions/base_spec.rb +0 -81
  231. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  232. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  233. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  234. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  235. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  236. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  237. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  238. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  239. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  240. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  241. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  242. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  243. data/spec/grape/exceptions/validation_spec.rb +0 -19
  244. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  245. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  246. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  247. data/spec/grape/grape_spec.rb +0 -9
  248. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  249. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  250. data/spec/grape/integration/rack_spec.rb +0 -51
  251. data/spec/grape/loading_spec.rb +0 -44
  252. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  253. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  254. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  255. data/spec/grape/middleware/base_spec.rb +0 -221
  256. data/spec/grape/middleware/error_spec.rb +0 -85
  257. data/spec/grape/middleware/exception_spec.rb +0 -294
  258. data/spec/grape/middleware/formatter_spec.rb +0 -461
  259. data/spec/grape/middleware/globals_spec.rb +0 -30
  260. data/spec/grape/middleware/stack_spec.rb +0 -155
  261. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  262. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  263. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  264. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  265. data/spec/grape/middleware/versioner_spec.rb +0 -21
  266. data/spec/grape/named_api_spec.rb +0 -19
  267. data/spec/grape/parser_spec.rb +0 -86
  268. data/spec/grape/path_spec.rb +0 -252
  269. data/spec/grape/presenters/presenter_spec.rb +0 -71
  270. data/spec/grape/request_spec.rb +0 -126
  271. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  272. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  273. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  274. data/spec/grape/util/stackable_values_spec.rb +0 -128
  275. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  276. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  277. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  278. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  279. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  280. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  281. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  282. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  283. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  284. data/spec/grape/validations/types_spec.rb +0 -111
  285. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  286. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  287. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  288. data/spec/grape/validations/validators/base_spec.rb +0 -38
  289. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  290. data/spec/grape/validations/validators/default_spec.rb +0 -463
  291. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  292. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  293. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  294. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  295. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  296. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  297. data/spec/grape/validations/validators/values_spec.rb +0 -733
  298. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  299. data/spec/grape/validations_spec.rb +0 -2030
  300. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  301. data/spec/integration/multi_json/json_spec.rb +0 -7
  302. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  303. data/spec/shared/deprecated_class_examples.rb +0 -16
  304. data/spec/shared/versioning_examples.rb +0 -215
  305. data/spec/spec_helper.rb +0 -52
  306. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  307. data/spec/support/chunks.rb +0 -14
  308. data/spec/support/content_type_helpers.rb +0 -15
  309. data/spec/support/endpoint_faker.rb +0 -25
  310. data/spec/support/file_streamer.rb +0 -13
  311. data/spec/support/integer_helpers.rb +0 -13
  312. data/spec/support/versioned_helpers.rb +0 -55
@@ -4,6 +4,9 @@ module Grape
4
4
  module Util
5
5
  # Base for classes which need to operate with own values kept
6
6
  # in the hash and inherited values kept in a Hash-like object.
7
+ #
8
+ # +@new_values+ is lazily allocated on first write so settings layers
9
+ # that only inherit (never override) don't carry an empty Hash each.
7
10
  class BaseInheritable
8
11
  attr_accessor :inherited_values, :new_values
9
12
 
@@ -11,32 +14,29 @@ module Grape
11
14
  # of the Hash class.
12
15
  def initialize(inherited_values = nil)
13
16
  @inherited_values = inherited_values || {}
14
- @new_values = {}
17
+ # @new_values stays nil until the first write.
15
18
  end
16
19
 
17
- def delete(key)
18
- new_values.delete key
20
+ def delete(*keys)
21
+ return [] unless @new_values
22
+
23
+ keys.map { |key| @new_values.delete(key) }
19
24
  end
20
25
 
21
26
  def initialize_copy(other)
22
27
  super
23
- self.inherited_values = other.inherited_values
24
- self.new_values = other.new_values.dup
28
+ @inherited_values = other.inherited_values
29
+ @new_values = other.new_values&.dup
25
30
  end
26
31
 
27
32
  def keys
28
- if new_values.any?
29
- combined = inherited_values.keys
30
- combined.concat(new_values.keys)
31
- combined.uniq!
32
- combined
33
- else
34
- inherited_values.keys
35
- end
33
+ return @inherited_values.keys if @new_values.nil? || @new_values.empty?
34
+
35
+ (@inherited_values.keys + @new_values.keys).uniq
36
36
  end
37
37
 
38
38
  def key?(name)
39
- inherited_values.key?(name) || new_values.key?(name)
39
+ @inherited_values.key?(name) || @new_values&.key?(name) || false
40
40
  end
41
41
  end
42
42
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
4
- require 'forwardable'
5
-
6
3
  module Grape
7
4
  module Util
8
5
  class Cache
@@ -12,6 +9,7 @@ module Grape
12
9
 
13
10
  class << self
14
11
  extend Forwardable
12
+
15
13
  def_delegators :cache, :[]
16
14
  def_delegators :instance, :cache
17
15
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module DeepFreeze
6
+ module_function
7
+
8
+ # Recursively freezes Hash (keys and values), Array (elements), and String
9
+ # objects. All other types are returned as-is.
10
+ #
11
+ # Intentionally left unfrozen:
12
+ # - Procs / lambdas — may be deferred DB-backed callables
13
+ # - Coercers (e.g. ArrayCoercer) — use lazy ivar memoization at request time
14
+ # - Classes / Modules — shared constants that must remain open
15
+ # - ParamsScope — self-freezes at the end of its own initialize
16
+ def deep_freeze(obj)
17
+ case obj
18
+ when Hash
19
+ obj.each do |k, v|
20
+ deep_freeze(k)
21
+ deep_freeze(v)
22
+ end
23
+ obj.freeze
24
+ when Array
25
+ obj.each { |v| deep_freeze(v) }
26
+ obj.freeze
27
+ when String
28
+ obj.freeze
29
+ else
30
+ obj
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grape
4
4
  module Util
5
- class EndpointConfiguration < LazyValueHash
5
+ class EndpointConfiguration < Lazy::ValueHash
6
6
  end
7
7
  end
8
8
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ if Gem::Version.new(Rack.release) >= Gem::Version.new('3')
6
+ require 'rack/headers'
7
+ Header = Rack::Headers
8
+ else
9
+ require 'rack/utils'
10
+ Header = Rack::Utils::HeaderHash
11
+ end
12
+ end
13
+ end
@@ -5,7 +5,17 @@ module Grape
5
5
  # A branchable, inheritable settings object which can store both stackable
6
6
  # and inheritable values (see InheritableValues and StackableValues).
7
7
  class InheritableSetting
8
- attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent, :point_in_time_copies
8
+ attr_reader :route, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent
9
+
10
+ # Lazy-allocated; +api_class+ and +point_in_time_copies+ are rarely
11
+ # written on most settings layers, so don't pay for a Hash/Array each.
12
+ def api_class
13
+ @api_class ||= {}
14
+ end
15
+
16
+ def point_in_time_copies
17
+ @point_in_time_copies ||= []
18
+ end
9
19
 
10
20
  # Retrieve global settings.
11
21
  def self.global
@@ -23,17 +33,14 @@ module Grape
23
33
  # instance can then be set to inherit from an existing instance (see
24
34
  # #inherit_from).
25
35
  def initialize
26
- self.route = {}
27
- self.api_class = {}
28
- self.namespace = InheritableValues.new # only inheritable from a parent when
36
+ @route = {}
37
+ @namespace = InheritableValues.new # only inheritable from a parent when
29
38
  # used with a mount, or should every API::Class be a separate namespace by default?
30
- self.namespace_inheritable = InheritableValues.new
31
- self.namespace_stackable = StackableValues.new
32
- self.namespace_reverse_stackable = ReverseStackableValues.new
33
-
34
- self.point_in_time_copies = []
35
-
36
- self.parent = nil
39
+ @namespace_inheritable = InheritableValues.new
40
+ @namespace_stackable = StackableValues.new
41
+ @namespace_reverse_stackable = ReverseStackableValues.new
42
+ @parent = nil
43
+ # @api_class and @point_in_time_copies stay nil until first access.
37
44
  end
38
45
 
39
46
  # Return the class-level global properties.
@@ -48,14 +55,14 @@ module Grape
48
55
  def inherit_from(parent)
49
56
  return if parent.nil?
50
57
 
51
- self.parent = parent
58
+ @parent = parent
52
59
 
53
60
  namespace_inheritable.inherited_values = parent.namespace_inheritable
54
61
  namespace_stackable.inherited_values = parent.namespace_stackable
55
62
  namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable
56
- self.route = parent.route.merge(route)
63
+ @route = parent.route.merge(route)
57
64
 
58
- point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent }
65
+ @point_in_time_copies&.each { |cloned_one| cloned_one.inherit_from parent }
59
66
  end
60
67
 
61
68
  # Create a point-in-time copy of this settings instance, with clones of
@@ -63,19 +70,11 @@ module Grape
63
70
  # changed via #inherit_from, it will copy that inheritence to any copies
64
71
  # which were made.
65
72
  def point_in_time_copy
66
- self.class.new.tap do |new_setting|
67
- point_in_time_copies << new_setting
68
- new_setting.point_in_time_copies = []
69
-
70
- new_setting.namespace = namespace.clone
71
- new_setting.namespace_inheritable = namespace_inheritable.clone
72
- new_setting.namespace_stackable = namespace_stackable.clone
73
- new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone
74
- new_setting.route = route.clone
75
- new_setting.api_class = api_class
76
-
77
- new_setting.inherit_from(parent)
78
- end
73
+ new_setting = self.class.new
74
+ point_in_time_copies << new_setting
75
+ new_setting.copy_state_from(self)
76
+ new_setting.inherit_from(parent)
77
+ new_setting
79
78
  end
80
79
 
81
80
  # Resets the instance store of per-route settings.
@@ -95,6 +94,31 @@ module Grape
95
94
  namespace_reverse_stackable: namespace_reverse_stackable.to_hash
96
95
  }
97
96
  end
97
+
98
+ def ==(other)
99
+ other.is_a?(self.class) && to_hash == other.to_hash
100
+ end
101
+ alias eql? ==
102
+
103
+ def namespace_stackable_with_hash(key)
104
+ data = namespace_stackable[key]
105
+ return if data.blank?
106
+
107
+ data.each_with_object({}) { |value, result| result.deep_merge!(value) }
108
+ end
109
+
110
+ protected
111
+
112
+ # Used by +point_in_time_copy+ to populate a freshly-built instance
113
+ # with cloned state from another instance of the same class.
114
+ def copy_state_from(source)
115
+ @namespace = source.namespace.clone
116
+ @namespace_inheritable = source.namespace_inheritable.clone
117
+ @namespace_stackable = source.namespace_stackable.clone
118
+ @namespace_reverse_stackable = source.namespace_reverse_stackable.clone
119
+ @route = source.route.clone
120
+ @api_class = source.api_class
121
+ end
98
122
  end
99
123
  end
100
124
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_inheritable'
4
-
5
3
  module Grape
6
4
  module Util
7
5
  class InheritableValues < BaseInheritable
8
6
  def [](name)
9
- values[name]
7
+ return @inherited_values[name] unless @new_values
8
+
9
+ @new_values.fetch(name) { @inherited_values[name] }
10
10
  end
11
11
 
12
12
  def []=(name, value)
13
- new_values[name] = value
13
+ (@new_values ||= {})[name] = value
14
14
  end
15
15
 
16
16
  def merge(new_hash)
@@ -24,7 +24,9 @@ module Grape
24
24
  protected
25
25
 
26
26
  def values
27
- @inherited_values.merge(@new_values)
27
+ return @inherited_values.merge(@new_values) if @new_values && !@new_values.empty?
28
+
29
+ @inherited_values.is_a?(Hash) ? @inherited_values.dup : @inherited_values.to_hash
28
30
  end
29
31
  end
30
32
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ # Abstract parent for lazy wrappers used by the remount/configuration
7
+ # machinery. Call sites can type-check with +is_a?(Grape::Util::Lazy::Base)+
8
+ # instead of enumerating the concrete subclasses.
9
+ class Base
10
+ def to_s
11
+ evaluate.to_s
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class Block < Base
7
+ def initialize(&new_block)
8
+ super()
9
+ @block = new_block
10
+ end
11
+
12
+ def evaluate_from(configuration)
13
+ @block.call(configuration)
14
+ end
15
+
16
+ def evaluate
17
+ @block.call({})
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class Value < Base
7
+ attr_reader :access_keys
8
+
9
+ def initialize(value, access_keys = [])
10
+ super()
11
+ @value = value
12
+ @access_keys = access_keys
13
+ end
14
+
15
+ def evaluate_from(configuration)
16
+ matching_lazy_value = configuration.fetch(@access_keys)
17
+ matching_lazy_value.evaluate
18
+ end
19
+
20
+ def evaluate
21
+ @value
22
+ end
23
+
24
+ def reached_by(parent_access_keys, access_key)
25
+ @access_keys = parent_access_keys + [access_key]
26
+ self
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueArray < ValueEnumerable
7
+ def initialize(array)
8
+ super
9
+ @value_hash = []
10
+ array.each_with_index do |value, index|
11
+ self[index] = value
12
+ end
13
+ end
14
+
15
+ def evaluate
16
+ @value_hash.map(&:evaluate)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueEnumerable < Value
7
+ def [](key)
8
+ return Value.new(nil).reached_by(access_keys, key) if @value_hash[key].nil?
9
+
10
+ @value_hash[key].reached_by(access_keys, key)
11
+ end
12
+
13
+ def fetch(access_keys)
14
+ access_keys.reduce(self) { |node, key| node[key] }
15
+ end
16
+
17
+ def []=(key, value)
18
+ value_class = case value
19
+ when Hash
20
+ ValueHash
21
+ when Array
22
+ ValueArray
23
+ else
24
+ Value
25
+ end
26
+ @value_hash[key] = value_class.new(value)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueHash < ValueEnumerable
7
+ def initialize(hash)
8
+ super
9
+ @value_hash = ActiveSupport::HashWithIndifferentAccess.new
10
+ hash.each do |key, value|
11
+ self[key] = value
12
+ end
13
+ end
14
+
15
+ def evaluate
16
+ @value_hash.transform_values(&:evaluate)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ class MediaType
6
+ attr_reader :type, :subtype, :vendor, :version, :format
7
+
8
+ # based on the HTTP Accept header with the pattern:
9
+ # application/vnd.:vendor-:version+:format
10
+ VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z/
11
+
12
+ def initialize(type:, subtype:)
13
+ @type = type
14
+ @subtype = subtype
15
+ VENDOR_VERSION_HEADER_REGEX.match(subtype) do |m|
16
+ @vendor = m[:vendor]
17
+ @version = m[:version]
18
+ @format = m[:format]
19
+ end
20
+ end
21
+
22
+ def ==(other)
23
+ self.class == other.class &&
24
+ other.type == type &&
25
+ other.subtype == subtype &&
26
+ other.vendor == vendor &&
27
+ other.version == version &&
28
+ other.format == format
29
+ end
30
+ alias eql? ==
31
+
32
+ def hash
33
+ [self.class, type, subtype, vendor, version, format].hash
34
+ end
35
+
36
+ class << self
37
+ def best_quality(header, available_media_types)
38
+ parse(best_quality_media_type(header, available_media_types))
39
+ end
40
+
41
+ def parse(media_type)
42
+ return if media_type.blank?
43
+
44
+ type, subtype = media_type.split('/', 2)
45
+ return if type.blank? || subtype.blank?
46
+
47
+ new(type:, subtype:)
48
+ end
49
+
50
+ def match?(media_type)
51
+ return false if media_type.blank?
52
+
53
+ subtype = media_type.split('/', 2).last
54
+ return false if subtype.blank?
55
+
56
+ VENDOR_VERSION_HEADER_REGEX.match?(subtype)
57
+ end
58
+
59
+ def best_quality_media_type(header, available_media_types)
60
+ header.blank? ? available_media_types.first : Rack::Utils.best_q_match(header, available_media_types)
61
+ end
62
+ end
63
+
64
+ private_class_method :best_quality_media_type
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module PathNormalizer
6
+ # Taken from Rails
7
+ # call("/foo") # => "/foo"
8
+ # call("/foo/") # => "/foo"
9
+ # call("foo") # => "/foo"
10
+ # call("") # => "/"
11
+ # call("/%ab") # => "/%AB"
12
+ # https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
13
+ def self.call(path)
14
+ return '/' unless path
15
+ return path if path == '/'
16
+
17
+ # Fast path for the overwhelming majority of paths that don't need to be normalized
18
+ return path if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))
19
+
20
+ # Slow path
21
+ encoding = path.encoding
22
+ path = "/#{path}"
23
+ path.squeeze!('/')
24
+
25
+ unless path == '/'
26
+ path.delete_suffix!('/')
27
+ path.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }
28
+ end
29
+
30
+ path.force_encoding(encoding)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Registry
6
+ def register(klass)
7
+ short_name = build_short_name(klass)
8
+ return if short_name.nil?
9
+
10
+ warn "#{short_name} is already registered with class #{registry[short_name]}. It will be overridden globally with the following: #{klass.name}" if registry.key?(short_name)
11
+ registry[short_name] = klass
12
+ end
13
+
14
+ private
15
+
16
+ def build_short_name(klass)
17
+ return if klass.name.blank?
18
+
19
+ klass.name.demodulize.underscore
20
+ end
21
+
22
+ def registry
23
+ @registry ||= ActiveSupport::HashWithIndifferentAccess.new
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'stackable_values'
4
-
5
3
  module Grape
6
4
  module Util
7
5
  class ReverseStackableValues < StackableValues
@@ -10,10 +8,7 @@ module Grape
10
8
  def concat_values(inherited_value, new_value)
11
9
  return inherited_value unless new_value
12
10
 
13
- [].tap do |value|
14
- value.concat(new_value)
15
- value.concat(inherited_value)
16
- end
11
+ new_value + inherited_value
17
12
  end
18
13
  end
19
14
  end
@@ -1,28 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_inheritable'
4
-
5
3
  module Grape
6
4
  module Util
7
5
  class StackableValues < BaseInheritable
8
- # Even if there is no value, an empty array will be returned.
6
+ EMPTY = [].freeze
7
+
8
+ # Even if there is no value, an empty (frozen) array will be returned.
9
9
  def [](name)
10
- inherited_value = inherited_values[name]
11
- new_value = new_values[name]
10
+ inherited_value = @inherited_values[name]
11
+ new_value = @new_values && @new_values[name]
12
12
 
13
- return new_value || [] unless inherited_value
13
+ return new_value || EMPTY unless inherited_value
14
14
 
15
15
  concat_values(inherited_value, new_value)
16
16
  end
17
17
 
18
18
  def []=(name, value)
19
- new_values[name] ||= []
20
- new_values[name].push value
19
+ @new_values ||= {}
20
+ @new_values[name] ||= []
21
+ @new_values[name].push value
21
22
  end
22
23
 
23
24
  def to_hash
24
- keys.each_with_object({}) do |key, result|
25
- result[key] = self[key]
25
+ keys.to_h do |key|
26
+ [key, self[key]]
26
27
  end
27
28
  end
28
29
 
@@ -31,10 +32,7 @@ module Grape
31
32
  def concat_values(inherited_value, new_value)
32
33
  return inherited_value unless new_value
33
34
 
34
- [].tap do |value|
35
- value.concat(inherited_value)
36
- value.concat(new_value)
37
- end
35
+ inherited_value + new_value
38
36
  end
39
37
  end
40
38
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Translation
6
+ FALLBACK_LOCALE = :en
7
+ private_constant :FALLBACK_LOCALE
8
+ # Sentinel returned by I18n when a key is missing (passed as the default:
9
+ # value). Using a named class rather than plain Object.new makes it
10
+ # identifiable in debug output and immune to backends that call .to_s on
11
+ # the default before returning it.
12
+ MISSING = Class.new { def inspect = 'Grape::Util::Translation::MISSING' }.new.freeze
13
+ private_constant :MISSING
14
+
15
+ private
16
+
17
+ # Extra keyword args (**) are forwarded verbatim to I18n as interpolation
18
+ # variables (e.g. +min:+, +max:+ from LengthValidator's Hash message).
19
+ # Callers must not pass unintended keyword arguments — any extra keyword
20
+ # will silently become an I18n interpolation variable.
21
+ def translate(key, default: MISSING, scope: 'grape.errors.messages', locale: nil, **)
22
+ i18n_opts = { default:, scope:, ** }
23
+ i18n_opts[:locale] = locale if locale
24
+ message = ::I18n.translate(key, **i18n_opts)
25
+ return message unless message.equal?(MISSING)
26
+
27
+ effective_default = default.equal?(MISSING) ? [*Array(scope), key].join('.') : default
28
+ return effective_default if fallback_locale?(locale) || fallback_locale_unavailable?
29
+
30
+ ::I18n.translate(key, default: effective_default, scope:, locale: FALLBACK_LOCALE, **)
31
+ end
32
+
33
+ def fallback_locale?(locale)
34
+ (locale || ::I18n.locale) == FALLBACK_LOCALE
35
+ end
36
+
37
+ def fallback_locale_unavailable?
38
+ ::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)
39
+ end
40
+ end
41
+ end
42
+ end