grape 1.8.0 → 3.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +391 -1
- data/CONTRIBUTING.md +3 -11
- data/README.md +648 -552
- data/UPGRADING.md +803 -7
- data/grape.gemspec +11 -14
- data/lib/grape/api/instance.rb +68 -158
- data/lib/grape/api.rb +71 -107
- data/lib/grape/content_types.rb +23 -11
- data/lib/grape/cookies.rb +28 -25
- data/lib/grape/declared_params_handler.rb +116 -0
- data/lib/grape/dry_types.rb +48 -6
- data/lib/grape/dsl/callbacks.rb +14 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +15 -63
- data/lib/grape/dsl/entity.rb +88 -0
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +83 -64
- data/lib/grape/dsl/inside_route.rb +56 -295
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +50 -82
- data/lib/grape/dsl/request_response.rb +144 -146
- data/lib/grape/dsl/rescue_options.rb +24 -0
- data/lib/grape/dsl/routing.rb +261 -186
- data/lib/grape/dsl/settings.rb +29 -135
- data/lib/grape/dsl/validations.rb +39 -32
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +19 -0
- data/lib/grape/endpoint.rb +277 -250
- data/lib/grape/{util/env.rb → env.rb} +1 -8
- data/lib/grape/error_formatter/base.rb +56 -21
- data/lib/grape/error_formatter/json.rb +13 -15
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +12 -18
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +5 -25
- data/lib/grape/exceptions/base.rb +22 -58
- data/lib/grape/exceptions/error_response.rb +45 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +25 -9
- data/lib/grape/exceptions/validation_array_errors.rb +5 -0
- data/lib/grape/exceptions/validation_errors.rb +22 -26
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +4 -6
- data/lib/grape/formatter/serializable_hash.rb +6 -10
- data/lib/grape/formatter/txt.rb +3 -5
- data/lib/grape/formatter/xml.rb +4 -6
- data/lib/grape/formatter.rb +7 -25
- data/lib/grape/json.rb +46 -0
- data/lib/grape/locale/en.yml +43 -42
- data/lib/grape/middleware/auth/base.rb +11 -33
- data/lib/grape/middleware/auth/dsl.rb +37 -31
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/base.rb +56 -33
- data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
- data/lib/grape/middleware/error.rb +183 -91
- data/lib/grape/middleware/formatter.rb +94 -105
- data/lib/grape/middleware/precomputed_content_types.rb +46 -0
- data/lib/grape/middleware/stack.rb +27 -38
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +72 -0
- data/lib/grape/middleware/versioner/header.rb +44 -129
- data/lib/grape/middleware/versioner/param.rb +4 -25
- data/lib/grape/middleware/versioner/path.rb +13 -32
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +18 -12
- data/lib/grape/params_builder/base.rb +20 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +15 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +6 -8
- data/lib/grape/parser/xml.rb +6 -8
- data/lib/grape/parser.rb +5 -23
- data/lib/grape/path.rb +38 -63
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +168 -26
- data/lib/grape/router/base_route.rb +48 -0
- data/lib/grape/router/greedy_route.rb +26 -0
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern.rb +30 -34
- data/lib/grape/router/route.rb +28 -61
- data/lib/grape/router.rb +71 -86
- data/lib/grape/serve_stream/file_body.rb +1 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +1 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +58 -0
- data/lib/grape/util/base_inheritable.rb +14 -14
- data/lib/grape/util/cache.rb +1 -3
- data/lib/grape/util/deep_freeze.rb +35 -0
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +51 -27
- data/lib/grape/util/inheritable_values.rb +7 -5
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +22 -0
- data/lib/grape/util/lazy/value.rb +31 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +31 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +67 -0
- data/lib/grape/util/path_normalizer.rb +34 -0
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +12 -14
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +43 -27
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +28 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +39 -0
- data/lib/grape/validations/param_scope_tracker.rb +62 -0
- data/lib/grape/validations/params_documentation.rb +52 -0
- data/lib/grape/validations/params_scope.rb +180 -303
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +2 -2
- data/lib/grape/validations/types/array_coercer.rb +4 -7
- data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +15 -28
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +11 -35
- data/lib/grape/validations/types/set_coercer.rb +1 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +11 -0
- data/lib/grape/validations/types.rb +29 -54
- data/lib/grape/validations/validations_spec.rb +164 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +120 -36
- data/lib/grape/validations/validators/coerce_validator.rb +18 -36
- data/lib/grape/validations/validators/contract_scope_validator.rb +45 -0
- data/lib/grape/validations/validators/default_validator.rb +13 -16
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +15 -5
- data/lib/grape/validations/validators/length_validator.rb +48 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +49 -0
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +6 -15
- data/lib/grape/validations/validators/values_validator.rb +36 -65
- data/lib/grape/validations.rb +8 -21
- data/lib/grape/version.rb +1 -2
- data/lib/grape/xml.rb +17 -0
- data/lib/grape.rb +75 -288
- metadata +79 -291
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -19
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
- data/lib/grape/extensions/hash.rb +0 -22
- data/lib/grape/extensions/hashie/mash.rb +0 -26
- data/lib/grape/http/headers.rb +0 -61
- data/lib/grape/middleware/globals.rb +0 -16
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/json.rb +0 -12
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
- data/lib/grape/util/registrable.rb +0 -15
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/util/xml.rb +0 -10
- data/lib/grape/validations/attributes_doc.rb +0 -58
- data/lib/grape/validations/types/build_coercer.rb +0 -94
- data/lib/grape/validations/validator_factory.rb +0 -15
- data/spec/grape/api/custom_validations_spec.rb +0 -213
- data/spec/grape/api/deeply_included_options_spec.rb +0 -56
- data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
- data/spec/grape/api/documentation_spec.rb +0 -59
- data/spec/grape/api/inherited_helpers_spec.rb +0 -114
- data/spec/grape/api/instance_spec.rb +0 -103
- data/spec/grape/api/invalid_format_spec.rb +0 -45
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
- data/spec/grape/api/nested_helpers_spec.rb +0 -50
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
- data/spec/grape/api/parameters_modification_spec.rb +0 -41
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
- data/spec/grape/api/recognize_path_spec.rb +0 -21
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
- data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
- data/spec/grape/api/shared_helpers_spec.rb +0 -36
- data/spec/grape/api_remount_spec.rb +0 -509
- data/spec/grape/api_spec.rb +0 -4356
- data/spec/grape/dsl/callbacks_spec.rb +0 -45
- data/spec/grape/dsl/desc_spec.rb +0 -98
- data/spec/grape/dsl/headers_spec.rb +0 -62
- data/spec/grape/dsl/helpers_spec.rb +0 -100
- data/spec/grape/dsl/inside_route_spec.rb +0 -531
- data/spec/grape/dsl/logger_spec.rb +0 -24
- data/spec/grape/dsl/middleware_spec.rb +0 -60
- data/spec/grape/dsl/parameters_spec.rb +0 -180
- data/spec/grape/dsl/request_response_spec.rb +0 -225
- data/spec/grape/dsl/routing_spec.rb +0 -275
- data/spec/grape/dsl/settings_spec.rb +0 -261
- data/spec/grape/dsl/validations_spec.rb +0 -55
- data/spec/grape/endpoint/declared_spec.rb +0 -846
- data/spec/grape/endpoint_spec.rb +0 -1085
- data/spec/grape/entity_spec.rb +0 -336
- data/spec/grape/exceptions/base_spec.rb +0 -81
- data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
- data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_option_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
- data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
- data/spec/grape/exceptions/validation_spec.rb +0 -19
- data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
- data/spec/grape/grape_spec.rb +0 -9
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
- data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
- data/spec/grape/integration/rack_spec.rb +0 -51
- data/spec/grape/loading_spec.rb +0 -44
- data/spec/grape/middleware/auth/base_spec.rb +0 -31
- data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
- data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
- data/spec/grape/middleware/base_spec.rb +0 -221
- data/spec/grape/middleware/error_spec.rb +0 -85
- data/spec/grape/middleware/exception_spec.rb +0 -294
- data/spec/grape/middleware/formatter_spec.rb +0 -461
- data/spec/grape/middleware/globals_spec.rb +0 -30
- data/spec/grape/middleware/stack_spec.rb +0 -155
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
- data/spec/grape/middleware/versioner/header_spec.rb +0 -345
- data/spec/grape/middleware/versioner/param_spec.rb +0 -171
- data/spec/grape/middleware/versioner/path_spec.rb +0 -62
- data/spec/grape/middleware/versioner_spec.rb +0 -21
- data/spec/grape/named_api_spec.rb +0 -19
- data/spec/grape/parser_spec.rb +0 -86
- data/spec/grape/path_spec.rb +0 -252
- data/spec/grape/presenters/presenter_spec.rb +0 -71
- data/spec/grape/request_spec.rb +0 -126
- data/spec/grape/util/inheritable_setting_spec.rb +0 -242
- data/spec/grape/util/inheritable_values_spec.rb +0 -79
- data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
- data/spec/grape/util/stackable_values_spec.rb +0 -128
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
- data/spec/grape/validations/attributes_doc_spec.rb +0 -153
- data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
- data/spec/grape/validations/params_scope_spec.rb +0 -1420
- data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
- data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
- data/spec/grape/validations/types_spec.rb +0 -111
- data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
- data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
- data/spec/grape/validations/validators/base_spec.rb +0 -38
- data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
- data/spec/grape/validations/validators/default_spec.rb +0 -463
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
- data/spec/grape/validations/validators/except_values_spec.rb +0 -192
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
- data/spec/grape/validations/validators/presence_spec.rb +0 -315
- data/spec/grape/validations/validators/regexp_spec.rb +0 -161
- data/spec/grape/validations/validators/same_as_spec.rb +0 -57
- data/spec/grape/validations/validators/values_spec.rb +0 -733
- data/spec/grape/validations/validators/zh-CN.yml +0 -10
- data/spec/grape/validations_spec.rb +0 -2030
- data/spec/integration/eager_load/eager_load_spec.rb +0 -15
- data/spec/integration/multi_json/json_spec.rb +0 -7
- data/spec/integration/multi_xml/xml_spec.rb +0 -7
- data/spec/shared/deprecated_class_examples.rb +0 -16
- data/spec/shared/versioning_examples.rb +0 -215
- data/spec/spec_helper.rb +0 -52
- data/spec/support/basic_auth_encode_helpers.rb +0 -11
- data/spec/support/chunks.rb +0 -14
- data/spec/support/content_type_helpers.rb +0 -15
- data/spec/support/endpoint_faker.rb +0 -25
- data/spec/support/file_streamer.rb +0 -13
- data/spec/support/integer_helpers.rb +0 -13
- data/spec/support/versioned_helpers.rb +0 -55
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,22 @@
|
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
{
|
|
13
|
+
before: :befores,
|
|
14
|
+
before_validation: :before_validations,
|
|
15
|
+
after_validation: :after_validations,
|
|
16
|
+
after: :afters,
|
|
17
|
+
finally: :finallies
|
|
18
|
+
}.each do |method_name, plural_key|
|
|
19
|
+
define_method method_name do |&block|
|
|
20
|
+
inheritable_setting.namespace_stackable[plural_key] = block
|
|
65
21
|
end
|
|
66
22
|
end
|
|
67
23
|
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.namespace_stackable[:contract_key_map]
|
|
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.namespace_stackable[:declared_params].last || [])
|
|
28
|
+
renamed_params = inheritable_setting.route[:renamed_params] || {}
|
|
29
|
+
route_params = options.dig(:route_options, :params) || {} # options = endpoint's option
|
|
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
|
|
@@ -49,70 +50,21 @@ module Grape
|
|
|
49
50
|
# # ...
|
|
50
51
|
# end
|
|
51
52
|
#
|
|
52
|
-
def desc(description, options
|
|
53
|
-
if
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if configuration.respond_to?(:evaluate)
|
|
57
|
-
configuration.evaluate
|
|
58
|
-
# Within `given` or `mounted blocks` the configuration is already evaluated
|
|
59
|
-
elsif configuration.is_a?(Hash)
|
|
60
|
-
configuration
|
|
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)
|
|
53
|
+
def desc(description, *legacy_options, **options, &config_block)
|
|
54
|
+
if legacy_options.any?
|
|
55
|
+
Grape.deprecator.warn('Passing a positional options Hash to `desc` is deprecated. Pass keyword arguments instead.')
|
|
56
|
+
options = legacy_options.first.merge(options)
|
|
75
57
|
end
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
59
|
+
settings =
|
|
60
|
+
if config_block
|
|
61
|
+
endpoint_config = defined?(configuration) ? configuration : nil
|
|
62
|
+
Grape::Util::ApiDescription.new(description, endpoint_config, &config_block).settings
|
|
63
|
+
else
|
|
64
|
+
options.merge(description:)
|
|
106
65
|
end
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
entity(*args)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def config_context.failure(*args)
|
|
113
|
-
http_codes(*args)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
66
|
+
inheritable_setting.namespace[:description] = settings
|
|
67
|
+
inheritable_setting.route[:description] = settings
|
|
116
68
|
end
|
|
117
69
|
end
|
|
118
70
|
end
|
|
@@ -0,0 +1,88 @@
|
|
|
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.
|
|
49
|
+
# @param object [Object] the object to locate the Entity class for
|
|
50
|
+
# @return [Class] the located Entity class, or nil if none is found
|
|
51
|
+
def entity_class_for_obj(object)
|
|
52
|
+
klass = object_class(object)
|
|
53
|
+
|
|
54
|
+
representations = inheritable_setting.namespace_stackable_with_hash(:representations)
|
|
55
|
+
if representations
|
|
56
|
+
potential = klass.ancestors.detect { |potential| representations.key?(potential) }
|
|
57
|
+
return representations[potential] if potential && representations[potential]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
return unless klass.const_defined?(:Entity)
|
|
61
|
+
|
|
62
|
+
entity = klass.const_get(:Entity)
|
|
63
|
+
entity if entity.respond_to?(:represent)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Resolves the class used to look up the Entity for +object+.
|
|
69
|
+
# @param object [Object] the object to represent.
|
|
70
|
+
# @return [Class] the object's collection element class, wrapped class, or its own class.
|
|
71
|
+
def object_class(object)
|
|
72
|
+
return object.klass if object.respond_to?(:klass)
|
|
73
|
+
return object.first.class if object.respond_to?(:first)
|
|
74
|
+
|
|
75
|
+
object.class
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param entity_class [Class] the entity class to use for representation.
|
|
79
|
+
# @param object [Object] the object to represent.
|
|
80
|
+
# @param options [Hash] additional options forwarded to the entity's `represent` call.
|
|
81
|
+
# @return the representation of the given object as done through the given entity_class.
|
|
82
|
+
def entity_representation_for(entity_class, object, options)
|
|
83
|
+
embeds = env.key?(Grape::Env::API_VERSION) ? { env:, version: env[Grape::Env::API_VERSION] } : { env: }
|
|
84
|
+
entity_class.represent(object, **embeds, **options)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/grape/dsl/headers.rb
CHANGED
|
@@ -10,9 +10,9 @@ module Grape
|
|
|
10
10
|
# 4. Delete a specifc header key-value pair
|
|
11
11
|
def header(key = nil, val = nil)
|
|
12
12
|
if key
|
|
13
|
-
val ? header[key
|
|
13
|
+
val ? header[key] = val : header.delete(key)
|
|
14
14
|
else
|
|
15
|
-
@header ||=
|
|
15
|
+
@header ||= Grape::Util::Header.new
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
alias headers header
|