grape 3.1.1 → 3.2.0
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 +21 -1
- data/README.md +76 -161
- data/UPGRADING.md +106 -0
- data/grape.gemspec +2 -2
- data/lib/grape/api/instance.rb +1 -1
- data/lib/grape/api.rb +1 -1
- data/lib/grape/declared_params_handler.rb +3 -3
- data/lib/grape/dsl/declared.rb +1 -1
- data/lib/grape/dsl/desc.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +9 -9
- data/lib/grape/dsl/parameters.rb +14 -14
- data/lib/grape/dsl/routing.rb +8 -8
- data/lib/grape/endpoint.rb +42 -49
- data/lib/grape/error_formatter/base.rb +2 -2
- data/lib/grape/exceptions/base.rb +18 -44
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- 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_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/validation.rb +7 -4
- data/lib/grape/exceptions/validation_errors.rb +13 -7
- data/lib/grape/locale/en.yml +0 -5
- data/lib/grape/middleware/auth/base.rb +2 -0
- data/lib/grape/middleware/base.rb +2 -4
- data/lib/grape/middleware/error.rb +2 -2
- data/lib/grape/middleware/formatter.rb +1 -1
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
- data/lib/grape/request.rb +2 -10
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router.rb +4 -2
- data/lib/grape/util/api_description.rb +1 -1
- data/lib/grape/util/deep_freeze.rb +35 -0
- data/lib/grape/util/inheritable_setting.rb +1 -1
- data/lib/grape/util/media_type.rb +1 -1
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +33 -18
- data/lib/grape/validations/contract_scope.rb +1 -7
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/param_scope_tracker.rb +57 -0
- data/lib/grape/validations/params_scope.rb +111 -107
- data/lib/grape/validations/single_attribute_iterator.rb +2 -2
- 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 +95 -18
- data/lib/grape/validations/validators/coerce_validator.rb +15 -35
- data/lib/grape/validations/validators/contract_scope_validator.rb +10 -8
- data/lib/grape/validations/validators/default_validator.rb +12 -18
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +13 -4
- data/lib/grape/validations/validators/length_validator.rb +21 -22
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
- data/lib/grape/validations/validators/mutually_exclusive_validator.rb +3 -1
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +8 -10
- data/lib/grape/validations/validators/same_as_validator.rb +6 -15
- data/lib/grape/validations/validators/values_validator.rb +29 -21
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +18 -1
- metadata +11 -13
- data/lib/grape/exceptions/conflicting_types.rb +0 -11
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/invalid_parameters.rb +0 -11
- data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/validations/validator_factory.rb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68c3425fcc271ae7241f0531bbd9cb6c466aca9ccfed37dc37c0a6f83a4500d2
|
|
4
|
+
data.tar.gz: d98863b6374c5e25bb95a101c891c0c97b4f3746c9be0c6d7cc4b5b03f1f30fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '059861dafdf89f21d71b23c09e8f5f618ebd42092af31600a3943e0ba9695abd4ee01e90f3cae97aedb7ae777c96e22e3963cab3d4527fcbf16b82ef19939a99'
|
|
7
|
+
data.tar.gz: feb02e6b1db04867d06b62d2096931c762028f7b5bf8776f13bf4a5902f4b70b538f016a7f5ada98ffa7fd3f1e22e16054fc229f33fcabf8cf3d8486a956d4c7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
|
-
### 3.
|
|
1
|
+
### 3.2.0 (Next)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* [#2662](https://github.com/ruby-grape/grape/pull/2662): Extract `Grape::Util::Translation` for shared I18n fallback logic - [@ericproulx](https://github.com/ericproulx).
|
|
6
|
+
* [#2656](https://github.com/ruby-grape/grape/pull/2656): Remove useless instance_variable_defined? checks - [@ericproulx](https://github.com/ericproulx).
|
|
7
|
+
* [#2619](https://github.com/ruby-grape/grape/pull/2619): Remove TOC from README.md and danger-toc check - [@alexanderadam](https://github.com/alexanderadam).
|
|
8
|
+
* [#2663](https://github.com/ruby-grape/grape/pull/2663): Refactor `ParamsScope` and `Parameters` DSL to use named kwargs - [@ericproulx](https://github.com/ericproulx).
|
|
9
|
+
* [#2664](https://github.com/ruby-grape/grape/pull/2664): Drop `test-prof` dependency - [@ericproulx](https://github.com/ericproulx).
|
|
10
|
+
* [#2665](https://github.com/ruby-grape/grape/pull/2665): Pass `attrs` directly to `AttributesIterator` instead of `validator` - [@ericproulx](https://github.com/ericproulx).
|
|
11
|
+
* [#2657](https://github.com/ruby-grape/grape/pull/2657): Instantiate validators at definition time - [@ericproulx](https://github.com/ericproulx).
|
|
12
|
+
* [#2667](https://github.com/ruby-grape/grape/pull/2667): Skip instrumentation in run_validators when no validators present - [@ericproulx](https://github.com/ericproulx).
|
|
13
|
+
* [#2670](https://github.com/ruby-grape/grape/pull/2670): Added support for Rack 3.2.6 and better handling to rack exceptions - [@ericproulx](https://github.com/ericproulx).
|
|
14
|
+
* [#2671](https://github.com/ruby-grape/grape/pull/2671): Use ruby 3.1 shorthand kwargs syntax - [@ericproulx](https://github.com/ericproulx).
|
|
15
|
+
* [#2672](https://github.com/ruby-grape/grape/pull/2672): Minor ruby optimizations - [@ericproulx](https://github.com/ericproulx).
|
|
16
|
+
* [#2675](https://github.com/ruby-grape/grape/pull/2675): Add `AGENTS.md` to please our future A.I. overlords - [@dblock](https://github.com/dblock).
|
|
2
17
|
|
|
3
18
|
#### Fixes
|
|
4
19
|
|
|
20
|
+
* [#2670](https://github.com/ruby-grape/grape/pull/2670): Fix `UnknownAuthStrategy` raised when custom auth strategy class inherits from `Grape::Middleware::Auth::Base` - [@dblock](https://github.com/dblock).
|
|
5
21
|
* [#2655](https://github.com/ruby-grape/grape/pull/2655): Fix `before_each` method to handle `nil` parameter correctly - [@ericproulx](https://github.com/ericproulx).
|
|
22
|
+
* [#2660](https://github.com/ruby-grape/grape/pull/2660): Fix thread safety: move mutable `ParamsScope` state (`index`, `params_meeting_dependency`) into a per-request `ParamScopeTracker` stored in `Fiber[]` - [@ericproulx](https://github.com/ericproulx).
|
|
23
|
+
* [#2666](https://github.com/ruby-grape/grape/pull/2666): Endpoint cleanup and minor optimizations - [@ericproulx](https://github.com/ericproulx).
|
|
24
|
+
* [#2676](https://github.com/ruby-grape/grape/pull/2676): Exclude ruby 3.2 for rails_edge - [@ericproulx](https://github.com/ericproulx).
|
|
25
|
+
* [#2677](https://github.com/ruby-grape/grape/pull/2677): Update actions/checkout to v6 - [@ericproulx](https://github.com/ericproulx).
|
|
6
26
|
|
|
7
27
|
### 3.1.0 (2026-01-25)
|
|
8
28
|
|
data/README.md
CHANGED
|
@@ -4,163 +4,13 @@
|
|
|
4
4
|
[](https://github.com/ruby-grape/grape/actions/workflows/test.yml)
|
|
5
5
|
[](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
|
-
- [Rails 7+ (Zeitwerk)](#rails-7-zeitwerk)
|
|
141
|
-
- [Rails 6 and Earlier](#rails-6-and-earlier)
|
|
142
|
-
- [Performance Monitoring](#performance-monitoring)
|
|
143
|
-
- [Active Support Instrumentation](#active-support-instrumentation)
|
|
144
|
-
- [Hook Points](#hook-points)
|
|
145
|
-
- [endpoint_run.grape](#endpoint_rungrape)
|
|
146
|
-
- [endpoint_render.grape](#endpoint_rendergrape)
|
|
147
|
-
- [endpoint_run_filters.grape](#endpoint_run_filtersgrape)
|
|
148
|
-
- [endpoint_run_validators.grape](#endpoint_run_validatorsgrape)
|
|
149
|
-
- [format_response.grape](#format_responsegrape)
|
|
150
|
-
- [Subscribe to Hooks](#subscribe-to-hooks)
|
|
151
|
-
- [Monitoring Products](#monitoring-products)
|
|
152
|
-
- [Contributing to Grape](#contributing-to-grape)
|
|
153
|
-
- [Security](#security)
|
|
154
|
-
- [License](#license)
|
|
155
|
-
- [Copyright](#copyright)
|
|
156
|
-
|
|
157
7
|
## What is Grape?
|
|
158
8
|
|
|
159
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.
|
|
160
10
|
|
|
161
11
|
## Stable Release
|
|
162
12
|
|
|
163
|
-
You're reading the documentation for the stable release of Grape, 3.
|
|
13
|
+
You're reading the documentation for the stable release of Grape, 3.2.0.
|
|
164
14
|
|
|
165
15
|
## Project Resources
|
|
166
16
|
|
|
@@ -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 3.
|
|
30
|
+
Ruby 3.2 or newer is required.
|
|
181
31
|
|
|
182
32
|
Grape is available as a gem, to install it run:
|
|
183
33
|
|
|
@@ -1923,9 +1773,9 @@ end
|
|
|
1923
1773
|
```ruby
|
|
1924
1774
|
class AlphaNumeric < Grape::Validations::Validators::Base
|
|
1925
1775
|
def validate_param!(attr_name, params)
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1776
|
+
return if params[attr_name].match?(/\A[[:alnum:]]+\z/)
|
|
1777
|
+
|
|
1778
|
+
validation_error!(attr_name, 'must consist of alpha-numeric characters')
|
|
1929
1779
|
end
|
|
1930
1780
|
end
|
|
1931
1781
|
```
|
|
@@ -1941,9 +1791,9 @@ You can also create custom classes that take parameters.
|
|
|
1941
1791
|
```ruby
|
|
1942
1792
|
class Length < Grape::Validations::Validators::Base
|
|
1943
1793
|
def validate_param!(attr_name, params)
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1794
|
+
return if params[attr_name].length <= @options
|
|
1795
|
+
|
|
1796
|
+
validation_error!(attr_name, "must be at the most #{@options} characters long")
|
|
1947
1797
|
end
|
|
1948
1798
|
end
|
|
1949
1799
|
```
|
|
@@ -1965,10 +1815,10 @@ class Admin < Grape::Validations::Validators::Base
|
|
|
1965
1815
|
# @attrs being [:admin_field] and once with @attrs being [:admin_false_field]
|
|
1966
1816
|
return unless request.params.key?(@attrs.first)
|
|
1967
1817
|
# check if admin flag is set to true
|
|
1968
|
-
return unless @
|
|
1818
|
+
return unless @options
|
|
1969
1819
|
# check if user is admin or not
|
|
1970
1820
|
# as an example get a token from request and check if it's admin or not
|
|
1971
|
-
|
|
1821
|
+
validation_error!(@attrs, 'Can not set admin-only field.') unless request.headers['X-Access-Token'] == 'admin'
|
|
1972
1822
|
end
|
|
1973
1823
|
end
|
|
1974
1824
|
```
|
|
@@ -1983,7 +1833,50 @@ params do
|
|
|
1983
1833
|
end
|
|
1984
1834
|
```
|
|
1985
1835
|
|
|
1986
|
-
|
|
1836
|
+
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`.
|
|
1837
|
+
|
|
1838
|
+
#### Available helpers
|
|
1839
|
+
|
|
1840
|
+
The following protected/private helpers are available in any `Grape::Validations::Validators::Base` subclass:
|
|
1841
|
+
|
|
1842
|
+
| Helper | Description |
|
|
1843
|
+
|---|---|
|
|
1844
|
+
| `default_message_key(key)` | Class-level macro. Declares the default I18n key for `validation_error!`. A per-option `:message` override still takes precedence. |
|
|
1845
|
+
| `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. |
|
|
1846
|
+
| `@options` | The validator option value, deep-frozen at initialization. |
|
|
1847
|
+
| `@attrs` | Frozen array of attribute names this validator applies to. |
|
|
1848
|
+
| `@scope` | The `ParamsScope` — use `@scope.full_name(attr_name)` for the fully-qualified param name. |
|
|
1849
|
+
| `option_value` | Returns `@options[:value]` if present, otherwise `@options`. |
|
|
1850
|
+
| `options_key?(key)` | Returns true if `@options` is a hash with a non-nil `key`. |
|
|
1851
|
+
| `hash_like?(obj)` | Returns true if `obj` responds to `key?`. |
|
|
1852
|
+
| `scrub(value)` | Returns `value` with invalid byte sequences scrubbed. |
|
|
1853
|
+
| `translate(key, **opts)` | I18n lookup with `:en` fallback and `grape.errors.messages` scope. Called at request time to respect per-request locale. |
|
|
1854
|
+
|
|
1855
|
+
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:
|
|
1856
|
+
|
|
1857
|
+
```ruby
|
|
1858
|
+
class SpecialValidator < Grape::Validations::Validators::Base
|
|
1859
|
+
default_message_key :special
|
|
1860
|
+
|
|
1861
|
+
def validate_param!(attr_name, params)
|
|
1862
|
+
return if valid?(params[attr_name])
|
|
1863
|
+
|
|
1864
|
+
validation_error!(attr_name)
|
|
1865
|
+
end
|
|
1866
|
+
end
|
|
1867
|
+
```
|
|
1868
|
+
|
|
1869
|
+
For interpolated messages that must respect per-request locale, call `translate` directly inside `validate_param!`:
|
|
1870
|
+
|
|
1871
|
+
```ruby
|
|
1872
|
+
class SpecialValidator < Grape::Validations::Validators::Base
|
|
1873
|
+
def validate_param!(attr_name, params)
|
|
1874
|
+
return if valid?(params[attr_name])
|
|
1875
|
+
|
|
1876
|
+
validation_error!(attr_name, translate(:special, min: 2, max: 10))
|
|
1877
|
+
end
|
|
1878
|
+
end
|
|
1879
|
+
```
|
|
1987
1880
|
|
|
1988
1881
|
### Validation Errors
|
|
1989
1882
|
|
|
@@ -2045,6 +1938,28 @@ Grape supports I18n for parameter-related error messages, but will fallback to E
|
|
|
2045
1938
|
|
|
2046
1939
|
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.
|
|
2047
1940
|
|
|
1941
|
+
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`:
|
|
1942
|
+
|
|
1943
|
+
```ruby
|
|
1944
|
+
# Good — scope defaults to 'grape.errors.messages', interpolation forwarded automatically
|
|
1945
|
+
translate(:special, min: 2, max: 10)
|
|
1946
|
+
|
|
1947
|
+
# Bad — format is unnecessary and risks conflicting with I18n reserved keys
|
|
1948
|
+
format I18n.t(:special, scope: 'grape.errors.messages'), min: 2, max: 10
|
|
1949
|
+
```
|
|
1950
|
+
|
|
1951
|
+
Example custom validator using an interpolated i18n message:
|
|
1952
|
+
|
|
1953
|
+
```ruby
|
|
1954
|
+
class SpecialValidator < Grape::Validations::Validators::Base
|
|
1955
|
+
def validate_param!(attr_name, params)
|
|
1956
|
+
return if valid?(params[attr_name])
|
|
1957
|
+
|
|
1958
|
+
validation_error!(attr_name, translate(:special, min: 2, max: 10))
|
|
1959
|
+
end
|
|
1960
|
+
end
|
|
1961
|
+
```
|
|
1962
|
+
|
|
2048
1963
|
### Custom Validation messages
|
|
2049
1964
|
|
|
2050
1965
|
Grape supports custom validation messages for parameter-related and coerce-related error messages.
|
data/UPGRADING.md
CHANGED
|
@@ -1,6 +1,112 @@
|
|
|
1
1
|
Upgrading Grape
|
|
2
2
|
===============
|
|
3
3
|
|
|
4
|
+
### Upgrading to >= 3.2
|
|
5
|
+
|
|
6
|
+
#### Rack parameter parsing errors now raise `Grape::Exceptions::RequestError`
|
|
7
|
+
|
|
8
|
+
Rack errors raised during parameter parsing (malformed multipart, parameter type conflicts, encoding issues, etc.) are now wrapped in `Grape::Exceptions::RequestError` instead of their previous specific exception classes (`Grape::Exceptions::EmptyMessageBody`, `Grape::Exceptions::TooManyMultipartFiles`, `Grape::Exceptions::TooDeepParameters`, `Grape::Exceptions::ConflictingTypes`, `Grape::Exceptions::InvalidParameters`). Those classes have been removed.
|
|
9
|
+
|
|
10
|
+
If you rescue any of these specific exceptions, update your rescue clauses to use `Grape::Exceptions::RequestError`:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# Before
|
|
14
|
+
rescue Grape::Exceptions::ConflictingTypes, Grape::Exceptions::TooDeepParameters => e
|
|
15
|
+
# ...
|
|
16
|
+
|
|
17
|
+
# After
|
|
18
|
+
rescue Grape::Exceptions::RequestError => e
|
|
19
|
+
# ...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The error message is now forwarded directly from Rack rather than translated through Grape's locale system. On Rack 3, all Rack bad-request errors share the `Rack::BadRequest` marker module and are covered by a single rescue.
|
|
23
|
+
|
|
24
|
+
#### `endpoint_run_validators.grape` notification no longer fired when there are no validators
|
|
25
|
+
|
|
26
|
+
`ActiveSupport::Notifications` subscribers listening to `endpoint_run_validators.grape` will no longer receive an event for endpoints that have no validators. If you rely on this notification to measure every request, subscribe to `endpoint_run.grape` instead, which always fires.
|
|
27
|
+
|
|
28
|
+
#### Custom validators: use `default_message_key` and `validation_error!`
|
|
29
|
+
|
|
30
|
+
Validators are now instantiated once at definition time and frozen. Any setup should happen in `initialize`, not in `validate_param!`.
|
|
31
|
+
|
|
32
|
+
If your custom validator did work in `validate_param!` that only depends on the validator's options (not the param value), move it to `initialize`. A common case is compiling a value derived from options — for example, building a `Regexp`. Previously this may have been cached back into `@options`, which now raises `FrozenError` since `@options` and its nested values are deep-frozen by the base class:
|
|
33
|
+
|
|
34
|
+
**Before:**
|
|
35
|
+
```ruby
|
|
36
|
+
class MyValidator < Grape::Validations::Validators::Base
|
|
37
|
+
def validate_param!(attr_name, params)
|
|
38
|
+
# raises FrozenError: @options is frozen, cannot store compiled pattern back into it
|
|
39
|
+
@options[:compiled] ||= Regexp.new(@options[:pattern])
|
|
40
|
+
validation_error!(attr_name) unless params[attr_name].match?(@options[:compiled])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**After:**
|
|
46
|
+
```ruby
|
|
47
|
+
class MyValidator < Grape::Validations::Validators::Base
|
|
48
|
+
def initialize(attrs, options, required, scope, opts)
|
|
49
|
+
super
|
|
50
|
+
@pattern = Regexp.new(@options[:pattern]).freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate_param!(attr_name, params)
|
|
54
|
+
validation_error!(attr_name) unless params[attr_name].match?(@pattern)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Any Array or Hash derived from options and stored in an ivar should be frozen, since the validator instance is shared across requests. `@options` itself (and any nested Hash/Array/String values within it) is deep-frozen by the base class, so mutations like `@options[:values] << 'extra'` will also raise a `FrozenError`.
|
|
60
|
+
|
|
61
|
+
#### Custom validators: rename `@option` to `@options`
|
|
62
|
+
|
|
63
|
+
The instance variable holding the validator's option value has been renamed from `@option` to `@options`. `@option` remains as an alias for backwards compatibility but will be removed in the next major release. Update any custom validators to use `@options` instead.
|
|
64
|
+
|
|
65
|
+
Several new helpers are available — see [Available helpers](README.md#available-helpers) in the README for full documentation and examples.
|
|
66
|
+
|
|
67
|
+
#### `with` now uses keyword arguments
|
|
68
|
+
|
|
69
|
+
The `with` DSL method now uses `**opts` instead of a positional hash. Calls using bare keyword syntax are unaffected:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# still works
|
|
73
|
+
with(type: String, documentation: { in: 'body' }) { ... }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
However, passing an explicit hash literal will now raise an `ArgumentError`:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# raises ArgumentError
|
|
80
|
+
with({ type: String }) { ... }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
See [#2663](https://github.com/ruby-grape/grape/pull/2663) for more information.
|
|
84
|
+
|
|
85
|
+
#### Custom validators: use `translate` instead of `I18n` directly
|
|
86
|
+
|
|
87
|
+
`Grape::Util::Translation` is now included in `Grape::Validations::Validators::Base`. Custom validators that previously called `I18n.t` or `I18n.translate` directly should switch to the `translate`, which provides the same `:en` fallback logic used by all built-in validators.
|
|
88
|
+
|
|
89
|
+
Key points:
|
|
90
|
+
- `scope` defaults to `'grape.errors.messages'` — no need to specify it for standard error message keys.
|
|
91
|
+
- Interpolation variables are passed directly to I18n.
|
|
92
|
+
- `format` is no longer needed — `translate` returns the fully interpolated string.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# Before
|
|
96
|
+
raise Grape::Exceptions::Validation.new(
|
|
97
|
+
params: [@scope.full_name(attr_name)],
|
|
98
|
+
message: format(I18n.t(:my_key, scope: 'grape.errors.messages'), min: 2, max: 10)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# After
|
|
102
|
+
raise Grape::Exceptions::Validation.new(
|
|
103
|
+
params: [@scope.full_name(attr_name)],
|
|
104
|
+
message: translate(:my_key, min: 2, max: 10)
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See [#2662](https://github.com/ruby-grape/grape/pull/2662) for more information.
|
|
109
|
+
|
|
4
110
|
### Upgrading to >= 3.1
|
|
5
111
|
|
|
6
112
|
#### Explicit kwargs for `namespace` and `route_param`
|
data/grape.gemspec
CHANGED
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
|
20
20
|
'rubygems_mfa_required' => 'true'
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
s.add_dependency 'activesupport', '>= 7.
|
|
23
|
+
s.add_dependency 'activesupport', '>= 7.2'
|
|
24
24
|
s.add_dependency 'dry-configurable'
|
|
25
25
|
s.add_dependency 'dry-types', '>= 1.1'
|
|
26
26
|
s.add_dependency 'mustermann-grape', '~> 1.1.0'
|
|
@@ -29,5 +29,5 @@ Gem::Specification.new do |s|
|
|
|
29
29
|
|
|
30
30
|
s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec']
|
|
31
31
|
s.require_paths = ['lib']
|
|
32
|
-
s.required_ruby_version = '>= 3.
|
|
32
|
+
s.required_ruby_version = '>= 3.2'
|
|
33
33
|
end
|
data/lib/grape/api/instance.rb
CHANGED
|
@@ -170,7 +170,7 @@ module Grape
|
|
|
170
170
|
allow_header = namespace_inheritable[:do_not_route_options] ? allowed_methods : [Rack::OPTIONS] | allowed_methods
|
|
171
171
|
last_route.app.options[:options_route_enabled] = true unless namespace_inheritable[:do_not_route_options] || allowed_methods.include?(Rack::OPTIONS)
|
|
172
172
|
|
|
173
|
-
greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header:
|
|
173
|
+
greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header:)
|
|
174
174
|
@router.associate_routes(greedy_route)
|
|
175
175
|
end
|
|
176
176
|
end
|
data/lib/grape/api.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Grape
|
|
|
44
44
|
def override_all_methods!
|
|
45
45
|
(base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
|
|
46
46
|
define_singleton_method(method_override) do |*args, **kwargs, &block|
|
|
47
|
-
add_setup(method: method_override, args
|
|
47
|
+
add_setup(method: method_override, args:, kwargs:, block:)
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -12,9 +12,9 @@ module Grape
|
|
|
12
12
|
def call(passed_params, declared_params, route_params, renamed_params)
|
|
13
13
|
recursive_declared(
|
|
14
14
|
passed_params,
|
|
15
|
-
declared_params
|
|
16
|
-
route_params
|
|
17
|
-
renamed_params:
|
|
15
|
+
declared_params:,
|
|
16
|
+
route_params:,
|
|
17
|
+
renamed_params:
|
|
18
18
|
)
|
|
19
19
|
end
|
|
20
20
|
|
data/lib/grape/dsl/declared.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Grape
|
|
|
23
23
|
raise MethodNotYetAvailable unless before_filter_passed
|
|
24
24
|
|
|
25
25
|
contract_key_map = inheritable_setting.namespace_stackable[:contract_key_map]
|
|
26
|
-
handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map:
|
|
26
|
+
handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map:)
|
|
27
27
|
declared_params = include_parent_namespaces ? inheritable_setting.route[:declared_params] : (inheritable_setting.namespace_stackable[:declared_params].last || [])
|
|
28
28
|
renamed_params = inheritable_setting.route[:renamed_params] || {}
|
|
29
29
|
route_params = options.dig(:route_options, :params) || {} # options = endpoint's option
|
data/lib/grape/dsl/desc.rb
CHANGED
|
@@ -55,7 +55,7 @@ module Grape
|
|
|
55
55
|
endpoint_config = defined?(configuration) ? configuration : nil
|
|
56
56
|
Grape::Util::ApiDescription.new(description, endpoint_config, &config_block).settings
|
|
57
57
|
else
|
|
58
|
-
options.merge(description:
|
|
58
|
+
options.merge(description:)
|
|
59
59
|
end
|
|
60
60
|
inheritable_setting.namespace[:description] = settings
|
|
61
61
|
inheritable_setting.route[:description] = settings
|
|
@@ -29,11 +29,11 @@ module Grape
|
|
|
29
29
|
status = self.status(status || inheritable_setting.namespace_inheritable[:default_error_status])
|
|
30
30
|
headers = additional_headers.present? ? header.merge(additional_headers) : header
|
|
31
31
|
throw :error,
|
|
32
|
-
message
|
|
33
|
-
status
|
|
34
|
-
headers
|
|
35
|
-
backtrace
|
|
36
|
-
original_exception:
|
|
32
|
+
message:,
|
|
33
|
+
status:,
|
|
34
|
+
headers:,
|
|
35
|
+
backtrace:,
|
|
36
|
+
original_exception:
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Redirect to a new url.
|
|
@@ -70,12 +70,12 @@ module Grape
|
|
|
70
70
|
when Integer
|
|
71
71
|
@status = status
|
|
72
72
|
when nil
|
|
73
|
-
return @status if
|
|
73
|
+
return @status if @status
|
|
74
74
|
|
|
75
75
|
if request.post?
|
|
76
76
|
201
|
|
77
77
|
elsif request.delete?
|
|
78
|
-
if
|
|
78
|
+
if @body.present?
|
|
79
79
|
200
|
|
80
80
|
else
|
|
81
81
|
204
|
|
@@ -114,7 +114,7 @@ module Grape
|
|
|
114
114
|
@body = ''
|
|
115
115
|
status 204
|
|
116
116
|
else
|
|
117
|
-
|
|
117
|
+
@body
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
@@ -272,7 +272,7 @@ module Grape
|
|
|
272
272
|
# @return the representation of the given object as done through
|
|
273
273
|
# the given entity_class.
|
|
274
274
|
def entity_representation_for(entity_class, object, options)
|
|
275
|
-
embeds = { env:
|
|
275
|
+
embeds = { env: }
|
|
276
276
|
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
|
277
277
|
entity_class.represent(object, **embeds, **options)
|
|
278
278
|
end
|
data/lib/grape/dsl/parameters.rb
CHANGED
|
@@ -124,13 +124,13 @@ module Grape
|
|
|
124
124
|
# end
|
|
125
125
|
def requires(*attrs, **opts, &block)
|
|
126
126
|
opts[:presence] = { value: true, message: opts[:message] }
|
|
127
|
-
opts = @group.deep_merge(opts) if
|
|
127
|
+
opts = @group.deep_merge(opts) if @group
|
|
128
128
|
|
|
129
129
|
if opts[:using]
|
|
130
|
-
require_required_and_optional_fields(attrs.first, opts)
|
|
130
|
+
require_required_and_optional_fields(attrs.first, using: opts[:using], except: opts[:except])
|
|
131
131
|
else
|
|
132
|
-
validate_attributes(attrs, opts, &block)
|
|
133
|
-
block ? new_scope(attrs, opts, &block) : push_declared_params(attrs, opts
|
|
132
|
+
validate_attributes(attrs, **opts, &block)
|
|
133
|
+
block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], &block) : push_declared_params(attrs, as: opts[:as])
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
|
|
@@ -140,7 +140,7 @@ module Grape
|
|
|
140
140
|
# @option (see #requires)
|
|
141
141
|
def optional(*attrs, **opts, &block)
|
|
142
142
|
type = opts[:type]
|
|
143
|
-
opts = @group.deep_merge(opts) if
|
|
143
|
+
opts = @group.deep_merge(opts) if @group
|
|
144
144
|
|
|
145
145
|
# check type for optional parameter group
|
|
146
146
|
if attrs && block
|
|
@@ -149,25 +149,25 @@ module Grape
|
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
if opts[:using]
|
|
152
|
-
require_optional_fields(attrs.first, opts)
|
|
152
|
+
require_optional_fields(attrs.first, using: opts[:using], except: opts[:except])
|
|
153
153
|
else
|
|
154
|
-
validate_attributes(attrs, opts, &block)
|
|
154
|
+
validate_attributes(attrs, **opts, &block)
|
|
155
155
|
|
|
156
|
-
block ? new_scope(attrs, opts, true, &block) : push_declared_params(attrs, opts
|
|
156
|
+
block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], optional: true, &block) : push_declared_params(attrs, as: opts[:as])
|
|
157
157
|
end
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
# Define common settings for one or more parameters
|
|
161
161
|
# @param (see #requires)
|
|
162
162
|
# @option (see #requires)
|
|
163
|
-
def with(
|
|
164
|
-
new_group_attrs =
|
|
165
|
-
new_group_scope(
|
|
163
|
+
def with(**opts, &)
|
|
164
|
+
new_group_attrs = @group&.deep_merge(opts) || opts
|
|
165
|
+
new_group_scope(new_group_attrs, &)
|
|
166
166
|
end
|
|
167
167
|
|
|
168
168
|
%i[mutually_exclusive exactly_one_of at_least_one_of all_or_none_of].each do |validator|
|
|
169
169
|
define_method validator do |*attrs, message: nil|
|
|
170
|
-
validates(attrs, validator => { value: true, message:
|
|
170
|
+
validates(attrs, validator => { value: true, message: })
|
|
171
171
|
end
|
|
172
172
|
end
|
|
173
173
|
|
|
@@ -224,8 +224,8 @@ module Grape
|
|
|
224
224
|
# @return hash of parameters relevant for the current scope
|
|
225
225
|
# @api private
|
|
226
226
|
def params(params)
|
|
227
|
-
params = @parent.
|
|
228
|
-
params = map_params(params, @element) if
|
|
227
|
+
params = @parent.qualifying_params.presence || @parent.params(params) if @parent
|
|
228
|
+
params = map_params(params, @element) if @element
|
|
229
229
|
params
|
|
230
230
|
end
|
|
231
231
|
|