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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +351 -0
- data/CONTRIBUTING.md +18 -10
- data/README.md +457 -228
- data/UPGRADING.md +1240 -1
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +65 -116
- data/lib/grape/api.rb +53 -62
- data/lib/grape/content_types.rb +58 -3
- data/lib/grape/cookies.rb +2 -6
- data/lib/grape/declared_params_handler.rb +116 -0
- data/lib/grape/dry_types.rb +48 -4
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +24 -67
- data/lib/grape/dsl/entity.rb +106 -0
- data/lib/grape/dsl/helpers.rb +80 -65
- data/lib/grape/dsl/inside_route.rb +56 -296
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +86 -93
- data/lib/grape/dsl/request_response.rb +139 -139
- data/lib/grape/dsl/rescue_options.rb +25 -0
- data/lib/grape/dsl/routing.rb +328 -203
- data/lib/grape/dsl/settings.rb +29 -135
- data/lib/grape/dsl/validations.rb +38 -44
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +25 -0
- data/lib/grape/endpoint.rb +285 -245
- data/lib/grape/env.rb +8 -3
- data/lib/grape/error_formatter/base.rb +27 -21
- data/lib/grape/error_formatter/json.rb +6 -0
- data/lib/grape/error_formatter/txt.rb +10 -10
- data/lib/grape/error_formatter.rb +6 -2
- data/lib/grape/exceptions/base.rb +20 -44
- data/lib/grape/exceptions/error_response.rb +48 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
- data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +25 -7
- data/lib/grape/exceptions/validation_array_errors.rb +4 -0
- data/lib/grape/exceptions/validation_errors.rb +21 -23
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +6 -9
- data/lib/grape/json.rb +38 -2
- data/lib/grape/locale/en.yml +3 -5
- data/lib/grape/middleware/auth/base.rb +4 -3
- data/lib/grape/middleware/auth/dsl.rb +9 -23
- data/lib/grape/middleware/base.rb +41 -36
- data/lib/grape/middleware/error.rb +271 -73
- data/lib/grape/middleware/formatter.rb +152 -63
- data/lib/grape/middleware/precomputed_content_types.rb +51 -0
- data/lib/grape/middleware/stack.rb +20 -11
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
- data/lib/grape/middleware/versioner/base.rb +40 -48
- data/lib/grape/middleware/versioner/header.rb +5 -23
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +54 -7
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +19 -11
- data/lib/grape/params_builder/base.rb +2 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/path.rb +8 -72
- data/lib/grape/precompiled_json.rb +50 -0
- data/lib/grape/request.rb +57 -20
- data/lib/grape/router/base_route.rb +53 -7
- data/lib/grape/router/greedy_route.rb +16 -6
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern/path.rb +78 -0
- data/lib/grape/router/pattern.rb +65 -37
- data/lib/grape/router/route.rb +66 -18
- data/lib/grape/router.rb +126 -115
- data/lib/grape/serve_stream/file_body.rb +7 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +7 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +67 -0
- data/lib/grape/util/cache.rb +22 -2
- data/lib/grape/util/deep_freeze.rb +34 -0
- data/lib/grape/util/freeze_on_new.rb +20 -0
- data/lib/grape/util/inheritable_setting.rb +777 -39
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +2 -9
- data/lib/grape/util/lazy/value.rb +2 -9
- data/lib/grape/util/lazy/value_enumerable.rb +13 -16
- data/lib/grape/util/media_type.rb +13 -9
- data/lib/grape/util/path_normalizer.rb +37 -0
- data/lib/grape/util/registry.rb +13 -3
- data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
- data/lib/grape/util/stackable_values.rb +39 -10
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +61 -29
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +8 -13
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +35 -0
- data/lib/grape/validations/param_scope_tracker.rb +62 -0
- data/lib/grape/validations/params_documentation.rb +52 -0
- data/lib/grape/validations/params_scope.rb +217 -304
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +6 -4
- data/lib/grape/validations/types/array_coercer.rb +7 -10
- data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +10 -15
- data/lib/grape/validations/types/json.rb +1 -3
- data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
- data/lib/grape/validations/types/primitive_coercer.rb +14 -33
- data/lib/grape/validations/types/set_coercer.rb +1 -1
- data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
- data/lib/grape/validations/types.rb +29 -51
- data/lib/grape/validations/validations_spec.rb +164 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +118 -34
- data/lib/grape/validations/validators/coerce_validator.rb +25 -37
- data/lib/grape/validations/validators/contract_scope_validator.rb +10 -5
- data/lib/grape/validations/validators/default_validator.rb +13 -20
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +14 -4
- data/lib/grape/validations/validators/length_validator.rb +23 -22
- data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +51 -0
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +7 -15
- data/lib/grape/validations/validators/values_validator.rb +34 -26
- data/lib/grape/version.rb +1 -2
- data/lib/grape/xml.rb +8 -1
- data/lib/grape.rb +58 -31
- metadata +62 -39
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -17
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/exceptions/conflicting_types.rb +0 -11
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/invalid_parameters.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
- data/lib/grape/extensions/hash.rb +0 -27
- data/lib/grape/extensions/hashie/mash.rb +0 -24
- data/lib/grape/middleware/globals.rb +0 -14
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/base_inheritable.rb +0 -43
- data/lib/grape/util/inheritable_values.rb +0 -29
- data/lib/grape/util/reverse_stackable_values.rb +0 -15
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
- data/lib/grape/validations/validator_factory.rb +0 -15
data/lib/grape/dsl/helpers.rb
CHANGED
|
@@ -3,80 +3,95 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module Helpers
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
37
|
+
private
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
def include_new_modules(modules)
|
|
40
|
+
return if modules.empty?
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
modules.each { |mod| make_inclusion(mod) }
|
|
43
|
+
end
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
def include_block(block)
|
|
46
|
+
return unless block
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
48
|
+
mod = Module.new
|
|
49
|
+
make_inclusion(mod) { mod.class_eval(&block) }
|
|
50
|
+
end
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
65
|
+
def define_boolean_in_mod(mod)
|
|
66
|
+
return if defined? mod::Boolean
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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.
|
|
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
|
-
|
|
7
|
-
include
|
|
8
|
-
include Grape::DSL::Headers
|
|
6
|
+
include Declared
|
|
7
|
+
include Entity
|
|
9
8
|
|
|
10
|
-
#
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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, '
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
159
|
+
return @stream if value.nil?
|
|
380
160
|
|
|
381
|
-
|
|
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
|
data/lib/grape/dsl/logger.rb
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|