grape 2.4.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +351 -0
  3. data/CONTRIBUTING.md +18 -10
  4. data/README.md +457 -228
  5. data/UPGRADING.md +1240 -1
  6. data/grape.gemspec +7 -7
  7. data/lib/grape/api/instance.rb +65 -116
  8. data/lib/grape/api.rb +53 -62
  9. data/lib/grape/content_types.rb +58 -3
  10. data/lib/grape/cookies.rb +2 -6
  11. data/lib/grape/declared_params_handler.rb +116 -0
  12. data/lib/grape/dry_types.rb +48 -4
  13. data/lib/grape/dsl/callbacks.rb +8 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +24 -67
  16. data/lib/grape/dsl/entity.rb +106 -0
  17. data/lib/grape/dsl/helpers.rb +80 -65
  18. data/lib/grape/dsl/inside_route.rb +56 -296
  19. data/lib/grape/dsl/logger.rb +4 -9
  20. data/lib/grape/dsl/middleware.rb +22 -40
  21. data/lib/grape/dsl/parameters.rb +86 -93
  22. data/lib/grape/dsl/request_response.rb +139 -139
  23. data/lib/grape/dsl/rescue_options.rb +25 -0
  24. data/lib/grape/dsl/routing.rb +328 -203
  25. data/lib/grape/dsl/settings.rb +29 -135
  26. data/lib/grape/dsl/validations.rb +38 -44
  27. data/lib/grape/dsl/version_options.rb +23 -0
  28. data/lib/grape/endpoint/options.rb +25 -0
  29. data/lib/grape/endpoint.rb +285 -245
  30. data/lib/grape/env.rb +8 -3
  31. data/lib/grape/error_formatter/base.rb +27 -21
  32. data/lib/grape/error_formatter/json.rb +6 -0
  33. data/lib/grape/error_formatter/txt.rb +10 -10
  34. data/lib/grape/error_formatter.rb +6 -2
  35. data/lib/grape/exceptions/base.rb +20 -44
  36. data/lib/grape/exceptions/error_response.rb +48 -0
  37. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  38. data/lib/grape/exceptions/internal_server_error.rb +16 -0
  39. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  40. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  41. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  42. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  43. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  44. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  45. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  46. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  47. data/lib/grape/exceptions/request_error.rb +11 -0
  48. data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
  49. data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
  50. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  51. data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
  52. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  53. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  54. data/lib/grape/exceptions/validation.rb +25 -7
  55. data/lib/grape/exceptions/validation_array_errors.rb +4 -0
  56. data/lib/grape/exceptions/validation_errors.rb +21 -23
  57. data/lib/grape/formatter/json.rb +1 -0
  58. data/lib/grape/formatter/serializable_hash.rb +6 -9
  59. data/lib/grape/json.rb +38 -2
  60. data/lib/grape/locale/en.yml +3 -5
  61. data/lib/grape/middleware/auth/base.rb +4 -3
  62. data/lib/grape/middleware/auth/dsl.rb +9 -23
  63. data/lib/grape/middleware/base.rb +41 -36
  64. data/lib/grape/middleware/error.rb +271 -73
  65. data/lib/grape/middleware/formatter.rb +152 -63
  66. data/lib/grape/middleware/precomputed_content_types.rb +51 -0
  67. data/lib/grape/middleware/stack.rb +20 -11
  68. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
  69. data/lib/grape/middleware/versioner/base.rb +40 -48
  70. data/lib/grape/middleware/versioner/header.rb +5 -23
  71. data/lib/grape/middleware/versioner/param.rb +2 -2
  72. data/lib/grape/middleware/versioner/path.rb +54 -7
  73. data/lib/grape/mountable.rb +22 -0
  74. data/lib/grape/namespace.rb +19 -11
  75. data/lib/grape/params_builder/base.rb +2 -0
  76. data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
  77. data/lib/grape/params_builder.rb +2 -19
  78. data/lib/grape/parser/json.rb +1 -1
  79. data/lib/grape/path.rb +8 -72
  80. data/lib/grape/precompiled_json.rb +50 -0
  81. data/lib/grape/request.rb +57 -20
  82. data/lib/grape/router/base_route.rb +53 -7
  83. data/lib/grape/router/greedy_route.rb +16 -6
  84. data/lib/grape/router/mustermann_pattern.rb +44 -0
  85. data/lib/grape/router/pattern/path.rb +78 -0
  86. data/lib/grape/router/pattern.rb +65 -37
  87. data/lib/grape/router/route.rb +66 -18
  88. data/lib/grape/router.rb +126 -115
  89. data/lib/grape/serve_stream/file_body.rb +7 -0
  90. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  91. data/lib/grape/serve_stream/stream_response.rb +7 -0
  92. data/lib/grape/testing.rb +33 -0
  93. data/lib/grape/util/api_description.rb +67 -0
  94. data/lib/grape/util/cache.rb +22 -2
  95. data/lib/grape/util/deep_freeze.rb +34 -0
  96. data/lib/grape/util/freeze_on_new.rb +20 -0
  97. data/lib/grape/util/inheritable_setting.rb +777 -39
  98. data/lib/grape/util/lazy/base.rb +16 -0
  99. data/lib/grape/util/lazy/block.rb +2 -9
  100. data/lib/grape/util/lazy/value.rb +2 -9
  101. data/lib/grape/util/lazy/value_enumerable.rb +13 -16
  102. data/lib/grape/util/media_type.rb +13 -9
  103. data/lib/grape/util/path_normalizer.rb +37 -0
  104. data/lib/grape/util/registry.rb +13 -3
  105. data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
  106. data/lib/grape/util/stackable_values.rb +39 -10
  107. data/lib/grape/util/translation.rb +42 -0
  108. data/lib/grape/validations/attributes_iterator.rb +61 -29
  109. data/lib/grape/validations/coerce_options.rb +21 -0
  110. data/lib/grape/validations/contract_scope.rb +8 -13
  111. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  112. data/lib/grape/validations/oneof_collector.rb +35 -0
  113. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  114. data/lib/grape/validations/params_documentation.rb +52 -0
  115. data/lib/grape/validations/params_scope.rb +217 -304
  116. data/lib/grape/validations/shared_options.rb +19 -0
  117. data/lib/grape/validations/single_attribute_iterator.rb +6 -4
  118. data/lib/grape/validations/types/array_coercer.rb +7 -10
  119. data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
  120. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  121. data/lib/grape/validations/types/dry_type_coercer.rb +10 -15
  122. data/lib/grape/validations/types/json.rb +1 -3
  123. data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
  124. data/lib/grape/validations/types/primitive_coercer.rb +14 -33
  125. data/lib/grape/validations/types/set_coercer.rb +1 -1
  126. data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
  127. data/lib/grape/validations/types.rb +29 -51
  128. data/lib/grape/validations/validations_spec.rb +164 -0
  129. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  130. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  131. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  132. data/lib/grape/validations/validators/base.rb +118 -34
  133. data/lib/grape/validations/validators/coerce_validator.rb +25 -37
  134. data/lib/grape/validations/validators/contract_scope_validator.rb +10 -5
  135. data/lib/grape/validations/validators/default_validator.rb +13 -20
  136. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  137. data/lib/grape/validations/validators/except_values_validator.rb +14 -4
  138. data/lib/grape/validations/validators/length_validator.rb +23 -22
  139. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  140. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  141. data/lib/grape/validations/validators/oneof_validator.rb +51 -0
  142. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  143. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  144. data/lib/grape/validations/validators/same_as_validator.rb +7 -15
  145. data/lib/grape/validations/validators/values_validator.rb +34 -26
  146. data/lib/grape/version.rb +1 -2
  147. data/lib/grape/xml.rb +8 -1
  148. data/lib/grape.rb +58 -31
  149. metadata +62 -39
  150. data/lib/grape/api/helpers.rb +0 -9
  151. data/lib/grape/dsl/api.rb +0 -17
  152. data/lib/grape/dsl/configuration.rb +0 -15
  153. data/lib/grape/exceptions/conflicting_types.rb +0 -11
  154. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  155. data/lib/grape/exceptions/invalid_parameters.rb +0 -11
  156. data/lib/grape/exceptions/missing_option.rb +0 -11
  157. data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
  158. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  159. data/lib/grape/exceptions/unknown_options.rb +0 -11
  160. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
  161. data/lib/grape/extensions/hash.rb +0 -27
  162. data/lib/grape/extensions/hashie/mash.rb +0 -24
  163. data/lib/grape/middleware/globals.rb +0 -14
  164. data/lib/grape/types/invalid_value.rb +0 -8
  165. data/lib/grape/util/base_inheritable.rb +0 -43
  166. data/lib/grape/util/inheritable_values.rb +0 -29
  167. data/lib/grape/util/reverse_stackable_values.rb +0 -15
  168. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  169. data/lib/grape/validations/attributes_doc.rb +0 -60
  170. data/lib/grape/validations/validator_factory.rb +0 -15
