grape 3.2.1 → 3.3.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 +80 -0
- data/README.md +116 -43
- data/UPGRADING.md +336 -1
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +7 -7
- data/lib/grape/api.rb +22 -25
- data/lib/grape/cookies.rb +2 -6
- data/lib/grape/declared_params_handler.rb +48 -50
- data/lib/grape/dsl/callbacks.rb +9 -3
- data/lib/grape/dsl/desc.rb +8 -2
- data/lib/grape/dsl/entity.rb +88 -0
- data/lib/grape/dsl/helpers.rb +27 -7
- data/lib/grape/dsl/inside_route.rb +38 -129
- data/lib/grape/dsl/logger.rb +3 -5
- data/lib/grape/dsl/parameters.rb +32 -38
- data/lib/grape/dsl/request_response.rb +53 -48
- data/lib/grape/dsl/rescue_options.rb +24 -0
- data/lib/grape/dsl/routing.rb +51 -35
- data/lib/grape/dsl/settings.rb +14 -8
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +19 -0
- data/lib/grape/endpoint.rb +96 -68
- data/lib/grape/env.rb +1 -3
- data/lib/grape/error_formatter/base.rb +23 -20
- data/lib/grape/error_formatter/json.rb +8 -4
- data/lib/grape/error_formatter/txt.rb +10 -10
- data/lib/grape/exceptions/base.rb +3 -1
- data/lib/grape/exceptions/error_response.rb +45 -0
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/validation.rb +14 -0
- data/lib/grape/exceptions/validation_array_errors.rb +4 -0
- data/lib/grape/exceptions/validation_errors.rb +12 -20
- data/lib/grape/formatter/serializable_hash.rb +5 -9
- data/lib/grape/json.rb +38 -2
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/base.rb +2 -3
- data/lib/grape/middleware/auth/dsl.rb +23 -8
- data/lib/grape/middleware/base.rb +22 -33
- data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
- data/lib/grape/middleware/error.rb +152 -62
- data/lib/grape/middleware/formatter.rb +66 -50
- data/lib/grape/middleware/precomputed_content_types.rb +46 -0
- data/lib/grape/middleware/stack.rb +5 -6
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
- data/lib/grape/middleware/versioner/base.rb +34 -38
- data/lib/grape/middleware/versioner/header.rb +3 -5
- data/lib/grape/middleware/versioner/path.rb +8 -3
- data/lib/grape/namespace.rb +3 -3
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/path.rb +14 -17
- data/lib/grape/request.rb +15 -8
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern.rb +6 -10
- data/lib/grape/router.rb +28 -42
- data/lib/grape/serve_stream/file_body.rb +1 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +1 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/base_inheritable.rb +13 -16
- data/lib/grape/util/inheritable_setting.rb +44 -27
- data/lib/grape/util/inheritable_values.rb +7 -3
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +2 -9
- data/lib/grape/util/lazy/value.rb +2 -9
- data/lib/grape/util/lazy/value_enumerable.rb +13 -16
- data/lib/grape/util/media_type.rb +1 -4
- data/lib/grape/util/path_normalizer.rb +34 -0
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/util/stackable_values.rb +11 -8
- data/lib/grape/validations/attributes_iterator.rb +13 -13
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/oneof_collector.rb +39 -0
- data/lib/grape/validations/param_scope_tracker.rb +14 -9
- data/lib/grape/validations/params_documentation.rb +25 -23
- data/lib/grape/validations/params_scope.rb +54 -172
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/types/array_coercer.rb +2 -2
- data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +3 -3
- data/lib/grape/validations/types/primitive_coercer.rb +10 -5
- data/lib/grape/validations/types/set_coercer.rb +1 -1
- data/lib/grape/validations/types/variant_collection_coercer.rb +8 -0
- data/lib/grape/validations/types.rb +23 -30
- data/lib/grape/validations/validations_spec.rb +149 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +1 -1
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +39 -22
- data/lib/grape/validations/validators/coerce_validator.rb +5 -3
- data/lib/grape/validations/validators/default_validator.rb +7 -8
- data/lib/grape/validations/validators/except_values_validator.rb +3 -2
- data/lib/grape/validations/validators/length_validator.rb +1 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +10 -7
- data/lib/grape/validations/validators/oneof_validator.rb +49 -0
- data/lib/grape/validations/validators/values_validator.rb +5 -5
- data/lib/grape/version.rb +1 -1
- data/lib/grape/xml.rb +8 -1
- data/lib/grape.rb +6 -6
- metadata +34 -18
- data/lib/grape/middleware/globals.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 533a2014e54eb4dd9c7932addb910af89c6d99f5717133cb081ba7e6d44676cc
|
|
4
|
+
data.tar.gz: bd0195007c0d69f4b92a2ca7651ff5e861639dc7a480540dece110877713573a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2967df7dd98a67aee9f0923883f4bec90a107292fd6fd6201a26d9c5aad9961fd75323cfad5ce0e3008925434a613f61590f41dadc73f5c06939cba4719a929
|
|
7
|
+
data.tar.gz: a3c9d88a32f75330d680fecbec0a01a8425acf742381c76831fd674fa440e5aae0591c72e58dc8b21e7bf5978fd6f1c865487eba110d5dd47410ab91ff624536
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,83 @@
|
|
|
1
|
+
### 3.3.0 (2026-06-20)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* [#2679](https://github.com/ruby-grape/grape/pull/2679): Extract entity dsl and refactor :with to keyword argument - [@ericproulx](https://github.com/ericproulx).
|
|
6
|
+
* [#2681](https://github.com/ruby-grape/grape/pull/2681): Extract `Grape::Endpoint.before_each` into `Grape::Testing` - [@ericproulx](https://github.com/ericproulx).
|
|
7
|
+
* [#2686](https://github.com/ruby-grape/grape/pull/2686): Add `Grape::Middleware::PrecomputedContentTypes` to warm content-type caches on the parent middleware instance (opt-in via `include`); short-circuit `Middleware::Base#merge_headers` when no headers were set - [@ericproulx](https://github.com/ericproulx).
|
|
8
|
+
* [#2683](https://github.com/ruby-grape/grape/pull/2683): Introduce `Grape::Util::Lazy::Base` for unified lazy-type dispatch - [@ericproulx](https://github.com/ericproulx).
|
|
9
|
+
* [#2685](https://github.com/ruby-grape/grape/pull/2685): Skip `run_filters` and `endpoint_run_filters.grape` instrumentation when the filter list is empty - [@ericproulx](https://github.com/ericproulx).
|
|
10
|
+
* [#2684](https://github.com/ruby-grape/grape/pull/2684): Readability refactors: case/when, guard clauses, small cleanups - [@ericproulx](https://github.com/ericproulx).
|
|
11
|
+
* [#2687](https://github.com/ruby-grape/grape/pull/2687): Skip backtrace capture on internal validation exceptions - [@ericproulx](https://github.com/ericproulx).
|
|
12
|
+
* [#2688](https://github.com/ruby-grape/grape/pull/2688): Consolidate user-registered rescue handler lookup into `Middleware::Error#registered_rescue_handler` backed by a shared `rescue_handler_from` primitive - [@ericproulx](https://github.com/ericproulx).
|
|
13
|
+
* [#2689](https://github.com/ruby-grape/grape/pull/2689): Avoid empty-hash merges on request hot paths - [@ericproulx](https://github.com/ericproulx).
|
|
14
|
+
* [#2690](https://github.com/ruby-grape/grape/pull/2690): Avoid allocating an empty array on every `StackableValues#[]` miss - [@ericproulx](https://github.com/ericproulx).
|
|
15
|
+
* [#2691](https://github.com/ruby-grape/grape/pull/2691): Precompute the prefix list in `Middleware::Versioner::Path` - [@ericproulx](https://github.com/ericproulx).
|
|
16
|
+
* [#2692](https://github.com/ruby-grape/grape/pull/2692): Replace per-request `Proc` allocation in `Router#transaction` with a `halt?` helper - [@ericproulx](https://github.com/ericproulx).
|
|
17
|
+
* [#2694](https://github.com/ruby-grape/grape/pull/2694): Split `Versioner::Base#available_media_types` into an `attr_reader` plus `build_available_media_types` - [@ericproulx](https://github.com/ericproulx).
|
|
18
|
+
* [#2695](https://github.com/ruby-grape/grape/pull/2695): Lift trailing `if/else` into guard clauses - [@ericproulx](https://github.com/ericproulx).
|
|
19
|
+
* [#2698](https://github.com/ruby-grape/grape/pull/2698): Collapse `DSL::RequestResponse#extract_handler` type-dispatch into a `case`/`when` - [@ericproulx](https://github.com/ericproulx).
|
|
20
|
+
* [#2697](https://github.com/ruby-grape/grape/pull/2697): Extract `Grape::Util::PathNormalizer` from `Grape::Router`; `Grape::Router.normalize_path` is now a deprecated alias - [@ericproulx](https://github.com/ericproulx).
|
|
21
|
+
* [#2696](https://github.com/ruby-grape/grape/pull/2696): Reduce per-request allocations on the request hot path; migrate middleware options to `attr_reader` and freeze `@options` post-init - [@ericproulx](https://github.com/ericproulx).
|
|
22
|
+
* [#2693](https://github.com/ruby-grape/grape/pull/2693): Introduce `Grape::Exceptions::ErrorResponse` value object to replace the implicit-schema Hash thrown via `throw` - [@ericproulx](https://github.com/ericproulx).
|
|
23
|
+
* [#2701](https://github.com/ruby-grape/grape/pull/2701): Replace `.tap` usages in `lib/` with explicit local variables - [@ericproulx](https://github.com/ericproulx).
|
|
24
|
+
* [#2704](https://github.com/ruby-grape/grape/pull/2704): Add `Grape::Endpoint#logger` so the API's configured logger is reachable inside route handlers, filters, and `rescue_from` blocks without a helper - [@ericproulx](https://github.com/ericproulx).
|
|
25
|
+
* [#2705](https://github.com/ruby-grape/grape/pull/2705): Add `Grape.config.warn_on_helper_overrides` (off by default) to emit a warning when a helper method masks a `Grape::Endpoint` instance method - [@ericproulx](https://github.com/ericproulx).
|
|
26
|
+
* [#2706](https://github.com/ruby-grape/grape/pull/2706): Refactor `ParamsScope#validates` and `ParamsDocumentation` around a frozen `Grape::Validations::ValidationsSpec` value object; the validations hash supplied by the DSL is no longer mutated and the helper chain becomes pure - [@ericproulx](https://github.com/ericproulx).
|
|
27
|
+
* [#2707](https://github.com/ruby-grape/grape/pull/2707): Tighten six guard conditions in `lib/` via De Morgan and `blank?`/`present?`/`include?` rewrites; no behaviour change - [@ericproulx](https://github.com/ericproulx).
|
|
28
|
+
* [#2708](https://github.com/ruby-grape/grape/pull/2708): Tighten dynamic `define_method` in `DSL::Callbacks` and `DSL::Routing` - [@ericproulx](https://github.com/ericproulx).
|
|
29
|
+
* [#2709](https://github.com/ruby-grape/grape/pull/2709): Lift trailing `if/else` into guard clauses; tighten `Util::Lazy::ValueEnumerable` - [@ericproulx](https://github.com/ericproulx).
|
|
30
|
+
* [#2702](https://github.com/ruby-grape/grape/pull/2702): Add `oneof:` option for `requires`/`optional` to accept a Hash parameter matching one of several variant schemas (resolves [#2385](https://github.com/ruby-grape/grape/issues/2385)) - [@ericproulx](https://github.com/ericproulx).
|
|
31
|
+
* [#2715](https://github.com/ruby-grape/grape/pull/2715): Normalize `==` / `eql?` aliasing across value-like classes - [@ericproulx](https://github.com/ericproulx).
|
|
32
|
+
* [#2710](https://github.com/ruby-grape/grape/pull/2710): Tidy up `Grape::DeclaredParamsHandler` - [@ericproulx](https://github.com/ericproulx).
|
|
33
|
+
* [#2712](https://github.com/ruby-grape/grape/pull/2712): Pass a `Grape::Exceptions::ErrorResponse` value object to `error_formatter#call` instead of separate kwargs - [@ericproulx](https://github.com/ericproulx).
|
|
34
|
+
* [#2714](https://github.com/ruby-grape/grape/pull/2714): Drop unused `Grape::Middleware::Globals` and its `grape.request*` env constants - [@ericproulx](https://github.com/ericproulx).
|
|
35
|
+
* [#2717](https://github.com/ruby-grape/grape/pull/2717): Convert `Grape::Exceptions::ErrorResponse` to a `Data` value object - [@ericproulx](https://github.com/ericproulx).
|
|
36
|
+
* [#2723](https://github.com/ruby-grape/grape/pull/2723): Deprecate passing a positional options Hash to `Grape::DSL::Desc#desc`; pass options as keyword arguments. Add a grape-swagger integration spec - [@ericproulx](https://github.com/ericproulx).
|
|
37
|
+
* [#2722](https://github.com/ruby-grape/grape/pull/2722): Introduce `Grape::Validations::CoerceOptions` value object for the internal coerce options - [@ericproulx](https://github.com/ericproulx).
|
|
38
|
+
* [#2721](https://github.com/ruby-grape/grape/pull/2721): Use an internal `Grape::Validations::SharedOptions` value object in `Validators::Base` (public `opts` Hash contract unchanged) - [@ericproulx](https://github.com/ericproulx).
|
|
39
|
+
* [#2719](https://github.com/ruby-grape/grape/pull/2719): Move content-type helpers from `Middleware::Base` into `PrecomputedContentTypes` - [@ericproulx](https://github.com/ericproulx).
|
|
40
|
+
* [#2716](https://github.com/ruby-grape/grape/pull/2716): Refactor `DSL::Routing#version`: guard clause, explicit kwargs in place of `**options`, and a `Grape::DSL::VersionOptions` value object stored internally - [@ericproulx](https://github.com/ericproulx).
|
|
41
|
+
* [#2720](https://github.com/ruby-grape/grape/pull/2720): Move declaration-coherence checks into `Grape::Validations::ValidationsSpec` - [@ericproulx](https://github.com/ericproulx).
|
|
42
|
+
* [#2725](https://github.com/ruby-grape/grape/pull/2725): Encapsulate `Grape::Validations::Validators::Base` state behind readers; add `required?`/`allow_blank?` predicates - [@ericproulx](https://github.com/ericproulx).
|
|
43
|
+
* [#2726](https://github.com/ruby-grape/grape/pull/2726): Reuse one `AttributesIterator` per validator and drop the unused `Enumerable` mixin - [@ericproulx](https://github.com/ericproulx).
|
|
44
|
+
* [#2728](https://github.com/ruby-grape/grape/pull/2728): Deprecate passing a positional options Hash to `auth`/`http_basic`/`http_digest`; pass keyword arguments instead - [@ericproulx](https://github.com/ericproulx).
|
|
45
|
+
* [#2733](https://github.com/ruby-grape/grape/pull/2733): Drop the dead `active_support/core_ext/hash/reverse_merge` require; call `ActiveSupport::HashWithIndifferentAccess.new(...)` directly at call sites - [@ericproulx](https://github.com/ericproulx).
|
|
46
|
+
* [#2734](https://github.com/ruby-grape/grape/pull/2734): Extract `options_route_enabled` from the Endpoint options Hash into a dedicated `attr_accessor` - [@ericproulx](https://github.com/ericproulx).
|
|
47
|
+
* [#2736](https://github.com/ruby-grape/grape/pull/2736): Collapse `Endpoint#run_validators` rescue branches via `ValidationErrors` flatten; `ValidationErrors#initialize` keyword renamed `errors:` → `exceptions:` - [@ericproulx](https://github.com/ericproulx).
|
|
48
|
+
* [#2747](https://github.com/ruby-grape/grape/pull/2747): Drop `Enumerable` from `Grape::Exceptions::ValidationErrors` and remove its public `#each`; rewrite `#full_messages` to walk `#errors` directly - [@ericproulx](https://github.com/ericproulx).
|
|
49
|
+
* [#2749](https://github.com/ruby-grape/grape/pull/2749): Middleware tidy-up — dedupe `Versioner::Base#build_available_media_types` (was emitting `application/vnd.<vendor>-<version>` once per content_type) and assorted `Formatter` cleanups (guard clauses, in-place merges, drop a no-op splat) - [@ericproulx](https://github.com/ericproulx).
|
|
50
|
+
* [#2718](https://github.com/ruby-grape/grape/pull/2718): Generalize middleware options to per-class `Options` `Data` value objects (`Middleware::Error`, `::Formatter`, `::Versioner::Base`); expose them via a new `config` reader, keep `options` Hash for back-compat, deprecate `Options#[]` Hash-style access - [@ericproulx](https://github.com/ericproulx).
|
|
51
|
+
* [#2746](https://github.com/ruby-grape/grape/pull/2746): Hoist `using:` / `except:` from `**opts` to explicit kwargs on `DSL::Parameters#requires` and `#optional` - [@ericproulx](https://github.com/ericproulx).
|
|
52
|
+
* [#2750](https://github.com/ruby-grape/grape/pull/2750): Bump minimum required Ruby to 3.3 - [@ericproulx](https://github.com/ericproulx).
|
|
53
|
+
* [#2742](https://github.com/ruby-grape/grape/pull/2742): Prune unused requires in `lib/grape.rb`; narrow `active_support/inflector` to `core_ext/string/inflections` - [@ericproulx](https://github.com/ericproulx).
|
|
54
|
+
* [#2741](https://github.com/ruby-grape/grape/pull/2741): Readability pass: guard clauses and small extractions across `lib/` - [@ericproulx](https://github.com/ericproulx).
|
|
55
|
+
* [#2740](https://github.com/ruby-grape/grape/pull/2740): Lazy-allocate `@api_class` and `@point_in_time_copies` on `Grape::Util::InheritableSetting` so unused settings layers don't carry an empty Hash and empty Array each - [@ericproulx](https://github.com/ericproulx).
|
|
56
|
+
* [#2739](https://github.com/ruby-grape/grape/pull/2739): Lazy-allocate `@new_values` in `Grape::Util::BaseInheritable` so settings layers that only inherit never carry an empty Hash; readers in `InheritableValues`/`StackableValues` handle nil - [@ericproulx](https://github.com/ericproulx).
|
|
57
|
+
* [#2737](https://github.com/ruby-grape/grape/pull/2737): `rescue_from` raises `ArgumentError` when a meta selector (`:all`, `:grape_exceptions`, `:internal_grape_exceptions`) is mixed with exception classes instead of silently dropping the classes - [@ericproulx](https://github.com/ericproulx).
|
|
58
|
+
* [#2735](https://github.com/ruby-grape/grape/pull/2735): Normalize `Grape::Endpoint#options` into an immutable `Grape::Endpoint::Options` `Data` value object; Hash-style `[]` reads kept for back-compat - [@ericproulx](https://github.com/ericproulx).
|
|
59
|
+
* [#2754](https://github.com/ruby-grape/grape/pull/2754): Merge routing args in place in `Router#process_route` instead of allocating a new Hash via `merge` - [@ericproulx](https://github.com/ericproulx).
|
|
60
|
+
* [#2753](https://github.com/ruby-grape/grape/pull/2753): Lazy-allocate `Grape::Validations::ParamScopeTracker`'s identity-keyed hashes so validating requests that never use the index / qualifying-params trackers allocate no hash - [@ericproulx](https://github.com/ericproulx).
|
|
61
|
+
* [#2752](https://github.com/ruby-grape/grape/pull/2752): Skip per-request `ActiveSupport::Notifications` payload and dispatch when no subscriber is listening, via private `instrument_<event>` guards on `Endpoint`/`Middleware::Formatter` - [@ericproulx](https://github.com/ericproulx).
|
|
62
|
+
* [#2755](https://github.com/ruby-grape/grape/pull/2755): Inline `mustermann-grape` into `Grape::Router::MustermannPattern` and depend on `mustermann` directly - [@ericproulx](https://github.com/ericproulx).
|
|
63
|
+
* [#2757](https://github.com/ruby-grape/grape/pull/2757): Build the `Grape::Cookies` jar only when a cookie is read or written (via a new `Grape::Request#cookies?` predicate gating response-cookie flushing), and drop the jar's now-redundant lazy-parse `Proc` - [@ericproulx](https://github.com/ericproulx).
|
|
64
|
+
* [#2756](https://github.com/ruby-grape/grape/pull/2756): Tighten dependency lower bounds to their compatibility floors (`rack >= 2.2.4`, `zeitwerk >= 2.6`, `dry-configurable >= 1.0`) - [@ericproulx](https://github.com/ericproulx).
|
|
65
|
+
* [#2762](https://github.com/ruby-grape/grape/pull/2762): Parse request bodies with `JSON.parse` in the stdlib JSON fallback, dropping the `create_additions: false` wrapper from #2759 (`JSON.parse` never honours `json_class`) - [@ericproulx](https://github.com/ericproulx).
|
|
66
|
+
|
|
67
|
+
#### Fixes
|
|
68
|
+
|
|
69
|
+
* [#2678](https://github.com/ruby-grape/grape/pull/2678): Update rubocop to 1.86.0 and autocorrect offenses - [@ericproulx](https://github.com/ericproulx).
|
|
70
|
+
* [#2682](https://github.com/ruby-grape/grape/pull/2682): Fix `Style/OptionalBooleanParameter` offenses - [@ericproulx](https://github.com/ericproulx).
|
|
71
|
+
* [#2699](https://github.com/ruby-grape/grape/pull/2699): Fix `Grape::Validations::Types::CustomTypeCoercer` dropping symbolized hash keys for `Array`/`Set` types; refactor the class for readability - [@ericproulx](https://github.com/ericproulx).
|
|
72
|
+
* [#2758](https://github.com/ruby-grape/grape/pull/2758): Fix `VariantCollectionCoercer#to_s` to return `"Array[Type, ...]"` notation instead of a raw object string - [@bogdan](https://github.com/bogdan).
|
|
73
|
+
* [#2700](https://github.com/ruby-grape/grape/pull/2700): Fix README typos, remove obsolete Ruby 2.4 / Fixnum section, and replace incorrect `requires + values + allow_blank` note with a correct one covering `optional + values` semantics (closes #2631) - [@ericproulx](https://github.com/ericproulx).
|
|
74
|
+
* [#2703](https://github.com/ruby-grape/grape/pull/2703): Catch exceptions raised inside `rescue_from` blocks; new `rescue_from :internal_grape_exceptions` opt-in for unrecognised internal errors (resolves [#2482](https://github.com/ruby-grape/grape/issues/2482)) - [@ericproulx](https://github.com/ericproulx).
|
|
75
|
+
* [#2706](https://github.com/ruby-grape/grape/pull/2706): Fix `optional :foo, message: 'oops'` raising `UnknownValidator` - [@ericproulx](https://github.com/ericproulx).
|
|
76
|
+
* [#2751](https://github.com/ruby-grape/grape/pull/2751): Fix structured error messages leaking the raw i18n key for an undefined optional step such as `summary` (closes #2748) - [@ericproulx](https://github.com/ericproulx).
|
|
77
|
+
* [#2759](https://github.com/ruby-grape/grape/pull/2759): Use `create_additions: false` in `Grape::Json.load` to prevent object instantiation via the `json_class` key when using the stdlib JSON fallback - [@dblock](https://github.com/dblock).
|
|
78
|
+
* [#2765](https://github.com/ruby-grape/grape/pull/2765): Detect the `MultiXML` constant to avoid the multi_xml 0.9 `MultiXml` deprecation - [@ericproulx](https://github.com/ericproulx).
|
|
79
|
+
* [#2764](https://github.com/ruby-grape/grape/pull/2764): Route `Grape::Json` through the non-deprecated `MultiJSON` API - [@ericproulx](https://github.com/ericproulx).
|
|
80
|
+
|
|
1
81
|
### 3.2.1 (2026-04-16)
|
|
2
82
|
|
|
3
83
|
#### Fixes
|
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, 3.
|
|
13
|
+
You're reading the documentation for the stable release of Grape, 3.3.0.
|
|
14
14
|
|
|
15
15
|
## Project Resources
|
|
16
16
|
|
|
@@ -27,7 +27,7 @@ The maintainers of Grape are working with Tidelift to deliver commercial support
|
|
|
27
27
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
30
|
-
Ruby 3.
|
|
30
|
+
Ruby 3.3 or newer is required.
|
|
31
31
|
|
|
32
32
|
Grape is available as a gem, to install it run:
|
|
33
33
|
|
|
@@ -311,7 +311,7 @@ mount ::Some::Api => '/some/api', with: { condition: true }
|
|
|
311
311
|
|
|
312
312
|
You can access `configuration` on the class (to use as dynamic attributes), inside blocks (like namespace)
|
|
313
313
|
|
|
314
|
-
If you want logic happening
|
|
314
|
+
If you want logic happening based on a `configuration`, you can use the helper `given`.
|
|
315
315
|
|
|
316
316
|
```ruby
|
|
317
317
|
class ConditionalEndpoint::API < Grape::API
|
|
@@ -462,7 +462,7 @@ vnd.vendor-and-or-resource-v1234+format
|
|
|
462
462
|
|
|
463
463
|
Basically all tokens between the final `-` and the `+` will be interpreted as the version.
|
|
464
464
|
|
|
465
|
-
Using this versioning strategy, clients should pass the desired version in the HTTP `Accept`
|
|
465
|
+
Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` header.
|
|
466
466
|
|
|
467
467
|
curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
|
|
468
468
|
|
|
@@ -484,7 +484,7 @@ Using this versioning strategy, clients should pass the desired version in the H
|
|
|
484
484
|
|
|
485
485
|
curl -H "Accept-Version:v1" http://localhost:9292/statuses/public_timeline
|
|
486
486
|
|
|
487
|
-
By default, the first matching version is used when no `Accept-Version` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept` header is supplied and the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.
|
|
487
|
+
By default, the first matching version is used when no `Accept-Version` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept-Version` header is supplied and the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.
|
|
488
488
|
|
|
489
489
|
#### Param
|
|
490
490
|
|
|
@@ -524,13 +524,13 @@ Grape.config.lint = true
|
|
|
524
524
|
```
|
|
525
525
|
|
|
526
526
|
### Bug in Rack::ETag under Rack 3.X
|
|
527
|
-
If you're using Rack 3.X and the `Rack::
|
|
527
|
+
If you're using Rack 3.X and the `Rack::ETag` middleware (used by [Rails](https://guides.rubyonrails.org/rails_on_rack.html#inspecting-middleware-stack)), a [bug](https://github.com/rack/rack/pull/2324) related to linting has been fixed in [3.1.13](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3113---2025-04-13) and [3.0.15](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3015---2025-04-13) respectively.
|
|
528
528
|
|
|
529
529
|
## Describing Methods
|
|
530
530
|
|
|
531
531
|
You can add a description to API methods and namespaces. The description would be used by [grape-swagger][grape-swagger] to generate swagger compliant documentation.
|
|
532
532
|
|
|
533
|
-
Note: Description block is only for documentation and won't
|
|
533
|
+
Note: Description block is only for documentation and won't affect API behavior.
|
|
534
534
|
|
|
535
535
|
```ruby
|
|
536
536
|
desc 'Returns your public timeline.' do
|
|
@@ -1182,28 +1182,6 @@ The following are all valid types, supported out of the box by Grape:
|
|
|
1182
1182
|
* Rack::Multipart::UploadedFile (alias `File`)
|
|
1183
1183
|
* JSON
|
|
1184
1184
|
|
|
1185
|
-
### Integer/Fixnum and Coercions
|
|
1186
|
-
|
|
1187
|
-
Please be aware that the behavior differs between Ruby 2.4 and earlier versions.
|
|
1188
|
-
In Ruby 2.4, values consisting of numbers are converted to Integer, but in earlier versions it will be treated as Fixnum.
|
|
1189
|
-
|
|
1190
|
-
```ruby
|
|
1191
|
-
params do
|
|
1192
|
-
requires :integers, type: Hash do
|
|
1193
|
-
requires :int, coerce: Integer
|
|
1194
|
-
end
|
|
1195
|
-
end
|
|
1196
|
-
get '/int' do
|
|
1197
|
-
params[:integers][:int].class
|
|
1198
|
-
end
|
|
1199
|
-
|
|
1200
|
-
...
|
|
1201
|
-
|
|
1202
|
-
get '/int' integers: { int: '45' }
|
|
1203
|
-
#=> Integer in ruby 2.4
|
|
1204
|
-
#=> Fixnum in earlier ruby versions
|
|
1205
|
-
```
|
|
1206
|
-
|
|
1207
1185
|
### Custom Types and Coercions
|
|
1208
1186
|
|
|
1209
1187
|
Aside from the default set of supported types listed above, any class can be used as a type as long as an explicit coercion method is supplied. If the type implements a class-level `parse` method, Grape will use it automatically. This method must take one string argument and return an instance of the correct type, or return an instance of `Grape::Types::InvalidValue` which optionally accepts a message to be returned in the response.
|
|
@@ -1356,6 +1334,56 @@ end
|
|
|
1356
1334
|
client.get('/', status_codes: %w(1 two)) # => [1, "two"]
|
|
1357
1335
|
```
|
|
1358
1336
|
|
|
1337
|
+
### Multiple Hash Schemas with `oneof`
|
|
1338
|
+
|
|
1339
|
+
A Hash parameter that may take one of several different shapes can be declared with the `oneof:` option. Each variant is a `Proc` that uses the normal `params` DSL — so the full validator surface (`requires`, `optional`, `regexp:`, `values:`, `allow_blank:`, nested `Hash`/`Array`, etc.) is available inside.
|
|
1340
|
+
|
|
1341
|
+
```ruby
|
|
1342
|
+
params do
|
|
1343
|
+
requires :value, type: Hash, oneof: [
|
|
1344
|
+
proc { requires :fixed_price, type: Float },
|
|
1345
|
+
proc do
|
|
1346
|
+
requires :time_unit, type: String
|
|
1347
|
+
requires :rate, type: Float
|
|
1348
|
+
end
|
|
1349
|
+
]
|
|
1350
|
+
end
|
|
1351
|
+
post '/pricing' do
|
|
1352
|
+
params[:value]
|
|
1353
|
+
end
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
Both of these requests succeed:
|
|
1357
|
+
|
|
1358
|
+
```bash
|
|
1359
|
+
curl -d '{"value":{"fixed_price":100.0}}' -H 'Content-Type: application/json' /pricing
|
|
1360
|
+
curl -d '{"value":{"time_unit":"hour","rate":50.0}}' -H 'Content-Type: application/json' /pricing
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
A request that doesn't match any variant returns `400` with `value does not match any of the allowed schemas`.
|
|
1364
|
+
|
|
1365
|
+
Variants are tried in declaration order; the first variant that validates without errors wins, and any coercions it performed (e.g. `"150.5"` → `150.5`) are applied to the request params. Nested hash structures inside variants work the same as elsewhere in Grape:
|
|
1366
|
+
|
|
1367
|
+
```ruby
|
|
1368
|
+
params do
|
|
1369
|
+
requires :options, type: Hash, oneof: [
|
|
1370
|
+
proc do
|
|
1371
|
+
requires :form, type: Hash do
|
|
1372
|
+
requires :colour, type: String
|
|
1373
|
+
optional :size, type: Integer
|
|
1374
|
+
end
|
|
1375
|
+
end,
|
|
1376
|
+
proc do
|
|
1377
|
+
requires :api, type: Hash do
|
|
1378
|
+
requires :authenticated, type: Grape::API::Boolean
|
|
1379
|
+
end
|
|
1380
|
+
end
|
|
1381
|
+
]
|
|
1382
|
+
end
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
`oneof:` requires `type: Hash`. The variants array must be non-empty and each entry must be a `Proc`; violating either raises an `ArgumentError` at definition time.
|
|
1386
|
+
|
|
1359
1387
|
### Validation of Nested Parameters
|
|
1360
1388
|
|
|
1361
1389
|
Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
|
|
@@ -1442,7 +1470,7 @@ params do
|
|
|
1442
1470
|
end
|
|
1443
1471
|
```
|
|
1444
1472
|
|
|
1445
|
-
You can organize settings into layers using nested `with
|
|
1473
|
+
You can organize settings into layers using nested `with` blocks. Each layer can use, add to, or change the settings of the layer above it. This helps to keep complex parameters organized and consistent, while still allowing for specific customizations to be made.
|
|
1446
1474
|
|
|
1447
1475
|
```ruby
|
|
1448
1476
|
params do
|
|
@@ -1551,14 +1579,16 @@ end
|
|
|
1551
1579
|
|
|
1552
1580
|
While Procs are convenient for single cases, consider using [Custom Validators](#custom-validators) in cases where a validation is used more than once.
|
|
1553
1581
|
|
|
1554
|
-
|
|
1582
|
+
When using `optional` together with `:values`, a missing key, a `nil` value, and any value that coerces to `nil` (such as `""` for `type: Symbol`) all pass validation — `optional` collapses "key may be absent" with "value may be nil". To reject blank values while still allowing the key to be absent, add `allow_blank: false`:
|
|
1555
1583
|
|
|
1556
1584
|
```ruby
|
|
1557
1585
|
params do
|
|
1558
|
-
|
|
1586
|
+
optional :state, type: Symbol, values: [:active, :inactive], allow_blank: false
|
|
1559
1587
|
end
|
|
1560
1588
|
```
|
|
1561
1589
|
|
|
1590
|
+
With `requires`, blank values are already rejected: `requires` enforces presence and `:values` rejects `nil`.
|
|
1591
|
+
|
|
1562
1592
|
#### `except_values`
|
|
1563
1593
|
|
|
1564
1594
|
Parameters can be restricted from having a specific set of values with the `:except_values` option.
|
|
@@ -1919,8 +1949,8 @@ To skip all subsequent validation checks when a specific param is found invalid,
|
|
|
1919
1949
|
The following example will not check if `:wine` is present unless it finds `:beer`.
|
|
1920
1950
|
```ruby
|
|
1921
1951
|
params do
|
|
1922
|
-
|
|
1923
|
-
|
|
1952
|
+
requires :beer, fail_fast: true
|
|
1953
|
+
requires :wine
|
|
1924
1954
|
end
|
|
1925
1955
|
```
|
|
1926
1956
|
The result of empty params would be a single `Grape::Exceptions::ValidationErrors` error.
|
|
@@ -1928,7 +1958,7 @@ The result of empty params would be a single `Grape::Exceptions::ValidationError
|
|
|
1928
1958
|
Similarly, no regular expression test will be performed if `:blah` is blank in the following example.
|
|
1929
1959
|
```ruby
|
|
1930
1960
|
params do
|
|
1931
|
-
|
|
1961
|
+
requires :blah, allow_blank: false, regexp: /blah/, fail_fast: true
|
|
1932
1962
|
end
|
|
1933
1963
|
```
|
|
1934
1964
|
|
|
@@ -2597,7 +2627,7 @@ You can set additional headers for the response. They will be merged with header
|
|
|
2597
2627
|
error!('Something went wrong', 500, 'X-Error-Detail' => 'Invalid token.')
|
|
2598
2628
|
```
|
|
2599
2629
|
|
|
2600
|
-
You can present documented errors with a Grape entity using the
|
|
2630
|
+
You can present documented errors with a Grape entity using the [grape-entity](https://github.com/ruby-grape/grape-entity) gem.
|
|
2601
2631
|
|
|
2602
2632
|
```ruby
|
|
2603
2633
|
module API
|
|
@@ -2708,12 +2738,12 @@ end
|
|
|
2708
2738
|
|
|
2709
2739
|
The error format will match the request format. See "Content-Types" below.
|
|
2710
2740
|
|
|
2711
|
-
Custom error formatters for existing and additional types can be defined with a proc.
|
|
2741
|
+
Custom error formatters for existing and additional types can be defined with a proc. The formatter receives a `Grape::Exceptions::ErrorResponse` value object as `error:` plus three context kwargs — `env:`, `include_backtrace:`, `include_original_exception:`. Pull just the keys you need with `**` to ignore the rest:
|
|
2712
2742
|
|
|
2713
2743
|
```ruby
|
|
2714
2744
|
class Twitter::API < Grape::API
|
|
2715
|
-
error_formatter :txt, ->(
|
|
2716
|
-
"error: #{message} from #{backtrace}"
|
|
2745
|
+
error_formatter :txt, ->(error:, **) {
|
|
2746
|
+
"error #{error.status}: #{error.message} from #{error.backtrace}"
|
|
2717
2747
|
}
|
|
2718
2748
|
end
|
|
2719
2749
|
```
|
|
@@ -2722,8 +2752,8 @@ You can also use a module or class.
|
|
|
2722
2752
|
|
|
2723
2753
|
```ruby
|
|
2724
2754
|
module CustomFormatter
|
|
2725
|
-
def self.call(
|
|
2726
|
-
{ message: message, backtrace: backtrace }
|
|
2755
|
+
def self.call(error:, **)
|
|
2756
|
+
{ status: error.status, message: error.message, backtrace: error.backtrace }
|
|
2727
2757
|
end
|
|
2728
2758
|
end
|
|
2729
2759
|
|
|
@@ -2753,6 +2783,41 @@ class Twitter::API < Grape::API
|
|
|
2753
2783
|
end
|
|
2754
2784
|
```
|
|
2755
2785
|
|
|
2786
|
+
#### Re-raising from inside a `rescue_from` block
|
|
2787
|
+
|
|
2788
|
+
A `rescue_from` block can re-raise an exception to invoke a different handler. This is useful for translating one exception class into another:
|
|
2789
|
+
|
|
2790
|
+
```ruby
|
|
2791
|
+
class Twitter::API < Grape::API
|
|
2792
|
+
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
|
2793
|
+
raise Api::Exceptions::InvalidValueError, e.full_messages
|
|
2794
|
+
end
|
|
2795
|
+
|
|
2796
|
+
rescue_from Api::Exceptions::InvalidValueError do |e|
|
|
2797
|
+
error!({ errors: e.message }, 422)
|
|
2798
|
+
end
|
|
2799
|
+
end
|
|
2800
|
+
```
|
|
2801
|
+
|
|
2802
|
+
The first handler re-raises; the second handler runs against the new exception.
|
|
2803
|
+
|
|
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 `env['grape.exception']` 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.
|
|
2805
|
+
|
|
2806
|
+
You can take control of the internal-error path by opting in with `rescue_from :internal_grape_exceptions`:
|
|
2807
|
+
|
|
2808
|
+
```ruby
|
|
2809
|
+
class Twitter::API < Grape::API
|
|
2810
|
+
rescue_from :internal_grape_exceptions do |e|
|
|
2811
|
+
Sentry.capture_exception(e)
|
|
2812
|
+
error!({ message: 'Something went wrong' }, 500)
|
|
2813
|
+
end
|
|
2814
|
+
end
|
|
2815
|
+
```
|
|
2816
|
+
|
|
2817
|
+
When this handler is registered the framework hands the original exception to you, and you own the response shape and any logging. The framework deliberately does not log internal errors itself — it has no way to know your preferred format or destination.
|
|
2818
|
+
|
|
2819
|
+
A second raise inside the redispatched handler is not redispatched again — it goes straight to the framework's generic 500. This bounds the chain at one redispatch and prevents loops.
|
|
2820
|
+
|
|
2756
2821
|
You can also rescue all exceptions with a code block and handle the Rack response at the lowest level.
|
|
2757
2822
|
|
|
2758
2823
|
```ruby
|
|
@@ -3967,7 +4032,15 @@ end
|
|
|
3967
4032
|
|
|
3968
4033
|
### Stubbing Helpers
|
|
3969
4034
|
|
|
3970
|
-
Because helpers are mixed in based on the context when an endpoint is defined, it can be difficult to stub or mock them for testing.
|
|
4035
|
+
Because helpers are mixed in based on the context when an endpoint is defined, it can be difficult to stub or mock them for testing. `Grape::Endpoint.before_each` allows you to define behavior on the endpoint that will run before every request.
|
|
4036
|
+
|
|
4037
|
+
This feature is provided by `Grape::Testing`, a standalone module intended for test environments only. Add it to your test helper:
|
|
4038
|
+
|
|
4039
|
+
```ruby
|
|
4040
|
+
require 'grape/testing'
|
|
4041
|
+
```
|
|
4042
|
+
|
|
4043
|
+
Then use it in your tests:
|
|
3971
4044
|
|
|
3972
4045
|
```ruby
|
|
3973
4046
|
describe 'an endpoint that needs helpers stubbed' do
|
|
@@ -3978,7 +4051,7 @@ describe 'an endpoint that needs helpers stubbed' do
|
|
|
3978
4051
|
end
|
|
3979
4052
|
|
|
3980
4053
|
after do
|
|
3981
|
-
Grape::Endpoint.
|
|
4054
|
+
Grape::Endpoint.reset_before_each
|
|
3982
4055
|
end
|
|
3983
4056
|
|
|
3984
4057
|
it 'stubs the helper' do
|