grape 2.4.0 → 3.2.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 +109 -0
- data/CONTRIBUTING.md +2 -10
- data/README.md +137 -181
- data/UPGRADING.md +154 -0
- data/grape.gemspec +4 -4
- data/lib/grape/api/instance.rb +51 -104
- data/lib/grape/api.rb +29 -42
- data/lib/grape/content_types.rb +1 -4
- data/lib/grape/declared_params_handler.rb +118 -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 +8 -67
- data/lib/grape/dsl/helpers.rb +59 -64
- data/lib/grape/dsl/inside_route.rb +28 -189
- data/lib/grape/dsl/logger.rb +3 -6
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +24 -51
- data/lib/grape/dsl/request_response.rb +136 -139
- data/lib/grape/dsl/routing.rb +240 -200
- data/lib/grape/dsl/settings.rb +23 -135
- data/lib/grape/dsl/validations.rb +38 -44
- data/lib/grape/endpoint.rb +169 -205
- data/lib/grape/error_formatter/base.rb +4 -2
- data/lib/grape/exceptions/base.rb +19 -45
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- 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_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 +7 -4
- data/lib/grape/exceptions/validation_errors.rb +13 -7
- data/lib/grape/locale/en.yml +0 -5
- data/lib/grape/middleware/auth/base.rb +2 -0
- data/lib/grape/middleware/auth/dsl.rb +9 -10
- data/lib/grape/middleware/base.rb +6 -4
- data/lib/grape/middleware/error.rb +4 -14
- data/lib/grape/middleware/formatter.rb +11 -7
- data/lib/grape/middleware/stack.rb +3 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
- data/lib/grape/middleware/versioner/base.rb +42 -40
- data/lib/grape/middleware/versioner/header.rb +2 -18
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +2 -2
- data/lib/grape/namespace.rb +15 -8
- data/lib/grape/params_builder/base.rb +2 -0
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/request.rb +2 -10
- data/lib/grape/router/base_route.rb +14 -5
- data/lib/grape/router/greedy_route.rb +11 -5
- data/lib/grape/router/pattern.rb +6 -20
- data/lib/grape/router/route.rb +7 -11
- data/lib/grape/router.rb +42 -65
- data/lib/grape/util/api_description.rb +58 -0
- data/lib/grape/util/base_inheritable.rb +5 -2
- data/lib/grape/util/cache.rb +1 -0
- data/lib/grape/util/deep_freeze.rb +35 -0
- data/lib/grape/util/inheritable_setting.rb +8 -1
- data/lib/grape/util/media_type.rb +2 -2
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +35 -20
- data/lib/grape/validations/contract_scope.rb +2 -8
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/param_scope_tracker.rb +57 -0
- data/lib/grape/validations/params_documentation.rb +50 -0
- data/lib/grape/validations/params_scope.rb +147 -156
- data/lib/grape/validations/single_attribute_iterator.rb +2 -2
- data/lib/grape/validations/types/array_coercer.rb +2 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
- data/lib/grape/validations/types/primitive_coercer.rb +1 -28
- data/lib/grape/validations/types.rb +10 -25
- 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 +95 -25
- data/lib/grape/validations/validators/coerce_validator.rb +15 -35
- data/lib/grape/validations/validators/contract_scope_validator.rb +9 -5
- data/lib/grape/validations/validators/default_validator.rb +12 -18
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +13 -4
- data/lib/grape/validations/validators/length_validator.rb +21 -22
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- 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 +6 -15
- data/lib/grape/validations/validators/values_validator.rb +29 -21
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +29 -23
- metadata +31 -26
- 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/types/invalid_value.rb +0 -8
- 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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
class ApiDescription
|
|
6
|
+
DSL_METHODS = %i[
|
|
7
|
+
body_name
|
|
8
|
+
consumes
|
|
9
|
+
default
|
|
10
|
+
deprecated
|
|
11
|
+
detail
|
|
12
|
+
entity
|
|
13
|
+
headers
|
|
14
|
+
hidden
|
|
15
|
+
http_codes
|
|
16
|
+
is_array
|
|
17
|
+
named
|
|
18
|
+
nickname
|
|
19
|
+
params
|
|
20
|
+
produces
|
|
21
|
+
security
|
|
22
|
+
summary
|
|
23
|
+
tags
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
def initialize(description, endpoint_configuration, &)
|
|
27
|
+
@endpoint_configuration = endpoint_configuration
|
|
28
|
+
@attributes = { description: }
|
|
29
|
+
instance_eval(&)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
DSL_METHODS.each do |attribute|
|
|
33
|
+
define_method attribute do |value|
|
|
34
|
+
@attributes[attribute] = value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
alias success entity
|
|
39
|
+
alias failure http_codes
|
|
40
|
+
|
|
41
|
+
def configuration
|
|
42
|
+
@configuration ||= eval_endpoint_config(@endpoint_configuration)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def settings
|
|
46
|
+
@attributes
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def eval_endpoint_config(configuration)
|
|
52
|
+
return configuration if configuration.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
configuration.evaluate
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -14,8 +14,11 @@ module Grape
|
|
|
14
14
|
@new_values = {}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def delete(
|
|
18
|
-
|
|
17
|
+
def delete(*keys)
|
|
18
|
+
keys.map do |key|
|
|
19
|
+
# since delete returns the deleted value, seems natural to `map` the result
|
|
20
|
+
new_values.delete key
|
|
21
|
+
end
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
def initialize_copy(other)
|
data/lib/grape/util/cache.rb
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module DeepFreeze
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# Recursively freezes Hash (keys and values), Array (elements), and String
|
|
9
|
+
# objects. All other types are returned as-is.
|
|
10
|
+
#
|
|
11
|
+
# Intentionally left unfrozen:
|
|
12
|
+
# - Procs / lambdas — may be deferred DB-backed callables
|
|
13
|
+
# - Coercers (e.g. ArrayCoercer) — use lazy ivar memoization at request time
|
|
14
|
+
# - Classes / Modules — shared constants that must remain open
|
|
15
|
+
# - ParamsScope — self-freezes at the end of its own initialize
|
|
16
|
+
def deep_freeze(obj)
|
|
17
|
+
case obj
|
|
18
|
+
when Hash
|
|
19
|
+
obj.each do |k, v|
|
|
20
|
+
deep_freeze(k)
|
|
21
|
+
deep_freeze(v)
|
|
22
|
+
end
|
|
23
|
+
obj.freeze
|
|
24
|
+
when Array
|
|
25
|
+
obj.each { |v| deep_freeze(v) }
|
|
26
|
+
obj.freeze
|
|
27
|
+
when String
|
|
28
|
+
obj.freeze
|
|
29
|
+
else
|
|
30
|
+
obj
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -55,7 +55,7 @@ module Grape
|
|
|
55
55
|
namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable
|
|
56
56
|
self.route = parent.route.merge(route)
|
|
57
57
|
|
|
58
|
-
point_in_time_copies.
|
|
58
|
+
point_in_time_copies.each { |cloned_one| cloned_one.inherit_from parent }
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Create a point-in-time copy of this settings instance, with clones of
|
|
@@ -95,6 +95,13 @@ module Grape
|
|
|
95
95
|
namespace_reverse_stackable: namespace_reverse_stackable.to_hash
|
|
96
96
|
}
|
|
97
97
|
end
|
|
98
|
+
|
|
99
|
+
def namespace_stackable_with_hash(key)
|
|
100
|
+
data = namespace_stackable[key]
|
|
101
|
+
return if data.blank?
|
|
102
|
+
|
|
103
|
+
data.each_with_object({}) { |value, result| result.deep_merge!(value) }
|
|
104
|
+
end
|
|
98
105
|
end
|
|
99
106
|
end
|
|
100
107
|
end
|
|
@@ -7,7 +7,7 @@ module Grape
|
|
|
7
7
|
|
|
8
8
|
# based on the HTTP Accept header with the pattern:
|
|
9
9
|
# application/vnd.:vendor-:version+:format
|
|
10
|
-
VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z
|
|
10
|
+
VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z/
|
|
11
11
|
|
|
12
12
|
def initialize(type:, subtype:)
|
|
13
13
|
@type = type
|
|
@@ -47,7 +47,7 @@ module Grape
|
|
|
47
47
|
type, subtype = media_type.split('/', 2)
|
|
48
48
|
return if type.blank? || subtype.blank?
|
|
49
49
|
|
|
50
|
-
new(type
|
|
50
|
+
new(type:, subtype:)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def match?(media_type)
|
data/lib/grape/util/registry.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Grape
|
|
|
7
7
|
short_name = build_short_name(klass)
|
|
8
8
|
return if short_name.nil?
|
|
9
9
|
|
|
10
|
-
warn "#{short_name} is already registered with class #{klass}" if registry.key?(short_name)
|
|
10
|
+
warn "#{short_name} is already registered with class #{registry[short_name]}. It will be overridden globally with the following: #{klass.name}" if registry.key?(short_name)
|
|
11
11
|
registry[short_name] = klass
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Translation
|
|
6
|
+
FALLBACK_LOCALE = :en
|
|
7
|
+
private_constant :FALLBACK_LOCALE
|
|
8
|
+
# Sentinel returned by I18n when a key is missing (passed as the default:
|
|
9
|
+
# value). Using a named class rather than plain Object.new makes it
|
|
10
|
+
# identifiable in debug output and immune to backends that call .to_s on
|
|
11
|
+
# the default before returning it.
|
|
12
|
+
MISSING = Class.new { def inspect = 'Grape::Util::Translation::MISSING' }.new.freeze
|
|
13
|
+
private_constant :MISSING
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Extra keyword args (**) are forwarded verbatim to I18n as interpolation
|
|
18
|
+
# variables (e.g. +min:+, +max:+ from LengthValidator's Hash message).
|
|
19
|
+
# Callers must not pass unintended keyword arguments — any extra keyword
|
|
20
|
+
# will silently become an I18n interpolation variable.
|
|
21
|
+
def translate(key, default: MISSING, scope: 'grape.errors.messages', locale: nil, **)
|
|
22
|
+
i18n_opts = { default:, scope:, ** }
|
|
23
|
+
i18n_opts[:locale] = locale if locale
|
|
24
|
+
message = ::I18n.translate(key, **i18n_opts)
|
|
25
|
+
return message unless message.equal?(MISSING)
|
|
26
|
+
|
|
27
|
+
effective_default = default.equal?(MISSING) ? [*Array(scope), key].join('.') : default
|
|
28
|
+
return effective_default if fallback_locale?(locale) || fallback_locale_unavailable?
|
|
29
|
+
|
|
30
|
+
::I18n.translate(key, default: effective_default, scope:, locale: FALLBACK_LOCALE, **)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fallback_locale?(locale)
|
|
34
|
+
(locale || ::I18n.locale) == FALLBACK_LOCALE
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fallback_locale_unavailable?
|
|
38
|
+
::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -7,52 +7,67 @@ module Grape
|
|
|
7
7
|
|
|
8
8
|
attr_reader :scope
|
|
9
9
|
|
|
10
|
-
def initialize(
|
|
10
|
+
def initialize(attrs, scope, params)
|
|
11
|
+
@attrs = attrs
|
|
11
12
|
@scope = scope
|
|
12
|
-
@attrs = validator.attrs
|
|
13
13
|
@original_params = scope.params(params)
|
|
14
14
|
@params = Array.wrap(@original_params)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def each(&
|
|
18
|
-
do_each(@params, &
|
|
17
|
+
def each(&)
|
|
18
|
+
do_each(@params, &) # because we need recursion for nested arrays
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
|
-
def do_each(params_to_process,
|
|
24
|
-
@scope.reset_index # gets updated depending on the size of params_to_process
|
|
23
|
+
def do_each(params_to_process, parent_indices = [], &block)
|
|
25
24
|
params_to_process.each_with_index do |resource_params, index|
|
|
26
25
|
# when we get arrays of arrays it means that target element located inside array
|
|
27
|
-
# we need this because we want to know parent arrays
|
|
26
|
+
# we need this because we want to know parent arrays indices
|
|
28
27
|
if resource_params.is_a?(Array)
|
|
29
|
-
do_each(resource_params, [index] +
|
|
28
|
+
do_each(resource_params, [index] + parent_indices, &block)
|
|
30
29
|
next
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
if @scope.type == Array
|
|
34
33
|
next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
store_indices(@scope, index, parent_indices)
|
|
36
|
+
elsif @original_params.is_a?(Array)
|
|
37
|
+
# Lateral scope (no @element) whose params resolved to an array —
|
|
38
|
+
# delegate index tracking to the nearest array-typed ancestor so
|
|
39
|
+
# that full_name produces the correct bracketed index.
|
|
40
|
+
target = @scope.nearest_array_ancestor
|
|
41
|
+
store_indices(target, index, parent_indices) if target
|
|
43
42
|
end
|
|
44
43
|
|
|
45
|
-
yield_attributes(resource_params,
|
|
44
|
+
yield_attributes(resource_params, &block)
|
|
46
45
|
end
|
|
47
46
|
end
|
|
48
47
|
|
|
49
|
-
def
|
|
48
|
+
def store_indices(target_scope, index, parent_indices)
|
|
49
|
+
# No tracker means we're outside a ParamScopeTracker.track block (e.g.
|
|
50
|
+
# a unit test that invokes a validator directly). Index tracking is
|
|
51
|
+
# skipped — full_name will produce bracket-less names — but validation
|
|
52
|
+
# continues rather than crashing.
|
|
53
|
+
tracker = ParamScopeTracker.current or return
|
|
54
|
+
parent_scope = target_scope.parent
|
|
55
|
+
parent_indices.each do |parent_index|
|
|
56
|
+
break unless parent_scope
|
|
57
|
+
|
|
58
|
+
tracker.store_index(parent_scope, parent_index)
|
|
59
|
+
parent_scope = parent_scope.parent
|
|
60
|
+
end
|
|
61
|
+
tracker.store_index(target_scope, index)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def yield_attributes(_resource_params)
|
|
50
65
|
raise NotImplementedError
|
|
51
66
|
end
|
|
52
67
|
|
|
53
|
-
# This is a special case so that we can ignore
|
|
54
|
-
# values are missing lower down. Unfortunately we can remove this
|
|
55
|
-
#
|
|
68
|
+
# This is a special case so that we can ignore trees where option
|
|
69
|
+
# values are missing lower down. Unfortunately we can't remove this
|
|
70
|
+
# at the parameter parsing stage as they are required to ensure
|
|
56
71
|
# the correct indexing is maintained
|
|
57
72
|
def skip?(val)
|
|
58
73
|
val == Grape::DSL::Parameters::EmptyOptionalValue
|
|
@@ -20,14 +20,8 @@ module Grape
|
|
|
20
20
|
key_map = contract.key_map
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
api.namespace_stackable
|
|
24
|
-
|
|
25
|
-
validator_options = {
|
|
26
|
-
validator_class: Grape::Validations.require_validator(:contract_scope),
|
|
27
|
-
opts: { schema: contract, fail_fast: false }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
api.namespace_stackable(:validations, validator_options)
|
|
23
|
+
api.inheritable_setting.namespace_stackable[:contract_key_map] = key_map
|
|
24
|
+
api.inheritable_setting.namespace_stackable[:validations] = Validators::ContractScopeValidator.new(schema: contract)
|
|
31
25
|
end
|
|
32
26
|
end
|
|
33
27
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
# Holds per-request mutable state that must not live on shared ParamsScope
|
|
6
|
+
# instances. Both trackers are identity-keyed hashes so that ParamsScope
|
|
7
|
+
# objects can serve as keys without relying on value equality.
|
|
8
|
+
#
|
|
9
|
+
# Lifecycle is managed by Endpoint#run_validators via +.track+.
|
|
10
|
+
# Use +.current+ to access the instance for the running request.
|
|
11
|
+
class ParamScopeTracker
|
|
12
|
+
# Fiber-local key used to store the current tracker.
|
|
13
|
+
# Fiber[] (Ruby 3.0+) is used instead of Thread.current[] so that
|
|
14
|
+
# fiber-based servers (e.g. Falcon with async) isolate each request's
|
|
15
|
+
# tracker within its own fiber rather than sharing state across all
|
|
16
|
+
# fibers running on the same thread.
|
|
17
|
+
FIBER_KEY = :grape_param_scope_tracker
|
|
18
|
+
EMPTY_PARAMS = [].freeze
|
|
19
|
+
|
|
20
|
+
def self.track
|
|
21
|
+
previous = Fiber[FIBER_KEY]
|
|
22
|
+
Fiber[FIBER_KEY] = new
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
Fiber[FIBER_KEY] = previous
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.current
|
|
29
|
+
Fiber[FIBER_KEY]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@index_tracker = {}.compare_by_identity
|
|
34
|
+
@qualifying_params_tracker = {}.compare_by_identity
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def store_index(scope, index)
|
|
38
|
+
@index_tracker.store(scope, index)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def index_for(scope)
|
|
42
|
+
@index_tracker[scope]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns qualifying params for +scope+, or EMPTY_PARAMS if none were stored.
|
|
46
|
+
# Note: an explicitly stored empty array and "never stored" are treated identically
|
|
47
|
+
# by callers (both yield a blank result that falls through to the parent params).
|
|
48
|
+
def qualifying_params(scope)
|
|
49
|
+
@qualifying_params_tracker.fetch(scope, EMPTY_PARAMS)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def store_qualifying_params(scope, params)
|
|
53
|
+
@qualifying_params_tracker.store(scope, params)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
# Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
|
|
6
|
+
# internal API), the class only cleans up attributes to avoid junk in RAM.
|
|
7
|
+
|
|
8
|
+
module ParamsDocumentation
|
|
9
|
+
def document_params(attrs, validations, type = nil, values = nil, except_values = nil)
|
|
10
|
+
return validations.except!(:desc, :description, :documentation) if @api.inheritable_setting.namespace_inheritable[:do_not_document]
|
|
11
|
+
|
|
12
|
+
documented_attrs = attrs.each_with_object({}) do |name, memo|
|
|
13
|
+
memo[full_name(name)] = extract_details(validations, type, values, except_values)
|
|
14
|
+
end
|
|
15
|
+
@api.inheritable_setting.namespace_stackable[:params] = documented_attrs
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def extract_details(validations, type, values, except_values)
|
|
21
|
+
{}.tap do |details|
|
|
22
|
+
details[:required] = validations.key?(:presence)
|
|
23
|
+
details[:type] = TypeCache[type] if type
|
|
24
|
+
details[:values] = values if values
|
|
25
|
+
details[:except_values] = except_values if except_values
|
|
26
|
+
details[:default] = validations[:default] if validations.key?(:default)
|
|
27
|
+
if validations.key?(:length)
|
|
28
|
+
details[:min_length] = validations[:length][:min] if validations[:length].key?(:min)
|
|
29
|
+
details[:max_length] = validations[:length][:max] if validations[:length].key?(:max)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc = validations.delete(:desc) || validations.delete(:description)
|
|
33
|
+
details[:desc] = desc if desc
|
|
34
|
+
|
|
35
|
+
documentation = validations.delete(:documentation)
|
|
36
|
+
details[:documentation] = documentation if documentation
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class TypeCache < Grape::Util::Cache
|
|
41
|
+
def initialize
|
|
42
|
+
super
|
|
43
|
+
@cache = Hash.new do |h, type|
|
|
44
|
+
h[type] = type.to_s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|