@@ -3,80 +3,95 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Helpers
6
- extend ActiveSupport::Concern
7
- include Grape::DSL::Configuration
8
-
9
- module ClassMethods
10
- # Add helper methods that will be accessible from any
11
- # endpoint within this namespace (and child namespaces).
12
- #
13
- # When called without a block, all known helpers within this scope
14
- # are included.
15
- #
16
- # @param [Array] new_modules optional array of modules to include
17
- # @param [Block] block optional block of methods to include
18
- #
19
- # @example Define some helpers.
20
- #
21
- # class ExampleAPI < Grape::API
22
- # helpers do
23
- # def current_user
24
- # User.find_by_id(params[:token])
25
- # end
26
- # end
27
- # end
28
- #
29
- # @example Include many modules
30
- #
31
- # class ExampleAPI < Grape::API
32
- # helpers Authentication, Mailer, OtherModule
33
- # end
34
- #
35
- def helpers(*new_modules, &block)
36
- include_new_modules(new_modules)
37
- include_block(block)
38
- include_all_in_scope if !block && new_modules.empty?
39
- end
6
+ # Add helper methods that will be accessible from any
7
+ # endpoint within this namespace (and child namespaces).
8
+ #
9
+ # When called without a block, all known helpers within this scope
10
+ # are included.
11
+ #
12
+ # @param [Array] new_modules optional array of modules to include
13
+ # @param [Block] block optional block of methods to include
14
+ #
15
+ # @example Define some helpers.
16
+ #
17
+ # class ExampleAPI < Grape::API
18
+ # helpers do
19
+ # def current_user
20
+ # User.find_by_id(params[:token])
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # @example Include many modules
26
+ #
27
+ # class ExampleAPI < Grape::API
28
+ # helpers Authentication, Mailer, OtherModule
29
+ # end
30
+ #
31
+ def helpers(*new_modules, &block)
32
+ include_new_modules(new_modules)
33
+ include_block(block)
34
+ include_all_in_scope if !block && new_modules.empty?
35
+ end
40
36
 
