grape 1.8.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 +517 -1
- data/CONTRIBUTING.md +19 -11
- data/README.md +841 -545
- data/UPGRADING.md +1553 -7
- data/grape.gemspec +11 -14
- data/lib/grape/api/instance.rb +77 -160
- data/lib/grape/api.rb +75 -107
- data/lib/grape/content_types.rb +67 -9
- 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 +8 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +25 -63
- data/lib/grape/dsl/entity.rb +106 -0
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +83 -64
- data/lib/grape/dsl/inside_route.rb +69 -296
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +88 -94
- data/lib/grape/dsl/request_response.rb +143 -147
- data/lib/grape/dsl/rescue_options.rb +25 -0
- data/lib/grape/dsl/routing.rb +333 -191
- 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 +25 -0
- data/lib/grape/endpoint.rb +319 -269
- data/lib/grape/{util/env.rb → env.rb} +8 -8
- data/lib/grape/error_formatter/base.rb +57 -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 +9 -25
- data/lib/grape/exceptions/base.rb +22 -58
- 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 +11 -0
- 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 +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 +28 -11
- 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 +5 -6
- data/lib/grape/formatter/serializable_hash.rb +7 -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 +44 -42
- data/lib/grape/middleware/auth/base.rb +11 -33
- data/lib/grape/middleware/auth/dsl.rb +12 -34
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/base.rb +56 -33
- data/lib/grape/middleware/error.rb +290 -91
- data/lib/grape/middleware/formatter.rb +164 -106
- data/lib/grape/middleware/precomputed_content_types.rb +51 -0
- data/lib/grape/middleware/stack.rb +38 -40
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +66 -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 +48 -25
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +21 -14
- 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 +8 -94
- data/lib/grape/precompiled_json.rb +50 -0
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +200 -26
- data/lib/grape/router/base_route.rb +85 -0
- data/lib/grape/router/greedy_route.rb +30 -0
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern/path.rb +78 -0
- data/lib/grape/router/pattern.rb +77 -35
- data/lib/grape/router/route.rb +79 -60
- data/lib/grape/router.rb +128 -104
- 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 -5
- data/lib/grape/util/deep_freeze.rb +34 -0
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/freeze_on_new.rb +20 -0
- data/lib/grape/util/header.rb +13 -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 +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 +74 -0
- data/lib/grape/util/path_normalizer.rb +37 -0
- data/lib/grape/util/registry.rb +37 -0
- data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
- data/lib/grape/util/stackable_values.rb +40 -16
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +61 -28
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +29 -0
- 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 +223 -306
- 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 -12
- 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 +17 -28
- data/lib/grape/validations/types/json.rb +1 -5
- data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
- data/lib/grape/validations/types/primitive_coercer.rb +14 -35
- data/lib/grape/validations/types/set_coercer.rb +1 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
- 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 +118 -37
- data/lib/grape/validations/validators/coerce_validator.rb +26 -38
- data/lib/grape/validations/validators/contract_scope_validator.rb +46 -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 +50 -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 +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 +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 +96 -288
- metadata +83 -294
- 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/base_inheritable.rb +0 -43
- data/lib/grape/util/inheritable_values.rb +0 -31
- 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/reverse_stackable_values.rb +0 -20
- 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
data/lib/grape/content_types.rb
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/util/registrable'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module ContentTypes
|
|
7
|
-
|
|
5
|
+
module_function
|
|
8
6
|
|
|
9
7
|
# Content types are listed in order of preference.
|
|
10
|
-
|
|
8
|
+
DEFAULTS = {
|
|
11
9
|
xml: 'application/xml',
|
|
12
10
|
serializable_hash: 'application/json',
|
|
13
11
|
json: 'application/json',
|
|
@@ -15,13 +13,73 @@ module Grape
|
|
|
15
13
|
txt: 'text/plain'
|
|
16
14
|
}.freeze
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
MIME_TYPES = Grape::ContentTypes::DEFAULTS.except(:serializable_hash).invert.freeze
|
|
17
|
+
|
|
18
|
+
def content_types_for(from_settings)
|
|
19
|
+
from_settings.presence || DEFAULTS
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def mime_types_for(from_settings)
|
|
23
|
+
return MIME_TYPES if from_settings == Grape::ContentTypes::DEFAULTS
|
|
24
|
+
|
|
25
|
+
MimeTypesCache[from_settings]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Every format under both spellings in one plain Hash, so a lookup is a
|
|
29
|
+
# single +Hash#[]+. +HashWithIndifferentAccess+ converted the key on every
|
|
30
|
+
# read instead, and this is read two or three times per request — to
|
|
31
|
+
# negotiate the format, and again to set the response content type.
|
|
32
|
+
#
|
|
33
|
+
# Keys arrive as Symbols: the +content_type+ DSL symbolizes what it is
|
|
34
|
+
# given and the defaults are Symbols. The key is stored as it came too,
|
|
35
|
+
# so a middleware constructed directly with String keys still answers to
|
|
36
|
+
# either spelling, as the indifferent hash did.
|
|
37
|
+
def lookup_for(from_settings)
|
|
38
|
+
LookupCache[from_settings]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The media type of a content-type header: the part before any `;`
|
|
42
|
+
# parameters, with surrounding whitespace removed
|
|
43
|
+
# (e.g. `'text/html'` for `'text/html; charset=utf-8'`). Returns nil for a
|
|
44
|
+
# nil content type. Skips the split (and its allocation) when there are no
|
|
45
|
+
# parameters, which is the common case.
|
|
46
|
+
def media_type(content_type)
|
|
47
|
+
return if content_type.nil?
|
|
48
|
+
|
|
49
|
+
base = content_type.include?(';') ? content_type.split(';', 2).first : content_type
|
|
50
|
+
base.strip
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Both tables below are derived from nothing but the content-type registry,
|
|
54
|
+
# and one content-type-aware middleware is built per API instance — so an
|
|
55
|
+
# app mounting N APIs held N copies of tables it only ever reads. Keying
|
|
56
|
+
# the cache on the registry itself collapses them: Hash keys compare by
|
|
57
|
+
# value, so every API that registers the same content types shares one
|
|
58
|
+
# table.
|
|
59
|
+
#
|
|
60
|
+
# Both the key and the table are frozen: the caller's registry stays
|
|
61
|
+
# reachable (through +middleware.options[:content_types]+, among others)
|
|
62
|
+
# and mutating a live key would corrupt a cache that is now shared
|
|
63
|
+
# process-wide.
|
|
64
|
+
|
|
65
|
+
class MimeTypesCache < Grape::Util::Cache
|
|
66
|
+
def initialize
|
|
67
|
+
super
|
|
68
|
+
@cache = Hash.new do |h, from_settings|
|
|
69
|
+
h[from_settings.dup.freeze] = from_settings.invert.transform_keys! { |mime_type| Grape::ContentTypes.media_type(mime_type) }.freeze
|
|
70
|
+
end
|
|
21
71
|
end
|
|
72
|
+
end
|
|
22
73
|
|
|
23
|
-
|
|
24
|
-
|
|
74
|
+
class LookupCache < Grape::Util::Cache
|
|
75
|
+
def initialize
|
|
76
|
+
super
|
|
77
|
+
@cache = Hash.new do |h, from_settings|
|
|
78
|
+
h[from_settings.dup.freeze] = from_settings.each_with_object({}) do |(format, media_type), lookup|
|
|
79
|
+
lookup[format] = media_type
|
|
80
|
+
lookup[format.is_a?(String) ? format.to_sym : format.to_s] = media_type
|
|
81
|
+
end.freeze
|
|
82
|
+
end
|
|
25
83
|
end
|
|
26
84
|
end
|
|
27
85
|
end
|
data/lib/grape/cookies.rb
CHANGED
|
@@ -2,42 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
class Cookies
|
|
5
|
-
|
|
6
|
-
@cookies = {}
|
|
7
|
-
@send_cookies = {}
|
|
8
|
-
end
|
|
5
|
+
extend Forwardable
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
DELETED_COOKIES_ATTRS = {
|
|
8
|
+
max_age: '0',
|
|
9
|
+
value: '',
|
|
10
|
+
expires: Time.at(0)
|
|
11
|
+
}.freeze
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
def_delegators :cookies, :[], :each
|
|
14
|
+
|
|
15
|
+
def initialize(rack_cookies)
|
|
16
|
+
@cookies = ActiveSupport::HashWithIndifferentAccess.new(rack_cookies)
|
|
17
|
+
@send_cookies = nil
|
|
21
18
|
end
|
|
22
19
|
|
|
23
|
-
def
|
|
24
|
-
@
|
|
20
|
+
def response_cookies
|
|
21
|
+
return unless @send_cookies
|
|
22
|
+
|
|
23
|
+
send_cookies.each do |name|
|
|
24
|
+
yield name, cookies[name]
|
|
25
|
+
end
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def []=(name, value)
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
cookies[name] = value
|
|
30
|
+
send_cookies << name
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
# see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
|
|
34
|
+
def delete(name, **opts)
|
|
35
|
+
self.[]=(name, opts.merge(DELETED_COOKIES_ATTRS))
|
|
34
36
|
end
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
attr_reader :cookies
|
|
41
|
+
|
|
42
|
+
def send_cookies
|
|
43
|
+
@send_cookies ||= Set.new
|
|
40
44
|
end
|
|
41
|
-
# rubocop:enable Layout/SpaceBeforeBrackets
|
|
42
45
|
end
|
|
43
46
|
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
class DeclaredParamsHandler
|
|
5
|
+
def initialize(include_missing: true, evaluate_given: false, stringify: false, contract_key_map: nil)
|
|
6
|
+
@include_missing = include_missing
|
|
7
|
+
@evaluate_given = evaluate_given
|
|
8
|
+
@stringify = stringify
|
|
9
|
+
@contract_key_map = contract_key_map
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(passed_params, declared_params, route_params, renamed_params)
|
|
13
|
+
recursive_declared(
|
|
14
|
+
passed_params,
|
|
15
|
+
declared_params:,
|
|
16
|
+
route_params:,
|
|
17
|
+
renamed_params:
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def recursive_declared(passed_params, declared_params:, route_params:, renamed_params:, params_nested_path: [])
|
|
24
|
+
res = if passed_params.is_a?(Array)
|
|
25
|
+
passed_params.map do |passed_param|
|
|
26
|
+
recursive_declared(passed_param, declared_params:, params_nested_path:, renamed_params:, route_params:)
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@contract_key_map&.each { |key_map| key_map.write(passed_params, res) }
|
|
33
|
+
|
|
34
|
+
res
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
|
|
38
|
+
declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
|
|
39
|
+
next if @evaluate_given && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
|
|
40
|
+
|
|
41
|
+
declared_hash_attr(
|
|
42
|
+
passed_params,
|
|
43
|
+
declared_param: declared_param_attr.key,
|
|
44
|
+
params_nested_path:,
|
|
45
|
+
memo:,
|
|
46
|
+
renamed_params:,
|
|
47
|
+
route_params:
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
|
|
53
|
+
return declare_leaf(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:) unless declared_param.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
declared_param.each_pair do |parent, children|
|
|
56
|
+
declare_nested(passed_params, parent:, children:, params_nested_path:, memo:, renamed_params:, route_params:)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def declare_nested(passed_params, parent:, children:, params_nested_path:, memo:, renamed_params:, route_params:)
|
|
61
|
+
return unless @include_missing || passed_params.key?(parent)
|
|
62
|
+
|
|
63
|
+
memo_key = build_memo_key(params_nested_path, parent, renamed_params)
|
|
64
|
+
passed_children = passed_params[parent] || passed_params.class.new
|
|
65
|
+
nested_path = nested_path_for(params_nested_path, parent)
|
|
66
|
+
|
|
67
|
+
memo[memo_key] = handle_passed_param(nested_path, route_params:, has_passed_children: passed_children.any?) do
|
|
68
|
+
recursive_declared(
|
|
69
|
+
passed_children,
|
|
70
|
+
declared_params: children,
|
|
71
|
+
params_nested_path: nested_path,
|
|
72
|
+
renamed_params:,
|
|
73
|
+
route_params:
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The declared param has no children. Find its value or set it to nil.
|
|
79
|
+
def declare_leaf(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
|
|
80
|
+
return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))
|
|
81
|
+
|
|
82
|
+
memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)
|
|
83
|
+
passed_param = passed_params[declared_param]
|
|
84
|
+
|
|
85
|
+
memo[memo_key] = passed_param || handle_passed_param(nested_path_for(params_nested_path, declared_param), route_params:) do
|
|
86
|
+
passed_param
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def build_memo_key(params_nested_path, declared_param, renamed_params)
|
|
91
|
+
renamed_param_name = renamed_params[nested_path_for(params_nested_path, declared_param)]
|
|
92
|
+
param = renamed_param_name || declared_param
|
|
93
|
+
@stringify ? param.to_s : param.to_sym
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def nested_path_for(parent_path, key)
|
|
97
|
+
parent_path + [key.to_s]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def handle_passed_param(params_nested_path, route_params:, has_passed_children: false)
|
|
101
|
+
return yield if has_passed_children
|
|
102
|
+
|
|
103
|
+
key = params_nested_path.first
|
|
104
|
+
key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
|
|
105
|
+
|
|
106
|
+
type = route_params.dig(key, :type)
|
|
107
|
+
return yield if type.nil?
|
|
108
|
+
|
|
109
|
+
return {} if type == 'Hash' && route_params.keys.none? { |k| k != key && k.start_with?("#{key}[") }
|
|
110
|
+
return [] if type == 'Array' || (type.start_with?('[') && !type.include?(','))
|
|
111
|
+
return Set.new if type == 'Set' || type.start_with?('#<Set', 'Set')
|
|
112
|
+
|
|
113
|
+
yield
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/grape/dry_types.rb
CHANGED
|
@@ -1,12 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'dry-types'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module DryTypes
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
# https://dry-rb.org/gems/dry-types/main/getting-started/
|
|
6
|
+
# limit to what Grape is using
|
|
7
|
+
include Dry.Types(:params, :coercible, :strict)
|
|
8
|
+
|
|
9
|
+
class StrictCache < Grape::Util::Cache
|
|
10
|
+
MAPPING = {
|
|
11
|
+
Grape::API::Boolean => DryTypes::Strict::Bool,
|
|
12
|
+
BigDecimal => DryTypes::Strict::Decimal,
|
|
13
|
+
Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
|
|
14
|
+
TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
|
|
15
|
+
FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
super
|
|
20
|
+
@cache = Hash.new do |h, strict_type|
|
|
21
|
+
h[strict_type] = MAPPING.fetch(strict_type) do
|
|
22
|
+
DryTypes.wrapped_dry_types_const_get(DryTypes::Strict, strict_type)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ParamsCache < Grape::Util::Cache
|
|
29
|
+
MAPPING = {
|
|
30
|
+
Grape::API::Boolean => DryTypes::Params::Bool,
|
|
31
|
+
BigDecimal => DryTypes::Params::Decimal,
|
|
32
|
+
Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
|
|
33
|
+
TrueClass => DryTypes::Params::Bool.constrained(eql: true),
|
|
34
|
+
FalseClass => DryTypes::Params::Bool.constrained(eql: false),
|
|
35
|
+
String => DryTypes::Coercible::String
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
def initialize
|
|
39
|
+
super
|
|
40
|
+
@cache = Hash.new do |h, params_type|
|
|
41
|
+
h[params_type] = MAPPING.fetch(params_type) do
|
|
42
|
+
DryTypes.wrapped_dry_types_const_get(DryTypes::Params, params_type)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.wrapped_dry_types_const_get(dry_type, type)
|
|
49
|
+
dry_type.const_get(type.name, false)
|
|
50
|
+
rescue NameError
|
|
51
|
+
raise ArgumentError, "type `#{type}` should support coercion via `[]`" unless type.respond_to?(:[])
|
|
52
|
+
end
|
|
11
53
|
end
|
|
12
54
|
end
|
data/lib/grape/dsl/callbacks.rb
CHANGED
|
@@ -2,66 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
|
-
# Blocks can be executed before or after every API call, using `before`, `after`,
|
|
6
|
-
# `before_validation` and `after_validation`.
|
|
7
|
-
#
|
|
8
|
-
# Before and after callbacks execute in the following order:
|
|
9
|
-
#
|
|
10
|
-
# 1. `before`
|
|
11
|
-
# 2. `before_validation`
|
|
12
|
-
# 3. _validations_
|
|
13
|
-
# 4. `after_validation`
|
|
14
|
-
# 5. _the API call_
|
|
15
|
-
# 6. `after`
|
|
16
|
-
#
|
|
17
|
-
# Steps 4, 5 and 6 only happen if validation succeeds.
|
|
18
5
|
module Callbacks
|
|
19
|
-
|
|
6
|
+
# before: execute the given block before validation, coercion, or any endpoint
|
|
7
|
+
# before_validation: execute the given block after `before`, but prior to validation or coercion
|
|
8
|
+
# after_validation: execute the given block after validations and coercions, but before any endpoint code
|
|
9
|
+
# after: execute the given block after the endpoint code has run except in unsuccessful
|
|
10
|
+
# finally: execute the given block after the endpoint code even if unsuccessful
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Execute the given block before validation, coercion, or any endpoint
|
|
25
|
-
# code is executed.
|
|
26
|
-
def before(&block)
|
|
27
|
-
namespace_stackable(:befores, block)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Execute the given block after `before`, but prior to validation or
|
|
31
|
-
# coercion.
|
|
32
|
-
def before_validation(&block)
|
|
33
|
-
namespace_stackable(:before_validations, block)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Execute the given block after validations and coercions, but before
|
|
37
|
-
# any endpoint code.
|
|
38
|
-
def after_validation(&block)
|
|
39
|
-
namespace_stackable(:after_validations, block)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Execute the given block after the endpoint code has run.
|
|
43
|
-
def after(&block)
|
|
44
|
-
namespace_stackable(:afters, block)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Allows you to specify a something that will always be executed after a call
|
|
48
|
-
# API call. Unlike the `after` block, this code will run even on
|
|
49
|
-
# unsuccesful requests.
|
|
50
|
-
# @example
|
|
51
|
-
# class ExampleAPI < Grape::API
|
|
52
|
-
# before do
|
|
53
|
-
# ApiLogger.start
|
|
54
|
-
# end
|
|
55
|
-
# finally do
|
|
56
|
-
# ApiLogger.close
|
|
57
|
-
# end
|
|
58
|
-
# end
|
|
59
|
-
#
|
|
60
|
-
# This will make sure that the ApiLogger is opened and closed around every
|
|
61
|
-
# request
|
|
62
|
-
# @param ensured_block [Proc] The block to be executed after every api_call
|
|
63
|
-
def finally(&block)
|
|
64
|
-
namespace_stackable(:finallies, block)
|
|
12
|
+
%i[before before_validation after_validation after finally].each do |callback_name|
|
|
13
|
+
define_method callback_name do |&block|
|
|
14
|
+
inheritable_setting.add_callback(callback_name, block)
|
|
65
15
|
end
|
|
66
16
|
end
|
|
67
17
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module Declared
|
|
6
|
+
# Denotes a situation where a DSL method has been invoked in a
|
|
7
|
+
# filter which it should not yet be available in
|
|
8
|
+
class MethodNotYetAvailable < StandardError
|
|
9
|
+
def initialize(msg = '#declared is not available prior to parameter validation')
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# A filtering method that will return a hash
|
|
15
|
+
# consisting only of keys that have been declared by a
|
|
16
|
+
# `params` statement against the current/target endpoint or parent
|
|
17
|
+
# namespaces.
|
|
18
|
+
# @param params [Hash] The initial hash to filter. Usually this will just be `params`
|
|
19
|
+
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
|
|
20
|
+
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
|
|
21
|
+
# you want only to return params declared against the current/target endpoint.
|
|
22
|
+
def declared(passed_params, include_parent_namespaces: true, include_missing: true, evaluate_given: false, stringify: false)
|
|
23
|
+
raise MethodNotYetAvailable unless before_filter_passed
|
|
24
|
+
|
|
25
|
+
contract_key_map = inheritable_setting.contract_key_maps
|
|
26
|
+
handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map:)
|
|
27
|
+
declared_params = include_parent_namespaces ? inheritable_setting.route_declared_params : (inheritable_setting.declared_params.last || [])
|
|
28
|
+
renamed_params = inheritable_setting.route_renamed_params
|
|
29
|
+
route_params = config.params
|
|
30
|
+
|
|
31
|
+
handler.call(passed_params, declared_params, route_params, renamed_params)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/grape/dsl/desc.rb
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module Desc
|
|
6
|
-
|
|
6
|
+
extend Grape::DSL::Settings
|
|
7
7
|
|
|
8
8
|
# Add a description to the next namespace or function.
|
|
9
9
|
# @param description [String] descriptive string for this endpoint
|
|
10
10
|
# or namespace
|
|
11
11
|
# @param options [Hash] other properties you can set to describe the
|
|
12
|
-
# endpoint or namespace. Optional.
|
|
12
|
+
# endpoint or namespace. Optional. Pass these as keyword arguments;
|
|
13
|
+
# passing a positional options Hash is deprecated.
|
|
13
14
|
# @option options :detail [String] additional detail about this endpoint
|
|
14
15
|
# @option options :summary [String] summary for this endpoint
|
|
15
16
|
# @option options :params [Hash] param types and info. normally, you set
|
|
@@ -18,6 +19,9 @@ module Grape
|
|
|
18
19
|
# successful call to this action
|
|
19
20
|
# @option options :http_codes [Array[Array]] possible HTTP codes this
|
|
20
21
|
# endpoint may return, with their meanings, in a 2d array
|
|
22
|
+
# @option options :default_response [Hash] the definition and entity used
|
|
23
|
+
# to present this endpoint's default response. Was `:default`, which is
|
|
24
|
+
# deprecated.
|
|
21
25
|
# @option options :named [String] a specific name to help find this route
|
|
22
26
|
# @option options :body_name [String] override the autogenerated body name param
|
|
23
27
|
# @option options :headers [Hash] HTTP headers this method can accept
|
|
@@ -49,70 +53,28 @@ module Grape
|
|
|
49
53
|
# # ...
|
|
50
54
|
# end
|
|
51
55
|
#
|
|
52
|
-
def desc(description, options
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
endpoint_configuration ||= {}
|
|
64
|
-
config_class = desc_container(endpoint_configuration)
|
|
65
|
-
|
|
66
|
-
config_class.configure do
|
|
67
|
-
description description
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
config_class.configure(&config_block)
|
|
71
|
-
ActiveSupport::Deprecation.warn('Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.') if options.any?
|
|
72
|
-
options = config_class.settings
|
|
73
|
-
else
|
|
74
|
-
options = options.merge(description: description)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
namespace_setting :description, options
|
|
78
|
-
route_setting :description, options
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Returns an object which configures itself via an instance-context DSL.
|
|
82
|
-
def desc_container(endpoint_configuration)
|
|
83
|
-
Module.new do
|
|
84
|
-
include Grape::Util::StrictHashConfiguration.module(
|
|
85
|
-
:summary,
|
|
86
|
-
:description,
|
|
87
|
-
:detail,
|
|
88
|
-
:params,
|
|
89
|
-
:entity,
|
|
90
|
-
:http_codes,
|
|
91
|
-
:named,
|
|
92
|
-
:body_name,
|
|
93
|
-
:headers,
|
|
94
|
-
:hidden,
|
|
95
|
-
:deprecated,
|
|
96
|
-
:is_array,
|
|
97
|
-
:nickname,
|
|
98
|
-
:produces,
|
|
99
|
-
:consumes,
|
|
100
|
-
:security,
|
|
101
|
-
:tags,
|
|
102
|
-
:default
|
|
103
|
-
)
|
|
104
|
-
config_context.define_singleton_method(:configuration) do
|
|
105
|
-
endpoint_configuration
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def config_context.success(*args)
|
|
109
|
-
entity(*args)
|
|
56
|
+
def desc(description, **options, &config_block)
|
|
57
|
+
resolved_options =
|
|
58
|
+
if options.key?(:default)
|
|
59
|
+
Grape.deprecator.warn('The `default` option of `desc` is deprecated. Use `default_response` instead.')
|
|
60
|
+
# Rebuilt rather than mutated in place: an explicit +default_response+
|
|
61
|
+
# still wins, since the merged Hash is the one that keeps its key.
|
|
62
|
+
{ default_response: options[:default] }.merge(options.except(:default))
|
|
63
|
+
else
|
|
64
|
+
options
|
|
110
65
|
end
|
|
111
66
|
|
|
112
|
-
|
|
113
|
-
|
|
67
|
+
settings =
|
|
68
|
+
if config_block
|
|
69
|
+
endpoint_config = defined?(configuration) ? configuration : nil
|
|
70
|
+
Grape::Util::ApiDescription.new(description, endpoint_config, &config_block).settings
|
|
71
|
+
else
|
|
72
|
+
resolved_options.merge(description:)
|
|
114
73
|
end
|
|
115
|
-
|
|
74
|
+
# Only the route scope is consumed downstream (by +route+ and the
|
|
75
|
+
# route's readers, e.g. +http_codes+); the namespace scope was
|
|
76
|
+
# write-only, so it is no longer populated.
|
|
77
|
+
inheritable_setting.route_description = settings
|
|
116
78
|
end
|
|
117
79
|
end
|
|
118
80
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module Entity
|
|
6
|
+
# Allows you to make use of Grape Entities by setting
|
|
7
|
+
# the response body to the serializable hash of the
|
|
8
|
+
# entity provided in the `:with` option. This has the
|
|
9
|
+
# added benefit of automatically passing along environment
|
|
10
|
+
# and version information to the serialization, making it
|
|
11
|
+
# very easy to do conditional exposures. See Entity docs
|
|
12
|
+
# for more info.
|
|
13
|
+
#
|
|
14
|
+
# @param args [Array] either `(object)` or `(key, object)` where key is a Symbol
|
|
15
|
+
# used to nest the representation under that key in the response body.
|
|
16
|
+
# @param root [Symbol, String, nil] wraps the representation under this root key.
|
|
17
|
+
# @param with [Class, nil] the entity class to use for representation.
|
|
18
|
+
# If omitted, the entity class is inferred from the object via {#entity_class_for_obj}.
|
|
19
|
+
# @param options [Hash] additional options forwarded to the entity's `represent` call.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
#
|
|
23
|
+
# get '/users/:id' do
|
|
24
|
+
# present User.find(params[:id]),
|
|
25
|
+
# with: API::Entities::User,
|
|
26
|
+
# admin: current_user.admin?
|
|
27
|
+
# end
|
|
28
|
+
def present(*args, root: nil, with: nil, **options)
|
|
29
|
+
key, object = args.count == 2 && args.first.is_a?(Symbol) ? args : [nil, args.first]
|
|
30
|
+
entity_class = with || entity_class_for_obj(object)
|
|
31
|
+
representation = entity_class ? entity_representation_for(entity_class, object, options) : object
|
|
32
|
+
representation = { root => representation } if root
|
|
33
|
+
|
|
34
|
+
if key
|
|
35
|
+
representation = body&.merge(key => representation) || { key => representation }
|
|
36
|
+
elsif entity_class.present? && body
|
|
37
|
+
raise ArgumentError, "representation of type `#{representation.class}` cannot be merged" unless representation.respond_to?(:merge)
|
|
38
|
+
|
|
39
|
+
representation = body.merge(representation)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
body representation
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Attempt to locate the Entity class for a given object, if not given
|
|
46
|
+
# explicitly. This is done by looking for the presence of Klass::Entity,
|
|
47
|
+
# where Klass is the class of the `object` parameter, or one of its
|
|
48
|
+
# ancestors. Object is excluded from the search: top-level constants
|
|
49
|
+
# live on it, so a global ::Entity class is not a representer.
|
|
50
|
+
# @param object [Object] the object to locate the Entity class for
|
|
51
|
+
# @return [Class] the located Entity class, or nil if none is found
|
|
52
|
+
def entity_class_for_obj(object)
|
|
53
|
+
entity_for_class(object.class) || entity_for_class(element_class(object))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# The class standing in for a collection or wrapper: ActiveRecord::Relation
|
|
59
|
+
# and the like expose #klass, anything else falls back to the class of its
|
|
60
|
+
# first element.
|
|
61
|
+
#
|
|
62
|
+
# Consulted only once the object's own class has come up empty, because
|
|
63
|
+
# both tests are duck-typed and plenty of single objects answer them —
|
|
64
|
+
# a Struct is Enumerable, so it responds to #first, and so does any model
|
|
65
|
+
# that includes Enumerable. Asking this first meant `represent Model,
|
|
66
|
+
# with: Entity` was silently ignored for those, the entity for the
|
|
67
|
+
# *element* type being looked up instead. Deferring it also keeps #first
|
|
68
|
+
# from being called at all when the object resolves on its own class.
|
|
69
|
+
#
|
|
70
|
+
# @param object [Object] the object to represent.
|
|
71
|
+
# @return [Class, nil]
|
|
72
|
+
def element_class(object)
|
|
73
|
+
return object.klass if object.respond_to?(:klass)
|
|
74
|
+
|
|
75
|
+
object.first.class if object.respond_to?(:first)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param klass [Class, nil] the class to look an entity up for.
|
|
79
|
+
# @return [Class, nil] the registered or conventionally named entity.
|
|
80
|
+
def entity_for_class(klass)
|
|
81
|
+
return if klass.nil?
|
|
82
|
+
|
|
83
|
+
representations = inheritable_setting.representations
|
|
84
|
+
if representations
|
|
85
|
+
potential = klass.ancestors.detect { |ancestor| representations.key?(ancestor) }
|
|
86
|
+
return representations[potential] if potential && representations[potential]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
owner = klass.ancestors.detect { |ancestor| ancestor != Object && ancestor.const_defined?(:Entity, false) }
|
|
90
|
+
return unless owner
|
|
91
|
+
|
|
92
|
+
entity = owner.const_get(:Entity, false)
|
|
93
|
+
entity if entity.respond_to?(:represent)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @param entity_class [Class] the entity class to use for representation.
|
|
97
|
+
# @param object [Object] the object to represent.
|
|
98
|
+
# @param options [Hash] additional options forwarded to the entity's `represent` call.
|
|
99
|
+
# @return the representation of the given object as done through the given entity_class.
|
|
100
|
+
def entity_representation_for(entity_class, object, options)
|
|
101
|
+
embeds = env.key?(Grape::Env::API_VERSION) ? { env:, version: env[Grape::Env::API_VERSION] } : { env: }
|
|
102
|
+
entity_class.represent(object, **embeds, **options)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|