grape 1.8.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +517 -1
- data/CONTRIBUTING.md +19 -11
- data/README.md +841 -545
- data/UPGRADING.md +1553 -7
- data/grape.gemspec +11 -14
- data/lib/grape/api/instance.rb +77 -160
- data/lib/grape/api.rb +75 -107
- data/lib/grape/content_types.rb +67 -9
- data/lib/grape/cookies.rb +28 -25
- data/lib/grape/declared_params_handler.rb +116 -0
- data/lib/grape/dry_types.rb +48 -6
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +25 -63
- data/lib/grape/dsl/entity.rb +106 -0
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +83 -64
- data/lib/grape/dsl/inside_route.rb +69 -296
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +88 -94
- data/lib/grape/dsl/request_response.rb +143 -147
- data/lib/grape/dsl/rescue_options.rb +25 -0
- data/lib/grape/dsl/routing.rb +333 -191
- data/lib/grape/dsl/settings.rb +29 -135
- data/lib/grape/dsl/validations.rb +39 -32
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +25 -0
- data/lib/grape/endpoint.rb +319 -269
- data/lib/grape/{util/env.rb → env.rb} +8 -8
- data/lib/grape/error_formatter/base.rb +57 -21
- data/lib/grape/error_formatter/json.rb +13 -15
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +12 -18
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +9 -25
- data/lib/grape/exceptions/base.rb +22 -58
- data/lib/grape/exceptions/error_response.rb +48 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +28 -11
- data/lib/grape/exceptions/validation_array_errors.rb +5 -0
- data/lib/grape/exceptions/validation_errors.rb +22 -26
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +5 -6
- data/lib/grape/formatter/serializable_hash.rb +7 -10
- data/lib/grape/formatter/txt.rb +3 -5
- data/lib/grape/formatter/xml.rb +4 -6
- data/lib/grape/formatter.rb +7 -25
- data/lib/grape/json.rb +46 -0
- data/lib/grape/locale/en.yml +44 -42
- data/lib/grape/middleware/auth/base.rb +11 -33
- data/lib/grape/middleware/auth/dsl.rb +12 -34
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/base.rb +56 -33
- data/lib/grape/middleware/error.rb +290 -91
- data/lib/grape/middleware/formatter.rb +164 -106
- data/lib/grape/middleware/precomputed_content_types.rb +51 -0
- data/lib/grape/middleware/stack.rb +38 -40
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +66 -0
- data/lib/grape/middleware/versioner/header.rb +44 -129
- data/lib/grape/middleware/versioner/param.rb +4 -25
- data/lib/grape/middleware/versioner/path.rb +48 -25
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +21 -14
- data/lib/grape/params_builder/base.rb +20 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +15 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +6 -8
- data/lib/grape/parser/xml.rb +6 -8
- data/lib/grape/parser.rb +5 -23
- data/lib/grape/path.rb +8 -94
- data/lib/grape/precompiled_json.rb +50 -0
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +200 -26
- data/lib/grape/router/base_route.rb +85 -0
- data/lib/grape/router/greedy_route.rb +30 -0
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern/path.rb +78 -0
- data/lib/grape/router/pattern.rb +77 -35
- data/lib/grape/router/route.rb +79 -60
- data/lib/grape/router.rb +128 -104
- data/lib/grape/serve_stream/file_body.rb +7 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +7 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +67 -0
- data/lib/grape/util/cache.rb +22 -5
- data/lib/grape/util/deep_freeze.rb +34 -0
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/freeze_on_new.rb +20 -0
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +777 -39
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +22 -0
- data/lib/grape/util/lazy/value.rb +31 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +31 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +74 -0
- data/lib/grape/util/path_normalizer.rb +37 -0
- data/lib/grape/util/registry.rb +37 -0
- data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
- data/lib/grape/util/stackable_values.rb +40 -16
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +61 -28
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +29 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +35 -0
- data/lib/grape/validations/param_scope_tracker.rb +62 -0
- data/lib/grape/validations/params_documentation.rb +52 -0
- data/lib/grape/validations/params_scope.rb +223 -306
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +6 -4
- data/lib/grape/validations/types/array_coercer.rb +7 -12
- data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +17 -28
- data/lib/grape/validations/types/json.rb +1 -5
- data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
- data/lib/grape/validations/types/primitive_coercer.rb +14 -35
- data/lib/grape/validations/types/set_coercer.rb +1 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
- data/lib/grape/validations/types.rb +29 -54
- data/lib/grape/validations/validations_spec.rb +164 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +118 -37
- data/lib/grape/validations/validators/coerce_validator.rb +26 -38
- data/lib/grape/validations/validators/contract_scope_validator.rb +46 -0
- data/lib/grape/validations/validators/default_validator.rb +13 -16
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +15 -5
- data/lib/grape/validations/validators/length_validator.rb +50 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +51 -0
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +7 -15
- data/lib/grape/validations/validators/values_validator.rb +36 -65
- data/lib/grape/validations.rb +8 -21
- data/lib/grape/version.rb +1 -2
- data/lib/grape/xml.rb +17 -0
- data/lib/grape.rb +96 -288
- metadata +83 -294
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -19
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
- data/lib/grape/extensions/hash.rb +0 -22
- data/lib/grape/extensions/hashie/mash.rb +0 -26
- data/lib/grape/http/headers.rb +0 -61
- data/lib/grape/middleware/globals.rb +0 -16
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/base_inheritable.rb +0 -43
- data/lib/grape/util/inheritable_values.rb +0 -31
- data/lib/grape/util/json.rb +0 -12
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
- data/lib/grape/util/registrable.rb +0 -15
- data/lib/grape/util/reverse_stackable_values.rb +0 -20
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/util/xml.rb +0 -10
- data/lib/grape/validations/attributes_doc.rb +0 -58
- data/lib/grape/validations/types/build_coercer.rb +0 -94
- data/lib/grape/validations/validator_factory.rb +0 -15
- data/spec/grape/api/custom_validations_spec.rb +0 -213
- data/spec/grape/api/deeply_included_options_spec.rb +0 -56
- data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
- data/spec/grape/api/documentation_spec.rb +0 -59
- data/spec/grape/api/inherited_helpers_spec.rb +0 -114
- data/spec/grape/api/instance_spec.rb +0 -103
- data/spec/grape/api/invalid_format_spec.rb +0 -45
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
- data/spec/grape/api/nested_helpers_spec.rb +0 -50
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
- data/spec/grape/api/parameters_modification_spec.rb +0 -41
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
- data/spec/grape/api/recognize_path_spec.rb +0 -21
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
- data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
- data/spec/grape/api/shared_helpers_spec.rb +0 -36
- data/spec/grape/api_remount_spec.rb +0 -509
- data/spec/grape/api_spec.rb +0 -4356
- data/spec/grape/dsl/callbacks_spec.rb +0 -45
- data/spec/grape/dsl/desc_spec.rb +0 -98
- data/spec/grape/dsl/headers_spec.rb +0 -62
- data/spec/grape/dsl/helpers_spec.rb +0 -100
- data/spec/grape/dsl/inside_route_spec.rb +0 -531
- data/spec/grape/dsl/logger_spec.rb +0 -24
- data/spec/grape/dsl/middleware_spec.rb +0 -60
- data/spec/grape/dsl/parameters_spec.rb +0 -180
- data/spec/grape/dsl/request_response_spec.rb +0 -225
- data/spec/grape/dsl/routing_spec.rb +0 -275
- data/spec/grape/dsl/settings_spec.rb +0 -261
- data/spec/grape/dsl/validations_spec.rb +0 -55
- data/spec/grape/endpoint/declared_spec.rb +0 -846
- data/spec/grape/endpoint_spec.rb +0 -1085
- data/spec/grape/entity_spec.rb +0 -336
- data/spec/grape/exceptions/base_spec.rb +0 -81
- data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
- data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_option_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
- data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
- data/spec/grape/exceptions/validation_spec.rb +0 -19
- data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
- data/spec/grape/grape_spec.rb +0 -9
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
- data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
- data/spec/grape/integration/rack_spec.rb +0 -51
- data/spec/grape/loading_spec.rb +0 -44
- data/spec/grape/middleware/auth/base_spec.rb +0 -31
- data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
- data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
- data/spec/grape/middleware/base_spec.rb +0 -221
- data/spec/grape/middleware/error_spec.rb +0 -85
- data/spec/grape/middleware/exception_spec.rb +0 -294
- data/spec/grape/middleware/formatter_spec.rb +0 -461
- data/spec/grape/middleware/globals_spec.rb +0 -30
- data/spec/grape/middleware/stack_spec.rb +0 -155
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
- data/spec/grape/middleware/versioner/header_spec.rb +0 -345
- data/spec/grape/middleware/versioner/param_spec.rb +0 -171
- data/spec/grape/middleware/versioner/path_spec.rb +0 -62
- data/spec/grape/middleware/versioner_spec.rb +0 -21
- data/spec/grape/named_api_spec.rb +0 -19
- data/spec/grape/parser_spec.rb +0 -86
- data/spec/grape/path_spec.rb +0 -252
- data/spec/grape/presenters/presenter_spec.rb +0 -71
- data/spec/grape/request_spec.rb +0 -126
- data/spec/grape/util/inheritable_setting_spec.rb +0 -242
- data/spec/grape/util/inheritable_values_spec.rb +0 -79
- data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
- data/spec/grape/util/stackable_values_spec.rb +0 -128
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
- data/spec/grape/validations/attributes_doc_spec.rb +0 -153
- data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
- data/spec/grape/validations/params_scope_spec.rb +0 -1420
- data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
- data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
- data/spec/grape/validations/types_spec.rb +0 -111
- data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
- data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
- data/spec/grape/validations/validators/base_spec.rb +0 -38
- data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
- data/spec/grape/validations/validators/default_spec.rb +0 -463
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
- data/spec/grape/validations/validators/except_values_spec.rb +0 -192
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
- data/spec/grape/validations/validators/presence_spec.rb +0 -315
- data/spec/grape/validations/validators/regexp_spec.rb +0 -161
- data/spec/grape/validations/validators/same_as_spec.rb +0 -57
- data/spec/grape/validations/validators/values_spec.rb +0 -733
- data/spec/grape/validations/validators/zh-CN.yml +0 -10
- data/spec/grape/validations_spec.rb +0 -2030
- data/spec/integration/eager_load/eager_load_spec.rb +0 -15
- data/spec/integration/multi_json/json_spec.rb +0 -7
- data/spec/integration/multi_xml/xml_spec.rb +0 -7
- data/spec/shared/deprecated_class_examples.rb +0 -16
- data/spec/shared/versioning_examples.rb +0 -215
- data/spec/spec_helper.rb +0 -52
- data/spec/support/basic_auth_encode_helpers.rb +0 -11
- data/spec/support/chunks.rb +0 -14
- data/spec/support/content_type_helpers.rb +0 -15
- data/spec/support/endpoint_faker.rb +0 -25
- data/spec/support/file_streamer.rb +0 -13
- data/spec/support/integer_helpers.rb +0 -13
- data/spec/support/versioned_helpers.rb +0 -55
data/README.md
CHANGED
|
@@ -1,172 +1,22 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/grape)
|
|
4
|
-
[](https://codeclimate.com/github/ruby-grape/grape)
|
|
4
|
+
[](https://github.com/ruby-grape/grape/actions/workflows/test.yml)
|
|
6
5
|
[](https://coveralls.io/github/ruby-grape/grape?branch=master)
|
|
7
|
-
[](https://inch-ci.org/github/ruby-grape/grape)
|
|
8
|
-
[](https://gitter.im/ruby-grape/grape?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
9
|
-
|
|
10
|
-
## Table of Contents
|
|
11
|
-
|
|
12
|
-
- [What is Grape?](#what-is-grape)
|
|
13
|
-
- [Stable Release](#stable-release)
|
|
14
|
-
- [Project Resources](#project-resources)
|
|
15
|
-
- [Grape for Enterprise](#grape-for-enterprise)
|
|
16
|
-
- [Installation](#installation)
|
|
17
|
-
- [Basic Usage](#basic-usage)
|
|
18
|
-
- [Mounting](#mounting)
|
|
19
|
-
- [All](#all)
|
|
20
|
-
- [Rack](#rack)
|
|
21
|
-
- [ActiveRecord without Rails](#activerecord-without-rails)
|
|
22
|
-
- [Rails 4](#rails-4)
|
|
23
|
-
- [Rails 5+](#rails-5)
|
|
24
|
-
- [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks)
|
|
25
|
-
- [Rails](#rails)
|
|
26
|
-
- [Rails < 5.2](#rails--52)
|
|
27
|
-
- [Rails 6.0](#rails-60)
|
|
28
|
-
- [Modules](#modules)
|
|
29
|
-
- [Remounting](#remounting)
|
|
30
|
-
- [Mount Configuration](#mount-configuration)
|
|
31
|
-
- [Versioning](#versioning)
|
|
32
|
-
- [Path](#path)
|
|
33
|
-
- [Header](#header)
|
|
34
|
-
- [Accept-Version Header](#accept-version-header)
|
|
35
|
-
- [Param](#param)
|
|
36
|
-
- [Describing Methods](#describing-methods)
|
|
37
|
-
- [Configuration](#configuration)
|
|
38
|
-
- [Parameters](#parameters)
|
|
39
|
-
- [Params Class](#params-class)
|
|
40
|
-
- [Declared](#declared)
|
|
41
|
-
- [Include Parent Namespaces](#include-parent-namespaces)
|
|
42
|
-
- [Include Missing](#include-missing)
|
|
43
|
-
- [Evaluate Given](#evaluate-given)
|
|
44
|
-
- [Parameter Validation and Coercion](#parameter-validation-and-coercion)
|
|
45
|
-
- [Supported Parameter Types](#supported-parameter-types)
|
|
46
|
-
- [Integer/Fixnum and Coercions](#integerfixnum-and-coercions)
|
|
47
|
-
- [Custom Types and Coercions](#custom-types-and-coercions)
|
|
48
|
-
- [Multipart File Parameters](#multipart-file-parameters)
|
|
49
|
-
- [First-Class JSON Types](#first-class-json-types)
|
|
50
|
-
- [Multiple Allowed Types](#multiple-allowed-types)
|
|
51
|
-
- [Validation of Nested Parameters](#validation-of-nested-parameters)
|
|
52
|
-
- [Dependent Parameters](#dependent-parameters)
|
|
53
|
-
- [Group Options](#group-options)
|
|
54
|
-
- [Renaming](#renaming)
|
|
55
|
-
- [Built-in Validators](#built-in-validators)
|
|
56
|
-
- [allow_blank](#allow_blank)
|
|
57
|
-
- [values](#values)
|
|
58
|
-
- [except_values](#except_values)
|
|
59
|
-
- [same_as](#same_as)
|
|
60
|
-
- [regexp](#regexp)
|
|
61
|
-
- [mutually_exclusive](#mutually_exclusive)
|
|
62
|
-
- [exactly_one_of](#exactly_one_of)
|
|
63
|
-
- [at_least_one_of](#at_least_one_of)
|
|
64
|
-
- [all_or_none_of](#all_or_none_of)
|
|
65
|
-
- [Nested mutually_exclusive, exactly_one_of, at_least_one_of, all_or_none_of](#nested-mutually_exclusive-exactly_one_of-at_least_one_of-all_or_none_of)
|
|
66
|
-
- [Namespace Validation and Coercion](#namespace-validation-and-coercion)
|
|
67
|
-
- [Custom Validators](#custom-validators)
|
|
68
|
-
- [Validation Errors](#validation-errors)
|
|
69
|
-
- [I18n](#i18n)
|
|
70
|
-
- [Custom Validation messages](#custom-validation-messages)
|
|
71
|
-
- [presence, allow_blank, values, regexp](#presence-allow_blank-values-regexp)
|
|
72
|
-
- [same_as](#same_as-1)
|
|
73
|
-
- [all_or_none_of](#all_or_none_of-1)
|
|
74
|
-
- [mutually_exclusive](#mutually_exclusive-1)
|
|
75
|
-
- [exactly_one_of](#exactly_one_of-1)
|
|
76
|
-
- [at_least_one_of](#at_least_one_of-1)
|
|
77
|
-
- [Coerce](#coerce)
|
|
78
|
-
- [With Lambdas](#with-lambdas)
|
|
79
|
-
- [Pass symbols for i18n translations](#pass-symbols-for-i18n-translations)
|
|
80
|
-
- [Overriding Attribute Names](#overriding-attribute-names)
|
|
81
|
-
- [With Default](#with-default)
|
|
82
|
-
- [Headers](#headers)
|
|
83
|
-
- [Request](#request)
|
|
84
|
-
- [Header Case Handling](#header-case-handling)
|
|
85
|
-
- [Response](#response)
|
|
86
|
-
- [Routes](#routes)
|
|
87
|
-
- [Helpers](#helpers)
|
|
88
|
-
- [Path Helpers](#path-helpers)
|
|
89
|
-
- [Parameter Documentation](#parameter-documentation)
|
|
90
|
-
- [Cookies](#cookies)
|
|
91
|
-
- [HTTP Status Code](#http-status-code)
|
|
92
|
-
- [Redirecting](#redirecting)
|
|
93
|
-
- [Recognizing Path](#recognizing-path)
|
|
94
|
-
- [Allowed Methods](#allowed-methods)
|
|
95
|
-
- [Raising Exceptions](#raising-exceptions)
|
|
96
|
-
- [Default Error HTTP Status Code](#default-error-http-status-code)
|
|
97
|
-
- [Handling 404](#handling-404)
|
|
98
|
-
- [Exception Handling](#exception-handling)
|
|
99
|
-
- [Rescuing exceptions inside namespaces](#rescuing-exceptions-inside-namespaces)
|
|
100
|
-
- [Unrescuable Exceptions](#unrescuable-exceptions)
|
|
101
|
-
- [Exceptions that should be rescued explicitly](#exceptions-that-should-be-rescued-explicitly)
|
|
102
|
-
- [Rails 3.x](#rails-3x)
|
|
103
|
-
- [Logging](#logging)
|
|
104
|
-
- [API Formats](#api-formats)
|
|
105
|
-
- [JSONP](#jsonp)
|
|
106
|
-
- [CORS](#cors)
|
|
107
|
-
- [Content-type](#content-type)
|
|
108
|
-
- [API Data Formats](#api-data-formats)
|
|
109
|
-
- [JSON and XML Processors](#json-and-xml-processors)
|
|
110
|
-
- [RESTful Model Representations](#restful-model-representations)
|
|
111
|
-
- [Grape Entities](#grape-entities)
|
|
112
|
-
- [Hypermedia and Roar](#hypermedia-and-roar)
|
|
113
|
-
- [Rabl](#rabl)
|
|
114
|
-
- [Active Model Serializers](#active-model-serializers)
|
|
115
|
-
- [Sending Raw or No Data](#sending-raw-or-no-data)
|
|
116
|
-
- [Authentication](#authentication)
|
|
117
|
-
- [Basic and Digest Auth](#basic-and-digest-auth)
|
|
118
|
-
- [Register custom middleware for authentication](#register-custom-middleware-for-authentication)
|
|
119
|
-
- [Describing and Inspecting an API](#describing-and-inspecting-an-api)
|
|
120
|
-
- [Current Route and Endpoint](#current-route-and-endpoint)
|
|
121
|
-
- [Before, After and Finally](#before-after-and-finally)
|
|
122
|
-
- [Anchoring](#anchoring)
|
|
123
|
-
- [Using Custom Middleware](#using-custom-middleware)
|
|
124
|
-
- [Grape Middleware](#grape-middleware)
|
|
125
|
-
- [Rails Middleware](#rails-middleware)
|
|
126
|
-
- [Remote IP](#remote-ip)
|
|
127
|
-
- [Writing Tests](#writing-tests)
|
|
128
|
-
- [Writing Tests with Rack](#writing-tests-with-rack)
|
|
129
|
-
- [RSpec](#rspec)
|
|
130
|
-
- [Airborne](#airborne)
|
|
131
|
-
- [MiniTest](#minitest)
|
|
132
|
-
- [Writing Tests with Rails](#writing-tests-with-rails)
|
|
133
|
-
- [RSpec](#rspec-1)
|
|
134
|
-
- [MiniTest](#minitest-1)
|
|
135
|
-
- [Stubbing Helpers](#stubbing-helpers)
|
|
136
|
-
- [Reloading API Changes in Development](#reloading-api-changes-in-development)
|
|
137
|
-
- [Reloading in Rack Applications](#reloading-in-rack-applications)
|
|
138
|
-
- [Reloading in Rails Applications](#reloading-in-rails-applications)
|
|
139
|
-
- [Performance Monitoring](#performance-monitoring)
|
|
140
|
-
- [Active Support Instrumentation](#active-support-instrumentation)
|
|
141
|
-
- [endpoint_run.grape](#endpoint_rungrape)
|
|
142
|
-
- [endpoint_render.grape](#endpoint_rendergrape)
|
|
143
|
-
- [endpoint_run_filters.grape](#endpoint_run_filtersgrape)
|
|
144
|
-
- [endpoint_run_validators.grape](#endpoint_run_validatorsgrape)
|
|
145
|
-
- [format_response.grape](#format_responsegrape)
|
|
146
|
-
- [Monitoring Products](#monitoring-products)
|
|
147
|
-
- [Contributing to Grape](#contributing-to-grape)
|
|
148
|
-
- [Security](#security)
|
|
149
|
-
- [License](#license)
|
|
150
|
-
- [Copyright](#copyright)
|
|
151
6
|
|
|
152
7
|
## What is Grape?
|
|
153
8
|
|
|
154
|
-
Grape is a REST-like API framework for Ruby. It's designed to run on Rack
|
|
155
|
-
or complement existing web application frameworks such as Rails and Sinatra by
|
|
156
|
-
providing a simple DSL to easily develop RESTful APIs. It has built-in support
|
|
157
|
-
for common conventions, including multiple formats, subdomain/prefix restriction,
|
|
158
|
-
content negotiation, versioning and much more.
|
|
9
|
+
Grape is a REST-like API framework for Ruby. It's designed to run on Rack or complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily develop RESTful APIs. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.
|
|
159
10
|
|
|
160
11
|
## Stable Release
|
|
161
12
|
|
|
162
|
-
You're reading the documentation for the stable release of Grape,
|
|
163
|
-
Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
|
|
13
|
+
You're reading the documentation for the stable release of Grape, 4.0.1.
|
|
164
14
|
|
|
165
15
|
## Project Resources
|
|
166
16
|
|
|
167
17
|
* [Grape Website](http://www.ruby-grape.org)
|
|
168
18
|
* [Documentation](http://www.rubydoc.info/gems/grape)
|
|
169
|
-
* Need help?
|
|
19
|
+
* Need help? [Open an Issue](https://github.com/ruby-grape/grape/issues)
|
|
170
20
|
* [Follow us on Twitter](https://twitter.com/grapeframework)
|
|
171
21
|
|
|
172
22
|
## Grape for Enterprise
|
|
@@ -177,7 +27,7 @@ The maintainers of Grape are working with Tidelift to deliver commercial support
|
|
|
177
27
|
|
|
178
28
|
## Installation
|
|
179
29
|
|
|
180
|
-
Ruby
|
|
30
|
+
Ruby 3.3 or newer is required.
|
|
181
31
|
|
|
182
32
|
Grape is available as a gem, to install it run:
|
|
183
33
|
|
|
@@ -186,8 +36,7 @@ Grape is available as a gem, to install it run:
|
|
|
186
36
|
## Basic Usage
|
|
187
37
|
|
|
188
38
|
Grape APIs are Rack applications that are created by subclassing `Grape::API`.
|
|
189
|
-
Below is a simple example showing some of the more common features of Grape in
|
|
190
|
-
the context of recreating parts of the Twitter API.
|
|
39
|
+
Below is a simple example showing some of the more common features of Grape in the context of recreating parts of the Twitter API.
|
|
191
40
|
|
|
192
41
|
```ruby
|
|
193
42
|
module Twitter
|
|
@@ -266,12 +115,16 @@ module Twitter
|
|
|
266
115
|
end
|
|
267
116
|
```
|
|
268
117
|
|
|
118
|
+
## Rails 7.1
|
|
119
|
+
|
|
120
|
+
Grape's [deprecator](https://api.rubyonrails.org/v7.1.0/classes/ActiveSupport/Deprecation.html) will be added to your application's deprecators [automatically](lib/grape/railtie.rb) as `:grape`, so that your application's configuration can be applied to it.
|
|
121
|
+
|
|
269
122
|
## Mounting
|
|
270
123
|
|
|
271
124
|
### All
|
|
272
125
|
|
|
273
126
|
|
|
274
|
-
By default Grape will compile the routes on the first route, it is possible to pre-load routes using the `compile!` method.
|
|
127
|
+
By default Grape will compile the routes on the first route, but it is possible to pre-load routes using the `compile!` method.
|
|
275
128
|
|
|
276
129
|
```ruby
|
|
277
130
|
Twitter::API.compile!
|
|
@@ -281,8 +134,7 @@ This can be added to your `config.ru` (if using rackup), `application.rb` (if us
|
|
|
281
134
|
|
|
282
135
|
### Rack
|
|
283
136
|
|
|
284
|
-
The above sample creates a Rack application that can be run from a rackup `config.ru` file
|
|
285
|
-
with `rackup`:
|
|
137
|
+
The above sample creates a Rack application that can be run from a rackup `config.ru` file with `rackup`:
|
|
286
138
|
|
|
287
139
|
```ruby
|
|
288
140
|
run Twitter::API
|
|
@@ -306,32 +158,9 @@ And would respond to the following routes:
|
|
|
306
158
|
|
|
307
159
|
Grape will also automatically respond to HEAD and OPTIONS for all GET, and just OPTIONS for all other routes.
|
|
308
160
|
|
|
309
|
-
### ActiveRecord without Rails
|
|
310
|
-
|
|
311
|
-
If you want to use ActiveRecord within Grape, you will need to make sure that ActiveRecord's connection pool
|
|
312
|
-
is handled correctly.
|
|
313
|
-
|
|
314
|
-
#### Rails 4
|
|
315
|
-
|
|
316
|
-
The easiest way to achieve that is by using ActiveRecord's `ConnectionManagement` middleware in your
|
|
317
|
-
`config.ru` before mounting Grape, e.g.:
|
|
318
|
-
|
|
319
|
-
```ruby
|
|
320
|
-
use ActiveRecord::ConnectionAdapters::ConnectionManagement
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
#### Rails 5+
|
|
324
|
-
|
|
325
|
-
Use [otr-activerecord](https://github.com/jhollinger/otr-activerecord) as follows:
|
|
326
|
-
|
|
327
|
-
```ruby
|
|
328
|
-
use OTR::ActiveRecord::ConnectionManagement
|
|
329
|
-
```
|
|
330
|
-
|
|
331
161
|
### Alongside Sinatra (or other frameworks)
|
|
332
162
|
|
|
333
|
-
If you wish to mount Grape alongside another Rack framework such as Sinatra, you can do so easily using
|
|
334
|
-
`Rack::Cascade`:
|
|
163
|
+
If you wish to mount Grape alongside another Rack framework such as Sinatra, you can do so easily using `Rack::Cascade`:
|
|
335
164
|
|
|
336
165
|
```ruby
|
|
337
166
|
# Example config.ru
|
|
@@ -367,21 +196,8 @@ Modify `config/routes`:
|
|
|
367
196
|
```ruby
|
|
368
197
|
mount Twitter::API => '/'
|
|
369
198
|
```
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
Modify `application.rb`:
|
|
374
|
-
|
|
375
|
-
```ruby
|
|
376
|
-
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
|
377
|
-
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
See [below](#reloading-api-changes-in-development) for additional code that enables reloading of API changes in development.
|
|
381
|
-
|
|
382
|
-
#### Rails 6.0
|
|
383
|
-
|
|
384
|
-
For Rails versions greater than 6.0.0.beta2, `Zeitwerk` autoloader is the default for CRuby. By default `Zeitwerk` inflects `api` as `Api` instead of `API`. To make our example work, you need to uncomment the lines at the bottom of `config/initializers/inflections.rb`, and add `API` as an acronym:
|
|
199
|
+
#### Zeitwerk
|
|
200
|
+
Rails's default autoloader is `Zeitwerk`. By default, it inflects `api` as `Api` instead of `API`. To make our example work, you need to uncomment the lines at the bottom of `config/initializers/inflections.rb`, and add `API` as an acronym:
|
|
385
201
|
|
|
386
202
|
```ruby
|
|
387
203
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
@@ -391,8 +207,7 @@ end
|
|
|
391
207
|
|
|
392
208
|
### Modules
|
|
393
209
|
|
|
394
|
-
You can mount multiple API implementations inside another one. These don't have to be
|
|
395
|
-
different versions, but may be components of the same API.
|
|
210
|
+
You can mount multiple API implementations inside another one. These don't have to be different versions, but may be components of the same API.
|
|
396
211
|
|
|
397
212
|
```ruby
|
|
398
213
|
class Twitter::API < Grape::API
|
|
@@ -409,7 +224,7 @@ class Twitter::API < Grape::API
|
|
|
409
224
|
end
|
|
410
225
|
```
|
|
411
226
|
|
|
412
|
-
|
|
227
|
+
Declarations as `before/after/rescue_from` can be placed before or after `mount`. In any case they will be inherited.
|
|
413
228
|
|
|
414
229
|
```ruby
|
|
415
230
|
class Twitter::API < Grape::API
|
|
@@ -417,8 +232,20 @@ class Twitter::API < Grape::API
|
|
|
417
232
|
header 'X-Base-Header', 'will be defined for all APIs that are mounted below'
|
|
418
233
|
end
|
|
419
234
|
|
|
235
|
+
rescue_from :all do
|
|
236
|
+
error!({ "error" => "Internal Server Error" }, 500)
|
|
237
|
+
end
|
|
238
|
+
|
|
420
239
|
mount Twitter::Users
|
|
421
240
|
mount Twitter::Search
|
|
241
|
+
|
|
242
|
+
after do
|
|
243
|
+
clean_cache!
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
rescue_from ZeroDivisionError do
|
|
247
|
+
error!({ "error" => "Not found" }, 404)
|
|
248
|
+
end
|
|
422
249
|
end
|
|
423
250
|
```
|
|
424
251
|
|
|
@@ -484,7 +311,7 @@ mount ::Some::Api => '/some/api', with: { condition: true }
|
|
|
484
311
|
|
|
485
312
|
You can access `configuration` on the class (to use as dynamic attributes), inside blocks (like namespace)
|
|
486
313
|
|
|
487
|
-
If you want logic happening
|
|
314
|
+
If you want logic happening based on a `configuration`, you can use the helper `given`.
|
|
488
315
|
|
|
489
316
|
```ruby
|
|
490
317
|
class ConditionalEndpoint::API < Grape::API
|
|
@@ -549,10 +376,69 @@ end
|
|
|
549
376
|
|
|
550
377
|
## Versioning
|
|
551
378
|
|
|
552
|
-
|
|
553
|
-
|
|
379
|
+
You have the option to provide various versions of your API by establishing a separate `Grape::API` class for each offered version and then integrating them into a primary `Grape::API` class. Ensure that newer versions are mounted before older ones. The default approach to versioning directs the request to the subsequent Rack middleware if a specific version is not found.
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
require 'v1'
|
|
383
|
+
require 'v2'
|
|
384
|
+
require 'v3'
|
|
385
|
+
class App < Grape::API
|
|
386
|
+
mount V3
|
|
387
|
+
mount V2
|
|
388
|
+
mount V1
|
|
389
|
+
end
|
|
390
|
+
```
|
|
554
391
|
|
|
555
|
-
|
|
392
|
+
To maintain the same endpoints from earlier API versions without rewriting them, you can indicate multiple versions within the previous API versions.
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
class V1 < Grape::API
|
|
396
|
+
version 'v1', 'v2', 'v3'
|
|
397
|
+
|
|
398
|
+
get '/foo' do
|
|
399
|
+
# your code for GET /foo
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
get '/other' do
|
|
403
|
+
# your code for GET /other
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
class V2 < Grape::API
|
|
408
|
+
version 'v2', 'v3'
|
|
409
|
+
|
|
410
|
+
get '/var' do
|
|
411
|
+
# your code for GET /var
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
class V3 < Grape::API
|
|
416
|
+
version 'v3'
|
|
417
|
+
|
|
418
|
+
get '/foo' do
|
|
419
|
+
# your new code for GET /foo
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Using the example provided, the subsequent endpoints will be accessible across various versions:
|
|
425
|
+
|
|
426
|
+
```shell
|
|
427
|
+
GET /v1/foo
|
|
428
|
+
GET /v1/other
|
|
429
|
+
GET /v2/foo # => Same behavior as v1
|
|
430
|
+
GET /v2/other # => Same behavior as v1
|
|
431
|
+
GET /v2/var # => New endpoint not available in v1
|
|
432
|
+
GET /v3/foo # => Different behavior to v1 and v2
|
|
433
|
+
GET /v3/other # => Same behavior as v1 and v2
|
|
434
|
+
GET /v3/var # => Same behavior as v2
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
There are four strategies in which clients can reach your API's endpoints: `:path`, `:header`, `:accept_version_header` and `:param`. The default strategy is `:path`.
|
|
438
|
+
|
|
439
|
+
### Strategies
|
|
440
|
+
|
|
441
|
+
#### Path
|
|
556
442
|
|
|
557
443
|
```ruby
|
|
558
444
|
version 'v1', using: :path
|
|
@@ -562,7 +448,7 @@ Using this versioning strategy, clients should pass the desired version in the U
|
|
|
562
448
|
|
|
563
449
|
curl http://localhost:9292/v1/statuses/public_timeline
|
|
564
450
|
|
|
565
|
-
|
|
451
|
+
#### Header
|
|
566
452
|
|
|
567
453
|
```ruby
|
|
568
454
|
version 'v1', using: :header, vendor: 'twitter'
|
|
@@ -576,20 +462,19 @@ vnd.vendor-and-or-resource-v1234+format
|
|
|
576
462
|
|
|
577
463
|
Basically all tokens between the final `-` and the `+` will be interpreted as the version.
|
|
578
464
|
|
|
579
|
-
Using this versioning strategy, clients should pass the desired version in the HTTP `Accept`
|
|
465
|
+
Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` header.
|
|
580
466
|
|
|
581
467
|
curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
|
|
582
468
|
|
|
583
|
-
By default, the first matching version is used when no `Accept` header is
|
|
584
|
-
supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
|
|
585
|
-
one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
|
|
586
|
-
is returned when no correct `Accept` header is supplied.
|
|
469
|
+
By default, the first matching version is used when no `Accept` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept` header is supplied.
|
|
587
470
|
|
|
588
|
-
When an invalid `Accept` header is supplied, a `406 Not Acceptable` error is returned if the `:cascade`
|
|
589
|
-
option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route
|
|
590
|
-
matches.
|
|
471
|
+
When an invalid `Accept` header is supplied, a `406 Not Acceptable` error is returned if the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.
|
|
591
472
|
|
|
592
|
-
|
|
473
|
+
Grape will evaluate the relative quality preference included in Accept headers and default to a quality of 1.0 when omitted. In the following example a Grape API that supports XML and JSON in that order will return JSON:
|
|
474
|
+
|
|
475
|
+
curl -H "Accept: text/xml;q=0.8, application/json;q=0.9" localhost:1234/resource
|
|
476
|
+
|
|
477
|
+
#### Accept-Version Header
|
|
593
478
|
|
|
594
479
|
```ruby
|
|
595
480
|
version 'v1', using: :accept_version_header
|
|
@@ -599,20 +484,15 @@ Using this versioning strategy, clients should pass the desired version in the H
|
|
|
599
484
|
|
|
600
485
|
curl -H "Accept-Version:v1" http://localhost:9292/statuses/public_timeline
|
|
601
486
|
|
|
602
|
-
By default, the first matching version is used when no `Accept-Version` header is
|
|
603
|
-
supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
|
|
604
|
-
one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
|
|
605
|
-
is returned when no correct `Accept` header is supplied and the `:cascade` option is set to `false`.
|
|
606
|
-
Otherwise a `404 Not Found` error is returned by Rack if no other route matches.
|
|
487
|
+
By default, the first matching version is used when no `Accept-Version` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept-Version` header is supplied and the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.
|
|
607
488
|
|
|
608
|
-
|
|
489
|
+
#### Param
|
|
609
490
|
|
|
610
491
|
```ruby
|
|
611
492
|
version 'v1', using: :param
|
|
612
493
|
```
|
|
613
494
|
|
|
614
|
-
Using this versioning strategy, clients should pass the desired version as a request parameter,
|
|
615
|
-
either in the URL query string or in the request body.
|
|
495
|
+
Using this versioning strategy, clients should pass the desired version as a request parameter, either in the URL query string or in the request body.
|
|
616
496
|
|
|
617
497
|
curl http://localhost:9292/statuses/public_timeline?apiver=v1
|
|
618
498
|
|
|
@@ -625,11 +505,32 @@ version 'v1', using: :param, parameter: 'v'
|
|
|
625
505
|
curl http://localhost:9292/statuses/public_timeline?v=v1
|
|
626
506
|
|
|
627
507
|
|
|
508
|
+
## Linting
|
|
509
|
+
|
|
510
|
+
You can check whether your API is in conformance with the [Rack's specification](https://github.com/rack/rack/blob/main/SPEC.rdoc) by calling `lint!` at the API level or through [configuration](#configuration).
|
|
511
|
+
|
|
512
|
+
```ruby
|
|
513
|
+
class Api < Grape::API
|
|
514
|
+
lint!
|
|
515
|
+
end
|
|
516
|
+
```
|
|
517
|
+
```ruby
|
|
518
|
+
Grape.configure do |config|
|
|
519
|
+
config.lint = true
|
|
520
|
+
end
|
|
521
|
+
```
|
|
522
|
+
```ruby
|
|
523
|
+
Grape.config.lint = true
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Bug in Rack::ETag under Rack 3.X
|
|
527
|
+
If you're using Rack 3.X and the `Rack::ETag` middleware (used by [Rails](https://guides.rubyonrails.org/rails_on_rack.html#inspecting-middleware-stack)), a [bug](https://github.com/rack/rack/pull/2324) related to linting has been fixed in [3.1.13](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3113---2025-04-13) and [3.0.15](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3015---2025-04-13) respectively.
|
|
528
|
+
|
|
628
529
|
## Describing Methods
|
|
629
530
|
|
|
630
531
|
You can add a description to API methods and namespaces. The description would be used by [grape-swagger][grape-swagger] to generate swagger compliant documentation.
|
|
631
532
|
|
|
632
|
-
Note: Description block is only for documentation and won't
|
|
533
|
+
Note: Description block is only for documentation and won't affect API behavior.
|
|
633
534
|
|
|
634
535
|
```ruby
|
|
635
536
|
desc 'Returns your public timeline.' do
|
|
@@ -665,7 +566,7 @@ end
|
|
|
665
566
|
* `params`: Define parameters directly from an `Entity`
|
|
666
567
|
* `success`: (former entity) The `Entity` to be used to present the success response for this route.
|
|
667
568
|
* `failure`: (former http_codes) A definition of the used failure HTTP Codes and Entities.
|
|
668
|
-
* `
|
|
569
|
+
* `default_response`: (former `default`) The definition and `Entity` used to present the default response for this route.
|
|
669
570
|
* `named`: A helper to give a route a name and find it with this name in the documentation Hash
|
|
670
571
|
* `headers`: A definition of the used Headers
|
|
671
572
|
* Other options can be found in [grape-swagger][grape-swagger]
|
|
@@ -678,6 +579,7 @@ Use `Grape.configure` to set up global settings at load time.
|
|
|
678
579
|
Currently the configurable settings are:
|
|
679
580
|
|
|
680
581
|
* `param_builder`: Sets the [Parameter Builder](#parameters), defaults to `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder`.
|
|
582
|
+
* `raise_rendering_errors`: Lets an error response that cannot be rendered propagate out of the middleware stack instead of being answered with a failsafe `500`, defaults to `false`. See [When the error response itself cannot be rendered](#when-the-error-response-itself-cannot-be-rendered).
|
|
681
583
|
|
|
682
584
|
To change a setting value make sure that at some point during load time the following code runs
|
|
683
585
|
|
|
@@ -691,10 +593,13 @@ For example, for the `param_builder`, the following code could run in an initial
|
|
|
691
593
|
|
|
692
594
|
```ruby
|
|
693
595
|
Grape.configure do |config|
|
|
694
|
-
config.param_builder =
|
|
596
|
+
config.param_builder = :hashie_mash
|
|
695
597
|
end
|
|
696
598
|
```
|
|
697
599
|
|
|
600
|
+
Available parameter builders are `:hash`, `:hash_with_indifferent_access`, and `:hashie_mash`.
|
|
601
|
+
See [params_builder](lib/grape/params_builder).
|
|
602
|
+
|
|
698
603
|
You can also configure a single API:
|
|
699
604
|
|
|
700
605
|
```ruby
|
|
@@ -703,13 +608,11 @@ API.configure do |config|
|
|
|
703
608
|
end
|
|
704
609
|
```
|
|
705
610
|
|
|
706
|
-
This will be available inside the API with `configuration`, as if it were
|
|
707
|
-
[mount configuration](#mount-configuration).
|
|
611
|
+
This will be available inside the API with `configuration`, as if it were [mount configuration](#mount-configuration).
|
|
708
612
|
|
|
709
613
|
## Parameters
|
|
710
614
|
|
|
711
|
-
Request parameters are available through the `params` hash object. This includes `GET`, `POST`
|
|
712
|
-
and `PUT` parameters, along with any named parameters you specify in your route strings.
|
|
615
|
+
Request parameters are available through the `params` hash object. This includes `GET`, `POST` and `PUT` parameters, along with any named parameters you specify in your route strings.
|
|
713
616
|
|
|
714
617
|
```ruby
|
|
715
618
|
get :public_timeline do
|
|
@@ -717,8 +620,7 @@ get :public_timeline do
|
|
|
717
620
|
end
|
|
718
621
|
```
|
|
719
622
|
|
|
720
|
-
Parameters are automatically populated from the request body on `POST` and `
|
|
721
|
-
XML content-types.
|
|
623
|
+
Parameters are automatically populated from the request body on `POST`, `PUT` and `QUERY` for form input, JSON and XML content-types.
|
|
722
624
|
|
|
723
625
|
The request:
|
|
724
626
|
|
|
@@ -764,7 +666,7 @@ By default parameters are available as `ActiveSupport::HashWithIndifferentAccess
|
|
|
764
666
|
|
|
765
667
|
```ruby
|
|
766
668
|
class API < Grape::API
|
|
767
|
-
|
|
669
|
+
build_with :hashie_mash
|
|
768
670
|
|
|
769
671
|
params do
|
|
770
672
|
optional :color, type: String
|
|
@@ -778,16 +680,15 @@ The class can also be overridden on individual parameter blocks using `build_wit
|
|
|
778
680
|
|
|
779
681
|
```ruby
|
|
780
682
|
params do
|
|
781
|
-
build_with
|
|
683
|
+
build_with :hash
|
|
782
684
|
optional :color, type: String
|
|
783
685
|
end
|
|
784
686
|
```
|
|
785
687
|
|
|
786
|
-
Or globally with the [Configuration](#configuration) `Grape.configure.param_builder`.
|
|
787
|
-
|
|
788
688
|
In the example above, `params["color"]` will return `nil` since `params` is a plain `Hash`.
|
|
789
689
|
|
|
790
|
-
Available parameter builders are
|
|
690
|
+
Available parameter builders are `:hash`, `:hash_with_indifferent_access`, and `:hashie_mash`.
|
|
691
|
+
See [params_builder](lib/grape/params_builder).
|
|
791
692
|
|
|
792
693
|
### Declared
|
|
793
694
|
|
|
@@ -1057,8 +958,7 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d
|
|
|
1057
958
|
}
|
|
1058
959
|
````
|
|
1059
960
|
|
|
1060
|
-
Note that an attribute with a `nil` value is not considered *missing* and will also be returned
|
|
1061
|
-
when `include_missing` is set to `false`:
|
|
961
|
+
Note that an attribute with a `nil` value is not considered *missing* and will also be returned when `include_missing` is set to `false`:
|
|
1062
962
|
|
|
1063
963
|
**Request**
|
|
1064
964
|
|
|
@@ -1176,6 +1076,35 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/child -d '{"chil
|
|
|
1176
1076
|
}
|
|
1177
1077
|
````
|
|
1178
1078
|
|
|
1079
|
+
### Parameter Precedence
|
|
1080
|
+
|
|
1081
|
+
Using `route_param` takes higher precedence over a regular parameter defined with same name:
|
|
1082
|
+
|
|
1083
|
+
```ruby
|
|
1084
|
+
params do
|
|
1085
|
+
requires :foo, type: String
|
|
1086
|
+
end
|
|
1087
|
+
route_param :foo do
|
|
1088
|
+
get do
|
|
1089
|
+
{ value: params[:foo] }
|
|
1090
|
+
end
|
|
1091
|
+
end
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
**Request**
|
|
1095
|
+
|
|
1096
|
+
```bash
|
|
1097
|
+
curl -X POST -H "Content-Type: application/json" localhost:9292/bar -d '{"foo": "baz"}'
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
**Response**
|
|
1101
|
+
|
|
1102
|
+
```json
|
|
1103
|
+
{
|
|
1104
|
+
"value": "bar"
|
|
1105
|
+
}
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1179
1108
|
## Parameter Validation and Coercion
|
|
1180
1109
|
|
|
1181
1110
|
You can define validations and coercion options for your parameters using a `params` block.
|
|
@@ -1197,8 +1126,7 @@ put ':id' do
|
|
|
1197
1126
|
end
|
|
1198
1127
|
```
|
|
1199
1128
|
|
|
1200
|
-
When a type is specified an implicit validation is done after the coercion to ensure
|
|
1201
|
-
the output type is the one declared.
|
|
1129
|
+
When a type is specified an implicit validation is done after the coercion to ensure the output type is the one declared.
|
|
1202
1130
|
|
|
1203
1131
|
Optional parameters can have a default value.
|
|
1204
1132
|
|
|
@@ -1210,9 +1138,7 @@ params do
|
|
|
1210
1138
|
end
|
|
1211
1139
|
```
|
|
1212
1140
|
|
|
1213
|
-
Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same
|
|
1214
|
-
number for each call to the endpoint of this `params` block. To have the default evaluate
|
|
1215
|
-
lazily with each request use a lambda, like `:random_number` above.
|
|
1141
|
+
Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same number for each call to the endpoint of this `params` block. To have the default evaluate lazily with each request use a lambda, like `:random_number` above.
|
|
1216
1142
|
|
|
1217
1143
|
Note that default values will be passed through to any validation options specified.
|
|
1218
1144
|
The following example will always fail if `:color` is not explicitly provided.
|
|
@@ -1231,6 +1157,15 @@ params do
|
|
|
1231
1157
|
end
|
|
1232
1158
|
```
|
|
1233
1159
|
|
|
1160
|
+
You can use the value of one parameter as the default value of some other parameter. In this case, if the `primary_color` parameter is not provided, it will have the same value as the `color` one. If both of them not provided, both of them will have `blue` value.
|
|
1161
|
+
|
|
1162
|
+
```ruby
|
|
1163
|
+
params do
|
|
1164
|
+
optional :color, type: String, default: 'blue'
|
|
1165
|
+
optional :primary_color, type: String, default: -> (params) { params[:color] }
|
|
1166
|
+
end
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1234
1169
|
### Supported Parameter Types
|
|
1235
1170
|
|
|
1236
1171
|
The following are all valid types, supported out of the box by Grape:
|
|
@@ -1248,36 +1183,9 @@ The following are all valid types, supported out of the box by Grape:
|
|
|
1248
1183
|
* Rack::Multipart::UploadedFile (alias `File`)
|
|
1249
1184
|
* JSON
|
|
1250
1185
|
|
|
1251
|
-
### Integer/Fixnum and Coercions
|
|
1252
|
-
|
|
1253
|
-
Please be aware that the behavior differs between Ruby 2.4 and earlier versions.
|
|
1254
|
-
In Ruby 2.4, values consisting of numbers are converted to Integer, but in earlier versions it will be treated as Fixnum.
|
|
1255
|
-
|
|
1256
|
-
```ruby
|
|
1257
|
-
params do
|
|
1258
|
-
requires :integers, type: Hash do
|
|
1259
|
-
requires :int, coerce: Integer
|
|
1260
|
-
end
|
|
1261
|
-
end
|
|
1262
|
-
get '/int' do
|
|
1263
|
-
params[:integers][:int].class
|
|
1264
|
-
end
|
|
1265
|
-
|
|
1266
|
-
...
|
|
1267
|
-
|
|
1268
|
-
get '/int' integers: { int: '45' }
|
|
1269
|
-
#=> Integer in ruby 2.4
|
|
1270
|
-
#=> Fixnum in earlier ruby versions
|
|
1271
|
-
```
|
|
1272
|
-
|
|
1273
1186
|
### Custom Types and Coercions
|
|
1274
1187
|
|
|
1275
|
-
Aside from the default set of supported types listed above, any class can be
|
|
1276
|
-
used as a type as long as an explicit coercion method is supplied. If the type
|
|
1277
|
-
implements a class-level `parse` method, Grape will use it automatically.
|
|
1278
|
-
This method must take one string argument and return an instance of the correct
|
|
1279
|
-
type, or return an instance of `Grape::Types::InvalidValue` which optionally
|
|
1280
|
-
accepts a message to be returned in the response.
|
|
1188
|
+
Aside from the default set of supported types listed above, any class can be used as a type as long as an explicit coercion method is supplied. If the type implements a class-level `parse` method, Grape will use it automatically. This method must take one string argument and return an instance of the correct type, or return an instance of `Grape::Types::InvalidValue` which optionally accepts a message to be returned in the response.
|
|
1281
1189
|
|
|
1282
1190
|
```ruby
|
|
1283
1191
|
class Color
|
|
@@ -1287,7 +1195,7 @@ class Color
|
|
|
1287
1195
|
end
|
|
1288
1196
|
|
|
1289
1197
|
def self.parse(value)
|
|
1290
|
-
return new(value) if %w[blue red green]
|
|
1198
|
+
return new(value) if %w[blue red green].include?(value)
|
|
1291
1199
|
|
|
1292
1200
|
Grape::Types::InvalidValue.new('Unsupported color')
|
|
1293
1201
|
end
|
|
@@ -1305,10 +1213,7 @@ get '/stuff' do
|
|
|
1305
1213
|
end
|
|
1306
1214
|
```
|
|
1307
1215
|
|
|
1308
|
-
Alternatively, a custom coercion method may be supplied for any type of parameter
|
|
1309
|
-
using `coerce_with`. Any class or object may be given that implements a `parse` or
|
|
1310
|
-
`call` method, in that order of precedence. The method must accept a single string
|
|
1311
|
-
parameter, and the return value must match the given `type`.
|
|
1216
|
+
Alternatively, a custom coercion method may be supplied for any type of parameter using `coerce_with`. Any class or object may be given that implements a `parse` or `call` method, in that order of precedence. The method must accept a single string parameter, and the return value must match the given `type`.
|
|
1312
1217
|
|
|
1313
1218
|
```ruby
|
|
1314
1219
|
params do
|
|
@@ -1332,9 +1237,7 @@ params do
|
|
|
1332
1237
|
end
|
|
1333
1238
|
```
|
|
1334
1239
|
|
|
1335
|
-
Grape will assert that coerced values match the given `type`, and will reject the request
|
|
1336
|
-
if they do not. To override this behaviour, custom types may implement a `parsed?` method
|
|
1337
|
-
that should accept a single argument and return `true` if the value passes type validation.
|
|
1240
|
+
Grape will assert that coerced values match the given `type`, and will reject the request if they do not. To override this behaviour, custom types may implement a `parsed?` method that should accept a single argument and return `true` if the value passes type validation.
|
|
1338
1241
|
|
|
1339
1242
|
```ruby
|
|
1340
1243
|
class SecureUri
|
|
@@ -1369,9 +1272,7 @@ end
|
|
|
1369
1272
|
|
|
1370
1273
|
### First-Class `JSON` Types
|
|
1371
1274
|
|
|
1372
|
-
Grape supports complex parameters given as JSON-formatted strings using the special `type: JSON`
|
|
1373
|
-
declaration. JSON objects and arrays of objects are accepted equally, with nested validation
|
|
1374
|
-
rules applied to all objects in either case:
|
|
1275
|
+
Grape supports complex parameters given as JSON-formatted strings using the special `type: JSON` declaration. JSON objects and arrays of objects are accepted equally, with nested validation rules applied to all objects in either case:
|
|
1375
1276
|
|
|
1376
1277
|
```ruby
|
|
1377
1278
|
params do
|
|
@@ -1390,8 +1291,7 @@ client.get('/', json: '{"int":4}') # => HTTP 400
|
|
|
1390
1291
|
client.get('/', json: '[{"int":4}]') # => HTTP 400
|
|
1391
1292
|
```
|
|
1392
1293
|
|
|
1393
|
-
Additionally `type: Array[JSON]` may be used, which explicitly marks the parameter as an array
|
|
1394
|
-
of objects. If a single object is supplied it will be wrapped.
|
|
1294
|
+
Additionally `type: Array[JSON]` may be used, which explicitly marks the parameter as an array of objects. If a single object is supplied it will be wrapped.
|
|
1395
1295
|
|
|
1396
1296
|
```ruby
|
|
1397
1297
|
params do
|
|
@@ -1403,8 +1303,7 @@ get '/' do
|
|
|
1403
1303
|
params[:json].each { |obj| ... } # always works
|
|
1404
1304
|
end
|
|
1405
1305
|
```
|
|
1406
|
-
For stricter control over the type of JSON structure which may be supplied,
|
|
1407
|
-
use `type: Array, coerce_with: JSON` or `type: Hash, coerce_with: JSON`.
|
|
1306
|
+
For stricter control over the type of JSON structure which may be supplied, use `type: Array, coerce_with: JSON` or `type: Hash, coerce_with: JSON`.
|
|
1408
1307
|
|
|
1409
1308
|
### Multiple Allowed Types
|
|
1410
1309
|
|
|
@@ -1423,8 +1322,7 @@ client.get('/', status_code: 300) # => 300
|
|
|
1423
1322
|
client.get('/', status_code: %w(404 NOT FOUND)) # => [404, "NOT", "FOUND"]
|
|
1424
1323
|
```
|
|
1425
1324
|
|
|
1426
|
-
As a special case, variant-member-type collections may also be declared, by
|
|
1427
|
-
passing a `Set` or `Array` with more than one member to `type`:
|
|
1325
|
+
As a special case, variant-member-type collections may also be declared, by passing a `Set` or `Array` with more than one member to `type`:
|
|
1428
1326
|
|
|
1429
1327
|
```ruby
|
|
1430
1328
|
params do
|
|
@@ -1437,14 +1335,61 @@ end
|
|
|
1437
1335
|
client.get('/', status_codes: %w(1 two)) # => [1, "two"]
|
|
1438
1336
|
```
|
|
1439
1337
|
|
|
1338
|
+
### Multiple Hash Schemas with `oneof`
|
|
1339
|
+
|
|
1340
|
+
A Hash parameter that may take one of several different shapes can be declared with the `oneof:` option. Each variant is a `Proc` that uses the normal `params` DSL — so the full validator surface (`requires`, `optional`, `regexp:`, `values:`, `allow_blank:`, nested `Hash`/`Array`, etc.) is available inside.
|
|
1341
|
+
|
|
1342
|
+
```ruby
|
|
1343
|
+
params do
|
|
1344
|
+
requires :value, type: Hash, oneof: [
|
|
1345
|
+
proc { requires :fixed_price, type: Float },
|
|
1346
|
+
proc do
|
|
1347
|
+
requires :time_unit, type: String
|
|
1348
|
+
requires :rate, type: Float
|
|
1349
|
+
end
|
|
1350
|
+
]
|
|
1351
|
+
end
|
|
1352
|
+
post '/pricing' do
|
|
1353
|
+
params[:value]
|
|
1354
|
+
end
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
Both of these requests succeed:
|
|
1358
|
+
|
|
1359
|
+
```bash
|
|
1360
|
+
curl -d '{"value":{"fixed_price":100.0}}' -H 'Content-Type: application/json' /pricing
|
|
1361
|
+
curl -d '{"value":{"time_unit":"hour","rate":50.0}}' -H 'Content-Type: application/json' /pricing
|
|
1362
|
+
```
|
|
1363
|
+
|
|
1364
|
+
A request that doesn't match any variant returns `400` with `value does not match any of the allowed schemas`.
|
|
1365
|
+
|
|
1366
|
+
Variants are tried in declaration order; the first variant that validates without errors wins, and any coercions it performed (e.g. `"150.5"` → `150.5`) are applied to the request params. Nested hash structures inside variants work the same as elsewhere in Grape:
|
|
1367
|
+
|
|
1368
|
+
```ruby
|
|
1369
|
+
params do
|
|
1370
|
+
requires :options, type: Hash, oneof: [
|
|
1371
|
+
proc do
|
|
1372
|
+
requires :form, type: Hash do
|
|
1373
|
+
requires :colour, type: String
|
|
1374
|
+
optional :size, type: Integer
|
|
1375
|
+
end
|
|
1376
|
+
end,
|
|
1377
|
+
proc do
|
|
1378
|
+
requires :api, type: Hash do
|
|
1379
|
+
requires :authenticated, type: Grape::API::Boolean
|
|
1380
|
+
end
|
|
1381
|
+
end
|
|
1382
|
+
]
|
|
1383
|
+
end
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
`oneof:` requires `type: Hash`. The variants array must be non-empty and each entry must be a `Proc`; violating either raises an `ArgumentError` at definition time.
|
|
1387
|
+
|
|
1440
1388
|
### Validation of Nested Parameters
|
|
1441
1389
|
|
|
1442
1390
|
Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
|
|
1443
|
-
In the [above example](#parameter-validation-and-coercion), this means `params[:media][:url]` is required along with `params[:id]`,
|
|
1444
|
-
and `
|
|
1445
|
-
With a block, `group`, `requires` and `optional` accept an additional option `type` which can
|
|
1446
|
-
be either `Array` or `Hash`, and defaults to `Array`. Depending on the value, the nested
|
|
1447
|
-
parameters will be treated either as values of a hash or as values of hashes in an array.
|
|
1391
|
+
In the [above example](#parameter-validation-and-coercion), this means `params[:media][:url]` is required along with `params[:id]`, and `params[:audio][:format]` is required only if `params[:audio]` is present.
|
|
1392
|
+
With a block, `group`, `requires` and `optional` accept an additional option `type` which can be either `Array` or `Hash`, and defaults to `Array`. Depending on the value, the nested parameters will be treated either as values of a hash or as values of hashes in an array.
|
|
1448
1393
|
|
|
1449
1394
|
```ruby
|
|
1450
1395
|
params do
|
|
@@ -1462,9 +1407,7 @@ end
|
|
|
1462
1407
|
|
|
1463
1408
|
### Dependent Parameters
|
|
1464
1409
|
|
|
1465
|
-
Suppose some of your parameters are only relevant if another parameter is given;
|
|
1466
|
-
Grape allows you to express this relationship through the `given` method in your
|
|
1467
|
-
parameters block, like so:
|
|
1410
|
+
Suppose some of your parameters are only relevant if another parameter is given; Grape allows you to express this relationship through the `given` method in your parameters block, like so:
|
|
1468
1411
|
|
|
1469
1412
|
```ruby
|
|
1470
1413
|
params do
|
|
@@ -1503,31 +1446,45 @@ Note: param in `given` should be the renamed one. In the example, it should be `
|
|
|
1503
1446
|
|
|
1504
1447
|
### Group Options
|
|
1505
1448
|
|
|
1506
|
-
Parameters options can be grouped. It can be useful if you want to extract
|
|
1507
|
-
|
|
1508
|
-
|
|
1449
|
+
Parameters options can be grouped. It can be useful if you want to extract common validation or types for several parameters.
|
|
1450
|
+
Within these groups, individual parameters can extend or selectively override the common settings, allowing you to maintain the defaults at the group level while still applying parameter-specific rules where necessary.
|
|
1451
|
+
|
|
1452
|
+
The example below presents a typical case when parameters share common options.
|
|
1509
1453
|
|
|
1510
1454
|
```ruby
|
|
1511
1455
|
params do
|
|
1512
|
-
requires :first_name, type: String, regexp: /w+/, desc: 'First name'
|
|
1513
|
-
|
|
1514
|
-
requires :last_name, type: String, regexp: /w+/, desc: 'Last name'
|
|
1456
|
+
requires :first_name, type: String, regexp: /w+/, desc: 'First name', documentation: { in: 'body' }
|
|
1457
|
+
optional :middle_name, type: String, regexp: /w+/, desc: 'Middle name', documentation: { in: 'body', x: { nullable: true } }
|
|
1458
|
+
requires :last_name, type: String, regexp: /w+/, desc: 'Last name', documentation: { in: 'body' }
|
|
1515
1459
|
end
|
|
1516
1460
|
```
|
|
1517
1461
|
|
|
1518
|
-
Grape allows you to present the same logic through the `with` method in your
|
|
1519
|
-
parameters block, like so:
|
|
1462
|
+
Grape allows you to present the same logic through the `with` method in your parameters block, like so:
|
|
1520
1463
|
|
|
1521
1464
|
```ruby
|
|
1522
1465
|
params do
|
|
1523
|
-
with(type: String, regexp: /w
|
|
1466
|
+
with(type: String, regexp: /w+/, documentation: { in: 'body' }) do
|
|
1524
1467
|
requires :first_name, desc: 'First name'
|
|
1525
|
-
|
|
1468
|
+
optional :middle_name, desc: 'Middle name', documentation: { x: { nullable: true } }
|
|
1526
1469
|
requires :last_name, desc: 'Last name'
|
|
1527
1470
|
end
|
|
1528
1471
|
end
|
|
1529
1472
|
```
|
|
1530
1473
|
|
|
1474
|
+
You can organize settings into layers using nested `with` blocks. Each layer can use, add to, or change the settings of the layer above it. This helps to keep complex parameters organized and consistent, while still allowing for specific customizations to be made.
|
|
1475
|
+
|
|
1476
|
+
```ruby
|
|
1477
|
+
params do
|
|
1478
|
+
with(documentation: { in: 'body' }) do # Applies documentation to all nested parameters
|
|
1479
|
+
with(type: String, regexp: /\w+/) do # Applies type and validation to names
|
|
1480
|
+
requires :first_name, desc: 'First name'
|
|
1481
|
+
requires :last_name, desc: 'Last name'
|
|
1482
|
+
end
|
|
1483
|
+
optional :age, type: Integer, desc: 'Age', documentation: { x: { nullable: true } } # Specific settings for 'age'
|
|
1484
|
+
end
|
|
1485
|
+
end
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1531
1488
|
### Renaming
|
|
1532
1489
|
|
|
1533
1490
|
You can rename parameters using `as`, which can be useful when refactoring existing APIs:
|
|
@@ -1550,13 +1507,9 @@ The value passed to `as` will be the key when calling `declared(params)`.
|
|
|
1550
1507
|
|
|
1551
1508
|
#### `allow_blank`
|
|
1552
1509
|
|
|
1553
|
-
Parameters can be defined as `allow_blank`, ensuring that they contain a value. By default, `requires`
|
|
1554
|
-
only validates that a parameter was sent in the request, regardless its value. With `allow_blank: false`,
|
|
1555
|
-
empty values or whitespace only values are invalid.
|
|
1510
|
+
Parameters can be defined as `allow_blank`, ensuring that they contain a value. By default, `requires` only validates that a parameter was sent in the request, regardless its value. With `allow_blank: false`, empty values or whitespace only values are invalid.
|
|
1556
1511
|
|
|
1557
|
-
`allow_blank` can be combined with both `requires` and `optional`. If the parameter is required, it has to contain
|
|
1558
|
-
a value. If it's optional, it's possible to not send it in the request, but if it's being sent, it has to have
|
|
1559
|
-
some value, and not an empty string/only whitespaces.
|
|
1512
|
+
`allow_blank` can be combined with both `requires` and `optional`. If the parameter is required, it has to contain a value. If it's optional, it's possible to not send it in the request, but if it's being sent, it has to have some value, and not an empty string/only whitespaces.
|
|
1560
1513
|
|
|
1561
1514
|
|
|
1562
1515
|
```ruby
|
|
@@ -1593,7 +1546,7 @@ Note endless ranges are also supported with ActiveSupport >= 6.0, but they requi
|
|
|
1593
1546
|
```ruby
|
|
1594
1547
|
params do
|
|
1595
1548
|
requires :minimum, type: Integer, values: 10..
|
|
1596
|
-
optional :maximum, type: Integer, values: ..10
|
|
1549
|
+
optional :maximum, type: Integer, values: ..10
|
|
1597
1550
|
end
|
|
1598
1551
|
```
|
|
1599
1552
|
|
|
@@ -1606,12 +1559,31 @@ params do
|
|
|
1606
1559
|
end
|
|
1607
1560
|
```
|
|
1608
1561
|
|
|
1562
|
+
An exclusive upper bound is expressed with the standard Ruby `...` range (three dots). Ruby has no range syntax for an exclusive *lower* bound; an arity-one `:values` Proc predicate (see below) is the recommended way to express one, though excluding a specific endpoint (e.g. zero) via `:except_values` also works:
|
|
1563
|
+
|
|
1564
|
+
```ruby
|
|
1565
|
+
params do
|
|
1566
|
+
requires :score, type: Float, values: 0.0...5.0 # 0.0 <= score < 5.0
|
|
1567
|
+
requires :positive, type: Float, values: ->(v) { v > 0.0 } # amount > 0.0 (recommended)
|
|
1568
|
+
requires :other, type: Float, values: 0.0.., except_values: [0.0] # amount > 0.0 (alternative)
|
|
1569
|
+
end
|
|
1570
|
+
```
|
|
1571
|
+
|
|
1572
|
+
Combined with `:type`, ranges (open or closed) cover common numeric bound checks without a dedicated validator:
|
|
1573
|
+
|
|
1574
|
+
```ruby
|
|
1575
|
+
params do
|
|
1576
|
+
requires :quantity, type: Integer, values: 1.. # must be a positive integer
|
|
1577
|
+
requires :discount, type: Float, values: 0.0..100.0 # must be between 0 and 100
|
|
1578
|
+
requires :rating, type: Integer, values: 5..5 # must be exactly 5
|
|
1579
|
+
requires :numbers, type: [Integer], values: 1.. # every element must be positive
|
|
1580
|
+
end
|
|
1581
|
+
```
|
|
1582
|
+
|
|
1609
1583
|
The `:values` option can also be supplied with a `Proc`, evaluated lazily with each request.
|
|
1610
|
-
If the Proc has arity zero (i.e. it takes no arguments) it is expected to return either a list
|
|
1611
|
-
or a range which will then be used to validate the parameter.
|
|
1584
|
+
If the Proc has arity zero (i.e. it takes no arguments) it is expected to return either a list or a range which will then be used to validate the parameter.
|
|
1612
1585
|
|
|
1613
|
-
For example, given a status model you may want to restrict by hashtags that you have
|
|
1614
|
-
previously defined in the `HashTag` model.
|
|
1586
|
+
For example, given a status model you may want to restrict by hashtags that you have previously defined in the `HashTag` model.
|
|
1615
1587
|
|
|
1616
1588
|
```ruby
|
|
1617
1589
|
params do
|
|
@@ -1619,10 +1591,7 @@ params do
|
|
|
1619
1591
|
end
|
|
1620
1592
|
```
|
|
1621
1593
|
|
|
1622
|
-
Alternatively, a Proc with arity one (i.e. taking one argument) can be used to explicitly validate
|
|
1623
|
-
each parameter value. In that case, the Proc is expected to return a truthy value if the parameter
|
|
1624
|
-
value is valid. The parameter will be considered invalid if the Proc returns a falsy value or if it
|
|
1625
|
-
raises a StandardError.
|
|
1594
|
+
Alternatively, a Proc with arity one (i.e. taking one argument) can be used to explicitly validate each parameter value. In that case, the Proc is expected to return a truthy value if the parameter value is valid. The parameter will be considered invalid if the Proc returns a falsy value or if it raises a StandardError.
|
|
1626
1595
|
|
|
1627
1596
|
```ruby
|
|
1628
1597
|
params do
|
|
@@ -1632,26 +1601,27 @@ end
|
|
|
1632
1601
|
|
|
1633
1602
|
While Procs are convenient for single cases, consider using [Custom Validators](#custom-validators) in cases where a validation is used more than once.
|
|
1634
1603
|
|
|
1635
|
-
|
|
1604
|
+
When using `optional` together with `:values`, a missing key, a `nil` value, and any value that coerces to `nil` (such as `""` for `type: Symbol`) all pass validation — `optional` collapses "key may be absent" with "value may be nil". To reject blank values while still allowing the key to be absent, add `allow_blank: false`:
|
|
1636
1605
|
|
|
1637
1606
|
```ruby
|
|
1638
1607
|
params do
|
|
1639
|
-
|
|
1608
|
+
optional :state, type: Symbol, values: [:active, :inactive], allow_blank: false
|
|
1640
1609
|
end
|
|
1641
1610
|
```
|
|
1642
1611
|
|
|
1612
|
+
With `requires`, blank values are already rejected: `requires` enforces presence and `:values` rejects `nil`.
|
|
1613
|
+
|
|
1643
1614
|
#### `except_values`
|
|
1644
1615
|
|
|
1645
1616
|
Parameters can be restricted from having a specific set of values with the `:except_values` option.
|
|
1646
1617
|
|
|
1647
|
-
The `except_values` validator behaves similarly to the `values` validator in that it accepts either
|
|
1648
|
-
an Array, a Range, or a Proc. Unlike the `values` validator, however, `except_values` only accepts
|
|
1649
|
-
Procs with arity zero.
|
|
1618
|
+
The `except_values` validator behaves similarly to the `values` validator in that it accepts either an Array, a Range (including open/beginless/endless ranges), or a Proc. Unlike the `values` validator, however, `except_values` only accepts Procs with arity zero.
|
|
1650
1619
|
|
|
1651
1620
|
```ruby
|
|
1652
1621
|
params do
|
|
1653
1622
|
requires :browser, except_values: [ 'ie6', 'ie7', 'ie8' ]
|
|
1654
1623
|
requires :port, except_values: { value: 0..1024, message: 'is not allowed' }
|
|
1624
|
+
requires :negative, type: Integer, except_values: ..-1
|
|
1655
1625
|
requires :hashtag, except_values: -> { Hashtag.FORBIDDEN_LIST }
|
|
1656
1626
|
end
|
|
1657
1627
|
```
|
|
@@ -1667,11 +1637,24 @@ params do
|
|
|
1667
1637
|
end
|
|
1668
1638
|
```
|
|
1669
1639
|
|
|
1640
|
+
#### `length`
|
|
1641
|
+
|
|
1642
|
+
Parameters with types that support `#length` method can be restricted to have a specific length with the `:length` option.
|
|
1643
|
+
|
|
1644
|
+
The validator accepts `:min` or `:max` or both options or only `:is` to validate that the value of the parameter is within the given limits.
|
|
1645
|
+
|
|
1646
|
+
```ruby
|
|
1647
|
+
params do
|
|
1648
|
+
requires :code, type: String, length: { is: 2 }
|
|
1649
|
+
requires :str, type: String, length: { min: 3 }
|
|
1650
|
+
requires :list, type: [Integer], length: { min: 3, max: 5 }
|
|
1651
|
+
requires :hash, type: Hash, length: { max: 5 }
|
|
1652
|
+
end
|
|
1653
|
+
```
|
|
1654
|
+
|
|
1670
1655
|
#### `regexp`
|
|
1671
1656
|
|
|
1672
|
-
Parameters can be restricted to match a specific regular expression with the `:regexp` option. If the value
|
|
1673
|
-
does not match the regular expression an error will be returned. Note that this is true for both `requires`
|
|
1674
|
-
and `optional` parameters.
|
|
1657
|
+
Parameters can be restricted to match a specific regular expression with the `:regexp` option. If the value does not match the regular expression an error will be returned. Note that this is true for both `requires` and `optional` parameters.
|
|
1675
1658
|
|
|
1676
1659
|
```ruby
|
|
1677
1660
|
params do
|
|
@@ -1806,8 +1789,7 @@ namespace :statuses do
|
|
|
1806
1789
|
end
|
|
1807
1790
|
```
|
|
1808
1791
|
|
|
1809
|
-
The `namespace` method has a number of aliases, including: `group`, `resource`,
|
|
1810
|
-
`resources`, and `segment`. Use whichever reads the best for your API.
|
|
1792
|
+
The `namespace` method has a number of aliases, including: `group`, `resource`, `resources`, and `segment`. Use whichever reads the best for your API.
|
|
1811
1793
|
|
|
1812
1794
|
You can conveniently define a route parameter as a namespace using `route_param`.
|
|
1813
1795
|
|
|
@@ -1844,9 +1826,9 @@ end
|
|
|
1844
1826
|
```ruby
|
|
1845
1827
|
class AlphaNumeric < Grape::Validations::Validators::Base
|
|
1846
1828
|
def validate_param!(attr_name, params)
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1829
|
+
return if params[attr_name].match?(/\A[[:alnum:]]+\z/)
|
|
1830
|
+
|
|
1831
|
+
validation_error!(attr_name, 'must consist of alpha-numeric characters')
|
|
1850
1832
|
end
|
|
1851
1833
|
end
|
|
1852
1834
|
```
|
|
@@ -1862,9 +1844,9 @@ You can also create custom classes that take parameters.
|
|
|
1862
1844
|
```ruby
|
|
1863
1845
|
class Length < Grape::Validations::Validators::Base
|
|
1864
1846
|
def validate_param!(attr_name, params)
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1847
|
+
return if params[attr_name].length <= @options
|
|
1848
|
+
|
|
1849
|
+
validation_error!(attr_name, "must be at the most #{@options} characters long")
|
|
1868
1850
|
end
|
|
1869
1851
|
end
|
|
1870
1852
|
```
|
|
@@ -1886,10 +1868,10 @@ class Admin < Grape::Validations::Validators::Base
|
|
|
1886
1868
|
# @attrs being [:admin_field] and once with @attrs being [:admin_false_field]
|
|
1887
1869
|
return unless request.params.key?(@attrs.first)
|
|
1888
1870
|
# check if admin flag is set to true
|
|
1889
|
-
return unless @
|
|
1871
|
+
return unless @options
|
|
1890
1872
|
# check if user is admin or not
|
|
1891
1873
|
# as an example get a token from request and check if it's admin or not
|
|
1892
|
-
|
|
1874
|
+
validation_error!(@attrs, 'Can not set admin-only field.') unless request.headers['X-Access-Token'] == 'admin'
|
|
1893
1875
|
end
|
|
1894
1876
|
end
|
|
1895
1877
|
```
|
|
@@ -1904,7 +1886,50 @@ params do
|
|
|
1904
1886
|
end
|
|
1905
1887
|
```
|
|
1906
1888
|
|
|
1907
|
-
|
|
1889
|
+
Each validator is instantiated once at route definition time and frozen. Any setup (option parsing, message building) should happen in `initialize`, not in `validate_param!` or `validate`.
|
|
1890
|
+
|
|
1891
|
+
#### Available helpers
|
|
1892
|
+
|
|
1893
|
+
The following protected/private helpers are available in any `Grape::Validations::Validators::Base` subclass:
|
|
1894
|
+
|
|
1895
|
+
| Helper | Description |
|
|
1896
|
+
|---|---|
|
|
1897
|
+
| `default_message_key(key)` | Class-level macro. Declares the default I18n key for `validation_error!`. A per-option `:message` override still takes precedence. |
|
|
1898
|
+
| `validation_error!(attr_name_or_params, message = @exception_message)` | Raises `Grape::Exceptions::Validation`. Accepts a single attribute name or a pre-computed array of full param names. |
|
|
1899
|
+
| `@options` | The validator option value, deep-frozen at initialization. |
|
|
1900
|
+
| `@attrs` | Frozen array of attribute names this validator applies to. |
|
|
1901
|
+
| `@scope` | The `ParamsScope` — use `@scope.full_name(attr_name)` for the fully-qualified param name. |
|
|
1902
|
+
| `option_value` | Returns `@options[:value]` if present, otherwise `@options`. |
|
|
1903
|
+
| `options_key?(key)` | Returns true if `@options` is a hash with a non-nil `key`. |
|
|
1904
|
+
| `hash_like?(obj)` | Returns true if `obj` responds to `key?`. |
|
|
1905
|
+
| `scrub(value)` | Returns `value` with invalid byte sequences scrubbed. |
|
|
1906
|
+
| `translate(key, **opts)` | I18n lookup with `:en` fallback and `grape.errors.messages` scope. Called at request time to respect per-request locale. |
|
|
1907
|
+
|
|
1908
|
+
Use `default_message_key` for a fixed I18n key. The message is resolved once at route definition time via `message`, so a per-option `:message` override still wins:
|
|
1909
|
+
|
|
1910
|
+
```ruby
|
|
1911
|
+
class SpecialValidator < Grape::Validations::Validators::Base
|
|
1912
|
+
default_message_key :special
|
|
1913
|
+
|
|
1914
|
+
def validate_param!(attr_name, params)
|
|
1915
|
+
return if valid?(params[attr_name])
|
|
1916
|
+
|
|
1917
|
+
validation_error!(attr_name)
|
|
1918
|
+
end
|
|
1919
|
+
end
|
|
1920
|
+
```
|
|
1921
|
+
|
|
1922
|
+
For interpolated messages that must respect per-request locale, call `translate` directly inside `validate_param!`:
|
|
1923
|
+
|
|
1924
|
+
```ruby
|
|
1925
|
+
class SpecialValidator < Grape::Validations::Validators::Base
|
|
1926
|
+
def validate_param!(attr_name, params)
|
|
1927
|
+
return if valid?(params[attr_name])
|
|
1928
|
+
|
|
1929
|
+
validation_error!(attr_name, translate(:special, min: 2, max: 10))
|
|
1930
|
+
end
|
|
1931
|
+
end
|
|
1932
|
+
```
|
|
1908
1933
|
|
|
1909
1934
|
### Validation Errors
|
|
1910
1935
|
|
|
@@ -1947,8 +1972,8 @@ To skip all subsequent validation checks when a specific param is found invalid,
|
|
|
1947
1972
|
The following example will not check if `:wine` is present unless it finds `:beer`.
|
|
1948
1973
|
```ruby
|
|
1949
1974
|
params do
|
|
1950
|
-
|
|
1951
|
-
|
|
1975
|
+
requires :beer, fail_fast: true
|
|
1976
|
+
requires :wine
|
|
1952
1977
|
end
|
|
1953
1978
|
```
|
|
1954
1979
|
The result of empty params would be a single `Grape::Exceptions::ValidationErrors` error.
|
|
@@ -1956,17 +1981,38 @@ The result of empty params would be a single `Grape::Exceptions::ValidationError
|
|
|
1956
1981
|
Similarly, no regular expression test will be performed if `:blah` is blank in the following example.
|
|
1957
1982
|
```ruby
|
|
1958
1983
|
params do
|
|
1959
|
-
|
|
1984
|
+
requires :blah, allow_blank: false, regexp: /blah/, fail_fast: true
|
|
1960
1985
|
end
|
|
1961
1986
|
```
|
|
1962
1987
|
|
|
1963
1988
|
### I18n
|
|
1964
1989
|
|
|
1965
|
-
Grape supports I18n for parameter-related error messages, but will fallback to English if
|
|
1966
|
-
translations for the default locale have not been provided. See [en.yml](lib/grape/locale/en.yml) for message keys.
|
|
1990
|
+
Grape supports I18n for parameter-related error messages, but will fallback to English if translations for the default locale have not been provided. See [en.yml](lib/grape/locale/en.yml) for message keys.
|
|
1967
1991
|
|
|
1968
1992
|
In case your app enforces available locales only and :en is not included in your available locales, Grape cannot fall back to English and will return the translation key for the error message. To avoid this behaviour, either provide a translation for your default locale or add :en to your available locales.
|
|
1969
1993
|
|
|
1994
|
+
Custom validators that inherit from `Grape::Validations::Validators::Base` have access to a `translate` helper (see `Grape::Util::Translation`) and should use it instead of calling `I18n` directly. It applies the same `:en` fallback as built-in validators, defaults `scope` to `'grape.errors.messages'`, and handles interpolation without needing `format`:
|
|
1995
|
+
|
|
1996
|
+
```ruby
|
|
1997
|
+
# Good — scope defaults to 'grape.errors.messages', interpolation forwarded automatically
|
|
1998
|
+
translate(:special, min: 2, max: 10)
|
|
1999
|
+
|
|
2000
|
+
# Bad — format is unnecessary and risks conflicting with I18n reserved keys
|
|
2001
|
+
format I18n.t(:special, scope: 'grape.errors.messages'), min: 2, max: 10
|
|
2002
|
+
```
|
|
2003
|
+
|
|
2004
|
+
Example custom validator using an interpolated i18n message:
|
|
2005
|
+
|
|
2006
|
+
```ruby
|
|
2007
|
+
class SpecialValidator < Grape::Validations::Validators::Base
|
|
2008
|
+
def validate_param!(attr_name, params)
|
|
2009
|
+
return if valid?(params[attr_name])
|
|
2010
|
+
|
|
2011
|
+
validation_error!(attr_name, translate(:special, min: 2, max: 10))
|
|
2012
|
+
end
|
|
2013
|
+
end
|
|
2014
|
+
```
|
|
2015
|
+
|
|
1970
2016
|
### Custom Validation messages
|
|
1971
2017
|
|
|
1972
2018
|
Grape supports custom validation messages for parameter-related and coerce-related error messages.
|
|
@@ -1988,6 +2034,16 @@ params do
|
|
|
1988
2034
|
end
|
|
1989
2035
|
```
|
|
1990
2036
|
|
|
2037
|
+
#### `length`
|
|
2038
|
+
|
|
2039
|
+
```ruby
|
|
2040
|
+
params do
|
|
2041
|
+
requires :code, type: String, length: { is: 2, message: 'code is expected to be exactly 2 characters long' }
|
|
2042
|
+
requires :str, type: String, length: { min: 5, message: 'str is expected to be at least 5 characters long' }
|
|
2043
|
+
requires :list, type: [Integer], length: { min: 2, max: 3, message: 'list is expected to have between 2 and 3 elements' }
|
|
2044
|
+
end
|
|
2045
|
+
```
|
|
2046
|
+
|
|
1991
2047
|
#### `all_or_none_of`
|
|
1992
2048
|
|
|
1993
2049
|
```ruby
|
|
@@ -2096,6 +2152,40 @@ params do
|
|
|
2096
2152
|
end
|
|
2097
2153
|
```
|
|
2098
2154
|
|
|
2155
|
+
### Using `dry-validation` or `dry-schema`
|
|
2156
|
+
|
|
2157
|
+
As an alternative to the `params` DSL described above, you can use a schema or `dry-validation` contract to describe an endpoint's parameters. This can be especially useful if you use the above already in some other parts of your application. If not, you'll need to add `dry-validation` or `dry-schema` to your `Gemfile`.
|
|
2158
|
+
|
|
2159
|
+
Then call `contract` with a contract or schema defined previously:
|
|
2160
|
+
|
|
2161
|
+
```rb
|
|
2162
|
+
CreateOrdersSchema = Dry::Schema.Params do
|
|
2163
|
+
required(:orders).array(:hash) do
|
|
2164
|
+
required(:name).filled(:string)
|
|
2165
|
+
optional(:volume).maybe(:integer, lt?: 9)
|
|
2166
|
+
end
|
|
2167
|
+
end
|
|
2168
|
+
|
|
2169
|
+
# ...
|
|
2170
|
+
|
|
2171
|
+
contract CreateOrdersSchema
|
|
2172
|
+
```
|
|
2173
|
+
|
|
2174
|
+
or with a block, using the [schema definition syntax](https://dry-rb.org/gems/dry-schema/1.13/#quick-start):
|
|
2175
|
+
|
|
2176
|
+
```rb
|
|
2177
|
+
contract do
|
|
2178
|
+
required(:orders).array(:hash) do
|
|
2179
|
+
required(:name).filled(:string)
|
|
2180
|
+
optional(:volume).maybe(:integer, lt?: 9)
|
|
2181
|
+
end
|
|
2182
|
+
end
|
|
2183
|
+
```
|
|
2184
|
+
|
|
2185
|
+
The latter will define a coercing schema (`Dry::Schema.Params`). When using the former approach, it's up to you to decide whether the input will need coercing.
|
|
2186
|
+
|
|
2187
|
+
The `params` and `contract` declarations can also be used together in the same API, e.g. to describe different parts of a nested namespace for an endpoint.
|
|
2188
|
+
|
|
2099
2189
|
## Headers
|
|
2100
2190
|
|
|
2101
2191
|
### Request
|
|
@@ -2123,8 +2213,9 @@ curl -H "secret_PassWord: swordfish" ...
|
|
|
2123
2213
|
|
|
2124
2214
|
The header name will have been normalized for you.
|
|
2125
2215
|
|
|
2126
|
-
- In the `header` helper names will be coerced into a
|
|
2127
|
-
- In the `
|
|
2216
|
+
- In the `header` helper names will be coerced into a downcased kebab case as `secret-password` if using Rack 3.
|
|
2217
|
+
- In the `header` helper names will be coerced into a capitalized kebab case as `Secret-PassWord` if using Rack < 3.
|
|
2218
|
+
- In the `env` collection they appear in all uppercase, in snake case, and prefixed with 'HTTP_' as `HTTP_SECRET_PASSWORD`
|
|
2128
2219
|
|
|
2129
2220
|
The header name will have been normalized per HTTP standards defined in [RFC2616 Section 4.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) regardless of what is being sent by a client.
|
|
2130
2221
|
|
|
@@ -2194,8 +2285,7 @@ namespace ':id' do
|
|
|
2194
2285
|
end
|
|
2195
2286
|
```
|
|
2196
2287
|
|
|
2197
|
-
Optionally, you can define requirements for your named route parameters using regular
|
|
2198
|
-
expressions on namespace or endpoint. The route will match only if all requirements are met.
|
|
2288
|
+
Optionally, you can define requirements for your named route parameters using regular expressions on namespace or endpoint. The route will match only if all requirements are met.
|
|
2199
2289
|
|
|
2200
2290
|
```ruby
|
|
2201
2291
|
get ':id', requirements: { id: /[0-9]*/ } do
|
|
@@ -2211,10 +2301,81 @@ namespace :outer, requirements: { id: /[0-9]*/ } do
|
|
|
2211
2301
|
end
|
|
2212
2302
|
```
|
|
2213
2303
|
|
|
2304
|
+
A requirement can also name one of the capture types of [Mustermann](https://github.com/sinatra/mustermann), the pattern library Grape routes with, instead of a regular expression. Besides constraining the match, a capture type that carries a converter hands the endpoint the converted value rather than a String.
|
|
2305
|
+
|
|
2306
|
+
```ruby
|
|
2307
|
+
get ':id', requirements: { id: Integer } do
|
|
2308
|
+
params[:id] # => 23, an Integer, for GET /23 — GET /michael is a 404
|
|
2309
|
+
end
|
|
2310
|
+
|
|
2311
|
+
get 'events/:on', requirements: { on: :date } do
|
|
2312
|
+
params[:on] # => #<Date: 2020-01-02>, for GET /events/2020-01-02
|
|
2313
|
+
end
|
|
2314
|
+
```
|
|
2315
|
+
|
|
2316
|
+
`Integer`, `Float`, `Symbol`, `Date` and `Gem::Version` convert, as do their symbol spellings `:integer`, `:float`, `:symbol`, `:date` and `:version`. `:uuid`, `:slug` and `:locale` constrain the match without converting. A name Mustermann has no capture type for raises when the API is defined.
|
|
2317
|
+
|
|
2318
|
+
A `params` block still owns what the endpoint sees, since validation runs after the router: declaring `requires :id, type: String` alongside `requirements: { id: Integer }` hands the endpoint `"23"`.
|
|
2319
|
+
|
|
2320
|
+
`route_param` names the parameter itself, so it takes the constraint on its own — `route_param :id, requirements: Integer` — and refuses a Hash, which would name the parameter a second time. A namespace or an endpoint can carry several captures, so requirements there are the Hash form keyed by param name.
|
|
2321
|
+
|
|
2322
|
+
`route_param :id` defines the parameter on its own — `params[:id]` reaches the endpoint, and the route documents it as a path capture. `type:` and `requirements:` are two ways of typing it, and they work at different layers. `type: Integer` declares `requires :id, type: Integer`: the value is coerced, appears in `declared(params)` and carries its type into the route's documented params, and a value the path let through but the type rejects is a 400. `requirements: Integer` types the route instead: a segment that does not match is not this route at all, so it is a 404, and the value arrives already converted by the router, undeclared and documented only as a capture. A declared `type: Integer` also narrows the path, but to digits alone, so `/-7` is a 404 there while the requirement matches it. Given both, the requirement decides what the route matches and the declared type decides what the endpoint sees.
|
|
2323
|
+
|
|
2324
|
+
#### Dots in a path segment
|
|
2325
|
+
|
|
2326
|
+
A path capture matches `[^/?#.]+`: a dot introduces the format suffix, so a captured segment never contains one. A dotted value therefore either fails to route or arrives truncated, and the declared type makes no difference — `type: String` and `type: Float` compile the same pattern as an untyped parameter, only `type: Integer` narrows it to digits.
|
|
2327
|
+
|
|
2328
|
+
```ruby
|
|
2329
|
+
class WithFormat < Grape::API
|
|
2330
|
+
format :json
|
|
2331
|
+
route_param :name do
|
|
2332
|
+
get { params[:name] } # GET /report.pdf => 404, the segment cannot hold the dot
|
|
2333
|
+
end
|
|
2334
|
+
end
|
|
2335
|
+
|
|
2336
|
+
class WithoutFormat < Grape::API
|
|
2337
|
+
route_param :name do
|
|
2338
|
+
# GET /report.pdf => 200, params[:name] is "report" and params[:format] is "pdf"
|
|
2339
|
+
get { params[:name] }
|
|
2340
|
+
end
|
|
2341
|
+
end
|
|
2342
|
+
```
|
|
2343
|
+
|
|
2344
|
+
The second one is the one to watch: the extension is taken as the format, so the endpoint is handed a truncated value and the client still gets a 200.
|
|
2345
|
+
|
|
2346
|
+
Percent-encoding the dot routes it through — `GET /report%2Epdf` hands the endpoint `"report.pdf"` — and so does a requirement that admits dots:
|
|
2347
|
+
|
|
2348
|
+
```ruby
|
|
2349
|
+
route_param :name, requirements: /.+/ do
|
|
2350
|
+
get { params[:name] } # => "report.pdf" for GET /report.pdf, "1.2.3" for GET /1.2.3
|
|
2351
|
+
end
|
|
2352
|
+
```
|
|
2353
|
+
|
|
2354
|
+
Such a regexp claims the extension as well, so `GET /a.b.json` hands the endpoint `"a.b.json"` and format-by-extension no longer applies to that route. A narrower constraint leaves the suffix alone: `get ':n', requirements: { n: Float }` matches `/4.2` and still reads `.json` as the format.
|
|
2355
|
+
|
|
2356
|
+
### The QUERY Method
|
|
2357
|
+
|
|
2358
|
+
Grape supports [`QUERY`](https://www.rfc-editor.org/info/rfc10008/), a safe and idempotent method that carries its query in the request content rather than in the URI. It is the method to reach for when a query is too large, too structured, or too sensitive to encode into a query string.
|
|
2359
|
+
|
|
2360
|
+
```ruby
|
|
2361
|
+
params do
|
|
2362
|
+
requires :q, type: String
|
|
2363
|
+
optional :limit, type: Integer
|
|
2364
|
+
end
|
|
2365
|
+
query '/search' do
|
|
2366
|
+
Status.search(params[:q], limit: params[:limit])
|
|
2367
|
+
end
|
|
2368
|
+
```
|
|
2369
|
+
|
|
2370
|
+
```
|
|
2371
|
+
curl -X QUERY 'http://localhost:9292/search' -H Content-Type:application/json -d '{"q": "grape", "limit": 10}'
|
|
2372
|
+
```
|
|
2373
|
+
|
|
2374
|
+
The content is parsed into `params` exactly as it is for `POST`, and a successful response defaults to `200`, not `201`. Because the content *is* the query, a `QUERY` request that arrives without a `Content-Type` is rejected with `400` instead of falling back to the API's default format.
|
|
2375
|
+
|
|
2214
2376
|
## Helpers
|
|
2215
2377
|
|
|
2216
|
-
You can define helper methods that your endpoints can use with the `helpers`
|
|
2217
|
-
macro by either giving a block or an array of modules.
|
|
2378
|
+
You can define helper methods that your endpoints can use with the `helpers` macro by either giving a block or an array of modules.
|
|
2218
2379
|
|
|
2219
2380
|
```ruby
|
|
2220
2381
|
module StatusHelpers
|
|
@@ -2453,11 +2614,36 @@ end
|
|
|
2453
2614
|
API.recognize_path '/statuses'
|
|
2454
2615
|
```
|
|
2455
2616
|
|
|
2617
|
+
Since version `2.1.0`, the `recognize_path` method takes into account the parameters type to determine which endpoint should match with given path.
|
|
2618
|
+
|
|
2619
|
+
```ruby
|
|
2620
|
+
class Books < Grape::API
|
|
2621
|
+
resource :books do
|
|
2622
|
+
route_param :id, type: Integer do
|
|
2623
|
+
# GET /books/:id
|
|
2624
|
+
get do
|
|
2625
|
+
#...
|
|
2626
|
+
end
|
|
2627
|
+
end
|
|
2628
|
+
|
|
2629
|
+
resource :share do
|
|
2630
|
+
# POST /books/share
|
|
2631
|
+
post do
|
|
2632
|
+
# ....
|
|
2633
|
+
end
|
|
2634
|
+
end
|
|
2635
|
+
end
|
|
2636
|
+
end
|
|
2637
|
+
|
|
2638
|
+
API.recognize_path '/books/1' # => /books/:id
|
|
2639
|
+
API.recognize_path '/books/share' # => /books/share
|
|
2640
|
+
API.recognize_path '/books/other' # => nil
|
|
2641
|
+
```
|
|
2642
|
+
|
|
2643
|
+
|
|
2456
2644
|
## Allowed Methods
|
|
2457
2645
|
|
|
2458
|
-
When you add a `GET` route for a resource, a route for the `HEAD`
|
|
2459
|
-
method will also be added automatically. You can disable this
|
|
2460
|
-
behavior with `do_not_route_head!`.
|
|
2646
|
+
When you add a `GET` route for a resource, a route for the `HEAD` method will also be added automatically. You can disable this behavior with `do_not_route_head!`.
|
|
2461
2647
|
|
|
2462
2648
|
``` ruby
|
|
2463
2649
|
class API < Grape::API
|
|
@@ -2469,11 +2655,7 @@ class API < Grape::API
|
|
|
2469
2655
|
end
|
|
2470
2656
|
```
|
|
2471
2657
|
|
|
2472
|
-
When you add a route for a resource, a route for the `OPTIONS`
|
|
2473
|
-
method will also be added. The response to an OPTIONS request will
|
|
2474
|
-
include an "Allow" header listing the supported methods. If the resource
|
|
2475
|
-
has `before` and `after` callbacks they will be executed, but no other callbacks will
|
|
2476
|
-
run.
|
|
2658
|
+
When you add a route for a resource, a route for the `OPTIONS` method will also be added. The response to an OPTIONS request will include an "Allow" header listing the supported methods. If the resource has `before` and `after` callbacks they will be executed, but no other callbacks will run.
|
|
2477
2659
|
|
|
2478
2660
|
```ruby
|
|
2479
2661
|
class API < Grape::API
|
|
@@ -2502,10 +2684,7 @@ curl -v -X OPTIONS http://localhost:3000/rt_count
|
|
|
2502
2684
|
|
|
2503
2685
|
You can disable this behavior with `do_not_route_options!`.
|
|
2504
2686
|
|
|
2505
|
-
If a request for a resource is made with an unsupported HTTP method, an
|
|
2506
|
-
HTTP 405 (Method Not Allowed) response will be returned. If the resource
|
|
2507
|
-
has `before` callbacks they will be executed, but no other callbacks will
|
|
2508
|
-
run.
|
|
2687
|
+
If a request for a resource is made with an unsupported HTTP method, an HTTP 405 (Method Not Allowed) response will be returned. If the resource has `before` callbacks they will be executed, but no other callbacks will run.
|
|
2509
2688
|
|
|
2510
2689
|
``` shell
|
|
2511
2690
|
curl -X DELETE -v http://localhost:3000/rt_count/
|
|
@@ -2531,8 +2710,7 @@ Anything that responds to `#to_s` can be given as a first argument to `error!`.
|
|
|
2531
2710
|
error! :not_found, 404
|
|
2532
2711
|
```
|
|
2533
2712
|
|
|
2534
|
-
You can also return JSON formatted objects by raising error! and passing a hash
|
|
2535
|
-
instead of a message.
|
|
2713
|
+
You can also return JSON formatted objects by raising error! and passing a hash instead of a message.
|
|
2536
2714
|
|
|
2537
2715
|
```ruby
|
|
2538
2716
|
error!({ error: 'unexpected error', detail: 'missing widget' }, 500)
|
|
@@ -2544,7 +2722,7 @@ You can set additional headers for the response. They will be merged with header
|
|
|
2544
2722
|
error!('Something went wrong', 500, 'X-Error-Detail' => 'Invalid token.')
|
|
2545
2723
|
```
|
|
2546
2724
|
|
|
2547
|
-
You can present documented errors with a Grape entity using the
|
|
2725
|
+
You can present documented errors with a Grape entity using the [grape-entity](https://github.com/ruby-grape/grape-entity) gem.
|
|
2548
2726
|
|
|
2549
2727
|
```ruby
|
|
2550
2728
|
module API
|
|
@@ -2597,8 +2775,7 @@ route :any, '*path' do
|
|
|
2597
2775
|
end
|
|
2598
2776
|
```
|
|
2599
2777
|
|
|
2600
|
-
It is very crucial to __define this endpoint at the very end of your API__, as it
|
|
2601
|
-
literally accepts every request.
|
|
2778
|
+
It is very crucial to __define this endpoint at the very end of your API__, as it literally accepts every request.
|
|
2602
2779
|
|
|
2603
2780
|
## Exception Handling
|
|
2604
2781
|
|
|
@@ -2632,6 +2809,19 @@ rescue_from :grape_exceptions do |e|
|
|
|
2632
2809
|
end
|
|
2633
2810
|
```
|
|
2634
2811
|
|
|
2812
|
+
The opt-in takes precedence over a catch-all handler, whether that catch-all is written as `rescue_from :all` or as a class such as `rescue_from StandardError`. So a validation failure stays a `400` rather than becoming whatever the catch-all returns:
|
|
2813
|
+
|
|
2814
|
+
```ruby
|
|
2815
|
+
class Twitter::API < Grape::API
|
|
2816
|
+
rescue_from StandardError do
|
|
2817
|
+
error!('server error', 500)
|
|
2818
|
+
end
|
|
2819
|
+
rescue_from :grape_exceptions # validation errors still answer 400
|
|
2820
|
+
end
|
|
2821
|
+
```
|
|
2822
|
+
|
|
2823
|
+
A handler registered for a specific Grape exception class is more precise than the opt-in and still wins, so `rescue_from Grape::Exceptions::ValidationErrors` keeps its own handler. The opt-in only outranks handlers that matched through a non-Grape ancestor.
|
|
2824
|
+
|
|
2635
2825
|
You can also rescue specific exceptions.
|
|
2636
2826
|
|
|
2637
2827
|
```ruby
|
|
@@ -2642,6 +2832,29 @@ end
|
|
|
2642
2832
|
|
|
2643
2833
|
In this case ```UserDefinedError``` must be inherited from ```StandardError```.
|
|
2644
2834
|
|
|
2835
|
+
When several classes could match, the one registered **first** in a scope wins — as with the clauses of a Ruby `rescue`. Register the more specific class before the broader one, or the narrower handler never runs:
|
|
2836
|
+
|
|
2837
|
+
```ruby
|
|
2838
|
+
class Twitter::API < Grape::API
|
|
2839
|
+
rescue_from ArgumentError do ... end # matched first
|
|
2840
|
+
rescue_from StandardError do ... end # everything else
|
|
2841
|
+
end
|
|
2842
|
+
```
|
|
2843
|
+
|
|
2844
|
+
Grape warns when a `rescue_from` is registered for a class an earlier one in the same scope already covers. This is about ordering within a scope; a handler in a nested namespace or a mounted API always takes precedence over one inherited from an enclosing scope, whatever the classes are.
|
|
2845
|
+
|
|
2846
|
+
A `rescue_from` also has to be declared **above** the routes it should cover. A route captures the handlers registered before it, so one declared below a route never runs for it:
|
|
2847
|
+
|
|
2848
|
+
```ruby
|
|
2849
|
+
class Twitter::API < Grape::API
|
|
2850
|
+
rescue_from :all do ... end # covers GET /statuses
|
|
2851
|
+
get :statuses do ... end
|
|
2852
|
+
rescue_from ArgumentError do ... end # never runs for GET /statuses
|
|
2853
|
+
end
|
|
2854
|
+
```
|
|
2855
|
+
|
|
2856
|
+
The same goes for `use`, `helpers` and the `before`/`after` callbacks.
|
|
2857
|
+
|
|
2645
2858
|
Notice that you could combine these two approaches (rescuing custom errors takes precedence). For example, it's useful for handling all exceptions except Grape validation errors.
|
|
2646
2859
|
|
|
2647
2860
|
```ruby
|
|
@@ -2656,12 +2869,12 @@ end
|
|
|
2656
2869
|
|
|
2657
2870
|
The error format will match the request format. See "Content-Types" below.
|
|
2658
2871
|
|
|
2659
|
-
Custom error formatters for existing and additional types can be defined with a proc.
|
|
2872
|
+
Custom error formatters for existing and additional types can be defined with a proc. The formatter receives a `Grape::Exceptions::ErrorResponse` value object as `error:` plus three context kwargs — `env:`, `include_backtrace:`, `include_original_exception:`. Pull just the keys you need with `**` to ignore the rest:
|
|
2660
2873
|
|
|
2661
2874
|
```ruby
|
|
2662
2875
|
class Twitter::API < Grape::API
|
|
2663
|
-
error_formatter :txt, ->(
|
|
2664
|
-
"error: #{message} from #{backtrace}"
|
|
2876
|
+
error_formatter :txt, ->(error:, **) {
|
|
2877
|
+
"error #{error.status}: #{error.message} from #{error.backtrace}"
|
|
2665
2878
|
}
|
|
2666
2879
|
end
|
|
2667
2880
|
```
|
|
@@ -2670,8 +2883,8 @@ You can also use a module or class.
|
|
|
2670
2883
|
|
|
2671
2884
|
```ruby
|
|
2672
2885
|
module CustomFormatter
|
|
2673
|
-
def self.call(
|
|
2674
|
-
{ message: message, backtrace: backtrace }
|
|
2886
|
+
def self.call(error:, **)
|
|
2887
|
+
{ status: error.status, message: error.message, backtrace: error.backtrace }
|
|
2675
2888
|
end
|
|
2676
2889
|
end
|
|
2677
2890
|
|
|
@@ -2701,6 +2914,43 @@ class Twitter::API < Grape::API
|
|
|
2701
2914
|
end
|
|
2702
2915
|
```
|
|
2703
2916
|
|
|
2917
|
+
#### Re-raising from inside a `rescue_from` block
|
|
2918
|
+
|
|
2919
|
+
A `rescue_from` block can re-raise an exception to invoke a different handler. This is useful for translating one exception class into another:
|
|
2920
|
+
|
|
2921
|
+
```ruby
|
|
2922
|
+
class Twitter::API < Grape::API
|
|
2923
|
+
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
|
2924
|
+
raise Api::Exceptions::InvalidValueError, e.full_messages
|
|
2925
|
+
end
|
|
2926
|
+
|
|
2927
|
+
rescue_from Api::Exceptions::InvalidValueError do |e|
|
|
2928
|
+
error!({ errors: e.message }, 422)
|
|
2929
|
+
end
|
|
2930
|
+
end
|
|
2931
|
+
```
|
|
2932
|
+
|
|
2933
|
+
The first handler re-raises; the second handler runs against the new exception.
|
|
2934
|
+
|
|
2935
|
+
If the re-raised exception has no registered `rescue_from` and is a `Grape::Exceptions::Base` subclass, it is rendered through the default Grape error path (using its own `status` and `message`). Anything else — typos, `NoMethodError`, an unrelated `StandardError` — is treated as an internal error: it is exposed on the rack env for upstream Rack middleware to observe, and rendered to the API consumer as a generic `500 Internal Server Error`. This avoids leaking internal detail in the response body.
|
|
2936
|
+
|
|
2937
|
+
Because the exception is answered rather than raised, nothing above Grape catches it, so it is published under `rack.exception` — the key error trackers read for a handled exception — as well as Grape's own `grape.exception`. Grape does no logging of its own here; register a `rescue_from :internal_grape_exceptions` handler to take that over.
|
|
2938
|
+
|
|
2939
|
+
You can take control of the internal-error path by opting in with `rescue_from :internal_grape_exceptions`:
|
|
2940
|
+
|
|
2941
|
+
```ruby
|
|
2942
|
+
class Twitter::API < Grape::API
|
|
2943
|
+
rescue_from :internal_grape_exceptions do |e|
|
|
2944
|
+
Sentry.capture_exception(e)
|
|
2945
|
+
error!({ message: 'Something went wrong' }, 500)
|
|
2946
|
+
end
|
|
2947
|
+
end
|
|
2948
|
+
```
|
|
2949
|
+
|
|
2950
|
+
When this handler is registered the framework hands the original exception to you, and you own the response shape and any logging. The framework deliberately does not log internal errors itself — it has no way to know your preferred format or destination.
|
|
2951
|
+
|
|
2952
|
+
A second raise inside the redispatched handler is not redispatched again — it goes straight to the framework's generic 500. This bounds the chain at one redispatch and prevents loops.
|
|
2953
|
+
|
|
2704
2954
|
You can also rescue all exceptions with a code block and handle the Rack response at the lowest level.
|
|
2705
2955
|
|
|
2706
2956
|
```ruby
|
|
@@ -2840,33 +3090,43 @@ Any exception that is not subclass of `StandardError` should be rescued explicit
|
|
|
2840
3090
|
Usually it is not a case for an application logic as such errors point to problems in Ruby runtime.
|
|
2841
3091
|
This is following [standard recommendations for exceptions handling](https://ruby-doc.org/core/Exception.html).
|
|
2842
3092
|
|
|
2843
|
-
|
|
3093
|
+
#### When the error response itself cannot be rendered
|
|
2844
3094
|
|
|
2845
|
-
|
|
2846
|
-
"406 Not Acceptable" will likely be handled and rendered by Rails handlers. For instance,
|
|
2847
|
-
accessing a nonexistent route "/api/foo" raises a 404, which inside rails will ultimately
|
|
2848
|
-
be translated to an `ActionController::RoutingError`, which most likely will get rendered
|
|
2849
|
-
to a HTML error page.
|
|
2850
|
-
|
|
2851
|
-
Most APIs will enjoy preventing downstream handlers from handling errors. You may set the
|
|
2852
|
-
`:cascade` option to `false` for the entire API or separately on specific `version` definitions,
|
|
2853
|
-
which will remove the `X-Cascade: true` header from API responses.
|
|
3095
|
+
A `rescue_from` handler can build a payload its error formatter cannot serialize. The common case is echoing request-derived text back to the client:
|
|
2854
3096
|
|
|
2855
3097
|
```ruby
|
|
2856
|
-
|
|
3098
|
+
class Missing < StandardError; end
|
|
3099
|
+
|
|
3100
|
+
class API < Grape::API
|
|
3101
|
+
format :json
|
|
3102
|
+
|
|
3103
|
+
rescue_from(Missing) { |e| error!({ error: 'not_found', detail: e.message }, 404) }
|
|
3104
|
+
|
|
3105
|
+
route_param(:id) { get { raise Missing, "no such thing: #{params[:id]}" } }
|
|
3106
|
+
end
|
|
2857
3107
|
```
|
|
2858
3108
|
|
|
3109
|
+
`GET /%C3%28` puts an invalid UTF-8 byte in `params[:id]`, and the JSON formatter raises when it reaches that byte in the message — after the handler has already returned, so `rescue_from :all` does not help.
|
|
3110
|
+
|
|
3111
|
+
Rather than let that exception escape the middleware stack, Grape answers `500`: first retrying the API's own format with the framework's `Internal Server Error` message, then falling back to a bare `text/plain` body if even that cannot be rendered.
|
|
3112
|
+
|
|
3113
|
+
The exception is published on the rack env under `rack.exception` — the key error trackers read to find an exception that was handled rather than raised — and on Grape's own `grape.exception`. It is also written to `rack.errors`, so it reaches the log with no tracker installed.
|
|
3114
|
+
|
|
3115
|
+
To opt out and have the exception propagate out of the middleware stack instead:
|
|
3116
|
+
|
|
2859
3117
|
```ruby
|
|
2860
|
-
|
|
3118
|
+
Grape.configure do |config|
|
|
3119
|
+
config.raise_rendering_errors = true
|
|
3120
|
+
end
|
|
2861
3121
|
```
|
|
2862
3122
|
|
|
3123
|
+
With this on the failsafe never runs: the exception is re-raised untouched, so neither env key is set and nothing is written to `rack.errors`.
|
|
3124
|
+
|
|
2863
3125
|
## Logging
|
|
2864
3126
|
|
|
2865
|
-
`Grape::API` provides a `logger` method which by default will return an instance of the `Logger`
|
|
2866
|
-
class from Ruby's standard library.
|
|
3127
|
+
`Grape::API` provides a `logger` method which by default will return an instance of the `Logger` class from Ruby's standard library.
|
|
2867
3128
|
|
|
2868
|
-
To log messages from within an endpoint, you need to define a helper to make the logger
|
|
2869
|
-
available in the endpoint context.
|
|
3129
|
+
To log messages from within an endpoint, you need to define a helper to make the logger available in the endpoint context.
|
|
2870
3130
|
|
|
2871
3131
|
```ruby
|
|
2872
3132
|
class API < Grape::API
|
|
@@ -2915,9 +3175,7 @@ For similar to Rails request logging try the [grape_logging](https://github.com/
|
|
|
2915
3175
|
|
|
2916
3176
|
## API Formats
|
|
2917
3177
|
|
|
2918
|
-
Your API can declare which content-types to support by using `content_type`. If you do not specify any, Grape will support
|
|
2919
|
-
_XML_, _JSON_, _BINARY_, and _TXT_ content-types. The default format is `:txt`; you can change this with `default_format`.
|
|
2920
|
-
Essentially, the two APIs below are equivalent.
|
|
3178
|
+
Your API can declare which content-types to support by using `content_type`. If you do not specify any, Grape will support _XML_, _JSON_, _BINARY_, and _TXT_ content-types. The default format is `:txt`; you can change this with `default_format`. Essentially, the two APIs below are equivalent.
|
|
2921
3179
|
|
|
2922
3180
|
```ruby
|
|
2923
3181
|
class Twitter::API < Grape::API
|
|
@@ -2936,9 +3194,7 @@ class Twitter::API < Grape::API
|
|
|
2936
3194
|
end
|
|
2937
3195
|
```
|
|
2938
3196
|
|
|
2939
|
-
If you declare any `content_type` whatsoever, the Grape defaults will be overridden. For example, the following API will only
|
|
2940
|
-
support the `:xml` and `:rss` content-types, but not `:txt`, `:json`, or `:binary`. Importantly, this means the `:txt`
|
|
2941
|
-
default format is not supported! So, make sure to set a new `default_format`.
|
|
3197
|
+
If you declare any `content_type` whatsoever, the Grape defaults will be overridden. For example, the following API will only support the `:xml` and `:rss` content-types, but not `:txt`, `:json`, or `:binary`. Importantly, this means the `:txt` default format is not supported! So, make sure to set a new `default_format`.
|
|
2942
3198
|
|
|
2943
3199
|
```ruby
|
|
2944
3200
|
class Twitter::API < Grape::API
|
|
@@ -2949,8 +3205,7 @@ class Twitter::API < Grape::API
|
|
|
2949
3205
|
end
|
|
2950
3206
|
```
|
|
2951
3207
|
|
|
2952
|
-
Serialization takes place automatically. For example, you do not have to call `to_json` in each JSON API endpoint
|
|
2953
|
-
implementation. The response format (and thus the automatic serialization) is determined in the following order:
|
|
3208
|
+
Serialization takes place automatically. For example, you do not have to call `to_json` in each JSON API endpoint implementation. The response format (and thus the automatic serialization) is determined in the following order:
|
|
2954
3209
|
* Use the file extension, if specified. If the file is .json, choose the JSON format.
|
|
2955
3210
|
* Use the value of the `format` parameter in the query string, if specified.
|
|
2956
3211
|
* Use the format set by the `format` option, if specified.
|
|
@@ -2973,20 +3228,15 @@ class MultipleFormatAPI < Grape::API
|
|
|
2973
3228
|
end
|
|
2974
3229
|
```
|
|
2975
3230
|
|
|
2976
|
-
* `GET /hello` (with an `Accept: */*` header) does not have an extension or a `format` parameter, so it will respond with
|
|
2977
|
-
JSON (the default format).
|
|
3231
|
+
* `GET /hello` (with an `Accept: */*` header) does not have an extension or a `format` parameter, so it will respond with JSON (the default format).
|
|
2978
3232
|
* `GET /hello.xml` has a recognized extension, so it will respond with XML.
|
|
2979
3233
|
* `GET /hello?format=xml` has a recognized `format` parameter, so it will respond with XML.
|
|
2980
|
-
* `GET /hello.xml?format=json` has a recognized extension (which takes precedence over the `format` parameter), so it will
|
|
2981
|
-
|
|
2982
|
-
* `GET /hello.xls`
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
* `GET /hello.xls` with an `Accept: text/plain` header has an unrecognized extension *and* an unrecognized `Accept` header,
|
|
2987
|
-
so it will respond with JSON (the default format).
|
|
2988
|
-
|
|
2989
|
-
You can override this process explicitly by specifying `env['api.format']` in the API itself.
|
|
3234
|
+
* `GET /hello.xml?format=json` has a recognized extension (which takes precedence over the `format` parameter), so it will respond with XML.
|
|
3235
|
+
* `GET /hello.xls` (with an `Accept: */*` header) has an extension, but that extension is not recognized, so it will respond with JSON (the default format).
|
|
3236
|
+
* `GET /hello.xls` with an `Accept: application/xml` header has an unrecognized extension, but the `Accept` header corresponds to a recognized format, so it will respond with XML.
|
|
3237
|
+
* `GET /hello.xls` with an `Accept: text/plain` header has an unrecognized extension *and* an unrecognized `Accept` header, so it will respond with JSON (the default format).
|
|
3238
|
+
|
|
3239
|
+
You can override this process explicitly by calling `api_format` in the API itself.
|
|
2990
3240
|
For example, the following API will let you upload arbitrary files and return their contents as an attachment with the correct MIME type.
|
|
2991
3241
|
|
|
2992
3242
|
```ruby
|
|
@@ -2994,15 +3244,14 @@ class Twitter::API < Grape::API
|
|
|
2994
3244
|
post 'attachment' do
|
|
2995
3245
|
filename = params[:file][:filename]
|
|
2996
3246
|
content_type MIME::Types.type_for(filename)[0].to_s
|
|
2997
|
-
|
|
3247
|
+
api_format :binary # there's no formatter for :binary, data will be returned "as is"
|
|
2998
3248
|
header 'Content-Disposition', "attachment; filename*=UTF-8''#{CGI.escape(filename)}"
|
|
2999
3249
|
params[:file][:tempfile].read
|
|
3000
3250
|
end
|
|
3001
3251
|
end
|
|
3002
3252
|
```
|
|
3003
3253
|
|
|
3004
|
-
You can have your API only respond to a single format with `format`. If you use this, the API will **not** respond to file
|
|
3005
|
-
extensions other than specified in `format`. For example, consider the following API.
|
|
3254
|
+
You can have your API only respond to a single format with `format`. If you use this, the API will **not** respond to file extensions other than specified in `format`. For example, consider the following API.
|
|
3006
3255
|
|
|
3007
3256
|
```ruby
|
|
3008
3257
|
class SingleFormatAPI < Grape::API
|
|
@@ -3017,14 +3266,10 @@ end
|
|
|
3017
3266
|
* `GET /hello` will respond with JSON.
|
|
3018
3267
|
* `GET /hello.json` will respond with JSON.
|
|
3019
3268
|
* `GET /hello.xml`, `GET /hello.foobar`, or *any* other extension will respond with an HTTP 404 error code.
|
|
3020
|
-
* `GET /hello?format=xml` will respond with an HTTP 406 error code, because the XML format specified by the request parameter
|
|
3021
|
-
|
|
3022
|
-
* `GET /hello` with an `Accept: application/xml` header will still respond with JSON, since it could not negotiate a
|
|
3023
|
-
recognized content-type from the headers and JSON is the effective default.
|
|
3269
|
+
* `GET /hello?format=xml` will respond with an HTTP 406 error code, because the XML format specified by the request parameter is not supported.
|
|
3270
|
+
* `GET /hello` with an `Accept: application/xml` header will still respond with JSON, since it could not negotiate a recognized content-type from the headers and JSON is the effective default.
|
|
3024
3271
|
|
|
3025
|
-
The formats apply to parsing, too. The following API will only respond to the JSON content-type and will not parse any other
|
|
3026
|
-
input than `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and
|
|
3027
|
-
`multipart/mixed`. All other requests will fail with an HTTP 406 error code.
|
|
3272
|
+
The formats apply to parsing, too. The following API will only respond to the JSON content-type and will not parse any other input than `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`. All other requests will fail with an HTTP 406 error code.
|
|
3028
3273
|
|
|
3029
3274
|
```ruby
|
|
3030
3275
|
class Twitter::API < Grape::API
|
|
@@ -3080,23 +3325,53 @@ end
|
|
|
3080
3325
|
Built-in formatters are the following.
|
|
3081
3326
|
|
|
3082
3327
|
* `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump`
|
|
3083
|
-
* `:xml`: use object's `to_xml` when available, usually via `MultiXml
|
|
3328
|
+
* `:xml`: use object's `to_xml` when available, usually via `MultiXml`
|
|
3084
3329
|
* `:txt`: use object's `to_txt` when available, otherwise `to_s`
|
|
3085
3330
|
* `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
|
|
3086
3331
|
* `:binary`: data will be returned "as is"
|
|
3087
3332
|
|
|
3088
|
-
|
|
3089
|
-
|
|
3333
|
+
#### Serving pre-rendered JSON
|
|
3334
|
+
|
|
3335
|
+
The `:json` and `:serializable_hash` formatters encode whatever the endpoint returned,
|
|
3336
|
+
so a String that already holds JSON gets encoded a second time. Wrap the body in
|
|
3337
|
+
`Grape::PrecompiledJson` to opt that response out of encoding.
|
|
3338
|
+
|
|
3339
|
+
```ruby
|
|
3340
|
+
class API < Grape::API
|
|
3341
|
+
format :json
|
|
3342
|
+
|
|
3343
|
+
get '/cached' do
|
|
3344
|
+
body Grape::PrecompiledJson.new(Rails.cache.read('payload')) # => '{"id":1}', served as-is
|
|
3345
|
+
end
|
|
3346
|
+
end
|
|
3347
|
+
```
|
|
3348
|
+
|
|
3349
|
+
An `Array` is joined into a JSON array without its members being parsed, so a cached
|
|
3350
|
+
collection is spliced together rather than round-tripped. Members may themselves be
|
|
3351
|
+
`Grape::PrecompiledJson` instances.
|
|
3090
3352
|
|
|
3091
|
-
|
|
3092
|
-
[
|
|
3093
|
-
|
|
3094
|
-
|
|
3353
|
+
```ruby
|
|
3354
|
+
Grape::PrecompiledJson.new(['{"id":1}', '{"id":2}']).to_s # => '[{"id":1},{"id":2}]'
|
|
3355
|
+
```
|
|
3356
|
+
|
|
3357
|
+
Bodies that are not wrapped keep encoding exactly as before, so this is opt-in per
|
|
3358
|
+
response rather than a mode the whole API is in. Wrap only the whole body: a wrapper
|
|
3359
|
+
nested inside a Hash or Array that is then handed to an encoder is serialized as an
|
|
3360
|
+
ordinary object, because no encoder knows to unwrap it. A value that is neither a
|
|
3361
|
+
String nor an Array raises `Grape::Exceptions::InvalidFormatter` and answers 500,
|
|
3362
|
+
rather than handing Rack a body it cannot serve. Nothing checks that the String
|
|
3363
|
+
actually holds JSON — that is the caller's responsibility.
|
|
3364
|
+
|
|
3365
|
+
Errors are unaffected: they are rendered by the error formatter, so `error!` still
|
|
3366
|
+
returns properly encoded JSON.
|
|
3367
|
+
|
|
3368
|
+
If a body is present in a request to an API, with a Content-Type header value that is of an unsupported type a "415 Unsupported Media Type" error code will be returned by Grape.
|
|
3369
|
+
|
|
3370
|
+
Response statuses that indicate no content as defined by [Rack](https://github.com/rack) [here](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567) will bypass serialization and the body entity - though there should be none - will not be modified.
|
|
3095
3371
|
|
|
3096
3372
|
### JSONP
|
|
3097
3373
|
|
|
3098
|
-
Grape supports JSONP via [Rack::JSONP](https://github.com/rack/rack-contrib), part of the
|
|
3099
|
-
[rack-contrib](https://github.com/rack/rack-contrib) gem. Add `rack-contrib` to your `Gemfile`.
|
|
3374
|
+
Grape supports JSONP via [Rack::JSONP](https://github.com/rack/rack-contrib), part of the [rack-contrib](https://github.com/rack/rack-contrib) gem. Add `rack-contrib` to your `Gemfile`.
|
|
3100
3375
|
|
|
3101
3376
|
```ruby
|
|
3102
3377
|
require 'rack/contrib'
|
|
@@ -3112,9 +3387,7 @@ end
|
|
|
3112
3387
|
|
|
3113
3388
|
### CORS
|
|
3114
3389
|
|
|
3115
|
-
Grape supports CORS via [Rack::CORS](https://github.com/cyu/rack-cors), part of the
|
|
3116
|
-
[rack-cors](https://github.com/cyu/rack-cors) gem. Add `rack-cors` to your `Gemfile`,
|
|
3117
|
-
then use the middleware in your config.ru file.
|
|
3390
|
+
Grape supports CORS via [Rack::CORS](https://github.com/cyu/rack-cors), part of the [rack-cors](https://github.com/cyu/rack-cors) gem. Add `rack-cors` to your `Gemfile`, then use the middleware in your config.ru file.
|
|
3118
3391
|
|
|
3119
3392
|
```ruby
|
|
3120
3393
|
require 'rack/cors'
|
|
@@ -3132,8 +3405,7 @@ run Twitter::API
|
|
|
3132
3405
|
|
|
3133
3406
|
## Content-type
|
|
3134
3407
|
|
|
3135
|
-
Content-type is set by the formatter. You can override the content-type of the response at runtime
|
|
3136
|
-
by setting the `Content-Type` header.
|
|
3408
|
+
Content-type is set by the formatter. You can override the content-type of the response at runtime by setting the `Content-Type` header.
|
|
3137
3409
|
|
|
3138
3410
|
```ruby
|
|
3139
3411
|
class API < Grape::API
|
|
@@ -3146,16 +3418,12 @@ end
|
|
|
3146
3418
|
|
|
3147
3419
|
## API Data Formats
|
|
3148
3420
|
|
|
3149
|
-
Grape accepts and parses input data sent with the POST and PUT methods as described in the Parameters
|
|
3150
|
-
section above. It also supports custom data formats. You must declare additional content-types via
|
|
3151
|
-
`content_type` and optionally supply a parser via `parser` unless a parser is already available within
|
|
3152
|
-
Grape to enable a custom format. Such a parser can be a function or a class.
|
|
3421
|
+
Grape accepts and parses input data sent with the POST and PUT methods as described in the Parameters section above. It also supports custom data formats. You must declare additional content-types via `content_type` and optionally supply a parser via `parser` unless a parser is already available within Grape to enable a custom format. Such a parser can be a function or a class.
|
|
3153
3422
|
|
|
3154
3423
|
With a parser, parsed data is available "as-is" in `env['api.request.body']`.
|
|
3155
3424
|
Without a parser, data is available "as-is" and in `env['api.request.input']`.
|
|
3156
3425
|
|
|
3157
|
-
The following example is a trivial parser that will assign any input with the "text/custom" content-type
|
|
3158
|
-
to `:value`. The parameter will be available via `params[:value]` inside the API call.
|
|
3426
|
+
The following example is a trivial parser that will assign any input with the "text/custom" content-type to `:value`. The parameter will be available via `params[:value]` inside the API call.
|
|
3159
3427
|
|
|
3160
3428
|
```ruby
|
|
3161
3429
|
module CustomParser
|
|
@@ -3189,9 +3457,7 @@ Grape uses `JSON` and `ActiveSupport::XmlMini` for JSON and XML parsing by defau
|
|
|
3189
3457
|
|
|
3190
3458
|
## RESTful Model Representations
|
|
3191
3459
|
|
|
3192
|
-
Grape supports a range of ways to present your data with some help from a generic `present` method,
|
|
3193
|
-
which accepts two arguments: the object to be presented and the options associated with it. The options
|
|
3194
|
-
hash may include `:with`, which defines the entity to expose.
|
|
3460
|
+
Grape supports a range of ways to present your data with some help from a generic `present` method, which accepts two arguments: the object to be presented and the options associated with it. The options hash may include `:with`, which defines the entity to expose.
|
|
3195
3461
|
|
|
3196
3462
|
### Grape Entities
|
|
3197
3463
|
|
|
@@ -3270,8 +3536,7 @@ The response will be
|
|
|
3270
3536
|
}
|
|
3271
3537
|
```
|
|
3272
3538
|
|
|
3273
|
-
In addition to separately organizing entities, it may be useful to put them as namespaced
|
|
3274
|
-
classes underneath the model they represent.
|
|
3539
|
+
In addition to separately organizing entities, it may be useful to put them as namespaced classes underneath the model they represent.
|
|
3275
3540
|
|
|
3276
3541
|
```ruby
|
|
3277
3542
|
class Status
|
|
@@ -3285,11 +3550,7 @@ class Status
|
|
|
3285
3550
|
end
|
|
3286
3551
|
```
|
|
3287
3552
|
|
|
3288
|
-
If you organize your entities this way, Grape will automatically detect the `Entity` class and
|
|
3289
|
-
use it to present your models. In this example, if you added `present Status.new` to your endpoint,
|
|
3290
|
-
Grape will automatically detect that there is a `Status::Entity` class and use that as the
|
|
3291
|
-
representative entity. This can still be overridden by using the `:with` option or an explicit
|
|
3292
|
-
`represents` call.
|
|
3553
|
+
If you organize your entities this way, Grape will automatically detect the `Entity` class and use it to present your models. In this example, if you added `present Status.new` to your endpoint, Grape will automatically detect that there is a `Status::Entity` class and use that as the representative entity. This can still be overridden by using the `:with` option or an explicit `represents` call.
|
|
3293
3554
|
|
|
3294
3555
|
You can present `hash` with `Grape::Presenters::Presenter` to keep things consistent.
|
|
3295
3556
|
|
|
@@ -3322,15 +3583,11 @@ You can use [Roar](https://github.com/apotonick/roar) to render HAL or Collectio
|
|
|
3322
3583
|
|
|
3323
3584
|
### Rabl
|
|
3324
3585
|
|
|
3325
|
-
You can use [Rabl](https://github.com/nesquena/rabl) templates with the help of the
|
|
3326
|
-
[grape-rabl](https://github.com/ruby-grape/grape-rabl) gem, which defines a custom Grape Rabl
|
|
3327
|
-
formatter.
|
|
3586
|
+
You can use [Rabl](https://github.com/nesquena/rabl) templates with the help of the [grape-rabl](https://github.com/ruby-grape/grape-rabl) gem, which defines a custom Grape Rabl formatter.
|
|
3328
3587
|
|
|
3329
3588
|
### Active Model Serializers
|
|
3330
3589
|
|
|
3331
|
-
You can use [Active Model Serializers](https://github.com/rails-api/active_model_serializers) serializers with the help of the
|
|
3332
|
-
[grape-active_model_serializers](https://github.com/jrhe/grape-active_model_serializers) gem, which defines a custom Grape AMS
|
|
3333
|
-
formatter.
|
|
3590
|
+
You can use [Active Model Serializers](https://github.com/rails-api/active_model_serializers) serializers with the help of the [grape-active_model_serializers](https://github.com/jrhe/grape-active_model_serializers) gem, which defines a custom Grape AMS formatter.
|
|
3334
3591
|
|
|
3335
3592
|
## Sending Raw or No Data
|
|
3336
3593
|
|
|
@@ -3370,9 +3627,7 @@ class API < Grape::API
|
|
|
3370
3627
|
end
|
|
3371
3628
|
```
|
|
3372
3629
|
|
|
3373
|
-
You can also set the response to a file with `sendfile`. This works with the
|
|
3374
|
-
[Rack::Sendfile](https://www.rubydoc.info/gems/rack/Rack/Sendfile) middleware to optimally send
|
|
3375
|
-
the file through your web server software.
|
|
3630
|
+
You can also set the response to a file with `sendfile`. This works with the [Rack::Sendfile](https://www.rubydoc.info/gems/rack/Rack/Sendfile) middleware to optimally send the file through your web server software.
|
|
3376
3631
|
|
|
3377
3632
|
```ruby
|
|
3378
3633
|
class API < Grape::API
|
|
@@ -3414,11 +3669,9 @@ end
|
|
|
3414
3669
|
|
|
3415
3670
|
## Authentication
|
|
3416
3671
|
|
|
3417
|
-
### Basic
|
|
3672
|
+
### Basic Auth
|
|
3418
3673
|
|
|
3419
|
-
Grape has built-in Basic
|
|
3420
|
-
is executed in the context of the current `Endpoint`). Authentication
|
|
3421
|
-
applies to the current namespace and any children, but not parents.
|
|
3674
|
+
Grape has built-in Basic authentication (the given `block` is executed in the context of the current `Endpoint`). Authentication applies to the current namespace and any children, but not parents.
|
|
3422
3675
|
|
|
3423
3676
|
```ruby
|
|
3424
3677
|
http_basic do |username, password|
|
|
@@ -3427,32 +3680,15 @@ http_basic do |username, password|
|
|
|
3427
3680
|
end
|
|
3428
3681
|
```
|
|
3429
3682
|
|
|
3430
|
-
Digest auth supports clear-text passwords and password hashes.
|
|
3431
|
-
|
|
3432
|
-
```ruby
|
|
3433
|
-
http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
|
|
3434
|
-
# lookup the user's password here
|
|
3435
|
-
end
|
|
3436
|
-
```
|
|
3437
|
-
|
|
3438
|
-
```ruby
|
|
3439
|
-
http_digest(realm: { realm: 'Test Api', opaque: 'app secret', passwords_hashed: true }) do |username|
|
|
3440
|
-
# lookup the user's password hash here
|
|
3441
|
-
end
|
|
3442
|
-
```
|
|
3443
|
-
|
|
3444
3683
|
### Register custom middleware for authentication
|
|
3445
3684
|
|
|
3446
|
-
Grape can use custom Middleware for authentication. How to implement these
|
|
3447
|
-
Middleware have a look at `Rack::Auth::Basic` or similar implementations.
|
|
3448
|
-
|
|
3685
|
+
Grape can use custom Middleware for authentication. How to implement these Middleware have a look at `Rack::Auth::Basic` or similar implementations.
|
|
3449
3686
|
|
|
3450
3687
|
For registering a Middleware you need the following options:
|
|
3451
3688
|
|
|
3452
3689
|
* `label` - the name for your authenticator to use it later
|
|
3453
3690
|
* `MiddlewareClass` - the MiddlewareClass to use for authentication
|
|
3454
|
-
* `option_lookup_proc` - A Proc with one Argument to lookup the options at
|
|
3455
|
-
runtime (return value is an `Array` as Parameter for the Middleware).
|
|
3691
|
+
* `option_lookup_proc` - A Proc with one Argument to lookup the options at runtime (return value is an `Array` as Parameter for the Middleware).
|
|
3456
3692
|
|
|
3457
3693
|
Example:
|
|
3458
3694
|
|
|
@@ -3476,7 +3712,7 @@ You can access the controller params, headers, and helpers through the context w
|
|
|
3476
3712
|
|
|
3477
3713
|
Grape routes can be reflected at runtime. This can notably be useful for generating documentation.
|
|
3478
3714
|
|
|
3479
|
-
Grape exposes arrays of API versions and compiled routes. Each route contains a `
|
|
3715
|
+
Grape exposes arrays of API versions and compiled routes. Each route contains a `prefix`, `version`, `namespace`, `method` and `params`. You can add custom route settings to the route metadata with `route_setting`.
|
|
3480
3716
|
|
|
3481
3717
|
```ruby
|
|
3482
3718
|
class TwitterAPI < Grape::API
|
|
@@ -3499,14 +3735,14 @@ TwitterAPI::routes[0].description # => 'Includes custom settings.'
|
|
|
3499
3735
|
TwitterAPI::routes[0].settings[:custom] # => { key: 'value' }
|
|
3500
3736
|
```
|
|
3501
3737
|
|
|
3502
|
-
Note that `Route#route_xyz` methods have been deprecated since 0.15.0.
|
|
3738
|
+
Note that `Route#route_xyz` methods have been deprecated since 0.15.0 and removed since 2.0.1.
|
|
3503
3739
|
|
|
3504
3740
|
Please use `Route#xyz` instead.
|
|
3505
3741
|
|
|
3506
3742
|
Note that difference of `Route#options` and `Route#settings`.
|
|
3507
3743
|
|
|
3508
|
-
The `options` can be referred from your route, it should be set by
|
|
3509
|
-
The `settings` can also be referred from your route, but it should be set by
|
|
3744
|
+
The `options` can be referred from your route, it should be set by specifying key and value on verb methods such as `get`, `post` and `put`.
|
|
3745
|
+
The `settings` can also be referred from your route, but it should be set by specifying key and value on `route_setting`.
|
|
3510
3746
|
|
|
3511
3747
|
## Current Route and Endpoint
|
|
3512
3748
|
|
|
@@ -3519,15 +3755,12 @@ class MyAPI < Grape::API
|
|
|
3519
3755
|
requires :id, type: Integer, desc: 'Identity.'
|
|
3520
3756
|
end
|
|
3521
3757
|
get 'params/:id' do
|
|
3522
|
-
route.
|
|
3758
|
+
route.params[params[:id]] # yields the parameter description
|
|
3523
3759
|
end
|
|
3524
3760
|
end
|
|
3525
3761
|
```
|
|
3526
3762
|
|
|
3527
|
-
The current endpoint responding to the request is `self` within the API block
|
|
3528
|
-
or `env['api.endpoint']` elsewhere. The endpoint has some interesting properties,
|
|
3529
|
-
such as `source` which gives you access to the original code block of the API
|
|
3530
|
-
implementation. This can be particularly useful for building a logger middleware.
|
|
3763
|
+
The current endpoint responding to the request is `self` within the API block or `env['api.endpoint']` elsewhere. The endpoint has some interesting properties, such as `source` which gives you access to the original code block of the API implementation. This can be particularly useful for building a logger middleware.
|
|
3531
3764
|
|
|
3532
3765
|
```ruby
|
|
3533
3766
|
class ApiLogger < Grape::Middleware::Base
|
|
@@ -3541,10 +3774,8 @@ end
|
|
|
3541
3774
|
|
|
3542
3775
|
## Before, After and Finally
|
|
3543
3776
|
|
|
3544
|
-
Blocks can be executed before or after every API call, using `before`, `after`,
|
|
3545
|
-
`
|
|
3546
|
-
If the API fails the `after` call will not be triggered, if you need code to execute for sure
|
|
3547
|
-
use the `finally`.
|
|
3777
|
+
Blocks can be executed before or after every API call, using `before`, `after`, `before_validation` and `after_validation`.
|
|
3778
|
+
If the API fails the `after` call will not be triggered, if you need code to execute for sure use the `finally`.
|
|
3548
3779
|
|
|
3549
3780
|
Before and after callbacks execute in the following order:
|
|
3550
3781
|
|
|
@@ -3558,13 +3789,9 @@ Before and after callbacks execute in the following order:
|
|
|
3558
3789
|
|
|
3559
3790
|
Steps 4, 5 and 6 only happen if validation succeeds.
|
|
3560
3791
|
|
|
3561
|
-
If a request for a resource is made with an unsupported HTTP method (returning
|
|
3562
|
-
HTTP 405) only `before` callbacks will be executed. The remaining callbacks will
|
|
3563
|
-
be bypassed.
|
|
3792
|
+
If a request for a resource is made with an unsupported HTTP method (returning HTTP 405) only `before` callbacks will be executed. The remaining callbacks will be bypassed.
|
|
3564
3793
|
|
|
3565
|
-
If a request for a resource is made that triggers the built-in `OPTIONS` handler,
|
|
3566
|
-
only `before` and `after` callbacks will be executed. The remaining callbacks will
|
|
3567
|
-
be bypassed.
|
|
3794
|
+
If a request for a resource is made that triggers the built-in `OPTIONS` handler, only `before` and `after` callbacks will be executed. The remaining callbacks will be bypassed.
|
|
3568
3795
|
|
|
3569
3796
|
For example, using a simple `before` block to set a header.
|
|
3570
3797
|
|
|
@@ -3709,11 +3936,7 @@ Instead of altering a response, you can also terminate and rewrite it from any c
|
|
|
3709
3936
|
|
|
3710
3937
|
## Anchoring
|
|
3711
3938
|
|
|
3712
|
-
Grape by default anchors all request paths, which means that the request URL
|
|
3713
|
-
should match from start to end to match, otherwise a `404 Not Found` is
|
|
3714
|
-
returned. However, this is sometimes not what you want, because it is not always
|
|
3715
|
-
known upfront what can be expected from the call. This is because Rack-mount by
|
|
3716
|
-
default anchors requests to match from the start to the end, or not at all.
|
|
3939
|
+
Grape by default anchors all request paths, which means that the request URL should match from start to end to match, otherwise a `404 Not Found` is returned. However, this is sometimes not what you want, because it is not always known upfront what can be expected from the call. This is because Rack-mount by default anchors requests to match from the start to the end, or not at all.
|
|
3717
3940
|
Rails solves this problem by using a `anchor: false` option in your routes.
|
|
3718
3941
|
In Grape this option can be used as well when a method is defined.
|
|
3719
3942
|
|
|
@@ -3729,12 +3952,44 @@ class TwitterAPI < Grape::API
|
|
|
3729
3952
|
end
|
|
3730
3953
|
```
|
|
3731
3954
|
|
|
3732
|
-
This will match all paths starting with '/statuses/'. There is one caveat though:
|
|
3733
|
-
the `
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3955
|
+
This will match all paths starting with '/statuses/'. There is one caveat though: the `params[:status]` parameter only holds the first part of the request url.
|
|
3956
|
+
Luckily this can be circumvented by using the described above syntax for path specification and using the `PATH_INFO` Rack environment variable, using `env['PATH_INFO']`. This will hold everything that comes after the '/statuses/' part.
|
|
3957
|
+
|
|
3958
|
+
## Instance Variables
|
|
3959
|
+
|
|
3960
|
+
You can use instance variables to pass information across the various stages of a request. An instance variable set within a `before` validator is accessible within the endpoint's code and can also be utilized within the `rescue_from` handler.
|
|
3961
|
+
|
|
3962
|
+
```ruby
|
|
3963
|
+
class TwitterAPI < Grape::API
|
|
3964
|
+
before do
|
|
3965
|
+
@var = 1
|
|
3966
|
+
end
|
|
3967
|
+
|
|
3968
|
+
rescue_from :all do
|
|
3969
|
+
puts @var # => 1
|
|
3970
|
+
end
|
|
3971
|
+
|
|
3972
|
+
get '/' do
|
|
3973
|
+
puts @var # => 1
|
|
3974
|
+
raise
|
|
3975
|
+
end
|
|
3976
|
+
end
|
|
3977
|
+
```
|
|
3978
|
+
|
|
3979
|
+
The values of instance variables cannot be shared among various endpoints within the same API. This limitation arises due to Grape generating a new instance for each request made. Consequently, instance variables set within an endpoint during one request differ from those set during a subsequent request, as they exist within separate instances.
|
|
3980
|
+
|
|
3981
|
+
```ruby
|
|
3982
|
+
class TwitterAPI < Grape::API
|
|
3983
|
+
get '/first' do
|
|
3984
|
+
@var = 1
|
|
3985
|
+
puts @var # => 1
|
|
3986
|
+
end
|
|
3987
|
+
|
|
3988
|
+
get '/second' do
|
|
3989
|
+
puts @var # => nil
|
|
3990
|
+
end
|
|
3991
|
+
end
|
|
3992
|
+
```
|
|
3738
3993
|
|
|
3739
3994
|
## Using Custom Middleware
|
|
3740
3995
|
|
|
@@ -3943,8 +4198,7 @@ describe Twitter::API do
|
|
|
3943
4198
|
end
|
|
3944
4199
|
```
|
|
3945
4200
|
|
|
3946
|
-
In Rails, HTTP request tests would go into the `spec/requests` group. You may want your API code to go into
|
|
3947
|
-
`app/api` - you can match that layout under `spec` by adding the following in `spec/rails_helper.rb`.
|
|
4201
|
+
In Rails, HTTP request tests would go into the `spec/requests` group. You may want your API code to go into `app/api` - you can match that layout under `spec` by adding the following in `spec/rails_helper.rb`.
|
|
3948
4202
|
|
|
3949
4203
|
```ruby
|
|
3950
4204
|
RSpec.configure do |config|
|
|
@@ -3978,10 +4232,15 @@ end
|
|
|
3978
4232
|
|
|
3979
4233
|
### Stubbing Helpers
|
|
3980
4234
|
|
|
3981
|
-
Because helpers are mixed in based on the context when an endpoint is defined, it can
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4235
|
+
Because helpers are mixed in based on the context when an endpoint is defined, it can be difficult to stub or mock them for testing. `Grape::Endpoint.before_each` allows you to define behavior on the endpoint that will run before every request.
|
|
4236
|
+
|
|
4237
|
+
This feature is provided by `Grape::Testing`, a standalone module intended for test environments only. Add it to your test helper:
|
|
4238
|
+
|
|
4239
|
+
```ruby
|
|
4240
|
+
require 'grape/testing'
|
|
4241
|
+
```
|
|
4242
|
+
|
|
4243
|
+
Then use it in your tests:
|
|
3985
4244
|
|
|
3986
4245
|
```ruby
|
|
3987
4246
|
describe 'an endpoint that needs helpers stubbed' do
|
|
@@ -3992,7 +4251,7 @@ describe 'an endpoint that needs helpers stubbed' do
|
|
|
3992
4251
|
end
|
|
3993
4252
|
|
|
3994
4253
|
after do
|
|
3995
|
-
Grape::Endpoint.
|
|
4254
|
+
Grape::Endpoint.reset_before_each
|
|
3996
4255
|
end
|
|
3997
4256
|
|
|
3998
4257
|
it 'stubs the helper' do
|
|
@@ -4009,6 +4268,25 @@ Use [grape-reload](https://github.com/AlexYankee/grape-reload).
|
|
|
4009
4268
|
|
|
4010
4269
|
### Reloading in Rails Applications
|
|
4011
4270
|
|
|
4271
|
+
#### Rails 7+ (Zeitwerk)
|
|
4272
|
+
|
|
4273
|
+
Rails 7+ uses [Zeitwerk](https://github.com/fxn/zeitwerk) as the default autoloader, which automatically handles reloading of code in development mode without any additional configuration.
|
|
4274
|
+
|
|
4275
|
+
If your API files are in `app/api`, Zeitwerk will automatically autoload and reload them. No additional configuration is needed.
|
|
4276
|
+
|
|
4277
|
+
If you encounter issues with reloading, ensure that:
|
|
4278
|
+
|
|
4279
|
+
1. Your API files follow Zeitwerk naming conventions (file names should match class names).
|
|
4280
|
+
2. The `config.enable_reloading` is set to `true` in `config/environments/development.rb` (this is the default).
|
|
4281
|
+
|
|
4282
|
+
For troubleshooting autoloading issues, have a look at the [Rails documentation](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#troubleshooting).
|
|
4283
|
+
|
|
4284
|
+
See the [Rails Autoloading and Reloading Constants guide](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html) for more information.
|
|
4285
|
+
|
|
4286
|
+
#### Rails 6 and Earlier
|
|
4287
|
+
|
|
4288
|
+
For Rails versions before 7, you need to configure reloading manually.
|
|
4289
|
+
|
|
4012
4290
|
Add API paths to `config/application.rb`.
|
|
4013
4291
|
|
|
4014
4292
|
```ruby
|
|
@@ -4027,28 +4305,12 @@ if Rails.env.development?
|
|
|
4027
4305
|
api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
|
|
4028
4306
|
Rails.application.reload_routes!
|
|
4029
4307
|
end
|
|
4030
|
-
|
|
4308
|
+
ActiveSupport::Reloader.to_prepare do
|
|
4031
4309
|
api_reloader.execute_if_updated
|
|
4032
4310
|
end
|
|
4033
4311
|
end
|
|
4034
4312
|
```
|
|
4035
4313
|
|
|
4036
|
-
For Rails >= 5.1.4, change this:
|
|
4037
|
-
|
|
4038
|
-
```ruby
|
|
4039
|
-
ActionDispatch::Callbacks.to_prepare do
|
|
4040
|
-
api_reloader.execute_if_updated
|
|
4041
|
-
end
|
|
4042
|
-
```
|
|
4043
|
-
|
|
4044
|
-
to this:
|
|
4045
|
-
|
|
4046
|
-
```ruby
|
|
4047
|
-
ActiveSupport::Reloader.to_prepare do
|
|
4048
|
-
api_reloader.execute_if_updated
|
|
4049
|
-
end
|
|
4050
|
-
```
|
|
4051
|
-
|
|
4052
4314
|
See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-rails-3-reload-lib-directory-for-each-request/4368838#4368838) for more information.
|
|
4053
4315
|
|
|
4054
4316
|
## Performance Monitoring
|
|
@@ -4057,27 +4319,30 @@ See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-
|
|
|
4057
4319
|
|
|
4058
4320
|
Grape has built-in support for [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) which provides simple hook points to instrument key parts of your application.
|
|
4059
4321
|
|
|
4060
|
-
The following are currently supported:
|
|
4061
4322
|
|
|
4062
|
-
####
|
|
4323
|
+
#### Hook Points
|
|
4324
|
+
|
|
4325
|
+
The following hook points are currently supported:
|
|
4326
|
+
|
|
4327
|
+
##### endpoint_run.grape
|
|
4063
4328
|
|
|
4064
4329
|
The main execution of an endpoint, includes filters and rendering.
|
|
4065
4330
|
|
|
4066
4331
|
* *endpoint* - The endpoint instance
|
|
4067
4332
|
|
|
4068
|
-
|
|
4333
|
+
##### endpoint_render.grape
|
|
4069
4334
|
|
|
4070
4335
|
The execution of the main content block of the endpoint.
|
|
4071
4336
|
|
|
4072
4337
|
* *endpoint* - The endpoint instance
|
|
4073
4338
|
|
|
4074
|
-
|
|
4339
|
+
##### endpoint_run_filters.grape
|
|
4075
4340
|
|
|
4076
4341
|
* *endpoint* - The endpoint instance
|
|
4077
4342
|
* *filters* - The filters being executed
|
|
4078
4343
|
* *type* - The type of filters (before, before_validation, after_validation, after)
|
|
4079
4344
|
|
|
4080
|
-
|
|
4345
|
+
##### endpoint_run_validators.grape
|
|
4081
4346
|
|
|
4082
4347
|
The execution of validators.
|
|
4083
4348
|
|
|
@@ -4085,7 +4350,7 @@ The execution of validators.
|
|
|
4085
4350
|
* *validators* - The validators being executed
|
|
4086
4351
|
* *request* - The request being validated
|
|
4087
4352
|
|
|
4088
|
-
|
|
4353
|
+
##### format_response.grape
|
|
4089
4354
|
|
|
4090
4355
|
Serialization or template rendering.
|
|
4091
4356
|
|
|
@@ -4094,12 +4359,44 @@ Serialization or template rendering.
|
|
|
4094
4359
|
|
|
4095
4360
|
See the [ActiveSupport::Notifications documentation](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) for information on how to subscribe to these events.
|
|
4096
4361
|
|
|
4362
|
+
#### Subscribe to Hooks
|
|
4363
|
+
|
|
4364
|
+
Once subscribed to the instrumentation, you can intercept the events reported above.
|
|
4365
|
+
|
|
4366
|
+
```ruby
|
|
4367
|
+
ActiveSupport::Notifications.subscribe(/<api_path>/) do |name, start, finish, id, payload|
|
|
4368
|
+
# your code to intercept the notification
|
|
4369
|
+
end
|
|
4370
|
+
```
|
|
4371
|
+
|
|
4372
|
+
The request data, the API’s internal data, and the response can be retrieved from the payload.
|
|
4373
|
+
|
|
4374
|
+
You can use `payload.fetch(:endpoint)` or directly `payload[:endpoint]`.
|
|
4375
|
+
|
|
4376
|
+
The `:endpoint` contains the data currently being processed, and access to attributes such as `body`, `request`, `params`, `headers`, `cookies` and `response_cookies`
|
|
4377
|
+
|
|
4378
|
+
For example, `payload[:endpoint].body` provides the current state of the response.
|
|
4379
|
+
|
|
4380
|
+
```ruby
|
|
4381
|
+
ActiveSupport::Notifications.subscribe(/v1/) do |name, start, finish, id, payload|
|
|
4382
|
+
hook_record = {
|
|
4383
|
+
hook: name
|
|
4384
|
+
status: payload[:env]&.dig("api.endpoint")&.status
|
|
4385
|
+
format: payload[:env]&.dig("api.format")
|
|
4386
|
+
body: payload[:endpoint]&.body
|
|
4387
|
+
duration: (finish - start) * 1000
|
|
4388
|
+
}
|
|
4389
|
+
# your code to save the notification
|
|
4390
|
+
end
|
|
4391
|
+
```
|
|
4392
|
+
|
|
4097
4393
|
### Monitoring Products
|
|
4098
4394
|
|
|
4099
4395
|
Grape integrates with following third-party tools:
|
|
4100
4396
|
|
|
4101
4397
|
* **New Relic** - [built-in support](https://docs.newrelic.com/docs/agents/ruby-agent/frameworks/grape-instrumentation) from v3.10.0 of the official [newrelic_rpm](https://github.com/newrelic/rpm) gem, also [newrelic-grape](https://github.com/xinminlabs/newrelic-grape) gem
|
|
4102
4398
|
* **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem
|
|
4399
|
+
* **Rails Performance** - [rails_performance](https://github.com/igorkasyanchuk/rails_performance) gem
|
|
4103
4400
|
* **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
|
|
4104
4401
|
* **[AppSignal](https://www.appsignal.com)** - [appsignal-ruby](https://github.com/appsignal/appsignal-ruby) gem, [documentation](http://docs.appsignal.com/getting-started/supported-frameworks.html#grape)
|
|
4105
4402
|
* **[ElasticAPM](https://www.elastic.co/products/apm)** - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)
|
|
@@ -4107,8 +4404,7 @@ Grape integrates with following third-party tools:
|
|
|
4107
4404
|
|
|
4108
4405
|
## Contributing to Grape
|
|
4109
4406
|
|
|
4110
|
-
Grape is work of hundreds of contributors. You're encouraged to submit pull requests, propose
|
|
4111
|
-
features and discuss issues.
|
|
4407
|
+
Grape is work of hundreds of contributors. You're encouraged to submit pull requests, propose features and discuss issues.
|
|
4112
4408
|
|
|
4113
4409
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
|
4114
4410
|
|