41
- protected
37
+ private
42
38
 
43
- def include_new_modules(modules)
44
- return if modules.empty?
39
+ def include_new_modules(modules)
40
+ return if modules.empty?
45
41
 
46
- modules.each { |mod| make_inclusion(mod) }
47
- end
42
+ modules.each { |mod| make_inclusion(mod) }
43
+ end
48
44
 
49
- def include_block(block)
50
- return unless block
45
+ def include_block(block)
46
+ return unless block
51
47
 
52
- Module.new.tap do |mod|
53
- make_inclusion(mod) { mod.class_eval(&block) }
54
- end
55
- end
48
+ mod = Module.new
49
+ make_inclusion(mod) { mod.class_eval(&block) }
50
+ end
56
51
 
57
- def make_inclusion(mod, &block)
58
- define_boolean_in_mod(mod)
59
- inject_api_helpers_to_mod(mod, &block)
60
- namespace_stackable(:helpers, mod)
61
- end
52
+ def make_inclusion(mod, &)
53
+ define_boolean_in_mod(mod)
54
+ inject_api_helpers_to_mod(mod, &)
55
+ inheritable_setting.add_helper(mod)
56
+ end
62
57
 
63
- def include_all_in_scope
64
- Module.new.tap do |mod|
65
- namespace_stackable(:helpers).each { |mod_to_include| mod.include mod_to_include }
66
- change!
67
- end
68
- end
58
+ def include_all_in_scope
59
+ mod = Module.new
60
+ inheritable_setting.helpers.each { |mod_to_include| mod.include mod_to_include }
61
+ change!
62
+ mod
63
+ end
69
64
 
70
- def define_boolean_in_mod(mod)
71
- return if defined? mod::Boolean
65
+ def define_boolean_in_mod(mod)
66
+ return if defined? mod::Boolean
72
67
 
73
- mod.const_set(:Boolean, Grape::API::Boolean)
74
- end
68
+ mod.const_set(:Boolean, Grape::API::Boolean)
69
+ end
70
+
71
+ def inject_api_helpers_to_mod(mod, &block)
72
+ mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
73
+ yield if block
74
+ warn_on_endpoint_overrides(mod) if Grape.config.warn_on_helper_overrides
75
+ mod.api_changed(self)
76
+ end
75
77
 
