grape 2.4.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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +351 -0
  3. data/CONTRIBUTING.md +18 -10
  4. data/README.md +457 -228
  5. data/UPGRADING.md +1240 -1
  6. data/grape.gemspec +7 -7
  7. data/lib/grape/api/instance.rb +65 -116
  8. data/lib/grape/api.rb +53 -62
  9. data/lib/grape/content_types.rb +58 -3
  10. data/lib/grape/cookies.rb +2 -6
  11. data/lib/grape/declared_params_handler.rb +116 -0
  12. data/lib/grape/dry_types.rb +48 -4
  13. data/lib/grape/dsl/callbacks.rb +8 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +24 -67
  16. data/lib/grape/dsl/entity.rb +106 -0
  17. data/lib/grape/dsl/helpers.rb +80 -65
  18. data/lib/grape/dsl/inside_route.rb +56 -296
  19. data/lib/grape/dsl/logger.rb +4 -9
  20. data/lib/grape/dsl/middleware.rb +22 -40
  21. data/lib/grape/dsl/parameters.rb +86 -93
  22. data/lib/grape/dsl/request_response.rb +139 -139
  23. data/lib/grape/dsl/rescue_options.rb +25 -0
  24. data/lib/grape/dsl/routing.rb +328 -203
  25. data/lib/grape/dsl/settings.rb +29 -135
  26. data/lib/grape/dsl/validations.rb +38 -44
  27. data/lib/grape/dsl/version_options.rb +23 -0
  28. data/lib/grape/endpoint/options.rb +25 -0
  29. data/lib/grape/endpoint.rb +285 -245
  30. data/lib/grape/env.rb +8 -3
  31. data/lib/grape/error_formatter/base.rb +27 -21
  32. data/lib/grape/error_formatter/json.rb +6 -0
  33. data/lib/grape/error_formatter/txt.rb +10 -10
  34. data/lib/grape/error_formatter.rb +6 -2
  35. data/lib/grape/exceptions/base.rb +20 -44
  36. data/lib/grape/exceptions/error_response.rb +48 -0
  37. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  38. data/lib/grape/exceptions/internal_server_error.rb +16 -0
  39. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  40. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  41. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  42. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  43. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  44. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  45. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  46. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  47. data/lib/grape/exceptions/request_error.rb +11 -0
  48. data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
  49. data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
  50. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  51. data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
  52. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  53. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  54. data/lib/grape/exceptions/validation.rb +25 -7
  55. data/lib/grape/exceptions/validation_array_errors.rb +4 -0
  56. data/lib/grape/exceptions/validation_errors.rb +21 -23
  57. data/lib/grape/formatter/json.rb +1 -0
  58. data/lib/grape/formatter/serializable_hash.rb +6 -9
  59. data/lib/grape/json.rb +38 -2
  60. data/lib/grape/locale/en.yml +3 -5
  61. data/lib/grape/middleware/auth/base.rb +4 -3
  62. data/lib/grape/middleware/auth/dsl.rb +9 -23
  63. data/lib/grape/middleware/base.rb +41 -36
  64. data/lib/grape/middleware/error.rb +271 -73
  65. data/lib/grape/middleware/formatter.rb +152 -63
  66. data/lib/grape/middleware/precomputed_content_types.rb +51 -0
  67. data/lib/grape/middleware/stack.rb +20 -11
  68. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
  69. data/lib/grape/middleware/versioner/base.rb +40 -48
  70. data/lib/grape/middleware/versioner/header.rb +5 -23
  71. data/lib/grape/middleware/versioner/param.rb +2 -2
  72. data/lib/grape/middleware/versioner/path.rb +54 -7
  73. data/lib/grape/mountable.rb +22 -0
  74. data/lib/grape/namespace.rb +19 -11
  75. data/lib/grape/params_builder/base.rb +2 -0
  76. data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
  77. data/lib/grape/params_builder.rb +2 -19
  78. data/lib/grape/parser/json.rb +1 -1
  79. data/lib/grape/path.rb +8 -72
  80. data/lib/grape/precompiled_json.rb +50 -0
  81. data/lib/grape/request.rb +57 -20
  82. data/lib/grape/router/base_route.rb +53 -7
  83. data/lib/grape/router/greedy_route.rb +16 -6
  84. data/lib/grape/router/mustermann_pattern.rb +44 -0
  85. data/lib/grape/router/pattern/path.rb +78 -0
  86. data/lib/grape/router/pattern.rb +65 -37
  87. data/lib/grape/router/route.rb +66 -18
  88. data/lib/grape/router.rb +126 -115
  89. data/lib/grape/serve_stream/file_body.rb +7 -0
  90. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  91. data/lib/grape/serve_stream/stream_response.rb +7 -0
  92. data/lib/grape/testing.rb +33 -0
  93. data/lib/grape/util/api_description.rb +67 -0
  94. data/lib/grape/util/cache.rb +22 -2
  95. data/lib/grape/util/deep_freeze.rb +34 -0
  96. data/lib/grape/util/freeze_on_new.rb +20 -0
  97. data/lib/grape/util/inheritable_setting.rb +777 -39
  98. data/lib/grape/util/lazy/base.rb +16 -0
  99. data/lib/grape/util/lazy/block.rb +2 -9
  100. data/lib/grape/util/lazy/value.rb +2 -9
  101. data/lib/grape/util/lazy/value_enumerable.rb +13 -16
  102. data/lib/grape/util/media_type.rb +13 -9
  103. data/lib/grape/util/path_normalizer.rb +37 -0
  104. data/lib/grape/util/registry.rb +13 -3
  105. data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
  106. data/lib/grape/util/stackable_values.rb +39 -10
  107. data/lib/grape/util/translation.rb +42 -0
  108. data/lib/grape/validations/attributes_iterator.rb +61 -29
  109. data/lib/grape/validations/coerce_options.rb +21 -0
  110. data/lib/grape/validations/contract_scope.rb +8 -13
  111. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  112. data/lib/grape/validations/oneof_collector.rb +35 -0
  113. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  114. data/lib/grape/validations/params_documentation.rb +52 -0
  115. data/lib/grape/validations/params_scope.rb +217 -304
  116. data/lib/grape/validations/shared_options.rb +19 -0
  117. data/lib/grape/validations/single_attribute_iterator.rb +6 -4
  118. data/lib/grape/validations/types/array_coercer.rb +7 -10
  119. data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
  120. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  121. data/lib/grape/validations/types/dry_type_coercer.rb +10 -15
  122. data/lib/grape/validations/types/json.rb +1 -3
  123. data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
  124. data/lib/grape/validations/types/primitive_coercer.rb +14 -33
  125. data/lib/grape/validations/types/set_coercer.rb +1 -1
  126. data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
  127. data/lib/grape/validations/types.rb +29 -51
  128. data/lib/grape/validations/validations_spec.rb +164 -0
  129. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  130. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  131. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  132. data/lib/grape/validations/validators/base.rb +118 -34
  133. data/lib/grape/validations/validators/coerce_validator.rb +25 -37
  134. data/lib/grape/validations/validators/contract_scope_validator.rb +10 -5
  135. data/lib/grape/validations/validators/default_validator.rb +13 -20
  136. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  137. data/lib/grape/validations/validators/except_values_validator.rb +14 -4
  138. data/lib/grape/validations/validators/length_validator.rb +23 -22
  139. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  140. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  141. data/lib/grape/validations/validators/oneof_validator.rb +51 -0
  142. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  143. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  144. data/lib/grape/validations/validators/same_as_validator.rb +7 -15
  145. data/lib/grape/validations/validators/values_validator.rb +34 -26
  146. data/lib/grape/version.rb +1 -2
  147. data/lib/grape/xml.rb +8 -1
  148. data/lib/grape.rb +58 -31
  149. metadata +62 -39
  150. data/lib/grape/api/helpers.rb +0 -9
  151. data/lib/grape/dsl/api.rb +0 -17
  152. data/lib/grape/dsl/configuration.rb +0 -15
  153. data/lib/grape/exceptions/conflicting_types.rb +0 -11
  154. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  155. data/lib/grape/exceptions/invalid_parameters.rb +0 -11
  156. data/lib/grape/exceptions/missing_option.rb +0 -11
  157. data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
  158. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  159. data/lib/grape/exceptions/unknown_options.rb +0 -11
  160. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
  161. data/lib/grape/extensions/hash.rb +0 -27
  162. data/lib/grape/extensions/hashie/mash.rb +0 -24
  163. data/lib/grape/middleware/globals.rb +0 -14
  164. data/lib/grape/types/invalid_value.rb +0 -8
  165. data/lib/grape/util/base_inheritable.rb +0 -43
  166. data/lib/grape/util/inheritable_values.rb +0 -29
  167. data/lib/grape/util/reverse_stackable_values.rb +0 -15
  168. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  169. data/lib/grape/validations/attributes_doc.rb +0 -60
  170. data/lib/grape/validations/validator_factory.rb +0 -15
