grape 3.3.4 → 4.0.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 +128 -0
- data/CONTRIBUTING.md +16 -0
- data/README.md +208 -8
- data/UPGRADING.md +756 -6
- data/lib/grape/api/instance.rb +40 -33
- data/lib/grape/content_types.rb +47 -1
- data/lib/grape/dry_types.rb +1 -1
- data/lib/grape/dsl/callbacks.rb +3 -9
- data/lib/grape/dsl/declared.rb +4 -4
- data/lib/grape/dsl/desc.rb +18 -8
- data/lib/grape/dsl/entity.rb +37 -19
- data/lib/grape/dsl/helpers.rb +3 -3
- data/lib/grape/dsl/inside_route.rb +20 -8
- data/lib/grape/dsl/middleware.rb +3 -3
- data/lib/grape/dsl/parameters.rb +54 -28
- data/lib/grape/dsl/request_response.rb +37 -39
- data/lib/grape/dsl/rescue_options.rb +6 -5
- data/lib/grape/dsl/routing.rb +116 -49
- data/lib/grape/dsl/settings.rb +1 -1
- data/lib/grape/dsl/validations.rb +3 -3
- data/lib/grape/dsl/version_options.rb +3 -3
- data/lib/grape/endpoint/options.rb +13 -7
- data/lib/grape/endpoint.rb +141 -118
- data/lib/grape/env.rb +7 -0
- data/lib/grape/error_formatter/base.rb +6 -5
- data/lib/grape/error_formatter.rb +6 -2
- data/lib/grape/exceptions/error_response.rb +4 -1
- data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
- data/lib/grape/exceptions/validation.rb +9 -8
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +1 -0
- data/lib/grape/locale/en.yml +1 -0
- data/lib/grape/middleware/auth/dsl.rb +5 -33
- data/lib/grape/middleware/error.rb +153 -46
- data/lib/grape/middleware/formatter.rb +95 -26
- data/lib/grape/middleware/precomputed_content_types.rb +12 -7
- data/lib/grape/middleware/stack.rb +13 -4
- data/lib/grape/middleware/versioner/base.rb +3 -9
- data/lib/grape/middleware/versioner/path.rb +49 -7
- data/lib/grape/namespace.rb +3 -2
- data/lib/grape/path.rb +8 -69
- data/lib/grape/precompiled_json.rb +50 -0
- data/lib/grape/request.rb +36 -4
- data/lib/grape/router/base_route.rb +47 -10
- data/lib/grape/router/greedy_route.rb +5 -1
- data/lib/grape/router/pattern/path.rb +78 -0
- data/lib/grape/router/pattern.rb +60 -14
- data/lib/grape/router/route.rb +62 -10
- data/lib/grape/router.rb +100 -57
- data/lib/grape/serve_stream/file_body.rb +6 -0
- data/lib/grape/serve_stream/stream_response.rb +6 -0
- data/lib/grape/util/api_description.rb +10 -1
- data/lib/grape/util/cache.rb +21 -2
- data/lib/grape/util/deep_freeze.rb +1 -2
- data/lib/grape/util/freeze_on_new.rb +20 -0
- data/lib/grape/util/inheritable_setting.rb +751 -37
- data/lib/grape/util/media_type.rb +10 -3
- data/lib/grape/util/path_normalizer.rb +12 -9
- data/lib/grape/util/registry.rb +12 -2
- data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
- data/lib/grape/util/stackable_values.rb +32 -6
- data/lib/grape/validations/attributes_iterator.rb +21 -4
- data/lib/grape/validations/contract_scope.rb +8 -7
- data/lib/grape/validations/oneof_collector.rb +6 -10
- data/lib/grape/validations/params_documentation.rb +2 -2
- data/lib/grape/validations/params_scope.rb +66 -26
- data/lib/grape/validations/single_attribute_iterator.rb +5 -3
- data/lib/grape/validations/types/array_coercer.rb +4 -6
- data/lib/grape/validations/types/custom_type_coercer.rb +7 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +3 -1
- data/lib/grape/validations/types/json.rb +1 -3
- data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
- data/lib/grape/validations/types/primitive_coercer.rb +12 -9
- data/lib/grape/validations/types/variant_collection_coercer.rb +8 -3
- data/lib/grape/validations/types.rb +5 -5
- data/lib/grape/validations/validations_spec.rb +14 -4
- data/lib/grape/validations/validators/base.rb +4 -7
- data/lib/grape/validations/validators/coerce_validator.rb +8 -2
- data/lib/grape/validations/validators/contract_scope_validator.rb +2 -1
- data/lib/grape/validations/validators/length_validator.rb +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +2 -0
- data/lib/grape/validations/validators/same_as_validator.rb +1 -0
- data/lib/grape/version.rb +2 -1
- data/lib/grape.rb +23 -2
- metadata +10 -9
- data/lib/grape/middleware/deprecated_options_hash_access.rb +0 -19
- data/lib/grape/util/base_inheritable.rb +0 -43
- data/lib/grape/util/inheritable_values.rb +0 -33
- data/lib/grape/util/reverse_stackable_values.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: 85a54d2cd2bfc91b5b60cf30c317884e37b9ddfce15a8792d03247908a447393
|
|
4
|
+
data.tar.gz: ea8ad0d56df3c5a53f0540c98f7bbc5f12d79069b845625a63c7746ba47521c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59aee78aea73250cc78f34e4eb87a380b709b2568310959331455b99b6c562d1085d43b99897a3063006f9d2e64adc0286ac2319245d1ffc645b28519c1358f6
|
|
7
|
+
data.tar.gz: 716b1feb2c1b5ad32213515353dab9b5dc9fd9dc7c6560bc4b6f7b4a82326df1c21c6c2bee517e86b7a252217bf55515ca1005507f737fa57c1c82d759e17fcf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,131 @@
|
|
|
1
|
+
### 4.0.0 (2026-09-07)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* [#2768](https://github.com/ruby-grape/grape/pull/2768): Remove Guard - [@ericproulx](https://github.com/ericproulx).
|
|
6
|
+
* [#2774](https://github.com/ruby-grape/grape/pull/2774): Make `forward_match` an internal kwarg instead of a route option - [@ericproulx](https://github.com/ericproulx).
|
|
7
|
+
* [#2775](https://github.com/ruby-grape/grape/pull/2775): Pass `http_methods` to `Grape::Endpoint` as a keyword instead of via options - [@ericproulx](https://github.com/ericproulx).
|
|
8
|
+
* [#2776](https://github.com/ruby-grape/grape/pull/2776): Pass `path` to `Grape::Endpoint` as a keyword instead of via options - [@ericproulx](https://github.com/ericproulx).
|
|
9
|
+
* [#2777](https://github.com/ruby-grape/grape/pull/2777): Add `Grape::Endpoint#mounted_app` reader and `app:` keyword - [@ericproulx](https://github.com/ericproulx).
|
|
10
|
+
* [#2778](https://github.com/ruby-grape/grape/pull/2778): Rename `Grape::Endpoint`'s `for:` keyword to `api:` and expose `#api` - [@ericproulx](https://github.com/ericproulx).
|
|
11
|
+
* [#2779](https://github.com/ruby-grape/grape/pull/2779): Promote route metadata (`version`, `namespace`, `prefix`, `requirements`, `anchor`, `settings`) to `Grape::Router::Route` keyword arguments, and drop the unused `Grape::Endpoint::Options` `format` member - [@ericproulx](https://github.com/ericproulx).
|
|
12
|
+
* [#2782](https://github.com/ruby-grape/grape/pull/2782): Remove the dead `format` keyword from `Grape::Router::Pattern` - [@ericproulx](https://github.com/ericproulx).
|
|
13
|
+
* [#2783](https://github.com/ruby-grape/grape/pull/2783): Read a route's `version`, `anchor` and `requirements` from its pattern instead of storing them again on the route - [@ericproulx](https://github.com/ericproulx).
|
|
14
|
+
* [#2784](https://github.com/ruby-grape/grape/pull/2784): Move HEAD route creation into `Grape::Router::Route#to_head` - [@ericproulx](https://github.com/ericproulx).
|
|
15
|
+
* [#2787](https://github.com/ruby-grape/grape/pull/2787): Stop populating the write-only `namespace_setting(:description)` in `desc` - [@ericproulx](https://github.com/ericproulx).
|
|
16
|
+
* [#2795](https://github.com/ruby-grape/grape/pull/2795): Make `Grape::Util::InheritableSetting#namespace_reverse_stackable` internal, exposing `rescue_handlers` / `base_only_rescue_handlers` / `add_rescue_handlers` instead - [@ericproulx](https://github.com/ericproulx).
|
|
17
|
+
* [#2793](https://github.com/ruby-grape/grape/pull/2793): Add a `Grape::Mountable` marker to identify a Grape app instead of duck-typing on `respond_to?(:inheritable_setting)` - [@ericproulx](https://github.com/ericproulx).
|
|
18
|
+
* [#2792](https://github.com/ruby-grape/grape/pull/2792): Move `Grape::Path` to `Grape::Router::Pattern::Path` and add a `Grape::Router::Pattern.build` factory (`Grape::Path` kept as a deprecated constant) - [@ericproulx](https://github.com/ericproulx).
|
|
19
|
+
* [#2785](https://github.com/ruby-grape/grape/pull/2785): Make route `params` a first-class endpoint input and split `Grape::Router::Route#params` into `#params` (declared definitions) and `#params_for(input)` (extracted values) - [@ericproulx](https://github.com/ericproulx).
|
|
20
|
+
* [#2786](https://github.com/ruby-grape/grape/pull/2786): Make route `requirements` and `anchor` explicit keyword arguments and first-class endpoint inputs instead of opaque `route_options` keys - [@ericproulx](https://github.com/ericproulx).
|
|
21
|
+
* [#2788](https://github.com/ruby-grape/grape/pull/2788): Compare the base API instead of `to_s` when refreshing a mounted app - [@ericproulx](https://github.com/ericproulx).
|
|
22
|
+
* [#2796](https://github.com/ruby-grape/grape/pull/2796): Remove `Grape::Util::ReverseStackableValues`, storing rescue handlers in plain per-scope hashes merged over `InheritableSetting#parent` - [@ericproulx](https://github.com/ericproulx).
|
|
23
|
+
* [#2797](https://github.com/ruby-grape/grape/pull/2797): Internalize params state (validations, declared params, param documentation, named params) behind `Grape::Util::InheritableSetting` accessors, and remove the unused `api_class` / `point_in_time_copies` readers - [@ericproulx](https://github.com/ericproulx).
|
|
24
|
+
* [#2798](https://github.com/ruby-grape/grape/pull/2798): Internalize callback filters behind `Grape::Util::InheritableSetting` accessors (`callbacks` / `add_callback`) - [@ericproulx](https://github.com/ericproulx).
|
|
25
|
+
* [#2799](https://github.com/ruby-grape/grape/pull/2799): Internalize the remaining rescue configuration (`rescue_options`, meta-selector handlers and flags) behind `Grape::Util::InheritableSetting` accessors - [@ericproulx](https://github.com/ericproulx).
|
|
26
|
+
* [#2800](https://github.com/ruby-grape/grape/pull/2800): Internalize content negotiation state (content types, formatters, parsers, error formatters) behind `Grape::Util::InheritableSetting` accessors - [@ericproulx](https://github.com/ericproulx).
|
|
27
|
+
* [#2801](https://github.com/ruby-grape/grape/pull/2801): Internalize `represent` registrations behind `Grape::Util::InheritableSetting` accessors (`representations` / `add_representation`), and remove the inert `:representations` seeding in `Grape::Endpoint#initialize` - [@ericproulx](https://github.com/ericproulx).
|
|
28
|
+
* [#2802](https://github.com/ruby-grape/grape/pull/2802): Internalize middleware and helper registrations behind `Grape::Util::InheritableSetting` accessors (`middleware` / `add_middleware`, `helpers` / `add_helper`) - [@ericproulx](https://github.com/ericproulx).
|
|
29
|
+
* [#2803](https://github.com/ruby-grape/grape/pull/2803): Internalize namespace and mount-path registrations behind `Grape::Util::InheritableSetting` accessors (`namespaces` / `add_namespace` / `namespace_path` / `namespace_requirements`, `mount_path` / `add_mount_path`) - [@ericproulx](https://github.com/ericproulx).
|
|
30
|
+
* [#2804](https://github.com/ruby-grape/grape/pull/2804): Internalize contract key maps behind `Grape::Util::InheritableSetting` accessors (`contract_key_maps` / `add_contract_key_map`) - [@ericproulx](https://github.com/ericproulx).
|
|
31
|
+
* [#2805](https://github.com/ruby-grape/grape/pull/2805): Internalize format and error-response defaults behind `Grape::Util::InheritableSetting` accessors (`format`, `default_format`, `default_error_formatter`, `default_error_status`) - [@ericproulx](https://github.com/ericproulx).
|
|
32
|
+
* [#2806](https://github.com/ruby-grape/grape/pull/2806): Internalize versioning state behind `Grape::Util::InheritableSetting` accessors (`version`, `version_options`, `root_prefix`, `cascade` / `cascade_defined?`) - [@ericproulx](https://github.com/ericproulx).
|
|
33
|
+
* [#2808](https://github.com/ruby-grape/grape/pull/2808): Internalize `build_params_with` and `auth` behind `Grape::Util::InheritableSetting` accessors - [@ericproulx](https://github.com/ericproulx).
|
|
34
|
+
* [#2807](https://github.com/ruby-grape/grape/pull/2807): Internalize routing scope flags behind `Grape::Util::InheritableSetting` accessors (`do_not_route_head`, `do_not_route_options`, `do_not_document`, `lint` — `!` writers / `?` readers), and drop the vestigial `namespace_inheritable` hash copy in `Grape::API::Instance`'s route-config collection - [@ericproulx](https://github.com/ericproulx).
|
|
35
|
+
* [#2810](https://github.com/ruby-grape/grape/pull/2810): Make `Grape::Util::InheritableSetting`'s raw stores internal where the ecosystem allows: `namespace_inheritable` is now protected and `namespace_stackable_with_hash` private, while `namespace_stackable` stays public for grape-swagger; `Router::Pattern::Path` reads a dedicated `path_settings` snapshot, and `mount_paths` exposes the full mount-path stack - [@ericproulx](https://github.com/ericproulx).
|
|
36
|
+
* [#2811](https://github.com/ruby-grape/grape/pull/2811): Internalize the route scope behind `Grape::Util::InheritableSetting` accessors (`route_validations`, `route_declared_params`, `route_renamed_params` / `add_route_renamed_param`, `route_description`, `route_settings`, `route_setting`, `inherit_route_params`), so no external code touches the raw `route` store - [@ericproulx](https://github.com/ericproulx).
|
|
37
|
+
* [#2823](https://github.com/ruby-grape/grape/pull/2823): Move stackable settings storage onto `Grape::Util::InheritableSetting`, resolving inheritance by walking `parent` instead of a second chain; `Grape::Util::StackableValues` becomes a read-only view kept for grape-swagger, and `Grape::Util::BaseInheritable` is removed - [@ericproulx](https://github.com/ericproulx).
|
|
38
|
+
* [#2828](https://github.com/ruby-grape/grape/pull/2828): Compare `Grape::Util::InheritableSetting` by own state instead of serializing both chains through `to_hash`, and give `Grape::Util::InheritableValues` an `==`, so the duplicate-route check in `route` stops costing O(n²) `to_hash` calls on APIs whose namespaces share relative paths - [@ericproulx](https://github.com/ericproulx).
|
|
39
|
+
* [#2831](https://github.com/ruby-grape/grape/pull/2831): Document and test open (beginless/endless) `Range`s as a `values:`/`except_values:` alternative to a dedicated numericality validator - [@dblock](https://github.com/dblock).
|
|
40
|
+
* [#2830](https://github.com/ruby-grape/grape/pull/2830): Remove `Grape::Util::InheritableValues`, resolving inheritable settings by walking `Grape::Util::InheritableSetting#parent` instead of layering a second chain of stores, and keep namespace settings in a plain Hash - [@ericproulx](https://github.com/ericproulx).
|
|
41
|
+
* [#2837](https://github.com/ruby-grape/grape/pull/2837): Document the definition-time rejection of an `Array`/`Set` of an uncoercible element type introduced by #2817, and pin it with specs - [@ericproulx](https://github.com/ericproulx).
|
|
42
|
+
* [#2836](https://github.com/ruby-grape/grape/pull/2836): Define `#hash` alongside the `eql?`/`==` pairs on `Grape::Endpoint`, `Grape::Util::InheritableSetting`, `Grape::Middleware::Stack::Middleware`, `Grape::ServeStream::StreamResponse` and `Grape::ServeStream::FileBody`, so equal objects hash alike in a `Hash`, `Set` or `uniq` - [@ericproulx](https://github.com/ericproulx).
|
|
43
|
+
* [#2835](https://github.com/ruby-grape/grape/pull/2835): Return the compiled instance from `Grape::API::Instance.compile!` so `call` and `recognize_path` no longer re-read `@instance`, which a concurrent `change!` could nil between the two reads - [@ericproulx](https://github.com/ericproulx).
|
|
44
|
+
* [#2849](https://github.com/ruby-grape/grape/pull/2849): Remove `http_digest`, which has had no strategy behind it since 2.0.0 and raised on the first request rather than when the API was defined (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
45
|
+
* [#2862](https://github.com/ruby-grape/grape/pull/2862): Rename the `desc` `default` key to `default_response`, the name grape-swagger reads, deprecating `default` (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
46
|
+
* [#2866](https://github.com/ruby-grape/grape/pull/2866): Take `as` as a keyword argument in `requires` and `optional` instead of carrying it in the options Hash, where it was dispatched as a no-op `AsValidator` - [@ericproulx](https://github.com/ericproulx).
|
|
47
|
+
* [#2867](https://github.com/ruby-grape/grape/pull/2867): Extract the shared body of `requires` and `optional` into one private declaration, and drop `validate_attributes`, whose only step defaulted a type that no declaration could reach - [@ericproulx](https://github.com/ericproulx).
|
|
48
|
+
* [#2870](https://github.com/ruby-grape/grape/pull/2870): Take `declared_params_scope` as a keyword argument in `push_declared_params` instead of stamping it into the caller's options Hash - [@ericproulx](https://github.com/ericproulx).
|
|
49
|
+
* [#2869](https://github.com/ruby-grape/grape/pull/2869): Add `Grape::PrecompiledJson`, a body wrapper the JSON formatters serve verbatim instead of encoding a second time - [@ericproulx](https://github.com/ericproulx).
|
|
50
|
+
* [#2878](https://github.com/ruby-grape/grape/pull/2878): Build an error response's backtrace only when `rescue_from ..., backtrace: true` asked for one (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
51
|
+
* [#2873](https://github.com/ruby-grape/grape/pull/2873): Read `Grape.config[:param_builder]` instead of routing the per-request lookup through dry-configurable's `method_missing` - [@ericproulx](https://github.com/ericproulx).
|
|
52
|
+
* [#2877](https://github.com/ruby-grape/grape/pull/2877): Write `route_info` into the path captures in `Grape::Router#process_route` instead of merging a second Hash into it - [@ericproulx](https://github.com/ericproulx).
|
|
53
|
+
* [#2874](https://github.com/ruby-grape/grape/pull/2874): Test the `Grape::Util::PathNormalizer` fast path with two `String#include?` calls instead of a regexp - [@ericproulx](https://github.com/ericproulx).
|
|
54
|
+
* [#2876](https://github.com/ruby-grape/grape/pull/2876): Return a bare Rack tuple from `Grape::Middleware::Formatter` instead of a `Rack::Response` the enclosing middleware immediately unwraps - [@ericproulx](https://github.com/ericproulx).
|
|
55
|
+
* [#2875](https://github.com/ruby-grape/grape/pull/2875): Skip the query-string parse and the `script_name + path_info` concatenation during content negotiation when neither can affect the result - [@ericproulx](https://github.com/ericproulx).
|
|
56
|
+
* [#2879](https://github.com/ruby-grape/grape/pull/2879): Skip path-param extraction for routes whose compiled pattern captures nothing, instead of re-running the regexp to build an empty Hash - [@ericproulx](https://github.com/ericproulx).
|
|
57
|
+
* [#2880](https://github.com/ruby-grape/grape/pull/2880): Remove `Grape::Middleware::Versioner`'s `pattern` option, a leftover of the 2010 versioner that `versions:` superseded and the `version` DSL never exposed (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
58
|
+
* [#2872](https://github.com/ruby-grape/grape/pull/2872): Include an endpoint's helpers once at compile time instead of on every request, so a request no longer builds a singleton class whose method cache starts cold (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
59
|
+
* [#2882](https://github.com/ruby-grape/grape/pull/2882): Strip mount path and prefix in `Grape::Middleware::Versioner::Path` with `each` instead of `Enumerable#reduce` - [@ericproulx](https://github.com/ericproulx).
|
|
60
|
+
* [#2881](https://github.com/ruby-grape/grape/pull/2881): Decide whether a request carries a body from the env, so `Grape::Middleware::Formatter` no longer builds a `Rack::Request` for GET, HEAD and OPTIONS - [@ericproulx](https://github.com/ericproulx).
|
|
61
|
+
* [#2883](https://github.com/ruby-grape/grape/pull/2883): Resolve a format's content type through a plain Hash carrying both spellings instead of a `HashWithIndifferentAccess` that converts the key on every read - [@ericproulx](https://github.com/ericproulx).
|
|
62
|
+
* [#2887](https://github.com/ruby-grape/grape/pull/2887): Add support for the HTTP `QUERY` method (RFC 10008), including the `query` DSL verb and parsing the request content into `params` - [@ericproulx](https://github.com/ericproulx).
|
|
63
|
+
* [#2885](https://github.com/ruby-grape/grape/pull/2885): Share the content-type lookup and mime-type tables between middleware instances that register the same content types, instead of building a copy per API - [@ericproulx](https://github.com/ericproulx).
|
|
64
|
+
* [#2891](https://github.com/ruby-grape/grape/pull/2891): Look registrations up in a plain Hash carrying both spellings instead of a `HashWithIndifferentAccess` - [@ericproulx](https://github.com/ericproulx).
|
|
65
|
+
* [#2888](https://github.com/ruby-grape/grape/pull/2888): Resolve an API instance's cascade setting once at compile time instead of walking the settings chain on every request - [@ericproulx](https://github.com/ericproulx).
|
|
66
|
+
* [#2889](https://github.com/ruby-grape/grape/pull/2889): Constrain the `:version` capture with a `Regexp` so Mustermann stops registering an identity param converter, which was defeating its `identity_params?` fast path on every path-versioned request - [@ericproulx](https://github.com/ericproulx).
|
|
67
|
+
* [#2890](https://github.com/ruby-grape/grape/pull/2890): Read the path version off the route the router already matched instead of re-deriving it from `PATH_INFO` in `Grape::Middleware::Versioner::Path` - [@ericproulx](https://github.com/ericproulx).
|
|
68
|
+
* [#2892](https://github.com/ruby-grape/grape/pull/2892): Resolve the params builder when `params` are first read rather than in every `Grape::Request` - [@ericproulx](https://github.com/ericproulx).
|
|
69
|
+
* [#2893](https://github.com/ruby-grape/grape/pull/2893): Scrub the format extension rather than the whole request path when negotiating a format - [@ericproulx](https://github.com/ericproulx).
|
|
70
|
+
* [#2894](https://github.com/ruby-grape/grape/pull/2894): Read the request method once in `default_status` instead of asking through `post?` and `delete?` - [@ericproulx](https://github.com/ericproulx).
|
|
71
|
+
* [#2895](https://github.com/ruby-grape/grape/pull/2895): Cut per-request work out of the endpoint, validation and router paths: scan the router's compiled union by capture number rather than by name, skip the Array boxing in `AttributesIterator` for a flat scope, read a coerced attribute once instead of three times, and resolve the formatter's config and the coercers' type checks once at build time - [@ericproulx](https://github.com/ericproulx).
|
|
72
|
+
* [#2900](https://github.com/ruby-grape/grape/pull/2900): Remove the deprecations announced in 3.2 and 3.3: `Grape::Router.normalize_path`, Hash access on middleware `Options` and their `DEFAULT_OPTIONS` constants, the positional options Hash for `auth`/`http_basic`/`desc`, a Hash returned from a `rescue_from` handler, and `@option` on validators (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
73
|
+
* [#2896](https://github.com/ruby-grape/grape/pull/2896): Bring test suite line coverage to 100% - [@dblock](https://github.com/dblock).
|
|
74
|
+
* [#2897](https://github.com/ruby-grape/grape/pull/2897): Improve test suite branch coverage - [@dblock](https://github.com/dblock).
|
|
75
|
+
* [#2909](https://github.com/ruby-grape/grape/pull/2909): Standardize bare Ruby exception messages (`ArgumentError`, etc.) to lowercase, unpunctuated, matching Ruby's own core/stdlib style, and wrap interpolated values in backticks via `rubocop-exception_messages` - [@dblock](https://github.com/dblock).
|
|
76
|
+
|
|
77
|
+
#### Fixes
|
|
78
|
+
|
|
79
|
+
* [#2899](https://github.com/ruby-grape/grape/pull/2899): Derive `Grape.deprecator`'s deprecation horizon from `Grape::VERSION` instead of the hardcoded `2.0` it has announced since 2023 - [@ericproulx](https://github.com/ericproulx).
|
|
80
|
+
* [#2898](https://github.com/ruby-grape/grape/pull/2898): Stop eager loading `grape/testing`, so the module is opt-in as its 3.3 release note has always said instead of being installed in every process (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
81
|
+
* [#2767](https://github.com/ruby-grape/grape/pull/2767): Update rubocop to 1.88.0 and rubocop-rspec to 3.10.2 - [@ericproulx](https://github.com/ericproulx).
|
|
82
|
+
* [#2863](https://github.com/ruby-grape/grape/pull/2863): Enable `Style/MutableConstant`'s `Recursive` option, so a literal nested inside a frozen constant has to be frozen too - [@ericproulx](https://github.com/ericproulx).
|
|
83
|
+
* [#2816](https://github.com/ruby-grape/grape/pull/2816): Guard `length`, `same_as` and `oneof` validators against non-hash params (500 → 400) - [@ericproulx](https://github.com/ericproulx).
|
|
84
|
+
* [#2815](https://github.com/ruby-grape/grape/pull/2815): Fix line-anchored regexes: `type: JSON` payloads containing a blank line were silently coerced to `nil` - [@ericproulx](https://github.com/ericproulx).
|
|
85
|
+
* [#2817](https://github.com/ruby-grape/grape/pull/2817): Remove request-time mutable state from Array and custom-type coercers, which also makes an `Array`/`Set` of an uncoercible element type raise when the API is defined instead of on the first request that supplies it (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
86
|
+
* [#2819](https://github.com/ruby-grape/grape/pull/2819): Freeze coercers at construction, synchronize `Grape::Util::Cache` lookups, and assign `Route#regexp_capture_index` eagerly - [@ericproulx](https://github.com/ericproulx).
|
|
87
|
+
* [#2825](https://github.com/ruby-grape/grape/pull/2825): Stop `present` entity autodetection from picking up a top-level `::Entity` constant - [@ericproulx](https://github.com/ericproulx).
|
|
88
|
+
* [#2827](https://github.com/ruby-grape/grape/pull/2827): Make the `cascade` DSL getter return the configured value (`cascade false` read back as `true`) - [@ericproulx](https://github.com/ericproulx).
|
|
89
|
+
* [#2829](https://github.com/ruby-grape/grape/pull/2829): Fix a cascading route handing over only to the last route registered for the path, making a middle version (3+ mounted versions with a catch-all) answer 406 - [@ericproulx](https://github.com/ericproulx).
|
|
90
|
+
* [#2826](https://github.com/ruby-grape/grape/pull/2826): Fix `api.version` not being set for the root route of a path-versioned API (`GET /v1`) - [@ericproulx](https://github.com/ericproulx).
|
|
91
|
+
* [#2844](https://github.com/ruby-grape/grape/pull/2844): Look an entity up by the presented object's own class before treating it as a collection, so `represent` is no longer skipped for models that respond to `#first` or `#klass` - [@ericproulx](https://github.com/ericproulx).
|
|
92
|
+
* [#2847](https://github.com/ruby-grape/grape/pull/2847): Match `Accept` media types case-insensitively, so a differently-cased header still negotiates the content type and resolves a vendor version - [@ericproulx](https://github.com/ericproulx).
|
|
93
|
+
* [#2838](https://github.com/ruby-grape/grape/pull/2838): Reject request params nested in more arrays than the block declares, instead of silently unwrapping them and passing validation, and report `type: Array[JSON]` errors against the element that failed - [@ericproulx](https://github.com/ericproulx).
|
|
94
|
+
* [#2842](https://github.com/ruby-grape/grape/pull/2842): Warn at definition time when a `rescue_from` class is already covered by one registered earlier in the same scope, since the later handler never runs - [@ericproulx](https://github.com/ericproulx).
|
|
95
|
+
* [#2853](https://github.com/ruby-grape/grape/pull/2853): Restore, behind a deprecation warning, the trailing positional options Hash of `requires`, `optional` and `use`, which #2618 turned into a parameter name - [@ericproulx](https://github.com/ericproulx).
|
|
96
|
+
* [#2856](https://github.com/ruby-grape/grape/pull/2856): Update simplecov - [@ericproulx](https://github.com/ericproulx).
|
|
97
|
+
* [#2841](https://github.com/ruby-grape/grape/pull/2841): Stop `use`, `helpers`, `rescue_from` and other registrations declared below a route from reaching it when an earlier registration had seeded the same key (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
98
|
+
* [#2846](https://github.com/ruby-grape/grape/pull/2846): Keep a `:version` path capture in `params` when the API declares no version, instead of always dropping it as Grape's own - [@ericproulx](https://github.com/ericproulx).
|
|
99
|
+
* [#2839](https://github.com/ruby-grape/grape/pull/2839): Tag path params as UTF-8 instead of leaving them ASCII-8BIT, so they compare equal to the non-ASCII literals an API declares (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
100
|
+
* [#2845](https://github.com/ruby-grape/grape/pull/2845): Render `redirect`'s default message as the plain text its content type announces, instead of letting the API's formatter re-encode it (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
101
|
+
* [#2840](https://github.com/ruby-grape/grape/pull/2840): Answer 500 instead of letting an exception escape the middleware stack when an error response cannot be rendered, exposing it on `rack.exception` and writing it to `rack.errors` so error trackers keep reporting it, with `Grape.config.raise_rendering_errors` to opt back out (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
102
|
+
* [#2855](https://github.com/ruby-grape/grape/pull/2855): Expose an unhandled exception raised inside a `rescue_from` block on `rack.exception`, so error trackers report it instead of the generic 500 arriving silently - [@ericproulx](https://github.com/ericproulx).
|
|
103
|
+
* [#2843](https://github.com/ruby-grape/grape/pull/2843): Let `rescue_from :grape_exceptions` take precedence over a catch-all registered as a class, so Grape errors keep their own status (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
104
|
+
* [#2858](https://github.com/ruby-grape/grape/pull/2858): Define `Grape::Router::GreedyRoute#params_for` instead of letting the call fall through to the route's options Hash - [@ericproulx](https://github.com/ericproulx).
|
|
105
|
+
* [#2859](https://github.com/ruby-grape/grape/pull/2859): Answer `Grape::Router::Route#success` and `#failure` from the description whichever way it was written - [@ericproulx](https://github.com/ericproulx).
|
|
106
|
+
* [#2861](https://github.com/ruby-grape/grape/pull/2861): Read `Grape::Router::Route#default` from the description instead of from the options Hash's own default value, which was always `nil` - [@ericproulx](https://github.com/ericproulx).
|
|
107
|
+
* [#2864](https://github.com/ruby-grape/grape/pull/2864): Apply a group's type check to `optional` the same way as `requires`, so a type supplied by an enclosing `with` is no longer invisible to it (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
108
|
+
* [#2865](https://github.com/ruby-grape/grape/pull/2865): Pass `required` from `requires`/`optional` as an argument instead of writing a `presence` key into the caller's options, deprecating a user-supplied `presence` and letting a `with` block's `message` reach the presence validator (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
109
|
+
* [#2871](https://github.com/ruby-grape/grape/pull/2871): Move the endpoint replacement behind `mount`'s private re-mounting path, deprecating the undocumented `refresh_already_mounted` option, and recognise a mount mapping by `Hash` rather than by `respond_to?(:each_pair)` (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
110
|
+
* [#2886](https://github.com/ruby-grape/grape/pull/2886): Compile the router's optimized map from the registered routes, so a route declared with a verb outside `Grape::HTTP_SUPPORTED_METHODS` is served instead of being advertised in `Allow` and answered with 405 - [@ericproulx](https://github.com/ericproulx).
|
|
111
|
+
* [#2901](https://github.com/ruby-grape/grape/pull/2901): Reject `error_formatter` without a formatter instead of registering `nil`, which left the format with no error formatter at all and answered every error for it with a failsafe 500 (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
112
|
+
* [#2902](https://github.com/ruby-grape/grape/pull/2902): Make `Grape::ErrorFormatter.formatter_for` a lookup that answers `nil` for an unregistered format, like `Grape::Parser.parser_for`, and move the `default_error_formatter` / `Grape::ErrorFormatter::Txt` fallback to `Grape::Middleware::Error`, which owns it; `default_error_formatter` naming nothing registered now raises `Grape::Exceptions::UnknownErrorFormatter` instead of silently storing `Txt` (see UPGRADING) - [@ericproulx](https://github.com/ericproulx).
|
|
113
|
+
* [#2904](https://github.com/ruby-grape/grape/pull/2904): Document and spec route `requirements` given as a Mustermann capture type, which constrains the match and hands the endpoint the converted value - [@ericproulx](https://github.com/ericproulx).
|
|
114
|
+
* [#2905](https://github.com/ruby-grape/grape/pull/2905): Nest any `route_param` requirement under the param name, not just a Regexp, and reject the `requirements` shapes that have no param to attach to where they are written rather than on the first request - [@ericproulx](https://github.com/ericproulx).
|
|
115
|
+
* [#2908](https://github.com/ruby-grape/grape/pull/2908): Stop reassigning method parameters across `lib`, so a parameter keeps the value its caller passed for the whole method; the `oneof` collection in `Grape::Validations::ParamsScope` no longer writes into the Hash it was given - [@ericproulx](https://github.com/ericproulx).
|
|
116
|
+
* [#2910](https://github.com/ruby-grape/grape/pull/2910): Restore `Grape::Middleware::Formatter`'s in-place content-type negotiation as `ensure_content_type!`, which #2908 had turned into a copy of the response headers on every response - [@ericproulx](https://github.com/ericproulx).
|
|
117
|
+
* [#2911](https://github.com/ruby-grape/grape/pull/2911): Copy response headers with `merge!` instead of `merge` in `Grape::API::Instance#call` and `Grape::Middleware::Error`, which allocated a `Grape::Util::Header` only to discard it - [@ericproulx](https://github.com/ericproulx).
|
|
118
|
+
* [#2907](https://github.com/ruby-grape/grape/pull/2907): Stop `Grape::Endpoint::Options` from appending the default `'/'` into the path Array it was given, so declaring a route with an empty Array of paths inside a `namespace`, `resource`, `group` or `route_param` block no longer mutates the caller's Array — and raises `FrozenError` at boot when it is frozen - [@ericproulx](https://github.com/ericproulx).
|
|
119
|
+
* [#2912](https://github.com/ruby-grape/grape/pull/2912): Correct the 4.0.0 release notes and file the `http_digest` removal note under `Upgrading to >= 4.0.0` - [@ericproulx](https://github.com/ericproulx).
|
|
120
|
+
|
|
121
|
+
### 3.3.5 (2026-07-30)
|
|
122
|
+
|
|
123
|
+
#### Fixes
|
|
124
|
+
|
|
125
|
+
* [#2814](https://github.com/ruby-grape/grape/pull/2814): Fix `type: [A, B]` combined with `values:`/`except_values:` raising `IncompatibleOptionValues` at definition time - [@ericproulx](https://github.com/ericproulx).
|
|
126
|
+
* [#2833](https://github.com/ruby-grape/grape/pull/2833): Fix `Grape::API::Instance.to_s` returning an empty string instead of the class name when the instance has no base (regression since 3.0.0) - [@ericproulx](https://github.com/ericproulx).
|
|
127
|
+
* [#2834](https://github.com/ruby-grape/grape/pull/2834): Fix cascaded routes (`X-Cascade: pass`) leaking `route_info` and path captures into the next matched route's `route` and `params` - [@ericproulx](https://github.com/ericproulx).
|
|
128
|
+
|
|
1
129
|
### 3.3.4 (2026-07-25)
|
|
2
130
|
|
|
3
131
|
#### Fixes
|
data/CONTRIBUTING.md
CHANGED
|
@@ -74,6 +74,22 @@ Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `
|
|
|
74
74
|
|
|
75
75
|
Make sure that `bundle exec rake` completes without errors.
|
|
76
76
|
|
|
77
|
+
##### Exception Messages
|
|
78
|
+
|
|
79
|
+
When you `raise` a bare Ruby exception (`ArgumentError`, `NotImplementedError`, etc.) for programmer errors — invalid DSL usage, misconfiguration and the like — write the message as a lowercase fragment with no trailing period, matching Ruby's own core exceptions (e.g. `TypeError: no implicit conversion from nil to integer`). This reads naturally when Ruby prints it after the exception class name and colon in a backtrace:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
raise ArgumentError, 'a block is required'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
rather than:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
raise ArgumentError, 'A block is required.'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This does not apply to `Grape::Exceptions::*` messages meant to be rendered in an API response body, which are already lowercase and unpunctuated by convention and typically come from locale YAML rather than inline strings.
|
|
92
|
+
|
|
77
93
|
#### Write Documentation
|
|
78
94
|
|
|
79
95
|
Document any external behavior in the [README](README.md).
|
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Grape is a REST-like API framework for Ruby. It's designed to run on Rack or com
|
|
|
10
10
|
|
|
11
11
|
## Stable Release
|
|
12
12
|
|
|
13
|
-
You're reading the documentation for the stable release of Grape,
|
|
13
|
+
You're reading the documentation for the stable release of Grape, 4.0.0.
|
|
14
14
|
|
|
15
15
|
## Project Resources
|
|
16
16
|
|
|
@@ -566,7 +566,7 @@ end
|
|
|
566
566
|
* `params`: Define parameters directly from an `Entity`
|
|
567
567
|
* `success`: (former entity) The `Entity` to be used to present the success response for this route.
|
|
568
568
|
* `failure`: (former http_codes) A definition of the used failure HTTP Codes and Entities.
|
|
569
|
-
* `
|
|
569
|
+
* `default_response`: (former `default`) The definition and `Entity` used to present the default response for this route.
|
|
570
570
|
* `named`: A helper to give a route a name and find it with this name in the documentation Hash
|
|
571
571
|
* `headers`: A definition of the used Headers
|
|
572
572
|
* Other options can be found in [grape-swagger][grape-swagger]
|
|
@@ -579,6 +579,7 @@ Use `Grape.configure` to set up global settings at load time.
|
|
|
579
579
|
Currently the configurable settings are:
|
|
580
580
|
|
|
581
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).
|
|
582
583
|
|
|
583
584
|
To change a setting value make sure that at some point during load time the following code runs
|
|
584
585
|
|
|
@@ -619,7 +620,7 @@ get :public_timeline do
|
|
|
619
620
|
end
|
|
620
621
|
```
|
|
621
622
|
|
|
622
|
-
Parameters are automatically populated from the request body on `POST` and `
|
|
623
|
+
Parameters are automatically populated from the request body on `POST`, `PUT` and `QUERY` for form input, JSON and XML content-types.
|
|
623
624
|
|
|
624
625
|
The request:
|
|
625
626
|
|
|
@@ -1558,6 +1559,27 @@ params do
|
|
|
1558
1559
|
end
|
|
1559
1560
|
```
|
|
1560
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
|
+
|
|
1561
1583
|
The `:values` option can also be supplied with a `Proc`, evaluated lazily with each request.
|
|
1562
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.
|
|
1563
1585
|
|
|
@@ -1593,12 +1615,13 @@ With `requires`, blank values are already rejected: `requires` enforces presence
|
|
|
1593
1615
|
|
|
1594
1616
|
Parameters can be restricted from having a specific set of values with the `:except_values` option.
|
|
1595
1617
|
|
|
1596
|
-
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.
|
|
1597
1619
|
|
|
1598
1620
|
```ruby
|
|
1599
1621
|
params do
|
|
1600
1622
|
requires :browser, except_values: [ 'ie6', 'ie7', 'ie8' ]
|
|
1601
1623
|
requires :port, except_values: { value: 0..1024, message: 'is not allowed' }
|
|
1624
|
+
requires :negative, type: Integer, except_values: ..-1
|
|
1602
1625
|
requires :hashtag, except_values: -> { Hashtag.FORBIDDEN_LIST }
|
|
1603
1626
|
end
|
|
1604
1627
|
```
|
|
@@ -2278,6 +2301,78 @@ namespace :outer, requirements: { id: /[0-9]*/ } do
|
|
|
2278
2301
|
end
|
|
2279
2302
|
```
|
|
2280
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
|
+
|
|
2281
2376
|
## Helpers
|
|
2282
2377
|
|
|
2283
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.
|
|
@@ -2714,6 +2809,19 @@ rescue_from :grape_exceptions do |e|
|
|
|
2714
2809
|
end
|
|
2715
2810
|
```
|
|
2716
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
|
+
|
|
2717
2825
|
You can also rescue specific exceptions.
|
|
2718
2826
|
|
|
2719
2827
|
```ruby
|
|
@@ -2724,6 +2832,29 @@ end
|
|
|
2724
2832
|
|
|
2725
2833
|
In this case ```UserDefinedError``` must be inherited from ```StandardError```.
|
|
2726
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
|
+
|
|
2727
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.
|
|
2728
2859
|
|
|
2729
2860
|
```ruby
|
|
@@ -2801,7 +2932,9 @@ end
|
|
|
2801
2932
|
|
|
2802
2933
|
The first handler re-raises; the second handler runs against the new exception.
|
|
2803
2934
|
|
|
2804
|
-
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
|
|
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.
|
|
2805
2938
|
|
|
2806
2939
|
You can take control of the internal-error path by opting in with `rescue_from :internal_grape_exceptions`:
|
|
2807
2940
|
|
|
@@ -2957,6 +3090,38 @@ Any exception that is not subclass of `StandardError` should be rescued explicit
|
|
|
2957
3090
|
Usually it is not a case for an application logic as such errors point to problems in Ruby runtime.
|
|
2958
3091
|
This is following [standard recommendations for exceptions handling](https://ruby-doc.org/core/Exception.html).
|
|
2959
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
|
+
|
|
2960
3125
|
## Logging
|
|
2961
3126
|
|
|
2962
3127
|
`Grape::API` provides a `logger` method which by default will return an instance of the `Logger` class from Ruby's standard library.
|
|
@@ -3165,6 +3330,41 @@ Built-in formatters are the following.
|
|
|
3165
3330
|
* `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
|
|
3166
3331
|
* `:binary`: data will be returned "as is"
|
|
3167
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
|
+
|
|
3168
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.
|
|
3169
3369
|
|
|
3170
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.
|
|
@@ -3765,13 +3965,13 @@ class TwitterAPI < Grape::API
|
|
|
3765
3965
|
@var = 1
|
|
3766
3966
|
end
|
|
3767
3967
|
|
|
3768
|
-
|
|
3968
|
+
rescue_from :all do
|
|
3769
3969
|
puts @var # => 1
|
|
3770
|
-
raise
|
|
3771
3970
|
end
|
|
3772
3971
|
|
|
3773
|
-
|
|
3972
|
+
get '/' do
|
|
3774
3973
|
puts @var # => 1
|
|
3974
|
+
raise
|
|
3775
3975
|
end
|
|
3776
3976
|
end
|
|
3777
3977
|
```
|