76
- def inject_api_helpers_to_mod(mod, &block)
77
- mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
78
- yield if block
79
- mod.api_changed(self)
78
+ # When +Grape.config.warn_on_helper_overrides+ is enabled, emit a
79
+ # warning to +$stderr+ for any helper method that masks an instance
80
+ # method on +Grape::Endpoint+. Helpers are mixed into the endpoint's
81
+ # singleton class and therefore take precedence over +Endpoint+
82
+ # instance methods — usually intentional, but a common source of
83
+ # surprise when the framework gains a method that already collides
84
+ # with an existing helper name.
85
+ def warn_on_endpoint_overrides(mod)
86
+ overridden = mod.instance_methods(false).select { |m| Grape::Endpoint.method_defined?(m) }
87
+ return if overridden.empty?
88
+
89
+ overridden.each do |name|
90
+ warn(
91
+ "Grape: helper method `#{name}` overrides Grape::Endpoint##{name}. " \
92
+ 'The helper takes precedence. To use the framework implementation, remove the helper. ' \
93
+ 'Silence this warning by setting Grape.config.warn_on_helper_overrides = false.'
94
+ )
80
95
  end
81
96
  end
82
97
 
@@ -100,7 +115,7 @@ module Grape
100
115
  def process_named_params
101
116
  return if @named_params.blank?
102
117
 
103
- api.namespace_stackable(:named_params, @named_params)
118
+ api.inheritable_setting.add_named_params(@named_params)
104
119
  end
105
120
  end
106
121
  end
@@ -3,152 +3,11 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module InsideRoute
6
- extend ActiveSupport::Concern
7
- include Grape::DSL::Settings
8
- include Grape::DSL::Headers
6
+ include Declared
7
+ include Entity
9
8
 
10
- # Denotes a situation where a DSL method has been invoked in a
11
- # filter which it should not yet be available in
12
- class MethodNotYetAvailable < StandardError; end
13
-
14
- # @param type [Symbol] The type of filter for which evaluation has been
15
- # completed
16
- # @return [Module] A module containing method overrides suitable for the
17
- # position in the filter evaluation sequence denoted by +type+. This
18
- # defaults to an empty module if no overrides are defined for the given
19
- # filter +type+.
20
- def self.post_filter_methods(type)
21
- @post_filter_modules ||= { before: PostBeforeFilter }
22
- @post_filter_modules[type]
23
- end
24
-
25
- # Methods which should not be available in filters until the before filter
26
- # has completed
27
- module PostBeforeFilter
28
- def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
29
- options.reverse_merge!(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
30
- declared_params ||= optioned_declared_params(options[:include_parent_namespaces])
31
-
32
- res = if passed_params.is_a?(Array)
33
- declared_array(passed_params, options, declared_params, params_nested_path)
34
- else
35
- declared_hash(passed_params, options, declared_params, params_nested_path)
36
- end
37
-
38
- if (key_maps = namespace_stackable(:contract_key_map))
39
- key_maps.each { |key_map| key_map.write(passed_params, res) }
40
- end
41
-
42
- res
43
- end
44
-
45
- private
46
-
47
- def declared_array(passed_params, options, declared_params, params_nested_path)
48
- passed_params.map do |passed_param|
49
- declared(passed_param || {}, options, declared_params, params_nested_path)
50
- end
51
- end
52
-
53
- def declared_hash(passed_params, options, declared_params, params_nested_path)
54
- declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
55
- next if options[:evaluate_given] && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
56
-
57
- declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo)
58
- end
59
- end
60
-
61
- def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo)
62
- renamed_params = route_setting(:renamed_params) || {}
63
- if declared_param.is_a?(Hash)
64
- declared_param.each_pair do |declared_parent_param, declared_children_params|
65
- params_nested_path_dup = params_nested_path.dup
66
- params_nested_path_dup << declared_parent_param.to_s
67
- next unless options[:include_missing] || passed_params.key?(declared_parent_param)
68
-
69
- rename_path = params_nested_path + [declared_parent_param.to_s]
70
- renamed_param_name = renamed_params[rename_path]
71
-
72
- memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
73
- passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
74
-
75
- memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
76
- declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
77
- end
78
- end
79
- else
80
- # If it is not a Hash then it does not have children.
81
- # Find its value or set it to nil.
82
- return unless options[:include_missing] || passed_params.try(:key?, declared_param)
83
-
84
- rename_path = params_nested_path + [declared_param.to_s]
85
- renamed_param_name = renamed_params[rename_path]
86
-
87
- memo_key = optioned_param_key(renamed_param_name || declared_param, options)
88
- passed_param = passed_params[declared_param]
89
-
90
- params_nested_path_dup = params_nested_path.dup
91
- params_nested_path_dup << declared_param.to_s
92
- memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
93
- passed_param
94
- end
95
- end
96
- end
97
-
98
- def handle_passed_param(params_nested_path, has_passed_children = false, &_block)
99
- return yield if has_passed_children
100
-
101
- key = params_nested_path[0]
102
- key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
103
-
104
- route_options_params = options[:route_options][:params] || {}
105
- type = route_options_params.dig(key, :type)
106
- has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }
107
-
108
- if type == 'Hash' && !has_children
109
- {}
110
- elsif type == 'Array' || (type&.start_with?('[') && type.exclude?(','))
111
- []
112
- elsif type == 'Set' || type&.start_with?('#<Set')
113
- Set.new
114
- else
115
- yield
116
- end
117
- end
118
-
119
- def optioned_param_key(declared_param, options)
120
- options[:stringify] ? declared_param.to_s : declared_param.to_sym
121
- end
122
-
123
- def optioned_declared_params(include_parent_namespaces)
124
- declared_params = if include_parent_namespaces
125
- # Declared params including parent namespaces
126
- route_setting(:declared_params)
127
- else
128
- # Declared params at current namespace
129
- namespace_stackable(:declared_params).last || []
130
- end
131
-
132
- raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
133
-
134
- declared_params
135
- end
136
- end
137
-
138
- # A filtering method that will return a hash
139
- # consisting only of keys that have been declared by a
140
- # `params` statement against the current/target endpoint or parent
141
- # namespaces.
142
- #
143
- # @see +PostBeforeFilter#declared+
144
- #
145
- # @param params [Hash] The initial hash to filter. Usually this will just be `params`
146
- # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
147
- # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
148
- # you want only to return params declared against the current/target endpoint.
149
- def declared(*)
150
- raise MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
151
- end
9
+ # Backward compatibility: alias exception class to previous location
10
+ MethodNotYetAvailable = Declared::MethodNotYetAvailable
152
11
 