data/README.md CHANGED
@@ -4,160 +4,13 @@
4
4
  [![test](https://github.com/ruby-grape/grape/actions/workflows/test.yml/badge.svg)](https://github.com/ruby-grape/grape/actions/workflows/test.yml)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master)
6
6
 
7
- ## Table of Contents
8
-
9
- - [What is Grape?](#what-is-grape)
10
- - [Stable Release](#stable-release)
11
- - [Project Resources](#project-resources)
12
- - [Grape for Enterprise](#grape-for-enterprise)
13
- - [Installation](#installation)
14
- - [Basic Usage](#basic-usage)
15
- - [Rails 7.1](#rails-71)
16
- - [Mounting](#mounting)
17
- - [All](#all)
18
- - [Rack](#rack)
19
- - [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks)
20
- - [Rails](#rails)
21
- - [Zeitwerk](#zeitwerk)
22
- - [Modules](#modules)
23
- - [Remounting](#remounting)
24
- - [Mount Configuration](#mount-configuration)
25
- - [Versioning](#versioning)
26
- - [Strategies](#strategies)
27
- - [Path](#path)
28
- - [Header](#header)
29
- - [Accept-Version Header](#accept-version-header)
30
- - [Param](#param)
31
- - [Linting](#linting)
32
- - [Bug in Rack::ETag under Rack 3.X](#bug-in-racketag-under-rack-3x)
33
- - [Describing Methods](#describing-methods)
34
- - [Configuration](#configuration)
35
- - [Parameters](#parameters)
36
- - [Params Class](#params-class)
37
- - [Declared](#declared)
38
- - [Include Parent Namespaces](#include-parent-namespaces)
39
- - [Include Missing](#include-missing)
40
- - [Evaluate Given](#evaluate-given)
41
- - [Parameter Precedence](#parameter-precedence)
42
- - [Parameter Validation and Coercion](#parameter-validation-and-coercion)
43
- - [Supported Parameter Types](#supported-parameter-types)
44
- - [Integer/Fixnum and Coercions](#integerfixnum-and-coercions)
45
- - [Custom Types and Coercions](#custom-types-and-coercions)
46
- - [Multipart File Parameters](#multipart-file-parameters)
47
- - [First-Class JSON Types](#first-class-json-types)
48
- - [Multiple Allowed Types](#multiple-allowed-types)
49
- - [Validation of Nested Parameters](#validation-of-nested-parameters)
50
- - [Dependent Parameters](#dependent-parameters)
51
- - [Group Options](#group-options)
52
- - [Renaming](#renaming)
53
- - [Built-in Validators](#built-in-validators)
54
- - [allow_blank](#allow_blank)
55
- - [values](#values)
56
- - [except_values](#except_values)
57
- - [same_as](#same_as)
58
- - [length](#length)
59
- - [regexp](#regexp)
60
- - [mutually_exclusive](#mutually_exclusive)
61
- - [exactly_one_of](#exactly_one_of)
62
- - [at_least_one_of](#at_least_one_of)
63
- - [all_or_none_of](#all_or_none_of)
64
- - [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)
65
- - [Namespace Validation and Coercion](#namespace-validation-and-coercion)
66
- - [Custom Validators](#custom-validators)
67
- - [Validation Errors](#validation-errors)
68
- - [I18n](#i18n)
69
- - [Custom Validation messages](#custom-validation-messages)
70
- - [presence, allow_blank, values, regexp](#presence-allow_blank-values-regexp)
71
- - [same_as](#same_as-1)
72
- - [length](#length-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
- - [Using dry-validation or dry-schema](#using-dry-validation-or-dry-schema)
83
- - [Headers](#headers)
84
- - [Request](#request)
85
- - [Header Case Handling](#header-case-handling)
86
- - [Response](#response)
87
- - [Routes](#routes)
88
- - [Helpers](#helpers)
89
- - [Path Helpers](#path-helpers)
90
- - [Parameter Documentation](#parameter-documentation)
91
- - [Cookies](#cookies)
92
- - [HTTP Status Code](#http-status-code)
93
- - [Redirecting](#redirecting)
94
- - [Recognizing Path](#recognizing-path)
95
- - [Allowed Methods](#allowed-methods)
96
- - [Raising Exceptions](#raising-exceptions)
97
- - [Default Error HTTP Status Code](#default-error-http-status-code)
98
- - [Handling 404](#handling-404)
99
- - [Exception Handling](#exception-handling)
100
- - [Rescuing exceptions inside namespaces](#rescuing-exceptions-inside-namespaces)
101
- - [Unrescuable Exceptions](#unrescuable-exceptions)
102
- - [Exceptions that should be rescued explicitly](#exceptions-that-should-be-rescued-explicitly)
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 Auth](#basic-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
- - [Instance Variables](#instance-variables)
124
- - [Using Custom Middleware](#using-custom-middleware)
125
- - [Grape Middleware](#grape-middleware)
126
- - [Rails Middleware](#rails-middleware)
127
- - [Remote IP](#remote-ip)
128
- - [Writing Tests](#writing-tests)
129
- - [Writing Tests with Rack](#writing-tests-with-rack)
130
- - [RSpec](#rspec)
131
- - [Airborne](#airborne)
132
- - [MiniTest](#minitest)
133
- - [Writing Tests with Rails](#writing-tests-with-rails)
134
- - [RSpec](#rspec-1)
135
- - [MiniTest](#minitest-1)
136
- - [Stubbing Helpers](#stubbing-helpers)
137
- - [Reloading API Changes in Development](#reloading-api-changes-in-development)
138
- - [Reloading in Rack Applications](#reloading-in-rack-applications)
139
- - [Reloading in Rails Applications](#reloading-in-rails-applications)
140
- - [Performance Monitoring](#performance-monitoring)
141
- - [Active Support Instrumentation](#active-support-instrumentation)
142
- - [endpoint_run.grape](#endpoint_rungrape)
143
- - [endpoint_render.grape](#endpoint_rendergrape)
144
- - [endpoint_run_filters.grape](#endpoint_run_filtersgrape)
145
- - [endpoint_run_validators.grape](#endpoint_run_validatorsgrape)
146
- - [format_response.grape](#format_responsegrape)
147
- - [Monitoring Products](#monitoring-products)
148
- - [Contributing to Grape](#contributing-to-grape)
149
- - [Security](#security)
150
- - [License](#license)
151
- - [Copyright](#copyright)
152
-
153
7
  ## What is Grape?
154
8
 
155
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.
156
10
 
157
11
  ## Stable Release
158
12
 
159
- You're reading the documentation for the stable release of Grape, 2.4.0.
160
- Please read [UPGRADING](https://github.com/ruby-grape/grape/blob/v2.4.0/UPGRADING.md) when upgrading from a previous version.
13
+ You're reading the documentation for the stable release of Grape, 4.0.1.
161
14
 
162
15
  ## Project Resources
163
16
 
@@ -174,7 +27,7 @@ The maintainers of Grape are working with Tidelift to deliver commercial support
174
27
 
175
28
  ## Installation
176
29
 
177
- Ruby 2.7 or newer is required.
30
+ Ruby 3.3 or newer is required.
178
31
 
179
32
  Grape is available as a gem, to install it run:
180
33
 
@@ -458,7 +311,7 @@ mount ::Some::Api => '/some/api', with: { condition: true }
458
311
 
459
312
  You can access `configuration` on the class (to use as dynamic attributes), inside blocks (like namespace)
460
313
 
461
- If you want logic happening given on an `configuration`, you can use the helper `given`.
314
+ If you want logic happening based on a `configuration`, you can use the helper `given`.
462
315
 
463
316
  ```ruby
464
317
  class ConditionalEndpoint::API < Grape::API
@@ -609,7 +462,7 @@ vnd.vendor-and-or-resource-v1234+format
609
462
 
610
463
  Basically all tokens between the final `-` and the `+` will be interpreted as the version.
611
464
 
612
- Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.
465
+ Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` header.
613
466
 
614
467
  curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
615
468
 
@@ -631,7 +484,7 @@ Using this versioning strategy, clients should pass the desired version in the H
631
484
 
632
485
  curl -H "Accept-Version:v1" http://localhost:9292/statuses/public_timeline
633
486
 
634
- 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` 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.
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.
635
488
 
636
489
  #### Param
637
490
 
@@ -671,13 +524,13 @@ Grape.config.lint = true
671
524
  ```
672
525
 
673
526
  ### Bug in Rack::ETag under Rack 3.X
674
- 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.
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.
675
528
 
676
529
  ## Describing Methods
677
530
 
678
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.
679
532
 
680
- Note: Description block is only for documentation and won't affects API behavior.
533
+ Note: Description block is only for documentation and won't affect API behavior.
681
534
 
682
535
  ```ruby
683
536
  desc 'Returns your public timeline.' do
@@ -713,7 +566,7 @@ end
713
566
  * `params`: Define parameters directly from an `Entity`
714
567
  * `success`: (former entity) The `Entity` to be used to present the success response for this route.
715
568
  * `failure`: (former http_codes) A definition of the used failure HTTP Codes and Entities.
716
- * `default`: The definition and `Entity` used to present the default response for this route.
569
+ * `default_response`: (former `default`) The definition and `Entity` used to present the default response for this route.
717
570
  * `named`: A helper to give a route a name and find it with this name in the documentation Hash
718
571
  * `headers`: A definition of the used Headers
719
572
  * Other options can be found in [grape-swagger][grape-swagger]
@@ -726,6 +579,7 @@ Use `Grape.configure` to set up global settings at load time.
726
579
  Currently the configurable settings are:
727
580
 
728
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).
729
583
 
730
584
  To change a setting value make sure that at some point during load time the following code runs
731
585
 
@@ -766,7 +620,7 @@ get :public_timeline do
766
620
  end
767
621
  ```
768
622
 
769
- Parameters are automatically populated from the request body on `POST` and `PUT` for form input, JSON and 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.
770
624
 
771
625
  The request:
772
626
 
@@ -1329,28 +1183,6 @@ The following are all valid types, supported out of the box by Grape:
1329
1183
  * Rack::Multipart::UploadedFile (alias `File`)
1330
1184
  * JSON
1331
1185
 
1332
- ### Integer/Fixnum and Coercions
1333
-
1334
- Please be aware that the behavior differs between Ruby 2.4 and earlier versions.
1335
- In Ruby 2.4, values consisting of numbers are converted to Integer, but in earlier versions it will be treated as Fixnum.
1336
-
1337
- ```ruby
1338
- params do
1339
- requires :integers, type: Hash do
1340
- requires :int, coerce: Integer
1341
- end
1342
- end
1343
- get '/int' do
1344
- params[:integers][:int].class
1345
- end
1346
-
1347
- ...
1348
-
1349
- get '/int' integers: { int: '45' }
1350
- #=> Integer in ruby 2.4
1351
- #=> Fixnum in earlier ruby versions
1352
- ```
1353
-
1354
1186
  ### Custom Types and Coercions
1355
1187
 
1356
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.
@@ -1503,6 +1335,56 @@ end
1503
1335
  client.get('/', status_codes: %w(1 two)) # => [1, "two"]
1504
1336
  ```
1505
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
+
1506
1388
  ### Validation of Nested Parameters
1507
1389
 
1508
1390
  Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
@@ -1589,7 +1471,7 @@ params do
1589
1471
  end
1590
1472
  ```
1591
1473
 
1592
- 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.
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.
1593
1475
 
1594
1476
  ```ruby
1595
1477
  params do
@@ -1677,6 +1559,27 @@ params do
1677
1559
  end
1678
1560
  ```
1679
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
+
1680
1583
  The `:values` option can also be supplied with a `Proc`, evaluated lazily with each request.
1681
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.
1682
1585
 
@@ -1698,24 +1601,27 @@ end
1698
1601
 
1699
1602
  While Procs are convenient for single cases, consider using [Custom Validators](#custom-validators) in cases where a validation is used more than once.
1700
1603
 
1701
- Note that [allow_blank](#allow_blank) validator applies while using `:values`. In the following example the absence of `:allow_blank` does not prevent `:state` from receiving blank values because `:allow_blank` defaults to `true`.
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`:
1702
1605
 
1703
1606
  ```ruby
1704
1607
  params do
1705
- requires :state, type: Symbol, values: [:active, :inactive]
1608
+ optional :state, type: Symbol, values: [:active, :inactive], allow_blank: false
1706
1609
  end
1707
1610
  ```
1708
1611
 
1612
+ With `requires`, blank values are already rejected: `requires` enforces presence and `:values` rejects `nil`.
1613
+
1709
1614
  #### `except_values`
1710
1615
 
1711
1616
  Parameters can be restricted from having a specific set of values with the `:except_values` option.
1712
1617
 
1713
- The `except_values` validator behaves similarly to the `values` validator in that it accepts either an Array, a Range, or a Proc. Unlike the `values` validator, however, `except_values` only accepts 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.
1714
1619
 
1715
1620
  ```ruby
1716
1621
  params do
1717
1622
  requires :browser, except_values: [ 'ie6', 'ie7', 'ie8' ]
1718
1623
  requires :port, except_values: { value: 0..1024, message: 'is not allowed' }
1624
+ requires :negative, type: Integer, except_values: ..-1
1719
1625
  requires :hashtag, except_values: -> { Hashtag.FORBIDDEN_LIST }
1720
1626
  end
1721
1627
  ```
@@ -1920,9 +1826,9 @@ end
1920
1826
  ```ruby
1921
1827
  class AlphaNumeric < Grape::Validations::Validators::Base
1922
1828
  def validate_param!(attr_name, params)
1923
- unless params[attr_name] =~ /\A[[:alnum:]]+\z/
1924
- raise Grape::Exceptions::Validation.new params: [@scope.full_name(attr_name)], message: 'must consist of alpha-numeric characters'
1925
- end
1829
+ return if params[attr_name].match?(/\A[[:alnum:]]+\z/)
1830
+
1831
+ validation_error!(attr_name, 'must consist of alpha-numeric characters')
1926
1832
  end
1927
1833
  end
1928
1834
  ```
@@ -1938,9 +1844,9 @@ You can also create custom classes that take parameters.
1938
1844
  ```ruby
1939
1845
  class Length < Grape::Validations::Validators::Base
1940
1846
  def validate_param!(attr_name, params)
1941
- unless params[attr_name].length <= @option
1942
- raise Grape::Exceptions::Validation.new params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long"
1943
- end
1847
+ return if params[attr_name].length <= @options
1848
+
1849
+ validation_error!(attr_name, "must be at the most #{@options} characters long")
1944
1850
  end
1945
1851
  end
1946
1852
  ```
@@ -1962,10 +1868,10 @@ class Admin < Grape::Validations::Validators::Base
1962
1868
  # @attrs being [:admin_field] and once with @attrs being [:admin_false_field]
1963
1869
  return unless request.params.key?(@attrs.first)
1964
1870
  # check if admin flag is set to true
1965
- return unless @option
1871
+ return unless @options
1966
1872
  # check if user is admin or not
1967
1873
  # as an example get a token from request and check if it's admin or not
1968
- raise Grape::Exceptions::Validation.new params: @attrs, message: 'Can not set admin-only field.' unless request.headers['X-Access-Token'] == 'admin'
1874
+ validation_error!(@attrs, 'Can not set admin-only field.') unless request.headers['X-Access-Token'] == 'admin'
1969
1875
  end
1970
1876
  end
1971
1877
  ```
@@ -1980,7 +1886,50 @@ params do
1980
1886
  end
1981
1887
  ```
1982
1888
 
1983
- Every validation will have its own instance of the validator, which means that the validator can have a state.
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
+ ```
1984
1933
 
1985
1934
  ### Validation Errors
1986
1935
 
@@ -2023,8 +1972,8 @@ To skip all subsequent validation checks when a specific param is found invalid,
2023
1972
  The following example will not check if `:wine` is present unless it finds `:beer`.
2024
1973
  ```ruby
2025
1974
  params do
2026
- required :beer, fail_fast: true
2027
- required :wine
1975
+ requires :beer, fail_fast: true
1976
+ requires :wine
2028
1977
  end
2029
1978
  ```
2030
1979
  The result of empty params would be a single `Grape::Exceptions::ValidationErrors` error.
@@ -2032,7 +1981,7 @@ The result of empty params would be a single `Grape::Exceptions::ValidationError
2032
1981
  Similarly, no regular expression test will be performed if `:blah` is blank in the following example.
2033
1982
  ```ruby
2034
1983
  params do
2035
- required :blah, allow_blank: false, regexp: /blah/, fail_fast: true
1984
+ requires :blah, allow_blank: false, regexp: /blah/, fail_fast: true
2036
1985
  end
2037
1986
  ```
2038
1987
 
@@ -2042,6 +1991,28 @@ Grape supports I18n for parameter-related error messages, but will fallback to E
2042
1991
 
2043
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.
2044
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
+
2045
2016
  ### Custom Validation messages
2046
2017
 
2047
2018
  Grape supports custom validation messages for parameter-related and coerce-related error messages.
@@ -2330,6 +2301,78 @@ namespace :outer, requirements: { id: /[0-9]*/ } do
2330
2301
  end
2331
2302
  ```
2332
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
+
2333
2376
  ## Helpers
2334
2377
 
2335
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.
@@ -2679,7 +2722,7 @@ You can set additional headers for the response. They will be merged with header
2679
2722
  error!('Something went wrong', 500, 'X-Error-Detail' => 'Invalid token.')
2680
2723
  ```
2681
2724
 
2682
- You can present documented errors with a Grape entity using the the [grape-entity](https://github.com/ruby-grape/grape-entity) gem.
2725
+ You can present documented errors with a Grape entity using the [grape-entity](https://github.com/ruby-grape/grape-entity) gem.
2683
2726
 
2684
2727
  ```ruby
2685
2728
  module API
@@ -2766,6 +2809,19 @@ rescue_from :grape_exceptions do |e|
2766
2809
  end
2767
2810
  ```
2768
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
+
2769
2825
  You can also rescue specific exceptions.
2770
2826
 
2771
2827
  ```ruby
@@ -2776,6 +2832,29 @@ end
2776
2832
 
2777
2833
  In this case ```UserDefinedError``` must be inherited from ```StandardError```.
2778
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
+
2779
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.
2780
2859
 
2781
2860
  ```ruby
@@ -2790,12 +2869,12 @@ end
2790
2869
 
2791
2870
  The error format will match the request format. See "Content-Types" below.
2792
2871
 
2793
- 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:
2794
2873
 
2795
2874
  ```ruby
2796
2875
  class Twitter::API < Grape::API
2797
- error_formatter :txt, ->(message, backtrace, options, env, original_exception) {
2798
- "error: #{message} from #{backtrace}"
2876
+ error_formatter :txt, ->(error:, **) {
2877
+ "error #{error.status}: #{error.message} from #{error.backtrace}"
2799
2878
  }
2800
2879
  end
2801
2880
  ```
@@ -2804,8 +2883,8 @@ You can also use a module or class.
2804
2883
 
2805
2884
  ```ruby
2806
2885
  module CustomFormatter
2807
- def self.call(message, backtrace, options, env, original_exception)
2808
- { message: message, backtrace: backtrace }
2886
+ def self.call(error:, **)
2887
+ { status: error.status, message: error.message, backtrace: error.backtrace }
2809
2888
  end
2810
2889
  end
2811
2890
 
@@ -2835,6 +2914,43 @@ class Twitter::API < Grape::API
2835
2914
  end
2836
2915
  ```
2837
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
+
2838
2954
  You can also rescue all exceptions with a code block and handle the Rack response at the lowest level.
2839
2955
 
2840
2956
  ```ruby
@@ -2974,6 +3090,38 @@ Any exception that is not subclass of `StandardError` should be rescued explicit
2974
3090
  Usually it is not a case for an application logic as such errors point to problems in Ruby runtime.
2975
3091
  This is following [standard recommendations for exceptions handling](https://ruby-doc.org/core/Exception.html).
2976
3092
 
3093
+ #### When the error response itself cannot be rendered
3094
+
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:
3096
+
3097
+ ```ruby
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
3107
+ ```
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
+
3117
+ ```ruby
3118
+ Grape.configure do |config|
3119
+ config.raise_rendering_errors = true
3120
+ end
3121
+ ```
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
+
2977
3125
  ## Logging
2978
3126
 
2979
3127
  `Grape::API` provides a `logger` method which by default will return an instance of the `Logger` class from Ruby's standard library.
@@ -3182,6 +3330,41 @@ Built-in formatters are the following.
3182
3330
  * `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
3183
3331
  * `:binary`: data will be returned "as is"
3184
3332
 
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.
3352
+
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
+
3185
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.
3186
3369
 
3187
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.
@@ -3782,13 +3965,13 @@ class TwitterAPI < Grape::API
3782
3965
  @var = 1
3783
3966
  end
3784
3967
 
3785
- get '/' do
3968
+ rescue_from :all do
3786
3969
  puts @var # => 1
3787
- raise
3788
3970
  end
3789
3971
 
3790
- rescue_from :all do
3972
+ get '/' do
3791
3973
  puts @var # => 1
3974
+ raise
3792
3975
  end
3793
3976
  end
3794
3977
  ```
@@ -4049,7 +4232,15 @@ end
4049
4232
 
4050
4233
  ### Stubbing Helpers
4051
4234
 
4052
- 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. The `Grape::Endpoint.before_each` method can help by allowing you to define behavior on the endpoint that will run before every request.
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:
4053
4244
 
4054
4245
  ```ruby
4055
4246
  describe 'an endpoint that needs helpers stubbed' do
@@ -4060,7 +4251,7 @@ describe 'an endpoint that needs helpers stubbed' do
4060
4251
  end
4061
4252
 
4062
4253
  after do
4063
- Grape::Endpoint.before_each nil
4254
+ Grape::Endpoint.reset_before_each
4064
4255
  end
4065
4256
 
4066
4257
  it 'stubs the helper' do
@@ -4077,6 +4268,25 @@ Use [grape-reload](https://github.com/AlexYankee/grape-reload).
4077
4268
 
4078
4269
  ### Reloading in Rails Applications
4079
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
+
4080
4290
  Add API paths to `config/application.rb`.
4081
4291
 
4082
4292
  ```ruby
@@ -4095,28 +4305,12 @@ if Rails.env.development?
4095
4305
  api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
4096
4306
  Rails.application.reload_routes!
4097
4307
  end
4098
- ActionDispatch::Callbacks.to_prepare do
4308
+ ActiveSupport::Reloader.to_prepare do
4099
4309
  api_reloader.execute_if_updated
4100
4310
  end
4101
4311
  end
4102
4312
  ```
4103
4313
 
4104
- For Rails >= 5.1.4, change this:
4105
-
4106
- ```ruby
4107
- ActionDispatch::Callbacks.to_prepare do
4108
- api_reloader.execute_if_updated
4109
- end
4110
- ```
4111
-
4112
- to this:
4113
-
4114
- ```ruby
4115
- ActiveSupport::Reloader.to_prepare do
4116
- api_reloader.execute_if_updated
4117
- end
4118
- ```
4119
-
4120
4314
  See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-rails-3-reload-lib-directory-for-each-request/4368838#4368838) for more information.
4121
4315
 
4122
4316
  ## Performance Monitoring
@@ -4125,27 +4319,30 @@ See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-
4125
4319
 
4126
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.
4127
4321
 
4128
- The following are currently supported:
4129
4322
 
4130
- #### endpoint_run.grape
4323
+ #### Hook Points
4324
+
4325
+ The following hook points are currently supported:
4326
+
4327
+ ##### endpoint_run.grape
4131
4328
 
4132
4329
  The main execution of an endpoint, includes filters and rendering.
4133
4330
 
4134
4331
  * *endpoint* - The endpoint instance
4135
4332
 
4136
- #### endpoint_render.grape
4333
+ ##### endpoint_render.grape
4137
4334
 
4138
4335
  The execution of the main content block of the endpoint.
4139
4336
 
4140
4337
  * *endpoint* - The endpoint instance
4141
4338
 
4142
- #### endpoint_run_filters.grape
4339
+ ##### endpoint_run_filters.grape
4143
4340
 
4144
4341
  * *endpoint* - The endpoint instance
4145
4342
  * *filters* - The filters being executed
4146
4343
  * *type* - The type of filters (before, before_validation, after_validation, after)
4147
4344
 
4148
- #### endpoint_run_validators.grape
4345
+ ##### endpoint_run_validators.grape
4149
4346
 
4150
4347
  The execution of validators.
4151
4348
 
@@ -4153,7 +4350,7 @@ The execution of validators.
4153
4350
  * *validators* - The validators being executed
4154
4351
  * *request* - The request being validated
4155
4352
 
4156
- #### format_response.grape
4353
+ ##### format_response.grape
4157
4354
 
4158
4355
  Serialization or template rendering.
4159
4356
 
@@ -4162,12 +4359,44 @@ Serialization or template rendering.
4162
4359
 
4163
4360
  See the [ActiveSupport::Notifications documentation](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) for information on how to subscribe to these events.
4164
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
+
4165
4393
  ### Monitoring Products
4166
4394
 
4167
4395
  Grape integrates with following third-party tools:
4168
4396
 
4169
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
4170
4398
  * **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem
4399
+ * **Rails Performance** - [rails_performance](https://github.com/igorkasyanchuk/rails_performance) gem
4171
4400
  * **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
4172
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)
4173
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)