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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
# Immutable value object holding the response-shaping booleans accepted
|
|
6
|
+
# by +Grape::DSL::RequestResponse#rescue_from+. Recorded on the
|
|
7
|
+
# inheritable settings via +Grape::Util::InheritableSetting#add_rescue_options+
|
|
8
|
+
# (the nearest scope's latest registration wins on read, see
|
|
9
|
+
# +#rescue_options+) and delegated to by +Grape::Middleware::Error+ (which forwards
|
|
10
|
+
# +backtrace+/+original_exception+ to the formatter as
|
|
11
|
+
# +include_backtrace+/+include_original_exception+).
|
|
12
|
+
#
|
|
13
|
+
# Defaults are duplicated on +#initialize+ here and on +#rescue_from+'s
|
|
14
|
+
# signature on purpose: keeping them on both sides means each entry point
|
|
15
|
+
# is self-documenting without needing to import a shared constant — the
|
|
16
|
+
# DSL signature shows what a user sees in the IDE, and the Data object
|
|
17
|
+
# has working defaults when constructed directly (spec fixtures, a
|
|
18
|
+
# middleware built by hand). The two must stay in lockstep.
|
|
19
|
+
RescueOptions = Data.define(:backtrace, :original_exception) do
|
|
20
|
+
def initialize(backtrace: false, original_exception: false)
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/grape/dsl/routing.rb
CHANGED
|
@@ -3,228 +3,370 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module Routing
|
|
6
|
-
|
|
7
|
-
include Grape::DSL::Configuration
|
|
8
|
-
|
|
9
|
-
module ClassMethods
|
|
10
|
-
attr_reader :endpoints
|
|
11
|
-
|
|
12
|
-
# Specify an API version.
|
|
13
|
-
#
|
|
14
|
-
# @example API with legacy support.
|
|
15
|
-
# class MyAPI < Grape::API
|
|
16
|
-
# version 'v2'
|
|
17
|
-
#
|
|
18
|
-
# get '/main' do
|
|
19
|
-
# {some: 'data'}
|
|
20
|
-
# end
|
|
21
|
-
#
|
|
22
|
-
# version 'v1' do
|
|
23
|
-
# get '/main' do
|
|
24
|
-
# {legacy: 'data'}
|
|
25
|
-
# end
|
|
26
|
-
# end
|
|
27
|
-
# end
|
|
28
|
-
#
|
|
29
|
-
def version(*args, &block)
|
|
30
|
-
if args.any?
|
|
31
|
-
options = args.extract_options!
|
|
32
|
-
options = options.reverse_merge(using: :path)
|
|
33
|
-
requested_versions = args.flatten
|
|
34
|
-
|
|
35
|
-
raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
|
|
36
|
-
|
|
37
|
-
@versions = versions | requested_versions
|
|
38
|
-
|
|
39
|
-
if block
|
|
40
|
-
within_namespace do
|
|
41
|
-
namespace_inheritable(:version, requested_versions)
|
|
42
|
-
namespace_inheritable(:version_options, options)
|
|
43
|
-
|
|
44
|
-
instance_eval(&block)
|
|
45
|
-
end
|
|
46
|
-
else
|
|
47
|
-
namespace_inheritable(:version, requested_versions)
|
|
48
|
-
namespace_inheritable(:version_options, options)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
6
|
+
attr_reader :endpoints
|
|
51
7
|
|
|
52
|
-
|
|
53
|
-
|
|
8
|
+
def given(conditional_option, &)
|
|
9
|
+
return unless conditional_option
|
|
54
10
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
11
|
+
mounted(&)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def mounted(&block)
|
|
15
|
+
evaluate_as_instance_with_configuration(block, lazy: true)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def cascade(value = nil)
|
|
19
|
+
return inheritable_setting.cascade_defined? ? inheritable_setting.cascade : true if value.nil?
|
|
20
|
+
|
|
21
|
+
inheritable_setting.cascade = value
|
|
22
|
+
end
|
|
59
23
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
24
|
+
# Specify an API version.
|
|
25
|
+
#
|
|
26
|
+
# Called without arguments, returns the most recently declared version
|
|
27
|
+
# (or +nil+). Called with one or more version strings, registers them
|
|
28
|
+
# and stores a {Grape::DSL::VersionOptions} value object on the
|
|
29
|
+
# inheritable settings; when given a block, the registration applies
|
|
30
|
+
# within a nested namespace.
|
|
31
|
+
#
|
|
32
|
+
# @example API with legacy support.
|
|
33
|
+
# class MyAPI < Grape::API
|
|
34
|
+
# version 'v2'
|
|
35
|
+
#
|
|
36
|
+
# get '/main' do
|
|
37
|
+
# {some: 'data'}
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# version 'v1' do
|
|
41
|
+
# get '/main' do
|
|
42
|
+
# {legacy: 'data'}
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# @param args [Array<String, Symbol>] one or more version identifiers.
|
|
48
|
+
# @param using [Symbol] versioning strategy — one of +:path+ (default),
|
|
49
|
+
# +:header+, +:param+, or +:accept_version_header+.
|
|
50
|
+
# @param cascade [Boolean] forward to subsequent routes via the
|
|
51
|
+
# +X-Cascade+ header on version mismatch. Defaults to +true+.
|
|
52
|
+
# @param parameter [String] name of the query/body parameter that
|
|
53
|
+
# carries the version when +using: :param+. Defaults to +'apiver'+.
|
|
54
|
+
# @param strict [Boolean] reject requests that don't supply a usable
|
|
55
|
+
# version (header strategies). Defaults to +false+.
|
|
56
|
+
# @param vendor [String, nil] vendor segment for the +:header+
|
|
57
|
+
# strategy (+application/vnd.<vendor>-<version>+); required when
|
|
58
|
+
# +using: :header+.
|
|
59
|
+
# @yield optional block to scope routes under this version.
|
|
60
|
+
# @return [String, nil] the most recently declared version.
|
|
61
|
+
# @raise [Grape::Exceptions::MissingVendorOption] when +using: :header+
|
|
62
|
+
# is supplied without a +:vendor+.
|
|
63
|
+
def version(*args, using: :path, cascade: true, parameter: 'apiver', strict: false, vendor: nil, &block)
|
|
64
|
+
return @versions&.last if args.empty?
|
|
65
|
+
|
|
66
|
+
raise Grape::Exceptions::MissingVendorOption.new if using == :header && vendor.nil?
|
|
67
|
+
|
|
68
|
+
requested_versions = args.flatten.map(&:to_s)
|
|
69
|
+
options = VersionOptions.new(using:, cascade:, parameter:, strict:, vendor:)
|
|
70
|
+
|
|
71
|
+
@versions = versions | requested_versions
|
|
72
|
+
|
|
73
|
+
if block
|
|
65
74
|
within_namespace do
|
|
66
|
-
|
|
75
|
+
inheritable_setting.version = requested_versions
|
|
76
|
+
inheritable_setting.version_options = options
|
|
77
|
+
instance_eval(&block)
|
|
67
78
|
end
|
|
79
|
+
else
|
|
80
|
+
inheritable_setting.version = requested_versions
|
|
81
|
+
inheritable_setting.version_options = options
|
|
68
82
|
end
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
namespace_inheritable(:do_not_route_head, true)
|
|
73
|
-
end
|
|
84
|
+
@versions&.last
|
|
85
|
+
end
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
end
|
|
87
|
+
# Define a root URL prefix for your entire API.
|
|
88
|
+
def prefix(prefix = nil)
|
|
89
|
+
return inheritable_setting.root_prefix if prefix.nil?
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
end
|
|
91
|
+
inheritable_setting.root_prefix = prefix.to_s
|
|
92
|
+
end
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
end
|
|
92
|
-
in_setting = inheritable_setting
|
|
93
|
-
|
|
94
|
-
if app.respond_to?(:inheritable_setting, true)
|
|
95
|
-
mount_path = Grape::Router.normalize_path(path)
|
|
96
|
-
app.top_level_setting.namespace_stackable[:mount_path] = mount_path
|
|
97
|
-
|
|
98
|
-
app.inherit_settings(inheritable_setting)
|
|
99
|
-
|
|
100
|
-
in_setting = app.top_level_setting
|
|
101
|
-
|
|
102
|
-
app.change!
|
|
103
|
-
change!
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
endpoints << Grape::Endpoint.new(
|
|
107
|
-
in_setting,
|
|
108
|
-
method: :any,
|
|
109
|
-
path: path,
|
|
110
|
-
app: app,
|
|
111
|
-
route_options: { anchor: false },
|
|
112
|
-
forward_match: !app.respond_to?(:inheritable_setting),
|
|
113
|
-
for: self
|
|
114
|
-
)
|
|
115
|
-
end
|
|
94
|
+
# Create a scope without affecting the URL.
|
|
95
|
+
#
|
|
96
|
+
# @param _name [Symbol] Purely placebo, just allows to name the scope to
|
|
97
|
+
# make the code more readable.
|
|
98
|
+
def scope(_name = nil, &block)
|
|
99
|
+
within_namespace do
|
|
100
|
+
nest(block)
|
|
116
101
|
end
|
|
102
|
+
end
|
|
117
103
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
104
|
+
def build_with(build_with)
|
|
105
|
+
inheritable_setting.build_params_with = build_with
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Do not route HEAD requests to GET requests automatically.
|
|
109
|
+
def do_not_route_head!
|
|
110
|
+
inheritable_setting.do_not_route_head!
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Do not automatically route OPTIONS.
|
|
114
|
+
def do_not_route_options!
|
|
115
|
+
inheritable_setting.do_not_route_options!
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def lint!
|
|
119
|
+
inheritable_setting.lint!
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def do_not_document!
|
|
123
|
+
inheritable_setting.do_not_document!
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def mount(mounts, opts = {})
|
|
127
|
+
mount_opts = opts
|
|
128
|
+
if opts[:refresh_already_mounted]
|
|
129
|
+
Grape.deprecator.warn('`refresh_already_mounted` is not a `mount` option and will be ignored in a future release.')
|
|
130
|
+
drop_endpoints_mounted_for(mounts)
|
|
131
|
+
# Dropped before the recursion below re-enters with the same options,
|
|
132
|
+
# so a Grape API does not warn once per mount and once per instance.
|
|
133
|
+
mount_opts = opts.except(:refresh_already_mounted)
|
|
146
134
|
end
|
|
147
135
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
route(supported_method, paths, options, &block)
|
|
136
|
+
normalize_mounts(mounts).each_pair do |app, path|
|
|
137
|
+
if app.respond_to?(:mount_instance)
|
|
138
|
+
mount({ app.mount_instance(configuration: mount_opts[:with] || {}) => path }, mount_opts)
|
|
139
|
+
next
|
|
153
140
|
end
|
|
154
|
-
|
|
141
|
+
in_setting = inheritable_setting
|
|
142
|
+
|
|
143
|
+
# Past the mount_instance branch above, a Grape app here is an already
|
|
144
|
+
# instantiated Grape::API::Instance (vs. a bare Rack app).
|
|
145
|
+
if app.is_a?(Grape::Mountable)
|
|
146
|
+
mount_path = Grape::Util::PathNormalizer.call(path)
|
|
147
|
+
app.top_level_setting.add_mount_path(mount_path)
|
|
148
|
+
|
|
149
|
+
app.inherit_settings(inheritable_setting)
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
#
|
|
161
|
-
# @example
|
|
162
|
-
#
|
|
163
|
-
# namespace :foo do
|
|
164
|
-
# get 'bar' do
|
|
165
|
-
# # defines the endpoint: GET /foo/bar
|
|
166
|
-
# end
|
|
167
|
-
# end
|
|
168
|
-
def namespace(space = nil, options = {}, &block)
|
|
169
|
-
@namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description
|
|
170
|
-
|
|
171
|
-
if space || block
|
|
172
|
-
within_namespace do
|
|
173
|
-
previous_namespace_description = @namespace_description
|
|
174
|
-
@namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {})
|
|
175
|
-
nest(block) do
|
|
176
|
-
namespace_stackable(:namespace, Namespace.new(space, **options)) if space
|
|
177
|
-
end
|
|
178
|
-
@namespace_description = previous_namespace_description
|
|
179
|
-
end
|
|
180
|
-
else
|
|
181
|
-
Namespace.joined_space_path(namespace_stackable(:namespace))
|
|
151
|
+
in_setting = app.top_level_setting
|
|
152
|
+
|
|
153
|
+
app.change!
|
|
154
|
+
change!
|
|
182
155
|
end
|
|
156
|
+
|
|
157
|
+
endpoints << Grape::Endpoint.new(
|
|
158
|
+
in_setting,
|
|
159
|
+
http_methods: :any,
|
|
160
|
+
path:,
|
|
161
|
+
app:,
|
|
162
|
+
anchor: false,
|
|
163
|
+
api: self
|
|
164
|
+
)
|
|
183
165
|
end
|
|
166
|
+
end
|
|
184
167
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
168
|
+
# Defines a route that will be recognized
|
|
169
|
+
# by the Grape API.
|
|
170
|
+
#
|
|
171
|
+
# @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.
|
|
172
|
+
# @param paths [String] One or more strings representing the URL segment(s) for this route.
|
|
173
|
+
# @param requirements [Hash] Regular-expression constraints for named path params; the route matches only when every requirement is satisfied.
|
|
174
|
+
# @param anchor [Boolean] Whether the route is anchored to the whole path. Defaults to `true`; pass `false` for catch-all routes (e.g. `'/(*:path)'`).
|
|
175
|
+
# @param route_options [Hash] Any additional custom options, carried through to `route.options`.
|
|
176
|
+
#
|
|
177
|
+
# @example Defining a basic route.
|
|
178
|
+
# class MyAPI < Grape::API
|
|
179
|
+
# route(:any, '/hello') do
|
|
180
|
+
# {hello: 'world'}
|
|
181
|
+
# end
|
|
182
|
+
# end
|
|
183
|
+
def route(methods, paths = ['/'], requirements: nil, anchor: true, **route_options, &)
|
|
184
|
+
validate_requirements!(requirements)
|
|
185
|
+
|
|
186
|
+
http_methods = methods == :any ? '*' : methods
|
|
187
|
+
endpoint_description = inheritable_setting.route_description
|
|
188
|
+
|
|
189
|
+
# +params+, +requirements+ and +anchor+ each travel as their own endpoint
|
|
190
|
+
# input; the route-options bag keeps the description's other keys
|
|
191
|
+
# (+success+, +tags+, …) plus any custom options.
|
|
192
|
+
params = prepare_params(endpoint_description[:params])
|
|
193
|
+
all_route_options = endpoint_description.except(:params)
|
|
194
|
+
all_route_options.deep_merge!(route_options) if route_options.present?
|
|
195
|
+
|
|
196
|
+
new_endpoint = Grape::Endpoint.new(
|
|
197
|
+
inheritable_setting,
|
|
198
|
+
http_methods:,
|
|
199
|
+
path: paths,
|
|
200
|
+
api: self,
|
|
201
|
+
params:,
|
|
202
|
+
requirements:,
|
|
203
|
+
anchor:,
|
|
204
|
+
route_options: all_route_options,
|
|
205
|
+
&
|
|
206
|
+
)
|
|
207
|
+
endpoints << new_endpoint unless endpoints.include?(new_endpoint)
|
|
208
|
+
|
|
209
|
+
inheritable_setting.route_end
|
|
210
|
+
reset_validations!
|
|
211
|
+
end
|
|
189
212
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
213
|
+
Grape::HTTP_SUPPORTED_METHODS.each do |supported_method|
|
|
214
|
+
define_method supported_method.downcase do |path = '/', **options, &block|
|
|
215
|
+
route(supported_method, path, **options, &block)
|
|
193
216
|
end
|
|
217
|
+
end
|
|
194
218
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
# Declare a "namespace", which prefixes all subordinate routes with its
|
|
220
|
+
# name. Any endpoints within a namespace, group, resource or segment,
|
|
221
|
+
# etc., will share their parent context as well as any configuration
|
|
222
|
+
# done in the namespace context.
|
|
223
|
+
#
|
|
224
|
+
# @example
|
|
225
|
+
#
|
|
226
|
+
# namespace :foo do
|
|
227
|
+
# get 'bar' do
|
|
228
|
+
# # defines the endpoint: GET /foo/bar
|
|
229
|
+
# end
|
|
230
|
+
# end
|
|
231
|
+
def namespace(space = nil, requirements: nil, **options, &block)
|
|
232
|
+
return inheritable_setting.namespace_path unless space || block
|
|
233
|
+
|
|
234
|
+
validate_requirements!(requirements)
|
|
235
|
+
|
|
236
|
+
within_namespace do
|
|
237
|
+
nest(block) do
|
|
238
|
+
inheritable_setting.add_namespace(Grape::Namespace.new(space, requirements:, **options)) if space
|
|
239
|
+
end
|
|
199
240
|
end
|
|
241
|
+
end
|
|
200
242
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
243
|
+
alias group namespace
|
|
244
|
+
alias resource namespace
|
|
245
|
+
alias resources namespace
|
|
246
|
+
alias segment namespace
|
|
204
247
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
# @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
|
|
210
|
-
def route_param(param, options = {}, &block)
|
|
211
|
-
options = options.dup
|
|
248
|
+
# An array of API routes.
|
|
249
|
+
def routes
|
|
250
|
+
@routes ||= endpoints.map(&:routes).flatten
|
|
251
|
+
end
|
|
212
252
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
253
|
+
# This method allows you to quickly define a parameter route segment
|
|
254
|
+
# in your API.
|
|
255
|
+
#
|
|
256
|
+
# @param param [Symbol] The name of the parameter you wish to declare.
|
|
257
|
+
# @option options [Regexp, Class, Symbol] The constraint the declared parameter must meet — a Regexp, or a capture type such as +Integer+.
|
|
258
|
+
def route_param(param, requirements: nil, type: nil, **, &)
|
|
259
|
+
# The param is named here, so the constraint is its own: nest whatever
|
|
260
|
+
# it is, not just a Regexp. A Hash would name the param twice, or key a
|
|
261
|
+
# capture this namespace does not introduce, and belongs on +namespace+.
|
|
262
|
+
raise ArgumentError, "route_param :`#{param}` constrains :`#{param}`; pass the constraint itself, or a Hash of requirements to the enclosing namespace" if requirements.respond_to?(:to_hash)
|
|
263
|
+
|
|
264
|
+
param_requirements = requirements ? { param.to_sym => requirements } : requirements
|
|
265
|
+
|
|
266
|
+
Grape::Validations::ParamsScope.new(api: self) do
|
|
267
|
+
requires param, type: type
|
|
268
|
+
end if type
|
|
269
|
+
|
|
270
|
+
namespace(":#{param}", requirements: param_requirements, **, &)
|
|
271
|
+
end
|
|
216
272
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
273
|
+
# @return array of defined versions
|
|
274
|
+
def versions
|
|
275
|
+
@versions ||= []
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
# Requirements are keyed by param name and merged across namespaces, so
|
|
281
|
+
# anything else has nothing to attach to. Rejected here rather than at
|
|
282
|
+
# the merge, which runs on the first request that builds the routes.
|
|
283
|
+
def validate_requirements!(requirements)
|
|
284
|
+
return if requirements.nil? || requirements.respond_to?(:to_hash)
|
|
285
|
+
|
|
286
|
+
raise ArgumentError, "requirements must be a Hash of param name => constraint, got `#{requirements.class}`"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Compose a route's params: the declared params (+params do … end+) deep-merged
|
|
290
|
+
# with any documented alongside +desc ..., params:+ (+description_params+).
|
|
291
|
+
def prepare_params(description_params)
|
|
292
|
+
endpoint_params = inheritable_setting.params_documentation || {}
|
|
293
|
+
return endpoint_params if description_params.blank?
|
|
294
|
+
|
|
295
|
+
endpoint_params.deep_merge(description_params)
|
|
296
|
+
end
|
|
220
297
|
|
|
221
|
-
|
|
298
|
+
# Remove all defined routes.
|
|
299
|
+
def reset_routes!
|
|
300
|
+
endpoints.each(&:reset_routes!)
|
|
301
|
+
@routes = nil
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def reset_endpoints!
|
|
305
|
+
@endpoints = []
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Re-mount +mounts+, replacing any endpoint already mounted for the same
|
|
309
|
+
# base API rather than adding a second one. Called by
|
|
310
|
+
# {Grape::API.refresh_mount_step} when a class-level method runs after the
|
|
311
|
+
# API was mounted.
|
|
312
|
+
def refresh_mounted_api(mounts, opts = {})
|
|
313
|
+
drop_endpoints_mounted_for(mounts)
|
|
314
|
+
mount(mounts, opts)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# A mounted Grape API is stored as the throwaway instance +mount+ built
|
|
318
|
+
# for it, never as the class that was written, so endpoints are matched on
|
|
319
|
+
# the base API both of them share.
|
|
320
|
+
def drop_endpoints_mounted_for(mounts)
|
|
321
|
+
normalize_mounts(mounts).each_key do |app|
|
|
322
|
+
endpoints.delete_if { |endpoint| same_mounted_app?(endpoint.mounted_app, app) }
|
|
222
323
|
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# A bare app mounts at the root. The test is +Hash+ rather than
|
|
327
|
+
# +respond_to?(:each_pair)+ because a Struct or an OpenStruct answers that
|
|
328
|
+
# too, and neither can express an app => path mapping — their keys are
|
|
329
|
+
# member names. Reading one as a mapping would silently mount nonsense
|
|
330
|
+
# instead of mounting the app itself.
|
|
331
|
+
def normalize_mounts(mounts)
|
|
332
|
+
mounts.is_a?(Hash) ? mounts : { mounts => '/' }
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Two mounts refer to the same app when they share the same base Grape
|
|
336
|
+
# API. +mount+ turns every mounted Grape API into a throwaway
|
|
337
|
+
# +mount_instance+ (a fresh +Class.new+ per mount), so object identity
|
|
338
|
+
# never holds across mounts; comparing the base is the real signal.
|
|
339
|
+
# Plain Rack apps have no base and are mounted as-is, so they fall back
|
|
340
|
+
# to object identity.
|
|
341
|
+
def same_mounted_app?(mounted, app)
|
|
342
|
+
return mounted.base.equal?(app.base) if mounted.respond_to?(:base) && app.respond_to?(:base)
|
|
343
|
+
|
|
344
|
+
mounted.equal?(app)
|
|
345
|
+
end
|
|
223
346
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
347
|
+
# Execute first the provided block, then each of the
|
|
348
|
+
# block passed in. Allows for simple 'before' setups
|
|
349
|
+
# of settings stack pushes.
|
|
350
|
+
def nest(*blocks, &block)
|
|
351
|
+
blocks.compact!
|
|
352
|
+
return instance_eval(&block) if blocks.empty?
|
|
353
|
+
|
|
354
|
+
evaluate_as_instance_with_configuration(block) if block
|
|
355
|
+
blocks.each { |b| evaluate_as_instance_with_configuration(b) }
|
|
356
|
+
reset_validations!
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def evaluate_as_instance_with_configuration(block, lazy: false)
|
|
360
|
+
lazy_block = Grape::Util::Lazy::Block.new do |configuration|
|
|
361
|
+
value_for_configuration = configuration
|
|
362
|
+
self.configuration = value_for_configuration.evaluate if value_for_configuration.is_a?(Grape::Util::Lazy::Base)
|
|
363
|
+
response = instance_eval(&block)
|
|
364
|
+
self.configuration = value_for_configuration
|
|
365
|
+
response
|
|
227
366
|
end
|
|
367
|
+
return lazy_block if @base && base_instance? && lazy
|
|
368
|
+
|
|
369
|
+
lazy_block.evaluate_from(configuration)
|
|
228
370
|
end
|
|
229
371
|
end
|
|
230
372
|
end
|