153
12
  # The API version as specified in the URL.
154
13
  def version
@@ -156,7 +15,7 @@ module Grape
156
15
  end
157
16
 
158
17
  def configuration
159
- options[:for].configuration.evaluate
18
+ config.api.configuration.evaluate
160
19
  end
161
20
 
162
21
  # End the request and display an error to the
@@ -168,32 +27,11 @@ module Grape
168
27
  # @param backtrace [Array<String>] The backtrace of the exception that caused the error.
169
28
  # @param original_exception [Exception] The original exception that caused the error.
170
29
  def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
171
- status = self.status(status || namespace_inheritable(:default_error_status))
30
+ resolved_status = self.status(status || inheritable_setting.default_error_status)
172
31
  headers = additional_headers.present? ? header.merge(additional_headers) : header
173
- throw :error,
174
- message: message,
175
- status: status,
176
- headers: headers,
177
- backtrace: backtrace,
178
- original_exception: original_exception
179
- end
180
-
181
- # Creates a Rack response based on the provided message, status, and headers.
182
- # The content type in the headers is set to the default content type unless provided.
183
- # The message is HTML-escaped if the content type is 'text/html'.
184
- #
185
- # @param message [String] The content of the response.
186
- # @param status [Integer] The HTTP status code.
187
- # @params headers [Hash] (optional) Headers for the response
188
- # (default: {Rack::CONTENT_TYPE => content_type}).
189
- #
190
- # Returns:
191
- # A Rack::Response object containing the specified message, status, and headers.
192
- #
193
- def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
194
- Grape.deprecator.warn('The rack_response method has been deprecated, use error! instead.')
195
- message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
196
- Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), headers)
32
+ throw :error, Grape::Exceptions::ErrorResponse.new(
33
+ message:, status: resolved_status, headers:, backtrace:, original_exception:
34
+ )
197
35
  end
198
36
 
199
37
  # Redirect to a new url.
@@ -215,6 +53,12 @@ module Grape
215
53
  end
216
54
  header 'Location', url
