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