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
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/middleware/base'
|
|
4
|
-
require 'grape/middleware/versioner/parse_media_type_patch'
|
|
5
|
-
|
|
6
3
|
module Grape
|
|
7
4
|
module Middleware
|
|
8
5
|
module Versioner
|
|
@@ -25,168 +22,86 @@ module Grape
|
|
|
25
22
|
# X-Cascade header to alert Grape::Router to attempt the next matched
|
|
26
23
|
# route.
|
|
27
24
|
class Header < Base
|
|
28
|
-
VENDOR_VERSION_HEADER_REGEX =
|
|
29
|
-
/\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
|
|
30
|
-
|
|
31
|
-
HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#{Regexp.last_match(0)}\^]+/.freeze
|
|
32
|
-
HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))+/.freeze
|
|
33
|
-
|
|
34
25
|
def before
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
match_best_quality_media_type! do |media_type|
|
|
27
|
+
env.update(
|
|
28
|
+
Grape::Env::API_TYPE => media_type.type,
|
|
29
|
+
Grape::Env::API_SUBTYPE => media_type.subtype,
|
|
30
|
+
Grape::Env::API_VENDOR => media_type.vendor,
|
|
31
|
+
Grape::Env::API_VERSION => media_type.version,
|
|
32
|
+
Grape::Env::API_FORMAT => media_type.format
|
|
33
|
+
)
|
|
43
34
|
end
|
|
44
35
|
end
|
|
45
36
|
|
|
46
37
|
private
|
|
47
38
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
strict_version_vendor_accept_header_presence_check
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def strict_accept_header_presence_check
|
|
54
|
-
return unless header.qvalues.empty?
|
|
55
|
-
|
|
56
|
-
fail_with_invalid_accept_header!('Accept header must be set.')
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def strict_version_vendor_accept_header_presence_check
|
|
60
|
-
return if versions.blank? || an_accept_header_with_version_and_vendor_is_present?
|
|
61
|
-
|
|
62
|
-
fail_with_invalid_accept_header!('API vendor or version not found.')
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def an_accept_header_with_version_and_vendor_is_present?
|
|
66
|
-
header.qvalues.keys.any? do |h|
|
|
67
|
-
VENDOR_VERSION_HEADER_REGEX.match?(h.sub('application/', ''))
|
|
68
|
-
end
|
|
69
|
-
end
|
|
39
|
+
def match_best_quality_media_type!
|
|
40
|
+
return unless vendor
|
|
70
41
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
42
|
+
strict_header_checks!
|
|
43
|
+
media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
|
|
44
|
+
return yield media_type if media_type
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
@media_type ||= header.best_of(available_media_types)
|
|
46
|
+
fail!
|
|
77
47
|
end
|
|
78
48
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
env[Grape::Env::API_TYPE] = type
|
|
82
|
-
env[Grape::Env::API_SUBTYPE] = subtype
|
|
83
|
-
|
|
84
|
-
return unless VENDOR_VERSION_HEADER_REGEX =~ subtype
|
|
85
|
-
|
|
86
|
-
env[Grape::Env::API_VENDOR] = Regexp.last_match[1]
|
|
87
|
-
env[Grape::Env::API_VERSION] = Regexp.last_match[2]
|
|
88
|
-
# weird that Grape::Middleware::Formatter also does this
|
|
89
|
-
env[Grape::Env::API_FORMAT] = Regexp.last_match[3]
|
|
49
|
+
def accept_header
|
|
50
|
+
env['HTTP_ACCEPT']
|
|
90
51
|
end
|
|
91
52
|
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
.new(message, error_headers)
|
|
95
|
-
end
|
|
53
|
+
def strict_header_checks!
|
|
54
|
+
return unless strict
|
|
96
55
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.new(message, error_headers)
|
|
56
|
+
accept_header_check!
|
|
57
|
+
version_and_vendor_check!
|
|
100
58
|
end
|
|
101
59
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
content_types.each_key do |extension|
|
|
105
|
-
versions.reverse_each do |version|
|
|
106
|
-
available_media_types << "application/vnd.#{vendor}-#{version}+#{extension}"
|
|
107
|
-
available_media_types << "application/vnd.#{vendor}-#{version}"
|
|
108
|
-
end
|
|
109
|
-
available_media_types << "application/vnd.#{vendor}+#{extension}"
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
available_media_types << "application/vnd.#{vendor}"
|
|
113
|
-
available_media_types.concat(content_types.values.flatten)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
60
|
+
def accept_header_check!
|
|
61
|
+
return if accept_header.present?
|
|
116
62
|
|
|
117
|
-
|
|
118
|
-
header.values.all? do |header_value|
|
|
119
|
-
vendor?(header_value) && request_vendor(header_value) != vendor
|
|
120
|
-
end
|
|
63
|
+
invalid_accept_header!('Accept header must be set.')
|
|
121
64
|
end
|
|
122
65
|
|
|
123
|
-
def
|
|
124
|
-
|
|
125
|
-
version?(header_value) && versions.exclude?(request_version(header_value))
|
|
126
|
-
end
|
|
127
|
-
end
|
|
66
|
+
def version_and_vendor_check!
|
|
67
|
+
return if versions.blank? || version_and_vendor?
|
|
128
68
|
|
|
129
|
-
|
|
130
|
-
Rack::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT]
|
|
131
|
-
rescue RuntimeError => e
|
|
132
|
-
fail_with_invalid_accept_header!(e.message)
|
|
69
|
+
invalid_accept_header!('API vendor or version not found.')
|
|
133
70
|
end
|
|
134
71
|
|
|
135
|
-
def
|
|
136
|
-
|
|
72
|
+
def q_values_mime_types
|
|
73
|
+
@q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
|
|
137
74
|
end
|
|
138
75
|
|
|
139
|
-
def
|
|
140
|
-
|
|
76
|
+
def version_and_vendor?
|
|
77
|
+
q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
|
|
141
78
|
end
|
|
142
79
|
|
|
143
|
-
def
|
|
144
|
-
|
|
80
|
+
def invalid_accept_header!(message)
|
|
81
|
+
raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
|
|
145
82
|
end
|
|
146
83
|
|
|
147
|
-
def
|
|
148
|
-
|
|
84
|
+
def invalid_version_header!(message)
|
|
85
|
+
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
|
149
86
|
end
|
|
150
87
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# (see Grape::Router for more
|
|
154
|
-
# information). To prevent # this behavior, and not add the `X-Cascade`
|
|
155
|
-
# header, one can set the `:cascade` option to `false`.
|
|
156
|
-
def cascade?
|
|
157
|
-
if version_options&.key?(:cascade)
|
|
158
|
-
version_options[:cascade]
|
|
159
|
-
else
|
|
160
|
-
true
|
|
161
|
-
end
|
|
162
|
-
end
|
|
88
|
+
def fail!
|
|
89
|
+
return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?
|
|
163
90
|
|
|
164
|
-
|
|
165
|
-
|
|
91
|
+
media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
|
|
92
|
+
vendor_not_found!(media_types) || version_not_found!(media_types)
|
|
166
93
|
end
|
|
167
94
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def vendor?(media_type)
|
|
171
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
|
172
|
-
subtype.present? && subtype[HAS_VENDOR_REGEX]
|
|
173
|
-
end
|
|
95
|
+
def vendor_not_found!(media_types)
|
|
96
|
+
return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
|
|
174
97
|
|
|
175
|
-
|
|
176
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
|
177
|
-
subtype.match(VENDOR_VERSION_HEADER_REGEX)[1]
|
|
98
|
+
invalid_accept_header!('API vendor not found.')
|
|
178
99
|
end
|
|
179
100
|
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
subtype.match(VENDOR_VERSION_HEADER_REGEX)[2]
|
|
183
|
-
end
|
|
101
|
+
def version_not_found!(media_types)
|
|
102
|
+
return unless media_types.all? { |media_type| media_type&.version && versions && !versions.include?(media_type.version) }
|
|
184
103
|
|
|
185
|
-
|
|
186
|
-
# @return [Boolean] whether the content type sets an API version
|
|
187
|
-
def version?(media_type)
|
|
188
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
|
189
|
-
subtype.present? && subtype[HAS_VERSION_REGEX]
|
|
104
|
+
invalid_version_header!('API version not found.')
|
|
190
105
|
end
|
|
191
106
|
end
|
|
192
107
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/middleware/base'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Middleware
|
|
7
5
|
module Versioner
|
|
@@ -21,31 +19,12 @@ module Grape
|
|
|
21
19
|
#
|
|
22
20
|
# env['api.version'] => 'v1'
|
|
23
21
|
class Param < Base
|
|
24
|
-
def default_options
|
|
25
|
-
{
|
|
26
|
-
version_options: {
|
|
27
|
-
parameter: 'apiver'
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
end
|
|
31
|
-
|
|
32
22
|
def before
|
|
33
|
-
potential_version =
|
|
34
|
-
return if potential_version.
|
|
35
|
-
|
|
36
|
-
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
|
37
|
-
env[Grape::Env::API_VERSION] = potential_version
|
|
38
|
-
env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def paramkey
|
|
44
|
-
version_options[:parameter] || default_options[:version_options][:parameter]
|
|
45
|
-
end
|
|
23
|
+
potential_version = query_params[parameter]
|
|
24
|
+
return if potential_version.blank?
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
|
|
26
|
+
version_not_found! unless potential_version_match?(potential_version)
|
|
27
|
+
env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter)
|
|
49
28
|
end
|
|
50
29
|
end
|
|
51
30
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/middleware/base'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Middleware
|
|
7
5
|
module Versioner
|
|
@@ -19,44 +17,69 @@ module Grape
|
|
|
19
17
|
# env['api.version'] => 'v1'
|
|
20
18
|
#
|
|
21
19
|
class Path < Base
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
20
|
+
def initialize(app, **options)
|
|
21
|
+
super
|
|
22
|
+
@prefixes = [mount_path, Grape::Util::PathNormalizer.call(prefix)].select { |p| p.present? && p != '/' }.freeze
|
|
26
23
|
end
|
|
27
24
|
|
|
28
25
|
def before
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
routed = routed_version
|
|
27
|
+
return env[Grape::Env::API_VERSION] = routed if routed
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
path = Grape::Router.normalize_path(path)
|
|
35
|
-
end
|
|
29
|
+
path_info = env[Grape::Env::GRAPE_NORMALIZED_PATH] || Grape::Util::PathNormalizer.call(env[Rack::PATH_INFO])
|
|
30
|
+
return if path_info == '/'
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
# `each` rather than `reduce`: Array does not override Enumerable's,
|
|
33
|
+
# so the generic accumulator path costs more than the work it drives
|
|
34
|
+
# on a list that holds at most a mount path and a prefix — and most
|
|
35
|
+
# often nothing at all.
|
|
36
|
+
@prefixes.each { |prefix| path_info = path_info.delete_prefix(prefix) if path_info.start_with?(prefix) }
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
slash_position = path_info.index('/', 1) # omit the first one
|
|
39
|
+
return version_from_first_segment(path_info, slash_position) if slash_position
|
|
40
|
+
|
|
41
|
+
version_from_only_segment(path_info)
|
|
43
42
|
end
|
|
44
43
|
|
|
45
44
|
private
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
# Under path versioning every route pattern carries the version as a
|
|
47
|
+
# named capture (Pattern::Path#build_parts inserts it), constrained to
|
|
48
|
+
# the declared versions -- so the router has already sliced the segment
|
|
49
|
+
# out and validated it, and re-deriving it from PATH_INFO would repeat
|
|
50
|
+
# the normalize, prefix-strip and slice below for the same answer.
|
|
51
|
+
#
|
|
52
|
+
# Nil, and the path parsed as before, when there are no routing args at
|
|
53
|
+
# all (the middleware used outside a Grape router) and for the greedy
|
|
54
|
+
# routes behind auto-OPTIONS and 405, which capture nothing. An Array
|
|
55
|
+
# means the route declared a +:version+ segment of its own on top of the
|
|
56
|
+
# versioning one: Mustermann then reports the capture as every position
|
|
57
|
+
# it matched rather than the single segment recorded here, so that too
|
|
58
|
+
# is left to the path parse.
|
|
59
|
+
def routed_version
|
|
60
|
+
version = env[Grape::Env::GRAPE_ROUTING_ARGS]&.[](:version)
|
|
61
|
+
version if version.is_a?(String)
|
|
62
|
+
end
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
def version_from_first_segment(path_info, slash_position)
|
|
65
|
+
potential_version = path_info[1..(slash_position - 1)]
|
|
66
|
+
version_not_found! unless potential_version_match?(potential_version)
|
|
67
|
+
env[Grape::Env::API_VERSION] = potential_version
|
|
52
68
|
end
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
# The path is a single segment (e.g. `GET /v1` — the root route of a
|
|
71
|
+
# path-versioned API). Nothing follows to disambiguate a version from a
|
|
72
|
+
# plain path or from a `.format` suffix (`/v1.json`), so the version is
|
|
73
|
+
# only recorded on an exact match against the declared versions, and an
|
|
74
|
+
# unmatched segment is left for the router to resolve — never a 404.
|
|
75
|
+
def version_from_only_segment(path_info)
|
|
76
|
+
candidate = path_info[1..]
|
|
77
|
+
candidate = candidate[0...candidate.rindex('.')] while candidate.include?('.') && !declared_version?(candidate)
|
|
78
|
+
env[Grape::Env::API_VERSION] = candidate if declared_version?(candidate)
|
|
56
79
|
end
|
|
57
80
|
|
|
58
|
-
def
|
|
59
|
-
|
|
81
|
+
def declared_version?(candidate)
|
|
82
|
+
versions.present? && versions.include?(candidate)
|
|
60
83
|
end
|
|
61
84
|
end
|
|
62
85
|
end
|
|
@@ -4,30 +4,23 @@
|
|
|
4
4
|
# on the requests. The current methods for determining version are:
|
|
5
5
|
#
|
|
6
6
|
# :header - version from HTTP Accept header.
|
|
7
|
+
# :accept_version_header - version from HTTP Accept-Version header
|
|
7
8
|
# :path - version from uri. e.g. /v1/resource
|
|
8
9
|
# :param - version from uri query string, e.g. /v1/resource?apiver=v1
|
|
9
|
-
#
|
|
10
10
|
# See individual classes for details.
|
|
11
11
|
module Grape
|
|
12
12
|
module Middleware
|
|
13
13
|
module Versioner
|
|
14
|
+
extend Grape::Util::Registry
|
|
15
|
+
|
|
14
16
|
module_function
|
|
15
17
|
|
|
16
|
-
# @param strategy [Symbol] :path, :header or :param
|
|
18
|
+
# @param strategy [Symbol] :path, :header, :accept_version_header or :param
|
|
17
19
|
# @return a middleware class based on strategy
|
|
18
20
|
def using(strategy)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
when :header
|
|
23
|
-
Header
|
|
24
|
-
when :param
|
|
25
|
-
Param
|
|
26
|
-
when :accept_version_header
|
|
27
|
-
AcceptVersionHeader
|
|
28
|
-
else
|
|
29
|
-
raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
|
|
30
|
-
end
|
|
21
|
+
raise Grape::Exceptions::InvalidVersionerOption, strategy unless registry.key?(strategy)
|
|
22
|
+
|
|
23
|
+
registry[strategy]
|
|
31
24
|
end
|
|
32
25
|
end
|
|
33
26
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
# Marker module for a mountable Grape application. Both {Grape::API} (the
|
|
5
|
+
# remountable user-facing class) and {Grape::API::Instance} (the compiled
|
|
6
|
+
# engine) extend it, so a mounted Grape app can be told apart from a bare
|
|
7
|
+
# Rack app with `is_a?(Grape::Mountable)` — a single, explicit predicate
|
|
8
|
+
# rather than duck-typing on an incidental internal method such as
|
|
9
|
+
# `inheritable_setting`.
|
|
10
|
+
#
|
|
11
|
+
# `Grape::API` and `Grape::API::Instance` are not related by inheritance and
|
|
12
|
+
# do not even respond to the same methods (the former to `mount_instance`,
|
|
13
|
+
# the latter to `inheritable_setting`/`endpoints`), so there is no common
|
|
14
|
+
# ancestor to key an `is_a?` check on without this marker.
|
|
15
|
+
#
|
|
16
|
+
# It answers identity only ("is this a Grape app?"). Capability checks that
|
|
17
|
+
# go on to call a stage-specific method — e.g. `respond_to?(:endpoints)`
|
|
18
|
+
# before reading `endpoints` — must stay as they are, since a `Mountable`
|
|
19
|
+
# does not necessarily respond to every such method.
|
|
20
|
+
module Mountable
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/grape/namespace.rb
CHANGED
|
@@ -1,45 +1,52 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/util/cache'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
# A container for endpoints or other namespaces, which allows for both
|
|
7
5
|
# logical grouping of endpoints as well as sharing common configuration.
|
|
8
6
|
# May also be referred to as group, segment, or resource.
|
|
9
7
|
class Namespace
|
|
10
|
-
attr_reader :space, :options
|
|
8
|
+
attr_reader :space, :requirements, :options
|
|
11
9
|
|
|
12
10
|
# @param space [String] the name of this namespace
|
|
13
11
|
# @param options [Hash] options hash
|
|
14
12
|
# @option options :requirements [Hash] param-regex pairs, all of which must
|
|
15
13
|
# be met by a request's params for all endpoints in this namespace, or
|
|
16
14
|
# validation will fail and return a 422.
|
|
17
|
-
def initialize(space, **options)
|
|
15
|
+
def initialize(space, requirements: nil, **options)
|
|
18
16
|
@space = space.to_s
|
|
17
|
+
@requirements = requirements
|
|
19
18
|
@options = options
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
# Retrieves the requirements from the options hash, if given.
|
|
23
|
-
# @return [Hash]
|
|
24
|
-
def requirements
|
|
25
|
-
options[:requirements] || {}
|
|
26
|
-
end
|
|
27
|
-
|
|
28
21
|
# (see ::joined_space_path)
|
|
29
22
|
def self.joined_space(settings)
|
|
30
23
|
settings&.map(&:space)
|
|
31
24
|
end
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
def ==(other)
|
|
27
|
+
other.class == self.class &&
|
|
28
|
+
other.space == space &&
|
|
29
|
+
other.requirements == requirements &&
|
|
30
|
+
other.options == options
|
|
31
|
+
end
|
|
32
|
+
alias eql? ==
|
|
33
|
+
|
|
34
|
+
def hash
|
|
35
|
+
[self.class, space, requirements, options].hash
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Join the namespaces from a list of Namespace objects to create a path
|
|
39
|
+
# prefix.
|
|
40
|
+
# @param settings [Array] list of Grape::Namespace objects.
|
|
35
41
|
def self.joined_space_path(settings)
|
|
36
|
-
|
|
42
|
+
JoinedSpaceCache[joined_space(settings)]
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
class JoinedSpaceCache < Grape::Util::Cache
|
|
40
46
|
def initialize
|
|
47
|
+
super
|
|
41
48
|
@cache = Hash.new do |h, joined_space|
|
|
42
|
-
h[joined_space] =
|
|
49
|
+
h[joined_space] = Grape::Util::PathNormalizer.call(joined_space.join('/'))
|
|
43
50
|
end
|
|
44
51
|
end
|
|
45
52
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ParamsBuilder
|
|
5
|
+
class Base
|
|
6
|
+
class << self
|
|
7
|
+
def call(_params)
|
|
8
|
+
raise NotImplementedError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def inherited(klass)
|
|
14
|
+
super
|
|
15
|
+
ParamsBuilder.register(klass)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module ParamsBuilder
|
|
5
|
+
extend Grape::Util::Registry
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def params_builder_for(short_name)
|
|
10
|
+
raise Grape::Exceptions::UnknownParamsBuilder, short_name unless registry.key?(short_name)
|
|
11
|
+
|
|
12
|
+
registry[short_name]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/grape/parser/json.rb
CHANGED
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Parser
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
rescue
|
|
10
|
-
|
|
11
|
-
raise Grape::Exceptions::InvalidMessageBody.new('application/json')
|
|
12
|
-
end
|
|
5
|
+
class Json < Base
|
|
6
|
+
def self.call(object, _env)
|
|
7
|
+
::Grape::Json.parse(object)
|
|
8
|
+
rescue ::Grape::Json::ParseError
|
|
9
|
+
# handle JSON parsing errors via the rescue handlers or provide error message
|
|
10
|
+
raise Grape::Exceptions::InvalidMessageBody.new('application/json')
|
|
13
11
|
end
|
|
14
12
|
end
|
|
15
13
|
end
|
data/lib/grape/parser/xml.rb
CHANGED
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Parser
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
rescue
|
|
10
|
-
|
|
11
|
-
raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
|
|
12
|
-
end
|
|
5
|
+
class Xml < Base
|
|
6
|
+
def self.call(object, _env)
|
|
7
|
+
::Grape::Xml.parse(object)
|
|
8
|
+
rescue ::Grape::Xml::ParseError
|
|
9
|
+
# handle XML parsing errors via the rescue handlers or provide error message
|
|
10
|
+
raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
|
|
13
11
|
end
|
|
14
12
|
end
|
|
15
13
|
end
|