217
55
  content_type 'text/plain'
56
+ # Render the message Grape generated as the plain text it is. Setting
57
+ # only the header left it to the API's own formatter, which on a JSON
58
+ # API returned the sentence wrapped in quotes under a text/plain content
59
+ # type. A caller-supplied body keeps the API's format: it may be
60
+ # structured, and the txt formatter would render a Hash through `to_s`.
61
+ api_format :txt unless body
218
62
  body body_message
219
63
  end
220
64
 
@@ -222,39 +66,21 @@ module Grape
222
66
  #
223
67
  # @param status [Integer] The HTTP Status Code to return for this request.
224
68
  def status(status = nil)
225
- case status
226
- when Symbol
227
- raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
69
+ return @status || default_status if status.nil?
228
70
 
71
+ case status
72
+ when Symbol, Integer
229
73
  @status = Rack::Utils.status_code(status)
230
- when Integer
231
- @status = status
232
- when nil
233
- return @status if instance_variable_defined?(:@status) && @status
234
-
235
- if request.post?
236
- 201
237
- elsif request.delete?
238
- if instance_variable_defined?(:@body) && @body.present?
239
- 200
240
- else
241
- 204
242
- end
243
- else
244
- 200
245
- end
246
74
  else
247
- raise ArgumentError, 'Status code must be Integer or Symbol.'
75
+ raise ArgumentError, 'status code must be Integer or Symbol'
248
76
  end
249
77
  end
250
78
 
251
79
  # Set response content-type
252
80
  def content_type(val = nil)
253
- if val
254
- header(Rack::CONTENT_TYPE, val)
255
- else
256
- header[Rack::CONTENT_TYPE]
257
- end
81
+ return header(Rack::CONTENT_TYPE, val) if val
82
+
83
+ header[Rack::CONTENT_TYPE]
258
84
  end
259
85
 
260
86
  # Allows you to define the response body as something other than the
@@ -274,7 +100,7 @@ module Grape
274
100
  @body = ''
275
101
  status 204
276
102
  else
277
- instance_variable_defined?(:@body) ? @body : nil
103
+ @body
278
104
  end
279
105
  end
280
106
 
@@ -288,7 +114,6 @@ module Grape
288
114
  #
289
115
  # DELETE /12 # => 204 No Content, ""
290
116
  def return_no_content
291
- status 204
292
117
  body false
293
118
  end
294
119
 
@@ -301,14 +126,12 @@ module Grape
301
126
  #
302
127
  # GET /file # => "contents of file"
303
128
  def sendfile(value = nil)
304
- if value.is_a?(String)
305
- file_body = Grape::ServeStream::FileBody.new(value)
306
- @stream = Grape::ServeStream::StreamResponse.new(file_body)
307
- elsif !value.is_a?(NilClass)
308
- raise ArgumentError, 'Argument must be a file path'
309
- else
310
- stream
311
- end
129
+ return stream if value.nil?
130
+
131
+ raise ArgumentError, 'argument must be a file path' unless value.is_a?(String)
132
+
133
+ file_body = Grape::ServeStream::FileBody.new(value)
134
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
312
135
  end
313
136
 
314
137
  # Allows you to define the response as a streamable object.
@@ -332,61 +155,10 @@ module Grape
332
155
  header Rack::CONTENT_LENGTH, nil
333
156
  header 'Transfer-Encoding', nil
334
157
  header Rack::CACHE_CONTROL, 'no-cache' # Skips ETag generation (reading the response up front)
335
- if value.is_a?(String)
336
- file_body = Grape::ServeStream::FileBody.new(value)
337
- @stream = Grape::ServeStream::StreamResponse.new(file_body)
338
- elsif value.respond_to?(:each)
339
- @stream = Grape::ServeStream::StreamResponse.new(value)
340
- elsif !value.is_a?(NilClass)
341
- raise ArgumentError, 'Stream object must respond to :each.'
342
- else
343
- @stream
344
- end
345
- end
346
-
347
- # Allows you to make use of Grape Entities by setting
348
- # the response body to the serializable hash of the
349
- # entity provided in the `:with` option. This has the
350
- # added benefit of automatically passing along environment
351
- # and version information to the serialization, making it
352
- # very easy to do conditional exposures. See Entity docs
353
- # for more info.
354
- #
355
- # @example
356
- #
357
- # get '/users/:id' do
358
- # present User.find(params[:id]),
359
- # with: API::Entities::User,
360
- # admin: current_user.admin?
361
- # end
362
- def present(*args)
363
- options = args.count > 1 ? args.extract_options! : {}
364
- key, object = if args.count == 2 && args.first.is_a?(Symbol)
365
- args
366
- else
367
- [nil, args.first]
368
- end
369
- entity_class = entity_class_for_obj(object, options)
370
-
371
- root = options.delete(:root)
372
-
373
- representation = if entity_class
374
- entity_representation_for(entity_class, object, options)
375
- else
376
- object
377
- end
378
158
 
