diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc7d3a3e..a5ab02cf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2918](https://github.com/ruby-grape/grape/pull/2918): Skip the dry-types round trip when a value already is the declared type - [@ericproulx](https://github.com/ericproulx). * [#2917](https://github.com/ruby-grape/grape/pull/2917): Read path captures out of the router's union match instead of re-running the route's pattern - [@ericproulx](https://github.com/ericproulx). * [#2921](https://github.com/ruby-grape/grape/pull/2921): Pin the router's request-time isolation regressions through requests instead of its instance variables - [@ericproulx](https://github.com/ericproulx). +* [#2930](https://github.com/ruby-grape/grape/pull/2930): Coerce through dry-types' non-raising call so a rejected value no longer builds a backtrace - [@ericproulx](https://github.com/ericproulx). * Your contribution here. ### 4.0.0 (2026-09-07) diff --git a/lib/grape/validations/types/dry_type_coercer.rb b/lib/grape/validations/types/dry_type_coercer.rb index a39fc02db..de77418af 100644 --- a/lib/grape/validations/types/dry_type_coercer.rb +++ b/lib/grape/validations/types/dry_type_coercer.rb @@ -43,11 +43,18 @@ def initialize(type, strict: false) # Coerces the given value to a type which was specified during # initialization as a type argument. # + # Given a block, dry-types reports a value it cannot coerce by calling + # the block instead of raising. Raising is what cost: its CoercionError + # is re-raised with the backtrace of the error underneath, and building + # that backtrace as strings at request depth took about 25 µs for every + # rejected value -- every `types: [Integer, String]` param given a + # string, every 400 for a mistyped value. + # # @param val [Object] def call(val) return if val.nil? - @coercer[val] + @coercer.call(val) { InvalidValue.new } rescue Dry::Types::CoercionError InvalidValue.new end