grape 0.1.1 → 2.4.0

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 (197) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1167 -0
  3. data/CONTRIBUTING.md +156 -0
  4. data/LICENSE +1 -1
  5. data/README.md +4192 -0
  6. data/UPGRADING.md +1697 -0
  7. data/grape.gemspec +28 -105
  8. data/grape.png +0 -0
  9. data/lib/grape/api/helpers.rb +9 -0
  10. data/lib/grape/api/instance.rb +247 -0
  11. data/lib/grape/api.rb +158 -194
  12. data/lib/grape/content_types.rb +31 -0
  13. data/lib/grape/cookies.rb +50 -0
  14. data/lib/grape/dry_types.rb +10 -0
  15. data/lib/grape/dsl/api.rb +17 -0
  16. data/lib/grape/dsl/callbacks.rb +69 -0
  17. data/lib/grape/dsl/configuration.rb +15 -0
  18. data/lib/grape/dsl/desc.rb +124 -0
  19. data/lib/grape/dsl/headers.rb +21 -0
  20. data/lib/grape/dsl/helpers.rb +108 -0
  21. data/lib/grape/dsl/inside_route.rb +454 -0
  22. data/lib/grape/dsl/logger.rb +22 -0
  23. data/lib/grape/dsl/middleware.rb +54 -0
  24. data/lib/grape/dsl/parameters.rb +266 -0
  25. data/lib/grape/dsl/request_response.rb +171 -0
  26. data/lib/grape/dsl/routing.rb +248 -0
  27. data/lib/grape/dsl/settings.rb +179 -0
  28. data/lib/grape/dsl/validations.rb +57 -0
  29. data/lib/grape/endpoint.rb +398 -68
  30. data/lib/grape/env.rb +20 -0
  31. data/lib/grape/error_formatter/base.rb +68 -0
  32. data/lib/grape/error_formatter/json.rb +28 -0
  33. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  34. data/lib/grape/error_formatter/txt.rb +21 -0
  35. data/lib/grape/error_formatter/xml.rb +11 -0
  36. data/lib/grape/error_formatter.rb +15 -0
  37. data/lib/grape/exceptions/base.rb +76 -0
  38. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  39. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  40. data/lib/grape/exceptions/incompatible_option_values.rb +11 -0
  41. data/lib/grape/exceptions/invalid_accept_header.rb +11 -0
  42. data/lib/grape/exceptions/invalid_formatter.rb +11 -0
  43. data/lib/grape/exceptions/invalid_message_body.rb +11 -0
  44. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  45. data/lib/grape/exceptions/invalid_response.rb +11 -0
  46. data/lib/grape/exceptions/invalid_version_header.rb +11 -0
  47. data/lib/grape/exceptions/invalid_versioner_option.rb +11 -0
  48. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +11 -0
  49. data/lib/grape/exceptions/method_not_allowed.rb +11 -0
  50. data/lib/grape/exceptions/missing_group_type.rb +13 -0
  51. data/lib/grape/exceptions/missing_mime_type.rb +11 -0
  52. data/lib/grape/exceptions/missing_option.rb +11 -0
  53. data/lib/grape/exceptions/missing_vendor_option.rb +11 -0
  54. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  55. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  56. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  57. data/lib/grape/exceptions/unknown_options.rb +11 -0
  58. data/lib/grape/exceptions/unknown_parameter.rb +11 -0
  59. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  60. data/lib/grape/exceptions/unknown_validator.rb +11 -0
  61. data/lib/grape/exceptions/unsupported_group_type.rb +13 -0
  62. data/lib/grape/exceptions/validation.rb +25 -0
  63. data/lib/grape/exceptions/validation_array_errors.rb +14 -0
  64. data/lib/grape/exceptions/validation_errors.rb +53 -0
  65. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +24 -0
  66. data/lib/grape/extensions/hash.rb +27 -0
  67. data/lib/grape/extensions/hashie/mash.rb +24 -0
  68. data/lib/grape/formatter/base.rb +16 -0
  69. data/lib/grape/formatter/json.rb +13 -0
  70. data/lib/grape/formatter/serializable_hash.rb +39 -0
  71. data/lib/grape/formatter/txt.rb +11 -0
  72. data/lib/grape/formatter/xml.rb +13 -0
  73. data/lib/grape/formatter.rb +17 -0
  74. data/lib/grape/json.rb +10 -0
  75. data/lib/grape/locale/en.yml +59 -0
  76. data/lib/grape/middleware/auth/base.rb +23 -0
  77. data/lib/grape/middleware/auth/dsl.rb +39 -0
  78. data/lib/grape/middleware/auth/strategies.rb +25 -0
  79. data/lib/grape/middleware/auth/strategy_info.rb +15 -0
  80. data/lib/grape/middleware/base.rb +89 -18
  81. data/lib/grape/middleware/error.rb +129 -10
  82. data/lib/grape/middleware/filter.rb +19 -0
  83. data/lib/grape/middleware/formatter.rb +124 -82
  84. data/lib/grape/middleware/globals.rb +14 -0
  85. data/lib/grape/middleware/stack.rb +109 -0
  86. data/lib/grape/middleware/versioner/accept_version_header.rb +38 -0
  87. data/lib/grape/middleware/versioner/base.rb +74 -0
  88. data/lib/grape/middleware/versioner/header.rb +127 -0
  89. data/lib/grape/middleware/versioner/param.rb +32 -0
  90. data/lib/grape/middleware/versioner/path.rb +40 -0
  91. data/lib/grape/middleware/versioner.rb +21 -21
  92. data/lib/grape/namespace.rb +46 -0
  93. data/lib/grape/params_builder/base.rb +18 -0
  94. data/lib/grape/params_builder/hash.rb +11 -0
  95. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  96. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  97. data/lib/grape/params_builder.rb +32 -0
  98. data/lib/grape/parser/base.rb +16 -0
  99. data/lib/grape/parser/json.rb +14 -0
  100. data/lib/grape/parser/xml.rb +14 -0
  101. data/lib/grape/parser.rb +15 -0
  102. data/lib/grape/path.rb +76 -0
  103. data/lib/grape/presenters/presenter.rb +11 -0
  104. data/lib/grape/railtie.rb +9 -0
  105. data/lib/grape/request.rb +190 -0
  106. data/lib/grape/router/base_route.rb +39 -0
  107. data/lib/grape/router/greedy_route.rb +20 -0
  108. data/lib/grape/router/pattern.rb +81 -0
  109. data/lib/grape/router/route.rb +62 -0
  110. data/lib/grape/router.rb +190 -0
  111. data/lib/grape/serve_stream/file_body.rb +36 -0
  112. data/lib/grape/serve_stream/sendfile_response.rb +21 -0
  113. data/lib/grape/serve_stream/stream_response.rb +23 -0
  114. data/lib/grape/types/invalid_value.rb +8 -0
  115. data/lib/grape/util/base_inheritable.rb +43 -0
  116. data/lib/grape/util/cache.rb +17 -0
  117. data/lib/grape/util/endpoint_configuration.rb +8 -0
  118. data/lib/grape/util/header.rb +13 -0
  119. data/lib/grape/util/inheritable_setting.rb +100 -0
  120. data/lib/grape/util/inheritable_values.rb +29 -0
  121. data/lib/grape/util/lazy/block.rb +29 -0
  122. data/lib/grape/util/lazy/value.rb +38 -0
  123. data/lib/grape/util/lazy/value_array.rb +21 -0
  124. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  125. data/lib/grape/util/lazy/value_hash.rb +21 -0
  126. data/lib/grape/util/media_type.rb +70 -0
  127. data/lib/grape/util/registry.rb +27 -0
  128. data/lib/grape/util/reverse_stackable_values.rb +15 -0
  129. data/lib/grape/util/stackable_values.rb +36 -0
  130. data/lib/grape/util/strict_hash_configuration.rb +108 -0
  131. data/lib/grape/validations/attributes_doc.rb +60 -0
  132. data/lib/grape/validations/attributes_iterator.rb +62 -0
  133. data/lib/grape/validations/contract_scope.rb +34 -0
  134. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  135. data/lib/grape/validations/params_scope.rb +538 -0
  136. data/lib/grape/validations/single_attribute_iterator.rb +26 -0
  137. data/lib/grape/validations/types/array_coercer.rb +61 -0
  138. data/lib/grape/validations/types/custom_type_coercer.rb +164 -0
  139. data/lib/grape/validations/types/custom_type_collection_coercer.rb +56 -0
  140. data/lib/grape/validations/types/dry_type_coercer.rb +66 -0
  141. data/lib/grape/validations/types/file.rb +31 -0
  142. data/lib/grape/validations/types/invalid_value.rb +17 -0
  143. data/lib/grape/validations/types/json.rb +71 -0
  144. data/lib/grape/validations/types/multiple_type_coercer.rb +57 -0
  145. data/lib/grape/validations/types/primitive_coercer.rb +73 -0
  146. data/lib/grape/validations/types/set_coercer.rb +35 -0
  147. data/lib/grape/validations/types/variant_collection_coercer.rb +51 -0
  148. data/lib/grape/validations/types.rb +213 -0
  149. data/lib/grape/validations/validator_factory.rb +15 -0
  150. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  151. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  152. data/lib/grape/validations/validators/as_validator.rb +14 -0
  153. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  154. data/lib/grape/validations/validators/base.rb +88 -0
  155. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  156. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  157. data/lib/grape/validations/validators/default_validator.rb +37 -0
  158. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  159. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  160. data/lib/grape/validations/validators/length_validator.rb +49 -0
  161. data/lib/grape/validations/validators/multiple_params_base.rb +34 -0
  162. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  163. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  164. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  165. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  166. data/lib/grape/validations/validators/values_validator.rb +60 -0
  167. data/lib/grape/validations.rb +21 -0
  168. data/lib/grape/version.rb +6 -0
  169. data/lib/grape/xml.rb +10 -0
  170. data/lib/grape.rb +81 -17
  171. metadata +250 -192
  172. data/.document +0 -5
  173. data/.gitignore +0 -23
  174. data/.rspec +0 -2
  175. data/.rvmrc +0 -1
  176. data/Gemfile +0 -21
  177. data/Gemfile.lock +0 -58
  178. data/README.markdown +0 -75
  179. data/Rakefile +0 -70
  180. data/VERSION +0 -1
  181. data/autotest/discover.rb +0 -1
  182. data/lib/grape/middleware/auth/basic.rb +0 -30
  183. data/lib/grape/middleware/auth/oauth2.rb +0 -55
  184. data/lib/grape/middleware/prefixer.rb +0 -21
  185. data/lib/grape/middleware_stack.rb +0 -35
  186. data/spec/grape/api_spec.rb +0 -343
  187. data/spec/grape/endpoint_spec.rb +0 -104
  188. data/spec/grape/middleware/auth/basic_spec.rb +0 -31
  189. data/spec/grape/middleware/auth/oauth2_spec.rb +0 -88
  190. data/spec/grape/middleware/base_spec.rb +0 -63
  191. data/spec/grape/middleware/error_spec.rb +0 -49
  192. data/spec/grape/middleware/formatter_spec.rb +0 -87
  193. data/spec/grape/middleware/prefixer_spec.rb +0 -30
  194. data/spec/grape/middleware/versioner_spec.rb +0 -40
  195. data/spec/grape/middleware_stack_spec.rb +0 -47
  196. data/spec/grape_spec.rb +0 -1
  197. data/spec/spec_helper.rb +0 -20
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module DSL
5
+ module Desc
6
+ include Grape::DSL::Settings
7
+
8
+ ROUTE_ATTRIBUTES = %i[
9
+ body_name
10
+ consumes
11
+ default
12
+ deprecated
13
+ description
14
+ detail
15
+ entity
16
+ headers
17
+ hidden
18
+ http_codes
19
+ is_array
20
+ named
21
+ nickname
22
+ params
23
+ produces
24
+ security
25
+ summary
26
+ tags
27
+ ].freeze
28
+
29
+ # Add a description to the next namespace or function.
30
+ # @param description [String] descriptive string for this endpoint
31
+ # or namespace
32
+ # @param options [Hash] other properties you can set to describe the
33
+ # endpoint or namespace. Optional.
34
+ # @option options :detail [String] additional detail about this endpoint
35
+ # @option options :summary [String] summary for this endpoint
36
+ # @option options :params [Hash] param types and info. normally, you set
37
+ # these via the `params` dsl method.
38
+ # @option options :entity [Grape::Entity] the entity returned upon a
39
+ # successful call to this action
40
+ # @option options :http_codes [Array[Array]] possible HTTP codes this
41
+ # endpoint may return, with their meanings, in a 2d array
42
+ # @option options :named [String] a specific name to help find this route
43
+ # @option options :body_name [String] override the autogenerated body name param
44
+ # @option options :headers [Hash] HTTP headers this method can accept
45
+ # @option options :hidden [Boolean] hide the endpoint or not
46
+ # @option options :deprecated [Boolean] deprecate the endpoint or not
47
+ # @option options :is_array [Boolean] response entity is array or not
48
+ # @option options :nickname [String] nickname of the endpoint
49
+ # @option options :produces [Array[String]] a list of MIME types the endpoint produce
50
+ # @option options :consumes [Array[String]] a list of MIME types the endpoint consume
51
+ # @option options :security [Array[Hash]] a list of security schemes
52
+ # @option options :tags [Array[String]] a list of tags
53
+ # @yield a block yielding an instance context with methods mapping to
54
+ # each of the above, except that :entity is also aliased as #success
55
+ # and :http_codes is aliased as #failure.
56
+ #
57
+ # @example
58
+ #
59
+ # desc 'create a user'
60
+ # post '/users' do
61
+ # # ...
62
+ # end
63
+ #
64
+ # desc 'find a user' do
65
+ # detail 'locates the user from the given user ID'
66
+ # failure [ [404, 'Couldn\'t find the given user' ] ]
67
+ # success User::Entity
68
+ # end
69
+ # get '/user/:id' do
70
+ # # ...
71
+ # end
72
+ #
73
+ def desc(description, options = nil, &config_block)
74
+ opts =
75
+ if config_block
76
+ desc_container(endpoint_configuration).then do |config_class|
77
+ config_class.configure do
78
+ description(description)
79
+ end
80
+
81
+ config_class.configure(&config_block)
82
+ config_class.settings
83
+ end
84
+ else
85
+ options&.merge(description: description) || { description: description }
86
+ end
87
+
88
+ namespace_setting :description, opts
89
+ route_setting :description, opts
90
+ end
91
+
92
+ # Returns an object which configures itself via an instance-context DSL.
93
+ def desc_container(endpoint_configuration)
94
+ Module.new do
95
+ include Grape::Util::StrictHashConfiguration.module(*ROUTE_ATTRIBUTES)
96
+ config_context.define_singleton_method(:configuration) do
97
+ endpoint_configuration
98
+ end
99
+
100
+ def config_context.success(*args)
101
+ entity(*args)
102
+ end
103
+
104
+ def config_context.failure(*args)
105
+ http_codes(*args)
106
+ end
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def endpoint_configuration
113
+ return {} unless defined?(configuration)
114
+
115
+ if configuration.respond_to?(:evaluate)
116
+ configuration.evaluate
117
+ # Within `given` or `mounted blocks` the configuration is already evaluated
118
+ elsif configuration.is_a?(Hash)
119
+ configuration
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module DSL
5
+ module Headers
6
+ # This method has four responsibilities:
7
+ # 1. Set a specifc header value by key
8
+ # 2. Retrieve a specifc header value by key
9
+ # 3. Retrieve all headers that have been set
10
+ # 4. Delete a specifc header key-value pair
11
+ def header(key = nil, val = nil)
12
+ if key
13
+ val ? header[key] = val : header.delete(key)
14
+ else
15
+ @header ||= Grape::Util::Header.new
16
+ end
17
+ end
18
+ alias headers header
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module DSL
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
40
+
41
+ protected
42
+
43
+ def include_new_modules(modules)
44
+ return if modules.empty?
45
+
46
+ modules.each { |mod| make_inclusion(mod) }
47
+ end
48
+
49
+ def include_block(block)
50
+ return unless block
51
+
52
+ Module.new.tap do |mod|
53
+ make_inclusion(mod) { mod.class_eval(&block) }
54
+ end
55
+ end
56
+
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
62
+
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
69
+
70
+ def define_boolean_in_mod(mod)
71
+ return if defined? mod::Boolean
72
+
73
+ mod.const_set(:Boolean, Grape::API::Boolean)
74
+ end
75
+
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)
80
+ end
81
+ end
82
+
83
+ # This module extends user defined helpers
84
+ # to provide some API-specific functionality.
85
+ module BaseHelper
86
+ attr_accessor :api
87
+
88
+ def params(name, &block)
89
+ @named_params ||= {}
90
+ @named_params[name] = block
91
+ end
92
+
93
+ def api_changed(new_api)
94
+ @api = new_api
95
+ process_named_params
96
+ end
97
+
98
+ protected
99
+
100
+ def process_named_params
101
+ return if @named_params.blank?
102
+
103
+ api.namespace_stackable(:named_params, @named_params)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,454 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module DSL
5
+ module InsideRoute
6
+ extend ActiveSupport::Concern
7
+ include Grape::DSL::Settings
8
+ include Grape::DSL::Headers
9
+
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
152
+
153
+ # The API version as specified in the URL.
154
+ def version
155
+ env[Grape::Env::API_VERSION]
156
+ end
157
+
158
+ def configuration
159
+ options[:for].configuration.evaluate
160
+ end
161
+
162
+ # End the request and display an error to the
163
+ # end user with the specified message.
164
+ #
165
+ # @param message [String] The message to display.
166
+ # @param status [Integer] The HTTP Status Code. Defaults to default_error_status, 500 if not set.
167
+ # @param additional_headers [Hash] Addtional headers for the response.
168
+ # @param backtrace [Array<String>] The backtrace of the exception that caused the error.
169
+ # @param original_exception [Exception] The original exception that caused the error.
170
+ def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
171
+ status = self.status(status || namespace_inheritable(:default_error_status))
172
+ 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)
197
+ end
198
+
199
+ # Redirect to a new url.
200
+ #
201
+ # @param url [String] The url to be redirect.
202
+ # @param permanent [Boolean] default false.
203
+ # @param body default a short message including the URL.
204
+ def redirect(url, permanent: false, body: nil)
205
+ body_message = body
206
+ if permanent
207
+ status 301
208
+ body_message ||= "This resource has been moved permanently to #{url}."
209
+ elsif http_version == 'HTTP/1.1' && !request.get?
210
+ status 303
211
+ body_message ||= "An alternate resource is located at #{url}."
212
+ else
213
+ status 302
214
+ body_message ||= "This resource has been moved temporarily to #{url}."
215
+ end
216
+ header 'Location', url
217
+ content_type 'text/plain'
218
+ body body_message
219
+ end
220
+
221
+ # Set or retrieve the HTTP status code.
222
+ #
223
+ # @param status [Integer] The HTTP Status Code to return for this request.
224
+ 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)
228
+
229
+ @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
+ else
247
+ raise ArgumentError, 'Status code must be Integer or Symbol.'
248
+ end
249
+ end
250
+
251
+ # Set response content-type
252
+ def content_type(val = nil)
253
+ if val
254
+ header(Rack::CONTENT_TYPE, val)
255
+ else
256
+ header[Rack::CONTENT_TYPE]
257
+ end
258
+ end
259
+
260
+ # Allows you to define the response body as something other than the
261
+ # return value.
262
+ #
263
+ # @example
264
+ # get '/body' do
265
+ # body "Body"
266
+ # "Not the Body"
267
+ # end
268
+ #
269
+ # GET /body # => "Body"
270
+ def body(value = nil)
271
+ if value
272
+ @body = value
273
+ elsif value == false
274
+ @body = ''
275
+ status 204
276
+ else
277
+ instance_variable_defined?(:@body) ? @body : nil
278
+ end
279
+ end
280
+
281
+ # Allows you to explicitly return no content.
282
+ #
283
+ # @example
284
+ # delete :id do
285
+ # return_no_content
286
+ # "not returned"
287
+ # end
288
+ #
289
+ # DELETE /12 # => 204 No Content, ""
290
+ def return_no_content
291
+ status 204
292
+ body false
293
+ end
294
+
295
+ # Allows you to send a file to the client via sendfile.
296
+ #
297
+ # @example
298
+ # get '/file' do
299
+ # sendfile FileStreamer.new(...)
300
+ # end
301
+ #
302
+ # GET /file # => "contents of file"
303
+ 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
312
+ end
313
+
314
+ # Allows you to define the response as a streamable object.
315
+ #
316
+ # If Content-Length and Transfer-Encoding are blank (among other conditions),
317
+ # Rack assumes this response can be streamed in chunks.
318
+ #
319
+ # @example
320
+ # get '/stream' do
321
+ # stream FileStreamer.new(...)
322
+ # end
323
+ #
324
+ # GET /stream # => "chunked contents of file"
325
+ #
326
+ # See:
327
+ # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
328
+ # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
329
+ def stream(value = nil)
330
+ return if value.nil? && @stream.nil?
331
+
332
+ header Rack::CONTENT_LENGTH, nil
333
+ header 'Transfer-Encoding', nil
334
+ 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
+
379
+ representation = { root => representation } if root
380
+
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
390
+ end
391
+
392
+ # Returns route information for the current request.
393
+ #
394
+ # @example
395
+ #
396
+ # desc "Returns the route description."
397
+ # get '/' do
398
+ # route.description
399
+ # end
400
+ def route
401
+ env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
402
+ end
403
+
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
+ def http_version
442
+ env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }
443
+ end
444
+
445
+ def api_format(format)
446
+ env[Grape::Env::API_FORMAT] = format
447
+ end
448
+
449
+ def context
450
+ self
451
+ end
452
+ end
453
+ end
454
+ end