379
- representation = { root => representation } if root
159
+ return @stream if value.nil?
380
160
 
381
- if key
382
- representation = (body || {}).merge(key => representation)
383
- elsif entity_class.present? && body
384
- raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
385
-
386
- representation = body.merge(representation)
387
- end
388
-
389
- body representation
161
+ @stream = Grape::ServeStream::StreamResponse.new(stream_body(value))
390
162
  end
391
163
 
392
164
  # Returns route information for the current request.
@@ -401,43 +173,6 @@ module Grape
401
173
  env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
402
174
  end
403
175
 
404
- # Attempt to locate the Entity class for a given object, if not given
405
- # explicitly. This is done by looking for the presence of Klass::Entity,
406
- # where Klass is the class of the `object` parameter, or one of its
407
- # ancestors.
408
- # @param object [Object] the object to locate the Entity class for
409
- # @param options [Hash]
410
- # @option options :with [Class] the explicit entity class to use
411
- # @return [Class] the located Entity class, or nil if none is found
412
- def entity_class_for_obj(object, options)
413
- entity_class = options.delete(:with)
414
-
415
- if entity_class.nil?
416
- # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
417
- object_class = if object.respond_to?(:klass)
418
- object.klass
419
- else
420
- object.respond_to?(:first) ? object.first.class : object.class
421
- end
422
-
423
- object_class.ancestors.each do |potential|
424
- entity_class ||= (namespace_stackable_with_hash(:representations) || {})[potential]
425
- end
426
-
427
- entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
428
- end
429
-
430
- entity_class
431
- end
432
-
433
- # @return the representation of the given object as done through
434
- # the given entity_class.
435
- def entity_representation_for(entity_class, object, options)
436
- embeds = { env: env }
437
- embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
438
- entity_class.represent(object, **embeds, **options)
439
- end
440
-
441
176
  def http_version
442
177
  env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }
443
178
  end
@@ -449,6 +184,31 @@ module Grape
449
184
  def context
450
185
  self
451
186
  end
187
+
188
+ private
189
+
190
+ # Wraps a stream +value+ into a body that responds to +:each+.
191
+ def stream_body(value)
192
+ return Grape::ServeStream::FileBody.new(value) if value.is_a?(String)
193
+
194
+ raise ArgumentError, 'stream object must respond to :each' unless value.respond_to?(:each)
195
+
196
+ value
197
+ end
198
+
199
+ # The default HTTP status when none has been set explicitly.
200
+ # Reads the request method once instead of asking through +post?+ and
201
+ # +delete?+, each of which reads it again. Every response that did not set
202
+ # a status of its own comes through here, so it is read straight off the
203
+ # env: +Grape::Request+ wraps the very same Hash and answers
204
+ # +request_method+ with the same lookup, two method calls further down.
205
+ def default_status
206
+ request_method = env[Rack::REQUEST_METHOD]
207
+ return 201 if request_method == Rack::POST
208
+ return 204 if request_method == Rack::DELETE && @body.blank?
209
+
210
+ 200
211
+ end
452
212
  end
453
213
  end
454
214
  end
@@ -3,19 +3,14 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Logger
6
- include Grape::DSL::Settings
7
-
8
- attr_writer :logger
9
-
10
6
  # Set or retrive the configured logger. If none was configured, this
11
7
  # method will create a new one, logging to stdout.
12
8
  # @param logger [Object] the new logger to use
13
9
  def logger(logger = nil)
14
- if logger
15
- global_setting(:logger, logger)
16
- else
17
- global_setting(:logger) || global_setting(:logger, ::Logger.new($stdout))
18
- end
10
+ global_settings = inheritable_setting.global
11
+ return global_settings[:logger] = logger if logger
12
+
13
+ global_settings[:logger] || global_settings[:logger] = ::Logger.new($stdout)
19
14
  end
20
15
  end
21
16
  end